Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
274
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/BUILD
generated
vendored
Normal file
274
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/BUILD
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"alpha.go",
|
||||
"annotate.go",
|
||||
"apiresources.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",
|
||||
"delete.go",
|
||||
"delete_flags.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/batch/internalversion:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
||||
"//pkg/kubectl:go_default_library",
|
||||
"//pkg/kubectl/apply/parse:go_default_library",
|
||||
"//pkg/kubectl/apply/strategy:go_default_library",
|
||||
"//pkg/kubectl/cmd/auth:go_default_library",
|
||||
"//pkg/kubectl/cmd/config:go_default_library",
|
||||
"//pkg/kubectl/cmd/create:go_default_library",
|
||||
"//pkg/kubectl/cmd/get:go_default_library",
|
||||
"//pkg/kubectl/cmd/rollout:go_default_library",
|
||||
"//pkg/kubectl/cmd/scalejob:go_default_library",
|
||||
"//pkg/kubectl/cmd/set:go_default_library",
|
||||
"//pkg/kubectl/cmd/templates:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/cmd/util/editor:go_default_library",
|
||||
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
||||
"//pkg/kubectl/cmd/wait:go_default_library",
|
||||
"//pkg/kubectl/explain:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/printers:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/resource:go_default_library",
|
||||
"//pkg/kubectl/metricsutil:go_default_library",
|
||||
"//pkg/kubectl/plugins:go_default_library",
|
||||
"//pkg/kubectl/polymorphichelpers:go_default_library",
|
||||
"//pkg/kubectl/proxy:go_default_library",
|
||||
"//pkg/kubectl/scheme:go_default_library",
|
||||
"//pkg/kubectl/util:go_default_library",
|
||||
"//pkg/kubectl/util/i18n:go_default_library",
|
||||
"//pkg/kubectl/util/term:go_default_library",
|
||||
"//pkg/kubectl/validation:go_default_library",
|
||||
"//pkg/printers:go_default_library",
|
||||
"//pkg/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/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||
"//vendor/k8s.io/client-go/dynamic:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/autoscaling/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/scale:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/portforward:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
|
||||
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
|
||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
||||
"//vendor/k8s.io/metrics/pkg/apis/metrics:go_default_library",
|
||||
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"annotate_test.go",
|
||||
"apply_test.go",
|
||||
"attach_test.go",
|
||||
"clusterinfo_dump_test.go",
|
||||
"cmd_printing_test.go",
|
||||
"cmd_test.go",
|
||||
"convert_test.go",
|
||||
"cp_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",
|
||||
"//test/e2e/testing-manifests:all-srcs",
|
||||
"//test/fixtures",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/testing:go_default_library",
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/kubectl/cmd/create:go_default_library",
|
||||
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/printers:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/resource:go_default_library",
|
||||
"//pkg/kubectl/plugins:go_default_library",
|
||||
"//pkg/kubectl/polymorphichelpers:go_default_library",
|
||||
"//pkg/kubectl/scheme:go_default_library",
|
||||
"//pkg/kubectl/util/i18n:go_default_library",
|
||||
"//pkg/kubectl/util/term:go_default_library",
|
||||
"//pkg/printers:go_default_library",
|
||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/client-go/dynamic/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
|
||||
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset/fake:go_default_library",
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
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/create:all-srcs",
|
||||
"//pkg/kubectl/cmd/get:all-srcs",
|
||||
"//pkg/kubectl/cmd/rollout:all-srcs",
|
||||
"//pkg/kubectl/cmd/scalejob:all-srcs",
|
||||
"//pkg/kubectl/cmd/set:all-srcs",
|
||||
"//pkg/kubectl/cmd/templates:all-srcs",
|
||||
"//pkg/kubectl/cmd/testdata/edit:all-srcs",
|
||||
"//pkg/kubectl/cmd/testing:all-srcs",
|
||||
"//pkg/kubectl/cmd/util:all-srcs",
|
||||
"//pkg/kubectl/cmd/wait:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = [
|
||||
"//build/visible_to:pkg_kubectl_cmd_CONSUMERS",
|
||||
],
|
||||
)
|
||||
50
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/alpha.go
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/alpha.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
// NewCmdAlpha creates a command that acts as an alternate root command for features in alpha
|
||||
func NewCmdAlpha(f cmdutil.Factory, streams genericclioptions.IOStreams) *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, streams))
|
||||
|
||||
// NewKubeletCommand() will hide the alpha command if it has no subcommands. Overriding
|
||||
// the help function ensures a reasonable message if someone types the hidden command anyway.
|
||||
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
|
||||
}
|
||||
383
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate.go
generated
vendored
Normal file
383
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate.go
generated
vendored
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
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/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
// AnnotateOptions have the data required to perform the annotate operation
|
||||
type AnnotateOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
// Filename options
|
||||
resource.FilenameOptions
|
||||
RecordFlags *genericclioptions.RecordFlags
|
||||
|
||||
// Common user flags
|
||||
overwrite bool
|
||||
local bool
|
||||
dryrun bool
|
||||
all bool
|
||||
resourceVersion string
|
||||
selector string
|
||||
fieldSelector string
|
||||
outputFormat string
|
||||
|
||||
// results of arg parsing
|
||||
resources []string
|
||||
newAnnotations map[string]string
|
||||
removeAnnotations []string
|
||||
Recorder genericclioptions.Recorder
|
||||
namespace string
|
||||
enforceNamespace bool
|
||||
builder *resource.Builder
|
||||
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||
includeUninitialized bool
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
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 NewAnnotateOptions(ioStreams genericclioptions.IOStreams) *AnnotateOptions {
|
||||
return &AnnotateOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("annotated").WithTypeSetter(scheme.Scheme),
|
||||
|
||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||
Recorder: genericclioptions.NoopRecorder{},
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdAnnotate(parent string, f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewAnnotateOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "annotate [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Update the annotations on a resource"),
|
||||
Long: annotateLong + "\n\n" + cmdutil.SuggestApiResources(parent),
|
||||
Example: annotateExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.RunAnnotate())
|
||||
},
|
||||
}
|
||||
|
||||
// bind flag structs
|
||||
o.RecordFlags.AddFlags(cmd)
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddIncludeUninitializedFlag(cmd)
|
||||
cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.")
|
||||
cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, annotation will NOT contact api-server but run locally.")
|
||||
cmd.Flags().StringVarP(&o.selector, "selector", "l", o.selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2).")
|
||||
cmd.Flags().StringVar(&o.fieldSelector, "field-selector", o.fieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
|
||||
cmd.Flags().BoolVar(&o.all, "all", o.all, "Select all resources, including uninitialized ones, in the namespace of the specified resource types.")
|
||||
cmd.Flags().StringVar(&o.resourceVersion, "resource-version", o.resourceVersion, i18n.T("If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource."))
|
||||
usage := "identifying the resource to update the annotation"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete adapts from the command line args and factory to the data required.
|
||||
func (o *AnnotateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
o.RecordFlags.Complete(cmd)
|
||||
o.Recorder, err = o.RecordFlags.ToRecorder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.outputFormat = cmdutil.GetFlagString(cmd, "output")
|
||||
o.dryrun = cmdutil.GetDryRunFlag(cmd)
|
||||
|
||||
if o.dryrun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object, out io.Writer) error {
|
||||
return printer.PrintObj(obj, out)
|
||||
}
|
||||
|
||||
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.includeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false)
|
||||
o.builder = f.NewBuilder()
|
||||
o.unstructuredClientForMapping = f.UnstructuredClientForMapping
|
||||
|
||||
// retrieves resource and annotation args from args
|
||||
// also checks args to verify that all resources are specified before annotations
|
||||
resources, annotationArgs, err := cmdutil.GetResourcesAndPairs(args, "annotation")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.resources = resources
|
||||
o.newAnnotations, o.removeAnnotations, err = parseAnnotations(annotationArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks to the AnnotateOptions to see if there is sufficient information run the command.
|
||||
func (o AnnotateOptions) Validate() error {
|
||||
if o.all && len(o.selector) > 0 {
|
||||
return fmt.Errorf("cannot set --all and --selector at the same time")
|
||||
}
|
||||
if o.all && len(o.fieldSelector) > 0 {
|
||||
return fmt.Errorf("cannot set --all and --field-selector at the same time")
|
||||
}
|
||||
if len(o.resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
|
||||
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
|
||||
}
|
||||
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() error {
|
||||
b := o.builder.
|
||||
Unstructured().
|
||||
LocalParam(o.local).
|
||||
ContinueOnError().
|
||||
NamespaceParam(o.namespace).DefaultNamespace().
|
||||
FilenameParam(o.enforceNamespace, &o.FilenameOptions).
|
||||
IncludeUninitialized(o.includeUninitialized).
|
||||
Flatten()
|
||||
|
||||
if !o.local {
|
||||
b = b.LabelSelectorParam(o.selector).
|
||||
FieldSelectorParam(o.fieldSelector).
|
||||
ResourceTypeOrNameArgs(o.all, o.resources...).
|
||||
Latest()
|
||||
}
|
||||
|
||||
r := b.Do()
|
||||
if err := r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var singleItemImpliedResource bool
|
||||
r.IntoSingleItemImplied(&singleItemImpliedResource)
|
||||
|
||||
// only apply resource version locking on a single resource.
|
||||
// we must perform this check after o.builder.Do() as
|
||||
// []o.resources can not not accurately return the proper number
|
||||
// of resources when they are not passed in "resource/name" format.
|
||||
if !singleItemImpliedResource && len(o.resourceVersion) > 0 {
|
||||
return fmt.Errorf("--resource-version may only be used with a single resource")
|
||||
}
|
||||
|
||||
return r.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var outputObj runtime.Object
|
||||
obj := info.Object
|
||||
|
||||
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 err := o.Recorder.Record(info.Object); err != nil {
|
||||
glog.V(4).Infof("error recording current command: %v", err)
|
||||
}
|
||||
if err := o.updateAnnotations(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
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 := o.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
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(outputObj, o.Out)
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
643
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate_test.go
generated
vendored
Normal file
643
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate_test.go
generated
vendored
Normal file
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
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 (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func 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: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
annotations: map[string]string{"a": "b"},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
annotations: map[string]string{"a": "c"},
|
||||
overwrite: true,
|
||||
expected: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "c"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
annotations: map[string]string{"c": "d"},
|
||||
expected: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b", "c": "d"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
annotations: map[string]string{"c": "d"},
|
||||
version: "2",
|
||||
expected: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b", "c": "d"},
|
||||
ResourceVersion: "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
annotations: map[string]string{},
|
||||
remove: []string{"a"},
|
||||
expected: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b", "c": "d"},
|
||||
},
|
||||
},
|
||||
annotations: map[string]string{"e": "f"},
|
||||
remove: []string{"a"},
|
||||
expected: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"c": "d",
|
||||
"e": "f",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b", "c": "d"},
|
||||
},
|
||||
},
|
||||
annotations: map[string]string{"e": "f"},
|
||||
remove: []string{"g"},
|
||||
expected: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"a": "b",
|
||||
"c": "d",
|
||||
"e": "f",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"a": "b", "c": "d"},
|
||||
},
|
||||
},
|
||||
remove: []string{"e"},
|
||||
expected: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"a": "b",
|
||||
"c": "d",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{},
|
||||
},
|
||||
annotations: map[string]string{"a": "b"},
|
||||
expected: &v1.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 {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
iostreams, _, bufOut, bufErr := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
|
||||
cmd.SetOutput(bufOut)
|
||||
|
||||
for k, v := range testCase.flags {
|
||||
cmd.Flags().Set(k, v)
|
||||
}
|
||||
options := NewAnnotateOptions(iostreams)
|
||||
err := options.Complete(tf, cmd, testCase.args)
|
||||
if err == nil {
|
||||
err = options.Validate()
|
||||
}
|
||||
if !testCase.errFn(err) {
|
||||
t.Errorf("%s: unexpected error: %v", k, err)
|
||||
return
|
||||
}
|
||||
if bufOut.Len() > 0 {
|
||||
t.Errorf("buffer should be empty: %s", string(bufOut.Bytes()))
|
||||
}
|
||||
if bufErr.Len() > 0 {
|
||||
t.Errorf("buffer should be empty: %s", string(bufErr.Bytes()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotateObject(t *testing.T) {
|
||||
pods, _, _ := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
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.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
|
||||
cmd.SetOutput(bufOut)
|
||||
options := NewAnnotateOptions(iostreams)
|
||||
args := []string{"pods/foo", "a=b", "c-"}
|
||||
if err := options.Complete(tf, cmd, args); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.Validate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.RunAnnotate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotateObjectFromFile(t *testing.T) {
|
||||
pods, _, _ := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
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.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
|
||||
cmd.SetOutput(bufOut)
|
||||
options := NewAnnotateOptions(iostreams)
|
||||
options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
|
||||
args := []string{"a=b", "c-"}
|
||||
if err := options.Complete(tf, cmd, args); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.Validate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.RunAnnotate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotateLocal(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
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.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
iostreams, _, _, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
|
||||
options := NewAnnotateOptions(iostreams)
|
||||
options.local = true
|
||||
options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
|
||||
args := []string{"a=b"}
|
||||
if err := options.Complete(tf, cmd, args); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.Validate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.RunAnnotate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotateMultipleObjects(t *testing.T) {
|
||||
pods, _, _ := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
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.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
iostreams, _, _, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
|
||||
cmd.SetOutput(iostreams.Out)
|
||||
options := NewAnnotateOptions(iostreams)
|
||||
options.all = true
|
||||
args := []string{"pods", "a=b", "c-"}
|
||||
if err := options.Complete(tf, cmd, args); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.Validate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := options.RunAnnotate(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
229
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiresources.go
generated
vendored
Normal file
229
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiresources.go
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
var (
|
||||
apiresourcesExample = templates.Examples(`
|
||||
# Print the supported API Resources
|
||||
kubectl api-resources
|
||||
|
||||
# Print the supported API Resources with more information
|
||||
kubectl api-resources -o wide
|
||||
|
||||
# Print the supported namespaced resources
|
||||
kubectl api-resources --namespaced=true
|
||||
|
||||
# Print the supported non-namespaced resources
|
||||
kubectl api-resources --namespaced=false
|
||||
|
||||
# Print the supported API Resources with specific APIGroup
|
||||
kubectl api-resources --api-group=extensions`)
|
||||
)
|
||||
|
||||
// ApiResourcesOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
||||
// referencing the cmd.Flags()
|
||||
type ApiResourcesOptions struct {
|
||||
Output string
|
||||
APIGroup string
|
||||
Namespaced bool
|
||||
Verbs []string
|
||||
NoHeaders bool
|
||||
Cached bool
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
// groupResource contains the APIGroup and APIResource
|
||||
type groupResource struct {
|
||||
APIGroup string
|
||||
APIResource metav1.APIResource
|
||||
}
|
||||
|
||||
func NewAPIResourceOptions(ioStreams genericclioptions.IOStreams) *ApiResourcesOptions {
|
||||
return &ApiResourcesOptions{
|
||||
IOStreams: ioStreams,
|
||||
Namespaced: true,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdApiResources(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewAPIResourceOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "api-resources",
|
||||
Short: "Print the supported API resources on the server",
|
||||
Long: "Print the supported API resources on the server",
|
||||
Example: apiresourcesExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Validate(cmd))
|
||||
cmdutil.CheckErr(o.RunApiResources(cmd, f))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
|
||||
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: wide|name.")
|
||||
|
||||
cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
|
||||
cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
|
||||
cmd.Flags().StringSliceVar(&o.Verbs, "verbs", o.Verbs, "Limit to resources that support the specified verbs.")
|
||||
cmd.Flags().BoolVar(&o.Cached, "cached", o.Cached, "Use the cached list of resources if available.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ApiResourcesOptions) Validate(cmd *cobra.Command) error {
|
||||
supportedOutputTypes := sets.NewString("", "wide", "name")
|
||||
if !supportedOutputTypes.Has(o.Output) {
|
||||
return fmt.Errorf("--output %v is not available", o.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ApiResourcesOptions) RunApiResources(cmd *cobra.Command, f cmdutil.Factory) error {
|
||||
w := printers.GetNewTabWriter(o.Out)
|
||||
defer w.Flush()
|
||||
|
||||
discoveryclient, err := f.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !o.Cached {
|
||||
// Always request fresh data from the server
|
||||
discoveryclient.Invalidate()
|
||||
}
|
||||
|
||||
lists, err := discoveryclient.ServerPreferredResources()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resources := []groupResource{}
|
||||
|
||||
groupChanged := cmd.Flags().Changed("api-group")
|
||||
nsChanged := cmd.Flags().Changed("namespaced")
|
||||
|
||||
for _, list := range lists {
|
||||
if len(list.APIResources) == 0 {
|
||||
continue
|
||||
}
|
||||
gv, err := schema.ParseGroupVersion(list.GroupVersion)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, resource := range list.APIResources {
|
||||
if len(resource.Verbs) == 0 {
|
||||
continue
|
||||
}
|
||||
// filter apiGroup
|
||||
if groupChanged && o.APIGroup != gv.Group {
|
||||
continue
|
||||
}
|
||||
// filter namespaced
|
||||
if nsChanged && o.Namespaced != resource.Namespaced {
|
||||
continue
|
||||
}
|
||||
// filter to resources that support the specified verbs
|
||||
if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
|
||||
continue
|
||||
}
|
||||
resources = append(resources, groupResource{
|
||||
APIGroup: gv.Group,
|
||||
APIResource: resource,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if o.NoHeaders == false && o.Output != "name" {
|
||||
if err = printContextHeaders(w, o.Output); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sort.Stable(sortableGroupResource(resources))
|
||||
for _, r := range resources {
|
||||
switch o.Output {
|
||||
case "name":
|
||||
name := r.APIResource.Name
|
||||
if len(r.APIGroup) > 0 {
|
||||
name += "." + r.APIGroup
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
|
||||
return err
|
||||
}
|
||||
case "wide":
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\n",
|
||||
r.APIResource.Name,
|
||||
strings.Join(r.APIResource.ShortNames, ","),
|
||||
r.APIGroup,
|
||||
r.APIResource.Namespaced,
|
||||
r.APIResource.Kind,
|
||||
r.APIResource.Verbs); err != nil {
|
||||
return err
|
||||
}
|
||||
case "":
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
|
||||
r.APIResource.Name,
|
||||
strings.Join(r.APIResource.ShortNames, ","),
|
||||
r.APIGroup,
|
||||
r.APIResource.Namespaced,
|
||||
r.APIResource.Kind); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printContextHeaders(out io.Writer, output string) error {
|
||||
columnNames := []string{"NAME", "SHORTNAMES", "APIGROUP", "NAMESPACED", "KIND"}
|
||||
if output == "wide" {
|
||||
columnNames = append(columnNames, "VERBS")
|
||||
}
|
||||
_, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
|
||||
return err
|
||||
}
|
||||
|
||||
type sortableGroupResource []groupResource
|
||||
|
||||
func (s sortableGroupResource) Len() int { return len(s) }
|
||||
func (s sortableGroupResource) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s sortableGroupResource) Less(i, j int) bool {
|
||||
ret := strings.Compare(s[i].APIGroup, s[j].APIGroup)
|
||||
if ret > 0 {
|
||||
return false
|
||||
} else if ret == 0 {
|
||||
return strings.Compare(s[i].APIResource.Name, s[j].APIResource.Name) < 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
89
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiversions.go
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiversions.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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"
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
apiversionsExample = templates.Examples(i18n.T(`
|
||||
# Print the supported API versions
|
||||
kubectl api-versions`))
|
||||
)
|
||||
|
||||
type ApiVersionsOptions struct {
|
||||
discoveryClient discovery.CachedDiscoveryInterface
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewApiVersionsOptions(ioStreams genericclioptions.IOStreams) *ApiVersionsOptions {
|
||||
return &ApiVersionsOptions{
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdApiVersions(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewApiVersionsOptions(ioStreams)
|
||||
cmd := &cobra.Command{
|
||||
Use: "api-versions",
|
||||
Short: "Print the supported API versions on the server, in the form of \"group/version\"",
|
||||
Long: "Print the supported API versions on the server, in the form of \"group/version\"",
|
||||
Example: apiversionsExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f))
|
||||
cmdutil.CheckErr(o.RunApiVersions())
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ApiVersionsOptions) Complete(f cmdutil.Factory) error {
|
||||
var err error
|
||||
o.discoveryClient, err = f.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ApiVersionsOptions) RunApiVersions() error {
|
||||
// Always request fresh data from the server
|
||||
o.discoveryClient.Invalidate()
|
||||
|
||||
groupList, err := o.discoveryClient.ServerGroups()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't get available api versions from server: %v\n", err)
|
||||
}
|
||||
apiVersions := metav1.ExtractGroupVersions(groupList)
|
||||
sort.Strings(apiVersions)
|
||||
for _, v := range apiVersions {
|
||||
fmt.Fprintln(o.Out, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
791
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply.go
generated
vendored
Normal file
791
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply.go
generated
vendored
Normal file
@@ -0,0 +1,791 @@
|
||||
/*
|
||||
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/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"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"
|
||||
"k8s.io/client-go/dynamic"
|
||||
oapi "k8s.io/kube-openapi/pkg/util/proto"
|
||||
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/openapi"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
"k8s.io/kubernetes/pkg/kubectl/validation"
|
||||
)
|
||||
|
||||
type ApplyOptions struct {
|
||||
RecordFlags *genericclioptions.RecordFlags
|
||||
Recorder genericclioptions.Recorder
|
||||
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
ToPrinter func(string) (printers.ResourcePrinter, error)
|
||||
|
||||
DeleteFlags *DeleteFlags
|
||||
DeleteOptions *DeleteOptions
|
||||
|
||||
Selector string
|
||||
DryRun bool
|
||||
Prune bool
|
||||
PruneResources []pruneResource
|
||||
cmdBaseName string
|
||||
All bool
|
||||
Overwrite bool
|
||||
OpenApiPatch bool
|
||||
PruneWhitelist []string
|
||||
ShouldIncludeUninitialized bool
|
||||
|
||||
Validator validation.Schema
|
||||
Builder *resource.Builder
|
||||
Mapper meta.RESTMapper
|
||||
DynamicClient dynamic.Interface
|
||||
OpenAPISchema openapi.Resources
|
||||
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
const (
|
||||
// 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 NewApplyOptions(ioStreams genericclioptions.IOStreams) *ApplyOptions {
|
||||
return &ApplyOptions{
|
||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||
DeleteFlags: NewDeleteFlags("that contains the configuration to apply"),
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
|
||||
Overwrite: true,
|
||||
OpenApiPatch: true,
|
||||
|
||||
Recorder: genericclioptions.NoopRecorder{},
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewApplyOptions(ioStreams)
|
||||
|
||||
// Store baseName for use in printing warnings / messages involving the base command name.
|
||||
// This is useful for downstream command that wrap this one.
|
||||
o.cmdBaseName = baseName
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "apply -f FILENAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
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(o.Complete(f, cmd))
|
||||
cmdutil.CheckErr(validateArgs(cmd, args))
|
||||
cmdutil.CheckErr(validatePruneAll(o.Prune, o.All, o.Selector))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
// bind flag structs
|
||||
o.DeleteFlags.AddFlags(cmd)
|
||||
o.RecordFlags.AddFlags(cmd)
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmd.MarkFlagRequired("filename")
|
||||
cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration")
|
||||
cmd.Flags().BoolVar(&o.Prune, "prune", o.Prune, "Automatically delete resource objects, including the uninitialized ones, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.")
|
||||
cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune")
|
||||
cmd.Flags().BoolVar(&o.OpenApiPatch, "openapi-patch", o.OpenApiPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddIncludeUninitializedFlag(cmd)
|
||||
|
||||
// apply subcommands
|
||||
cmd.AddCommand(NewCmdApplyViewLastApplied(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdApplySetLastApplied(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdApplyEditLastApplied(f, ioStreams))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
o.DryRun = cmdutil.GetDryRunFlag(cmd)
|
||||
|
||||
// allow for a success message operation to be specified at print time
|
||||
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
|
||||
o.PrintFlags.NamePrintFlags.Operation = operation
|
||||
if o.DryRun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
|
||||
return o.PrintFlags.ToPrinter()
|
||||
}
|
||||
|
||||
var err error
|
||||
o.RecordFlags.Complete(cmd)
|
||||
o.Recorder, err = o.RecordFlags.ToRecorder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dynamicClient, err := f.DynamicClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
|
||||
o.ShouldIncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, o.Prune)
|
||||
|
||||
o.OpenAPISchema, _ = f.OpenAPISchema()
|
||||
o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
|
||||
o.Builder = f.NewBuilder()
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DynamicClient, err = f.DynamicClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArgs(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePruneAll(prune, all bool, selector string) error {
|
||||
if all && len(selector) > 0 {
|
||||
return fmt.Errorf("cannot set --all and --selector at the same time")
|
||||
}
|
||||
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 (o *ApplyOptions) Run() error {
|
||||
var openapiSchema openapi.Resources
|
||||
if o.OpenApiPatch {
|
||||
openapiSchema = o.OpenAPISchema
|
||||
}
|
||||
|
||||
// include the uninitialized objects by default if --prune is true
|
||||
// unless explicitly set --include-uninitialized=false
|
||||
r := o.Builder.
|
||||
Unstructured().
|
||||
Schema(o.Validator).
|
||||
ContinueOnError().
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().
|
||||
FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
|
||||
LabelSelectorParam(o.Selector).
|
||||
IncludeUninitialized(o.ShouldIncludeUninitialized).
|
||||
Flatten().
|
||||
Do()
|
||||
if err := r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
if o.Prune {
|
||||
o.PruneResources, err = parsePruneResources(o.Mapper, o.PruneWhitelist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
output := *o.PrintFlags.OutputFormat
|
||||
shortOutput := output == "name"
|
||||
|
||||
visitedUids := sets.NewString()
|
||||
visitedNamespaces := sets.NewString()
|
||||
|
||||
var objs []runtime.Object
|
||||
|
||||
count := 0
|
||||
err = r.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.Namespaced() {
|
||||
visitedNamespaces.Insert(info.Namespace)
|
||||
}
|
||||
|
||||
if err := o.Recorder.Record(info.Object); err != nil {
|
||||
glog.V(4).Infof("error recording current command: %v", err)
|
||||
}
|
||||
|
||||
// Get the modified configuration of the object. Embed the result
|
||||
// as an annotation in the modified configuration, so that it will appear
|
||||
// in the patch sent to the server.
|
||||
modified, err := kubectl.GetModifiedConfiguration(info.Object, true, unstructured.UnstructuredJSONScheme)
|
||||
if err != nil {
|
||||
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%s\nfor:", info.String()), info.Source, err)
|
||||
}
|
||||
|
||||
// Print object only if output format other than "name" is specified
|
||||
printObject := len(output) > 0 && !shortOutput
|
||||
|
||||
if err := info.Get(); err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
|
||||
}
|
||||
// Create the resource if it doesn't exist
|
||||
// First, update the annotation used by kubectl apply
|
||||
if err := kubectl.CreateApplyAnnotation(info.Object, unstructured.UnstructuredJSONScheme); err != nil {
|
||||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||
}
|
||||
|
||||
if !o.DryRun {
|
||||
// Then create the resource and skip the three-way merge
|
||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||
if err != nil {
|
||||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||
}
|
||||
info.Refresh(obj, true)
|
||||
metadata, err := meta.Accessor(info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
visitedUids.Insert(string(metadata.GetUID()))
|
||||
}
|
||||
|
||||
count++
|
||||
|
||||
if printObject {
|
||||
objs = append(objs, info.Object)
|
||||
return nil
|
||||
}
|
||||
|
||||
printer, err := o.ToPrinter("created")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(info.Object, o.Out)
|
||||
}
|
||||
|
||||
if !o.DryRun {
|
||||
metadata, err := meta.Accessor(info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
annotationMap := metadata.GetAnnotations()
|
||||
if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok {
|
||||
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
|
||||
}
|
||||
|
||||
helper := resource.NewHelper(info.Client, info.Mapping)
|
||||
patcher := &patcher{
|
||||
mapping: info.Mapping,
|
||||
helper: helper,
|
||||
dynamicClient: o.DynamicClient,
|
||||
overwrite: o.Overwrite,
|
||||
backOff: clockwork.NewRealClock(),
|
||||
force: o.DeleteOptions.ForceDeletion,
|
||||
cascade: o.DeleteOptions.Cascade,
|
||||
timeout: o.DeleteOptions.Timeout,
|
||||
gracePeriod: o.DeleteOptions.GracePeriod,
|
||||
openapiSchema: openapiSchema,
|
||||
}
|
||||
|
||||
patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
|
||||
if err != nil {
|
||||
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
|
||||
}
|
||||
|
||||
info.Refresh(patchedObject, true)
|
||||
|
||||
visitedUids.Insert(string(metadata.GetUID()))
|
||||
|
||||
if string(patchBytes) == "{}" && !printObject {
|
||||
count++
|
||||
|
||||
printer, err := o.ToPrinter("unchanged")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(info.Object, o.Out)
|
||||
}
|
||||
}
|
||||
count++
|
||||
|
||||
if printObject {
|
||||
objs = append(objs, info.Object)
|
||||
return nil
|
||||
}
|
||||
|
||||
printer, err := o.ToPrinter("configured")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(info.Object, o.Out)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return fmt.Errorf("no objects passed to apply")
|
||||
}
|
||||
|
||||
// print objects
|
||||
if len(objs) > 0 {
|
||||
printer, err := o.ToPrinter("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objToPrint := objs[0]
|
||||
if len(objs) > 1 {
|
||||
list := &v1.List{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "List",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ListMeta: metav1.ListMeta{},
|
||||
}
|
||||
if err := meta.SetList(list, objs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objToPrint = list
|
||||
}
|
||||
if err := printer.PrintObj(objToPrint, o.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !o.Prune {
|
||||
return nil
|
||||
}
|
||||
|
||||
p := pruner{
|
||||
mapper: o.Mapper,
|
||||
dynamicClient: o.DynamicClient,
|
||||
|
||||
labelSelector: o.Selector,
|
||||
visitedUids: visitedUids,
|
||||
|
||||
cascade: o.DeleteOptions.Cascade,
|
||||
dryRun: o.DryRun,
|
||||
gracePeriod: o.DeleteOptions.GracePeriod,
|
||||
|
||||
toPrinter: o.ToPrinter,
|
||||
|
||||
out: o.Out,
|
||||
}
|
||||
|
||||
namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(o.Mapper, &(o.PruneResources))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving RESTMappings to prune: %v", err)
|
||||
}
|
||||
|
||||
for n := range visitedNamespaces {
|
||||
for _, m := range namespacedRESTMappings {
|
||||
if err := p.prune(n, m, o.ShouldIncludeUninitialized); err != nil {
|
||||
return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, m := range nonNamespacedRESTMappings {
|
||||
if err := p.prune(metav1.NamespaceNone, m, o.ShouldIncludeUninitialized); 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},
|
||||
{"batch", "v1beta1", "CronJob", 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
|
||||
dynamicClient dynamic.Interface
|
||||
|
||||
visitedUids sets.String
|
||||
labelSelector string
|
||||
fieldSelector string
|
||||
|
||||
cascade bool
|
||||
dryRun bool
|
||||
gracePeriod int
|
||||
|
||||
toPrinter func(string) (printers.ResourcePrinter, error)
|
||||
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, includeUninitialized bool) error {
|
||||
objList, err := p.dynamicClient.Resource(mapping.Resource).
|
||||
Namespace(namespace).
|
||||
List(metav1.ListOptions{
|
||||
LabelSelector: p.labelSelector,
|
||||
FieldSelector: p.fieldSelector,
|
||||
IncludeUninitialized: includeUninitialized,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objs, err := meta.ExtractList(objList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, obj := range objs {
|
||||
metadata, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
annots := metadata.GetAnnotations()
|
||||
if _, ok := annots[api.LastAppliedConfigAnnotation]; !ok {
|
||||
// don't prune resources not created with apply
|
||||
continue
|
||||
}
|
||||
uid := metadata.GetUID()
|
||||
if p.visitedUids.Has(string(uid)) {
|
||||
continue
|
||||
}
|
||||
name := metadata.GetName()
|
||||
if !p.dryRun {
|
||||
if err := p.delete(namespace, name, mapping); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
printer, err := p.toPrinter("pruned")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printer.PrintObj(obj, p.out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping) error {
|
||||
return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod)
|
||||
}
|
||||
|
||||
func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascade bool, gracePeriod int) error {
|
||||
options := &metav1.DeleteOptions{}
|
||||
if gracePeriod >= 0 {
|
||||
options = metav1.NewDeleteOptions(int64(gracePeriod))
|
||||
}
|
||||
policy := metav1.DeletePropagationForeground
|
||||
if !cascade {
|
||||
policy = metav1.DeletePropagationOrphan
|
||||
}
|
||||
options.PropagationPolicy = &policy
|
||||
return c.Resource(mapping.Resource).Namespace(namespace).Delete(name, options)
|
||||
}
|
||||
|
||||
func (p *patcher) delete(namespace, name string) error {
|
||||
return runDelete(namespace, name, p.mapping, p.dynamicClient, p.cascade, p.gracePeriod)
|
||||
}
|
||||
|
||||
type patcher struct {
|
||||
mapping *meta.RESTMapping
|
||||
helper *resource.Helper
|
||||
dynamicClient dynamic.Interface
|
||||
|
||||
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(unstructured.UnstructuredJSONScheme, obj)
|
||||
if err != nil {
|
||||
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", obj), source, err)
|
||||
}
|
||||
|
||||
// Retrieve the original configuration of the object from the annotation.
|
||||
original, err := kubectl.GetOriginalConfiguration(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 && errors.IsConflict(err) && p.force {
|
||||
patchBytes, patchObject, err = p.deleteAndCreate(current, modified, namespace, name)
|
||||
}
|
||||
return patchBytes, patchObject, err
|
||||
}
|
||||
|
||||
func (p *patcher) deleteAndCreate(original runtime.Object, modified []byte, namespace, name string) ([]byte, runtime.Object, error) {
|
||||
if err := p.delete(namespace, name); err != nil {
|
||||
return modified, nil, err
|
||||
}
|
||||
// TODO: use wait
|
||||
if err := wait.PollImmediate(1*time.Second, p.timeout, func() (bool, error) {
|
||||
if _, err := p.helper.Get(namespace, name, false); !errors.IsNotFound(err) {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
return modified, nil, err
|
||||
}
|
||||
versionedObject, _, err := unstructured.UnstructuredJSONScheme.Decode(modified, nil, nil)
|
||||
if err != nil {
|
||||
return modified, nil, err
|
||||
}
|
||||
createdObject, err := p.helper.Create(namespace, true, versionedObject)
|
||||
if err != nil {
|
||||
// restore the original object if we fail to create the new one
|
||||
// but still propagate and advertise error to user
|
||||
recreated, recreateErr := p.helper.Create(namespace, true, original)
|
||||
if recreateErr != nil {
|
||||
err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v\n", err, recreateErr)
|
||||
} else {
|
||||
createdObject = recreated
|
||||
}
|
||||
}
|
||||
return modified, createdObject, err
|
||||
}
|
||||
88
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_edit_last_applied.go
generated
vendored
Normal file
88
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_edit_last_applied.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
var (
|
||||
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, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := editor.NewEditOptions(editor.ApplyEditMode, ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit-last-applied (RESOURCE/NAME | -f FILENAME)",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Edit latest last-applied-configuration annotations of a resource/object",
|
||||
Long: applyEditLastAppliedLong,
|
||||
Example: applyEditLastAppliedExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := o.Complete(f, args, cmd); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
if err := o.Run(); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// bind flag structs
|
||||
o.RecordFlags.AddFlags(cmd)
|
||||
|
||||
usage := "to use to edit the resource"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
|
||||
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: yaml|json.")
|
||||
cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings,
|
||||
"Defaults to the line ending native to your platform.")
|
||||
cmdutil.AddIncludeUninitializedFlag(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
213
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_set_last_applied.go
generated
vendored
Normal file
213
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_set_last_applied.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
type SetLastAppliedOptions struct {
|
||||
CreateAnnotation bool
|
||||
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
FilenameOptions resource.FilenameOptions
|
||||
|
||||
infoList []*resource.Info
|
||||
namespace string
|
||||
enforceNamespace bool
|
||||
dryRun bool
|
||||
shortOutput bool
|
||||
output string
|
||||
patchBufferList []PatchBuffer
|
||||
builder *resource.Builder
|
||||
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
type PatchBuffer struct {
|
||||
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 NewSetLastAppliedOptions(ioStreams genericclioptions.IOStreams) *SetLastAppliedOptions {
|
||||
return &SetLastAppliedOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("configured").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdApplySetLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewSetLastAppliedOptions(ioStreams)
|
||||
cmd := &cobra.Command{
|
||||
Use: "set-last-applied -f FILENAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
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(o.Complete(f, cmd))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.RunSetLastApplied())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().BoolVar(&o.CreateAnnotation, "create-annotation", o.CreateAnnotation, "Will create 'last-applied-configuration' annotations if current objects doesn't have one")
|
||||
cmdutil.AddJsonFilenameFlag(cmd.Flags(), &o.FilenameOptions.Filenames, "Filename, directory, or URL to files that contains the last-applied-configuration annotations")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
o.dryRun = cmdutil.GetDryRunFlag(cmd)
|
||||
o.output = cmdutil.GetFlagString(cmd, "output")
|
||||
o.shortOutput = o.output == "name"
|
||||
|
||||
var err error
|
||||
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.builder = f.NewBuilder()
|
||||
o.unstructuredClientForMapping = f.UnstructuredClientForMapping
|
||||
|
||||
if o.dryRun {
|
||||
// TODO(juanvallejo): This can be cleaned up even further by creating
|
||||
// a PrintFlags struct that binds the --dry-run flag, and whose
|
||||
// ToPrinter method returns a printer that understands how to print
|
||||
// this success message.
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = printer.PrintObj
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *SetLastAppliedOptions) Validate() error {
|
||||
r := o.builder.
|
||||
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.(runtime.Unstructured))
|
||||
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%s\nfrom server for:", info.String()), info.Source, err)
|
||||
}
|
||||
}
|
||||
originalBuf, err := kubectl.GetOriginalConfiguration(info.Object)
|
||||
if err != nil {
|
||||
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
|
||||
}
|
||||
if originalBuf == nil && !o.CreateAnnotation {
|
||||
return fmt.Errorf("no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name)
|
||||
}
|
||||
|
||||
//only add to PatchBufferList when changed
|
||||
if !bytes.Equal(cmdutil.StripComments(originalBuf), cmdutil.StripComments(diffBuf)) {
|
||||
p := PatchBuffer{Patch: patchBuf, PatchType: patchType}
|
||||
o.patchBufferList = append(o.patchBufferList, p)
|
||||
o.infoList = append(o.infoList, info)
|
||||
} else {
|
||||
fmt.Fprintf(o.Out, "set-last-applied %s: no changes required.\n", info.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *SetLastAppliedOptions) RunSetLastApplied() error {
|
||||
for i, patch := range o.patchBufferList {
|
||||
info := o.infoList[i]
|
||||
finalObj := info.Object
|
||||
|
||||
if !o.dryRun {
|
||||
mapping := info.ResourceMapping()
|
||||
client, err := o.unstructuredClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
helper := resource.NewHelper(client, mapping)
|
||||
finalObj, err = helper.Patch(o.namespace, info.Name, patch.PatchType, patch.Patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := o.PrintObj(finalObj, o.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1324
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_test.go
generated
vendored
Normal file
1324
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
166
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_view_last_applied.go
generated
vendored
Normal file
166
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_view_last_applied.go
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"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/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/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
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
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 NewViewLastAppliedOptions(ioStreams genericclioptions.IOStreams) *ViewLastAppliedOptions {
|
||||
return &ViewLastAppliedOptions{
|
||||
OutputFormat: "yaml",
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdApplyViewLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := NewViewLastAppliedOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "view-last-applied (TYPE [NAME | -l label] | TYPE/NAME | -f FILENAME)",
|
||||
DisableFlagsInUseLine: true,
|
||||
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.Complete(cmd, f, args))
|
||||
cmdutil.CheckErr(options.Validate(cmd))
|
||||
cmdutil.CheckErr(options.RunApplyViewLastApplied(cmd))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&options.OutputFormat, "output", "o", options.OutputFormat, "Output format. Must be one of yaml|json")
|
||||
cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||
cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all resources in the namespace of the specified resource types")
|
||||
usage := "that contains the last-applied-configuration annotations"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ViewLastAppliedOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string) error {
|
||||
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
|
||||
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.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(cmd *cobra.Command) 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))
|
||||
default:
|
||||
return cmdutil.UsageErrorf(
|
||||
cmd,
|
||||
"Unexpected -o output mode: %s, the flag 'output' must be one of yaml|json",
|
||||
o.OutputFormat)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
341
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.go
generated
vendored
Normal file
341
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
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/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
|
||||
"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, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := &AttachOptions{
|
||||
StreamOptions: StreamOptions{
|
||||
IOStreams: streams,
|
||||
},
|
||||
|
||||
Attach: &DefaultRemoteAttach{},
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "attach (POD | TYPE/NAME) -c CONTAINER",
|
||||
DisableFlagsInUseLine: true,
|
||||
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(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
|
||||
cmd.Flags().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name. If omitted, the first container in the pod will be chosen")
|
||||
cmd.Flags().BoolVarP(&o.Stdin, "stdin", "i", o.Stdin, "Pass stdin to the container")
|
||||
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, "Stdin is a TTY")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// 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
|
||||
SuggestedCmdUsage string
|
||||
|
||||
Pod *api.Pod
|
||||
|
||||
Attach RemoteAttach
|
||||
PodClient coreclient.PodsGetter
|
||||
GetPodTimeout time.Duration
|
||||
Config *restclient.Config
|
||||
}
|
||||
|
||||
// Complete verifies command line arguments and loads data from the command environment
|
||||
func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []string) error {
|
||||
if len(argsIn) == 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "at least 1 argument is required for attach")
|
||||
}
|
||||
if len(argsIn) > 2 {
|
||||
return cmdutil.UsageErrorf(cmd, "expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v", len(argsIn), argsIn)
|
||||
}
|
||||
|
||||
namespace, _, err := f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
|
||||
if err != nil {
|
||||
return cmdutil.UsageErrorf(cmd, err.Error())
|
||||
}
|
||||
|
||||
builder := f.NewBuilder().
|
||||
WithScheme(legacyscheme.Scheme).
|
||||
NamespaceParam(namespace).DefaultNamespace()
|
||||
|
||||
switch len(argsIn) {
|
||||
case 1:
|
||||
builder.ResourceNames("pods", argsIn[0])
|
||||
case 2:
|
||||
builder.ResourceNames(argsIn[0], argsIn[1])
|
||||
}
|
||||
|
||||
obj, err := builder.Do().Object()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attachablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, obj, p.GetPodTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.PodName = attachablePod.Name
|
||||
p.Namespace = namespace
|
||||
|
||||
fullCmdName := ""
|
||||
cmdParent := cmd.Parent()
|
||||
if cmdParent != nil {
|
||||
fullCmdName = cmdParent.CommandPath()
|
||||
}
|
||||
if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
|
||||
p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", fullCmdName, p.PodName, p.Namespace)
|
||||
}
|
||||
|
||||
config, err := f.ToRESTConfig()
|
||||
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.ErrOut == nil {
|
||||
allErrs = append(allErrs, errors.New("both output and error output must be provided"))
|
||||
}
|
||||
if p.Attach == nil || p.PodClient == nil || p.Config == nil {
|
||||
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.ErrOut != nil {
|
||||
fmt.Fprintf(p.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
|
||||
}
|
||||
} else if !p.TTY && containerToAttach.TTY {
|
||||
// the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get
|
||||
// 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.ErrOut
|
||||
|
||||
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.ErrOut = nil
|
||||
}
|
||||
|
||||
fn := func() error {
|
||||
restClient, err := restclient.RESTClientFor(p.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: consider abstracting into a client invocation or client helper
|
||||
req := restClient.Post().
|
||||
Resource("pods").
|
||||
Name(pod.Name).
|
||||
Namespace(pod.Namespace).
|
||||
SubResource("attach")
|
||||
req.VersionedParams(&api.PodAttachOptions{
|
||||
Container: containerToAttach.Name,
|
||||
Stdin: p.Stdin,
|
||||
Stdout: p.Out != nil,
|
||||
Stderr: p.ErrOut != nil,
|
||||
TTY: t.Raw,
|
||||
}, legacyscheme.ParameterCodec)
|
||||
|
||||
return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
|
||||
}
|
||||
|
||||
if !p.Quiet && stderr != nil {
|
||||
fmt.Fprintln(stderr, "If you don't see a command prompt, try pressing enter.")
|
||||
}
|
||||
if err := t.Safe(fn); err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
if len(p.SuggestedCmdUsage) > 0 {
|
||||
fmt.Fprintf(p.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
|
||||
fmt.Fprintf(p.ErrOut, "%s\n", p.SuggestedCmdUsage)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
391
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach_test.go
generated
vendored
Normal file
391
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach_test.go
generated
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/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"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if test.obj != nil {
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.obj)}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
options := test.p
|
||||
cmdutil.AddPodRunningTimeoutFlag(cmd, test.timeout)
|
||||
|
||||
err := options.Complete(tf, cmd, test.args)
|
||||
if test.expectError && err == nil {
|
||||
t.Errorf("%s: unexpected non-error", test.name)
|
||||
}
|
||||
if !test.expectError && err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if options.PodName != test.expectedPod {
|
||||
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName)
|
||||
}
|
||||
if options.ContainerName != test.expectedContainer {
|
||||
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedContainer, options.ContainerName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttach(t *testing.T) {
|
||||
version := "v1"
|
||||
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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == test.podPath && m == "GET":
|
||||
body := objBody(codec, test.pod)
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
||||
case p == test.fetchPodPath && m == "GET":
|
||||
body := objBody(codec, test.pod)
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
||||
default:
|
||||
t.Errorf("%s: unexpected request: %s %#v\n%#v", p, req.Method, req.URL, req)
|
||||
return nil, fmt.Errorf("unexpected request")
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
|
||||
remoteAttach := &fakeRemoteAttach{}
|
||||
if test.remoteAttachErr {
|
||||
remoteAttach.err = fmt.Errorf("attach error")
|
||||
}
|
||||
params := &AttachOptions{
|
||||
StreamOptions: StreamOptions{
|
||||
ContainerName: test.container,
|
||||
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
|
||||
},
|
||||
Attach: remoteAttach,
|
||||
GetPodTimeout: 1000,
|
||||
}
|
||||
cmd := &cobra.Command{}
|
||||
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
|
||||
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := params.Run()
|
||||
if test.exepctedErr != "" && err.Error() != test.exepctedErr {
|
||||
t.Errorf("%s: Unexpected exec error: %v", test.name, err)
|
||||
return
|
||||
}
|
||||
if test.exepctedErr == "" && err != nil {
|
||||
t.Errorf("%s: Unexpected error: %v", test.name, err)
|
||||
return
|
||||
}
|
||||
if test.exepctedErr != "" {
|
||||
return
|
||||
}
|
||||
if remoteAttach.url.Path != test.attachPath {
|
||||
t.Errorf("%s: Did not get expected path for exec request: %q %q", test.name, test.attachPath, remoteAttach.url.Path)
|
||||
return
|
||||
}
|
||||
if remoteAttach.method != "POST" {
|
||||
t.Errorf("%s: Did not get method for attach request: %s", test.name, remoteAttach.method)
|
||||
}
|
||||
if remoteAttach.url.Query().Get("container") != "bar" {
|
||||
t.Errorf("%s: Did not have query parameters: %s", test.name, remoteAttach.url.Query())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachWarnings(t *testing.T) {
|
||||
version := "v1"
|
||||
tests := []struct {
|
||||
name, container, version, podPath, fetchPodPath, expectedErr 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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == test.podPath && m == "GET":
|
||||
body := objBody(codec, test.pod)
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
||||
case p == test.fetchPodPath && m == "GET":
|
||||
body := objBody(codec, test.pod)
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
|
||||
default:
|
||||
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
|
||||
return nil, fmt.Errorf("unexpected request")
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
|
||||
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
|
||||
ex := &fakeRemoteAttach{}
|
||||
params := &AttachOptions{
|
||||
StreamOptions: StreamOptions{
|
||||
ContainerName: test.container,
|
||||
IOStreams: streams,
|
||||
Stdin: test.stdin,
|
||||
TTY: test.tty,
|
||||
},
|
||||
Attach: ex,
|
||||
GetPodTimeout: 1000,
|
||||
}
|
||||
cmd := &cobra.Command{}
|
||||
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
|
||||
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := params.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if test.stdin && test.tty {
|
||||
if !test.pod.Spec.Containers[0].TTY {
|
||||
if !strings.Contains(bufErr.String(), test.expectedErr) {
|
||||
t.Errorf("%s: Expected TTY fallback warning for attach request: %s", test.name, bufErr.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
64
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/BUILD
generated
vendored
Normal file
64
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/BUILD
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
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/client/clientset_generated/internalclientset/typed/authorization/internalversion:go_default_library",
|
||||
"//pkg/kubectl/cmd/templates:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/printers:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/resource:go_default_library",
|
||||
"//pkg/kubectl/scheme:go_default_library",
|
||||
"//pkg/registry/rbac/reconciliation:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
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"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
|
||||
],
|
||||
)
|
||||
39
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/auth.go
generated
vendored
Normal file
39
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/auth.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
)
|
||||
|
||||
func NewCmdAuth(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
// Parent command to which all subcommands are added.
|
||||
cmds := &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Inspect authorization",
|
||||
Long: `Inspect authorization`,
|
||||
Run: cmdutil.DefaultSubCommandRun(streams.ErrOut),
|
||||
}
|
||||
|
||||
cmds.AddCommand(NewCmdCanI(f, streams))
|
||||
cmds.AddCommand(NewCmdReconcile(f, streams))
|
||||
|
||||
return cmds
|
||||
}
|
||||
242
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/cani.go
generated
vendored
Normal file
242
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/cani.go
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
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/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
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
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
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, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := &CanIOptions{
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "can-i VERB [TYPE | TYPE/NAME | NONRESOURCEURL]",
|
||||
DisableFlagsInUseLine: true,
|
||||
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 !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", o.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, err := f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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.ToRawKubeConfigLoader().Namespace()
|
||||
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.ErrOut, "Warning: the server doesn't have a resource type '%s'\n", groupResource.Resource)
|
||||
} else {
|
||||
fmt.Fprintf(o.ErrOut, "Warning: the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group)
|
||||
}
|
||||
return schema.GroupVersionResource{Resource: resourceArg}
|
||||
}
|
||||
}
|
||||
|
||||
return gvr
|
||||
}
|
||||
183
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/cani_test.go
generated
vendored
Normal file
183
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/cani_test.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
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"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.o.Out = ioutil.Discard
|
||||
test.o.ErrOut = ioutil.Discard
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
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.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}
|
||||
|
||||
if err := test.o.Complete(tf, test.args); err != nil {
|
||||
t.Errorf("%s: %v", test.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
actualAllowed, err := test.o.RunAccessCheck()
|
||||
switch {
|
||||
case test.serverErr == nil && err == nil:
|
||||
// pass
|
||||
case err != nil && test.serverErr != nil && strings.Contains(err.Error(), test.serverErr.Error()):
|
||||
// pass
|
||||
default:
|
||||
t.Errorf("%s: expected %v, got %v", test.name, test.serverErr, err)
|
||||
return
|
||||
}
|
||||
if actualAllowed != test.allowed {
|
||||
t.Errorf("%s: expected %v, got %v", test.name, test.allowed, actualAllowed)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
243
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/reconcile.go
generated
vendored
Normal file
243
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/reconcile.go
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
rbacv1client "k8s.io/client-go/kubernetes/typed/rbac/v1"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/reconciliation"
|
||||
)
|
||||
|
||||
// ReconcileOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
||||
// referencing the cmd.Flags()
|
||||
type ReconcileOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
FilenameOptions *resource.FilenameOptions
|
||||
DryRun bool
|
||||
|
||||
Visitor resource.Visitor
|
||||
RBACClient rbacv1client.RbacV1Interface
|
||||
NamespaceClient corev1client.CoreV1Interface
|
||||
|
||||
PrintObject printers.ResourcePrinterFunc
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
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 NewReconcileOptions(ioStreams genericclioptions.IOStreams) *ReconcileOptions {
|
||||
return &ReconcileOptions{
|
||||
FilenameOptions: &resource.FilenameOptions{},
|
||||
PrintFlags: genericclioptions.NewPrintFlags("reconciled").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdReconcile(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewReconcileOptions(streams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "reconcile -f FILENAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
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))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.RunReconcile())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to reconcile.")
|
||||
cmd.Flags().BoolVar(&o.DryRun, "dry-run", o.DryRun, "If true, display results but do not submit changes")
|
||||
cmd.MarkFlagRequired("filename")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return errors.New("no arguments are allowed")
|
||||
}
|
||||
|
||||
namespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := f.NewBuilder().
|
||||
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
|
||||
ContinueOnError().
|
||||
NamespaceParam(namespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, o.FilenameOptions).
|
||||
Flatten().
|
||||
Do()
|
||||
|
||||
if err := r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
o.Visitor = r
|
||||
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.RBACClient, err = rbacv1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.NamespaceClient, err = corev1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObject = printer.PrintObj
|
||||
return nil
|
||||
}
|
||||
|
||||
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.PrintObject == nil {
|
||||
return errors.New("ReconcileOptions.Print must be set")
|
||||
}
|
||||
if o.Out == nil {
|
||||
return errors.New("ReconcileOptions.Out must be set")
|
||||
}
|
||||
if o.ErrOut == 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
|
||||
}
|
||||
|
||||
switch t := info.Object.(type) {
|
||||
case *rbacv1.Role:
|
||||
reconcileOptions := reconciliation.ReconcileRoleOptions{
|
||||
Confirm: !o.DryRun,
|
||||
RemoveExtraPermissions: false,
|
||||
Role: reconciliation.RoleRuleOwner{Role: t},
|
||||
Client: reconciliation.RoleModifier{
|
||||
NamespaceClient: o.NamespaceClient.Namespaces(),
|
||||
Client: o.RBACClient,
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObject(result.Role.GetObject(), o.Out)
|
||||
|
||||
case *rbacv1.ClusterRole:
|
||||
reconcileOptions := reconciliation.ReconcileRoleOptions{
|
||||
Confirm: !o.DryRun,
|
||||
RemoveExtraPermissions: false,
|
||||
Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: t},
|
||||
Client: reconciliation.ClusterRoleModifier{
|
||||
Client: o.RBACClient.ClusterRoles(),
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObject(result.Role.GetObject(), o.Out)
|
||||
|
||||
case *rbacv1.RoleBinding:
|
||||
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
|
||||
Confirm: !o.DryRun,
|
||||
RemoveExtraSubjects: false,
|
||||
RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: t},
|
||||
Client: reconciliation.RoleBindingClientAdapter{
|
||||
Client: o.RBACClient,
|
||||
NamespaceClient: o.NamespaceClient.Namespaces(),
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObject(result.RoleBinding.GetObject(), o.Out)
|
||||
|
||||
case *rbacv1.ClusterRoleBinding:
|
||||
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
|
||||
Confirm: !o.DryRun,
|
||||
RemoveExtraSubjects: false,
|
||||
RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: t},
|
||||
Client: reconciliation.ClusterRoleBindingClientAdapter{
|
||||
Client: o.RBACClient.ClusterRoleBindings(),
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObject(result.RoleBinding.GetObject(), o.Out)
|
||||
|
||||
default:
|
||||
glog.V(1).Infof("skipping %#v", info.Object.GetObjectKind())
|
||||
// skip ignored resources
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
272
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/autoscale.go
generated
vendored
Normal file
272
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/autoscale.go
generated
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
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`))
|
||||
)
|
||||
|
||||
type AutoscaleOptions struct {
|
||||
FilenameOptions *resource.FilenameOptions
|
||||
|
||||
RecordFlags *genericclioptions.RecordFlags
|
||||
Recorder genericclioptions.Recorder
|
||||
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
ToPrinter func(string) (printers.ResourcePrinter, error)
|
||||
|
||||
Name string
|
||||
Generator string
|
||||
Min int32
|
||||
Max int32
|
||||
CpuPercent int32
|
||||
|
||||
createAnnotation bool
|
||||
args []string
|
||||
enforceNamespace bool
|
||||
namespace string
|
||||
dryRun bool
|
||||
builder *resource.Builder
|
||||
canBeAutoscaled polymorphichelpers.CanBeAutoscaledFunc
|
||||
generatorFunc func(string, *meta.RESTMapping) (kubectl.StructuredGenerator, error)
|
||||
|
||||
HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions {
|
||||
return &AutoscaleOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("autoscaled").WithTypeSetter(scheme.Scheme),
|
||||
FilenameOptions: &resource.FilenameOptions{},
|
||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||
Recorder: genericclioptions.NoopRecorder{},
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewAutoscaleOptions(ioStreams)
|
||||
|
||||
validArgs := []string{"deployment", "replicaset", "replicationcontroller"}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Auto-scale a Deployment, ReplicaSet, or ReplicationController"),
|
||||
Long: autoscaleLong,
|
||||
Example: autoscaleExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
ValidArgs: validArgs,
|
||||
}
|
||||
|
||||
// bind flag structs
|
||||
o.RecordFlags.AddFlags(cmd)
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmd.Flags().StringVar(&o.Generator, "generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
|
||||
cmd.Flags().Int32Var(&o.Min, "min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
|
||||
cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
|
||||
cmd.MarkFlagRequired("max")
|
||||
cmd.Flags().Int32Var(&o.CpuPercent, "cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used."))
|
||||
cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.dryRun = cmdutil.GetFlagBool(cmd, "dry-run")
|
||||
o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
o.builder = f.NewBuilder()
|
||||
o.canBeAutoscaled = polymorphichelpers.CanBeAutoscaledFn
|
||||
o.args = args
|
||||
o.RecordFlags.Complete(cmd)
|
||||
|
||||
o.Recorder, err = o.RecordFlags.ToRecorder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeClient, err := f.KubernetesClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.HPAClient = kubeClient.AutoscalingV1()
|
||||
|
||||
// get the generator
|
||||
o.generatorFunc = func(name string, mapping *meta.RESTMapping) (kubectl.StructuredGenerator, error) {
|
||||
switch o.Generator {
|
||||
case cmdutil.HorizontalPodAutoscalerV1GeneratorName:
|
||||
return &kubectl.HorizontalPodAutoscalerGeneratorV1{
|
||||
Name: name,
|
||||
MinReplicas: o.Min,
|
||||
MaxReplicas: o.Max,
|
||||
CPUPercent: o.CpuPercent,
|
||||
ScaleRefName: name,
|
||||
ScaleRefKind: mapping.GroupVersionKind.Kind,
|
||||
ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(),
|
||||
}, nil
|
||||
default:
|
||||
return nil, cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", o.Generator)
|
||||
}
|
||||
}
|
||||
|
||||
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
|
||||
o.PrintFlags.NamePrintFlags.Operation = operation
|
||||
if o.dryRun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
|
||||
return o.PrintFlags.ToPrinter()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) Validate() error {
|
||||
if o.Max < 1 {
|
||||
return fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", o.Max)
|
||||
}
|
||||
if o.Max < o.Min {
|
||||
return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) Run() error {
|
||||
r := o.builder.
|
||||
WithScheme(legacyscheme.Scheme).
|
||||
ContinueOnError().
|
||||
NamespaceParam(o.namespace).DefaultNamespace().
|
||||
FilenameParam(o.enforceNamespace, o.FilenameOptions).
|
||||
ResourceTypeOrNameArgs(false, o.args...).
|
||||
Flatten().
|
||||
Do()
|
||||
if err := r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count := 0
|
||||
err := r.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mapping := info.ResourceMapping()
|
||||
if err := o.canBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
generator, err := o.generatorFunc(info.Name, mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate new object
|
||||
object, err := generator.StructuredGenerate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hpa, ok := object.(*autoscalingv1.HorizontalPodAutoscaler)
|
||||
if !ok {
|
||||
return fmt.Errorf("generator made %T, not autoscalingv1.HorizontalPodAutoscaler", object)
|
||||
}
|
||||
|
||||
if err := o.Recorder.Record(hpa); err != nil {
|
||||
glog.V(4).Infof("error recording current command: %v", err)
|
||||
}
|
||||
|
||||
if o.dryRun {
|
||||
count++
|
||||
|
||||
printer, err := o.ToPrinter("created")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(hpa, o.Out)
|
||||
}
|
||||
|
||||
if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, hpa, cmdutil.InternalVersionJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(hpa)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count++
|
||||
printer, err := o.ToPrinter("autoscaled")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(actualHPA, o.Out)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return fmt.Errorf("no objects passed to autoscale")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
252
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/certificates.go
generated
vendored
Normal file
252
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/certificates.go
generated
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdCertificate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "certificate SUBCOMMAND",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Modify certificate resources."),
|
||||
Long: "Modify certificate resources.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(NewCmdCertificateApprove(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCertificateDeny(f, ioStreams))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type CertificateOptions struct {
|
||||
resource.FilenameOptions
|
||||
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
csrNames []string
|
||||
outputStyle string
|
||||
|
||||
clientSet internalclientset.Interface
|
||||
builder *resource.Builder
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
o.csrNames = args
|
||||
o.outputStyle = cmdutil.GetFlagString(cmd, "output")
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object, out io.Writer) error {
|
||||
return printer.PrintObj(obj, out)
|
||||
}
|
||||
|
||||
o.builder = f.NewBuilder()
|
||||
o.clientSet, err = f.ClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CertificateOptions) Validate() error {
|
||||
if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
|
||||
return fmt.Errorf("one or more CSRs must be specified as <name> or -f <filename>")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := CertificateOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("approved").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "approve (-f FILENAME | NAME)",
|
||||
DisableFlagsInUseLine: true,
|
||||
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(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Validate())
|
||||
cmdutil.CheckErr(options.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force")))
|
||||
},
|
||||
}
|
||||
|
||||
options.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.")
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to update")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *CertificateOptions) RunCertificateApprove(force bool) error {
|
||||
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
|
||||
var alreadyApproved bool
|
||||
for _, c := range csr.Status.Conditions {
|
||||
if c.Type == certificates.CertificateApproved {
|
||||
alreadyApproved = true
|
||||
}
|
||||
}
|
||||
if alreadyApproved {
|
||||
return csr, true
|
||||
}
|
||||
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, false
|
||||
})
|
||||
}
|
||||
|
||||
func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := CertificateOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("denied").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "deny (-f FILENAME | NAME)",
|
||||
DisableFlagsInUseLine: true,
|
||||
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(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Validate())
|
||||
cmdutil.CheckErr(options.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force")))
|
||||
},
|
||||
}
|
||||
|
||||
options.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.")
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to update")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *CertificateOptions) RunCertificateDeny(force bool) error {
|
||||
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
|
||||
var alreadyDenied bool
|
||||
for _, c := range csr.Status.Conditions {
|
||||
if c.Type == certificates.CertificateDenied {
|
||||
alreadyDenied = true
|
||||
}
|
||||
}
|
||||
if alreadyDenied {
|
||||
return csr, true
|
||||
}
|
||||
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, false
|
||||
})
|
||||
}
|
||||
|
||||
func (options *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet internalclientset.Interface, force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool)) error {
|
||||
var found int
|
||||
r := builder.
|
||||
WithScheme(legacyscheme.Scheme).
|
||||
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
|
||||
}
|
||||
for i := 0; ; i++ {
|
||||
csr := info.Object.(*certificates.CertificateSigningRequest)
|
||||
csr, hasCondition := modify(csr)
|
||||
if !hasCondition || force {
|
||||
csr, err = clientSet.Certificates().
|
||||
CertificateSigningRequests().
|
||||
UpdateApproval(csr)
|
||||
if errors.IsConflict(err) && i < 10 {
|
||||
if err := info.Get(); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
found++
|
||||
|
||||
return options.PrintObj(cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping), options.Out)
|
||||
})
|
||||
if found == 0 {
|
||||
fmt.Fprintf(options.Out, "No resources found\n")
|
||||
}
|
||||
return err
|
||||
}
|
||||
166
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo.go
generated
vendored
Normal file
166
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo.go
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
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"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
|
||||
ct "github.com/daviddengcn/go-colortext"
|
||||
"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`))
|
||||
)
|
||||
|
||||
type ClusterInfoOptions struct {
|
||||
genericclioptions.IOStreams
|
||||
|
||||
Namespace string
|
||||
|
||||
Builder *resource.Builder
|
||||
Client *restclient.Config
|
||||
}
|
||||
|
||||
func NewCmdClusterInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := &ClusterInfoOptions{
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "cluster-info",
|
||||
Short: i18n.T("Display cluster info"),
|
||||
Long: longDescr,
|
||||
Example: clusterinfoExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(NewCmdClusterInfoDump(f, ioStreams))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ClusterInfoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
var err error
|
||||
o.Client, err = f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
|
||||
if cmdNamespace == "" {
|
||||
cmdNamespace = metav1.NamespaceSystem
|
||||
}
|
||||
o.Namespace = cmdNamespace
|
||||
|
||||
o.Builder = f.NewBuilder()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ClusterInfoOptions) Run() error {
|
||||
printService(o.Out, "Kubernetes master", o.Client.Host)
|
||||
|
||||
// TODO use generalized labels once they are implemented (#341)
|
||||
b := o.Builder.
|
||||
WithScheme(legacyscheme.Scheme).
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().
|
||||
LabelSelectorParam("kubernetes.io/cluster-service=true").
|
||||
ResourceTypeOrNameArgs(false, []string{"services"}...).
|
||||
Latest()
|
||||
err := b.Do().Visit(func(r *resource.Info, err error) error {
|
||||
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(o.Client.GroupVersion.Group) == 0 {
|
||||
link = o.Client.Host + "/api/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
|
||||
} else {
|
||||
link = o.Client.Host + "/api/" + o.Client.GroupVersion.Group + "/" + o.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(o.Out, name, link)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
o.Out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"))
|
||||
return err
|
||||
|
||||
// TODO consider printing more information about cluster
|
||||
}
|
||||
|
||||
func printService(out io.Writer, name, link string) {
|
||||
ct.ChangeColor(ct.Green, false, ct.None, false)
|
||||
fmt.Fprint(out, name)
|
||||
ct.ResetColor()
|
||||
fmt.Fprint(out, " is running at ")
|
||||
ct.ChangeColor(ct.Yellow, false, ct.None, false)
|
||||
fmt.Fprint(out, link)
|
||||
ct.ResetColor()
|
||||
fmt.Fprintln(out, "")
|
||||
}
|
||||
269
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo_dump.go
generated
vendored
Normal file
269
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo_dump.go
generated
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
type ClusterInfoDumpOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
OutputDir string
|
||||
AllNamespaces bool
|
||||
Namespaces []string
|
||||
|
||||
Timeout time.Duration
|
||||
Clientset internalclientset.Interface
|
||||
Namespace string
|
||||
RESTClientGetter genericclioptions.RESTClientGetter
|
||||
LogsForObject polymorphichelpers.LogsForObjectFunc
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
// NewCmdCreateSecret groups subcommands to create various types of secrets
|
||||
func NewCmdClusterInfoDump(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := &ClusterInfoDumpOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme),
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "dump",
|
||||
Short: i18n.T("Dump lots of relevant info for debugging and diagnosis"),
|
||||
Long: dumpLong,
|
||||
Example: dumpExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&o.OutputDir, "output-directory", o.OutputDir, i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory"))
|
||||
cmd.Flags().StringSliceVar(&o.Namespaces, "namespaces", o.Namespaces, "A comma separated list of namespaces to dump.")
|
||||
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If true, dump all namespaces. If true, --namespaces is ignored.")
|
||||
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodLogsTimeout)
|
||||
return cmd
|
||||
}
|
||||
|
||||
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(dir string, defaultWriter io.Writer, filename string) io.Writer {
|
||||
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 (o *ClusterInfoDumpOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutputFmt := "json"
|
||||
o.PrintFlags.OutputFormat = &jsonOutputFmt
|
||||
o.PrintObj = printer.PrintObj
|
||||
|
||||
o.Timeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Clientset, err = f.ClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO this should eventually just be the completed kubeconfigflag struct
|
||||
o.RESTClientGetter = f
|
||||
o.LogsForObject = polymorphichelpers.LogsForObjectFn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ClusterInfoDumpOptions) Run() error {
|
||||
nodes, err := o.Clientset.Core().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := o.PrintObj(nodes, setupOutputWriter(o.OutputDir, o.Out, "nodes.json")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var namespaces []string
|
||||
if o.AllNamespaces {
|
||||
namespaceList, err := o.Clientset.Core().Namespaces().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ix := range namespaceList.Items {
|
||||
namespaces = append(namespaces, namespaceList.Items[ix].Name)
|
||||
}
|
||||
} else {
|
||||
if len(o.Namespaces) == 0 {
|
||||
namespaces = []string{
|
||||
metav1.NamespaceSystem,
|
||||
o.Namespace,
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, namespace := range namespaces {
|
||||
// TODO: this is repetitive in the extreme. Use reflection or
|
||||
// something to make this a for loop.
|
||||
events, err := o.Clientset.Core().Events(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.PrintObj(events, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "events.json"))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rcs, err := o.Clientset.Core().ReplicationControllers(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.PrintObj(rcs, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "replication-controllers.json"))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svcs, err := o.Clientset.Core().Services(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.PrintObj(svcs, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "services.json"))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sets, err := o.Clientset.Extensions().DaemonSets(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.PrintObj(sets, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "daemonsets.json"))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deps, err := o.Clientset.Extensions().Deployments(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.PrintObj(deps, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "deployments.json"))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rps, err := o.Clientset.Extensions().ReplicaSets(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.PrintObj(rps, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "replicasets.json"))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pods, err := o.Clientset.Core().Pods(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := o.PrintObj(pods, setupOutputWriter(o.OutputDir, o.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 := o.LogsForObject(o.RESTClientGetter, pod, &api.PodLogOptions{Container: container.Name}, timeout)
|
||||
if err != nil {
|
||||
// Print error and return.
|
||||
writer.Write([]byte(fmt.Sprintf("Create log request error: %s\n", err.Error())))
|
||||
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(o.OutputDir, o.Out, path.Join(namespace, pod.Name, "logs.txt"))
|
||||
|
||||
for i := range containers {
|
||||
printContainer(writer, containers[i], pod)
|
||||
}
|
||||
}
|
||||
}
|
||||
if o.OutputDir != "-" {
|
||||
fmt.Fprintf(o.Out, "Cluster info dumped to %s\n", o.OutputDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
70
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo_dump_test.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo_dump_test.go
generated
vendored
Normal 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func TestSetupOutputWriterNoOp(t *testing.T) {
|
||||
tests := []string{"", "-"}
|
||||
for _, test := range tests {
|
||||
_, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
f := cmdtesting.NewTestFactory()
|
||||
defer f.Cleanup()
|
||||
|
||||
writer := setupOutputWriter(test, buf, "/some/file/that/should/be/ignored")
|
||||
if writer != buf {
|
||||
t.Errorf("expected: %v, saw: %v", buf, 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)
|
||||
|
||||
_, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
f := cmdtesting.NewTestFactory()
|
||||
defer f.Cleanup()
|
||||
|
||||
writer := setupOutputWriter(dir, buf, file)
|
||||
if writer == buf {
|
||||
t.Errorf("expected: %v, saw: %v", buf, writer)
|
||||
}
|
||||
output := "some data here"
|
||||
writer.Write([]byte(output))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
437
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd.go
generated
vendored
Normal file
437
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd.go
generated
vendored
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
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 (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/auth"
|
||||
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/create"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/get"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/wait"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
const (
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
# $1 is the name of resource (required)
|
||||
# $2 is template string for kubectl get (optional)
|
||||
__kubectl_parse_get()
|
||||
{
|
||||
local template
|
||||
template="${2:-"{{ range .items }}{{ .metadata.name }} {{ end }}"}"
|
||||
local kubectl_out
|
||||
if kubectl_out=$(kubectl get $(__kubectl_override_flags) -o template --template="${template}" "$1" 2>/dev/null); then
|
||||
COMPREPLY+=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
__kubectl_get_resource()
|
||||
{
|
||||
if [[ ${#nouns[@]} -eq 0 ]]; then
|
||||
local kubectl_out
|
||||
if kubectl_out=$(kubectl api-resources $(__kubectl_override_flags) -o name --cached --request-timeout=5s --verbs=get 2>/dev/null); then
|
||||
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
__kubectl_parse_get "${nouns[${#nouns[@]} -1]}"
|
||||
}
|
||||
|
||||
__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.initContainers }}{{ .name }} {{end}}{{ range .spec.containers }}{{ .name }} {{ end }}"
|
||||
__kubectl_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
|
||||
}
|
||||
|
||||
__kubectl_cp()
|
||||
{
|
||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||
compopt -o nospace
|
||||
fi
|
||||
|
||||
case "$cur" in
|
||||
/*|[.~]*) # looks like a path
|
||||
return
|
||||
;;
|
||||
*:*) # TODO: complete remote files in the pod
|
||||
return
|
||||
;;
|
||||
*/*) # complete <namespace>/<pod>
|
||||
local template namespace kubectl_out
|
||||
template="{{ range .items }}{{ .metadata.namespace }}/{{ .metadata.name }}: {{ end }}"
|
||||
namespace="${cur%%/*}"
|
||||
if kubectl_out=( $(kubectl get $(__kubectl_override_flags) --namespace "${namespace}" -o template --template="${template}" pods 2>/dev/null) ); then
|
||||
COMPREPLY=( $(compgen -W "${kubectl_out[*]}" -- "${cur}") )
|
||||
fi
|
||||
return
|
||||
;;
|
||||
*) # complete namespaces, pods, and filedirs
|
||||
__kubectl_parse_get "namespace" "{{ range .items }}{{ .metadata.name }}/ {{ end }}"
|
||||
__kubectl_parse_get "pod" "{{ range .items }}{{ .metadata.name }}: {{ end }}"
|
||||
_filedir
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
__custom_func() {
|
||||
case ${last_command} in
|
||||
kubectl_get | kubectl_describe | kubectl_delete | kubectl_label | kubectl_edit | kubectl_patch |\
|
||||
kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_* |\
|
||||
kubectl_apply_edit-last-applied | kubectl_apply_view-last-applied)
|
||||
__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
|
||||
;;
|
||||
kubectl_cp)
|
||||
__kubectl_cp
|
||||
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",
|
||||
}
|
||||
)
|
||||
|
||||
func NewDefaultKubectlCommand() *cobra.Command {
|
||||
return NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr)
|
||||
}
|
||||
|
||||
// NewKubectlCommand creates the `kubectl` command and its nested children.
|
||||
func NewKubectlCommand(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://kubernetes.io/docs/reference/kubectl/overview/`),
|
||||
Run: runHelp,
|
||||
BashCompletionFunction: bashCompletionFunc,
|
||||
}
|
||||
|
||||
flags := cmds.PersistentFlags()
|
||||
flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags
|
||||
|
||||
// Normalize all flags that are coming from other packages or pre-configurations
|
||||
// a.k.a. change all "_" to "-". e.g. glog package
|
||||
flags.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
|
||||
|
||||
kubeConfigFlags := genericclioptions.NewConfigFlags()
|
||||
kubeConfigFlags.AddFlags(flags)
|
||||
matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
|
||||
matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())
|
||||
|
||||
cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
|
||||
|
||||
// Sending in 'nil' for the getLanguageFn() results in using
|
||||
// the LANG environment variable.
|
||||
//
|
||||
// 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(utilflag.WarnWordSepNormalizeFunc)
|
||||
|
||||
ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
|
||||
|
||||
groups := templates.CommandGroups{
|
||||
{
|
||||
Message: "Basic Commands (Beginner):",
|
||||
Commands: []*cobra.Command{
|
||||
create.NewCmdCreate(f, ioStreams),
|
||||
NewCmdExposeService(f, ioStreams),
|
||||
NewCmdRun(f, ioStreams),
|
||||
set.NewCmdSet(f, ioStreams),
|
||||
deprecatedAlias("run-container", NewCmdRun(f, ioStreams)),
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Basic Commands (Intermediate):",
|
||||
Commands: []*cobra.Command{
|
||||
NewCmdExplain("kubectl", f, ioStreams),
|
||||
get.NewCmdGet("kubectl", f, ioStreams),
|
||||
NewCmdEdit(f, ioStreams),
|
||||
NewCmdDelete(f, ioStreams),
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Deploy Commands:",
|
||||
Commands: []*cobra.Command{
|
||||
rollout.NewCmdRollout(f, ioStreams),
|
||||
NewCmdRollingUpdate(f, ioStreams),
|
||||
NewCmdScale(f, ioStreams),
|
||||
NewCmdAutoscale(f, ioStreams),
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Cluster Management Commands:",
|
||||
Commands: []*cobra.Command{
|
||||
NewCmdCertificate(f, ioStreams),
|
||||
NewCmdClusterInfo(f, ioStreams),
|
||||
NewCmdTop(f, ioStreams),
|
||||
NewCmdCordon(f, ioStreams),
|
||||
NewCmdUncordon(f, ioStreams),
|
||||
NewCmdDrain(f, ioStreams),
|
||||
NewCmdTaint(f, ioStreams),
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Troubleshooting and Debugging Commands:",
|
||||
Commands: []*cobra.Command{
|
||||
NewCmdDescribe("kubectl", f, ioStreams),
|
||||
NewCmdLogs(f, ioStreams),
|
||||
NewCmdAttach(f, ioStreams),
|
||||
NewCmdExec(f, ioStreams),
|
||||
NewCmdPortForward(f, ioStreams),
|
||||
NewCmdProxy(f, ioStreams),
|
||||
NewCmdCp(f, ioStreams),
|
||||
auth.NewCmdAuth(f, ioStreams),
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Advanced Commands:",
|
||||
Commands: []*cobra.Command{
|
||||
NewCmdApply("kubectl", f, ioStreams),
|
||||
NewCmdPatch(f, ioStreams),
|
||||
NewCmdReplace(f, ioStreams),
|
||||
wait.NewCmdWait(f, ioStreams),
|
||||
NewCmdConvert(f, ioStreams),
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Settings Commands:",
|
||||
Commands: []*cobra.Command{
|
||||
NewCmdLabel(f, ioStreams),
|
||||
NewCmdAnnotate("kubectl", f, ioStreams),
|
||||
NewCmdCompletion(ioStreams.Out, ""),
|
||||
},
|
||||
},
|
||||
}
|
||||
groups.Add(cmds)
|
||||
|
||||
filters := []string{"options"}
|
||||
|
||||
// Hide the "alpha" subcommand if there are no alpha commands in this build.
|
||||
alpha := NewCmdAlpha(f, ioStreams)
|
||||
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(f, clientcmd.NewDefaultPathOptions(), ioStreams))
|
||||
cmds.AddCommand(NewCmdPlugin(f, ioStreams))
|
||||
cmds.AddCommand(NewCmdVersion(f, ioStreams))
|
||||
cmds.AddCommand(NewCmdApiVersions(f, ioStreams))
|
||||
cmds.AddCommand(NewCmdApiResources(f, ioStreams))
|
||||
cmds.AddCommand(NewCmdOptions(ioStreams.Out))
|
||||
|
||||
return cmds
|
||||
}
|
||||
|
||||
func runHelp(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
}
|
||||
|
||||
func printDeprecationWarning(errOut io.Writer, command, alias string) {
|
||||
fmt.Fprintf(errOut, "%s is DEPRECATED and will be removed in a future version. Use %s instead.\n", 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
|
||||
}
|
||||
|
||||
var metadataAccessor = meta.NewAccessor()
|
||||
234
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_printing_test.go
generated
vendored
Normal file
234
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_printing_test.go
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
func TestIllegalPackageSourceCheckerThroughPrintFlags(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectInternalObjErr bool
|
||||
output string
|
||||
obj runtime.Object
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
name: "success printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "success printer: object containing package path with no forbidden prefix returns no error",
|
||||
expectInternalObjErr: false,
|
||||
obj: externalPod(),
|
||||
output: "",
|
||||
expectedOutput: "pod/foo succeeded\n",
|
||||
},
|
||||
{
|
||||
name: "name printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
output: "name",
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "json printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
output: "json",
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "json printer: object containing package path with no forbidden prefix returns no error",
|
||||
expectInternalObjErr: false,
|
||||
obj: externalPod(),
|
||||
output: "json",
|
||||
},
|
||||
{
|
||||
name: "yaml printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
output: "yaml",
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "yaml printer: object containing package path with no forbidden prefix returns no error",
|
||||
expectInternalObjErr: false,
|
||||
obj: externalPod(),
|
||||
output: "yaml",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
printFlags := genericclioptions.NewPrintFlags("succeeded").WithTypeSetter(scheme.Scheme)
|
||||
printFlags.OutputFormat = &tc.output
|
||||
|
||||
printer, err := printFlags.ToPrinter()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
output := bytes.NewBuffer([]byte{})
|
||||
|
||||
err = printer.PrintObj(tc.obj, output)
|
||||
if err != nil {
|
||||
if !tc.expectInternalObjErr {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
if !genericprinters.IsInternalObjectError(err) {
|
||||
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.expectInternalObjErr {
|
||||
t.Fatalf("expected internal object printer error, but got no error")
|
||||
}
|
||||
|
||||
if len(tc.expectedOutput) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.expectedOutput != output.String() {
|
||||
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIllegalPackageSourceCheckerDirectlyThroughPrinters(t *testing.T) {
|
||||
jsonPathPrinter, err := printers.NewJSONPathPrinter("{ .metadata.name }")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
goTemplatePrinter, err := printers.NewGoTemplatePrinter([]byte("{{ .metadata.name }}"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
customColumns, err := printers.NewCustomColumnsPrinterFromSpec("NAME:.metadata.name", scheme.Codecs.UniversalDecoder(), true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectInternalObjErr bool
|
||||
printer printers.ResourcePrinter
|
||||
obj runtime.Object
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
name: "json printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
printer: &genericprinters.JSONPrinter{},
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "yaml printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
printer: &genericprinters.YAMLPrinter{},
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "jsonpath printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
printer: jsonPathPrinter,
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "go-template printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
printer: goTemplatePrinter,
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "go-template printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
printer: goTemplatePrinter,
|
||||
obj: internalPod(),
|
||||
},
|
||||
{
|
||||
name: "custom-columns printer: object containing package path beginning with forbidden prefix is rejected",
|
||||
expectInternalObjErr: true,
|
||||
printer: customColumns,
|
||||
obj: internalPod(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
output := bytes.NewBuffer([]byte{})
|
||||
|
||||
err := tc.printer.PrintObj(tc.obj, output)
|
||||
if err != nil {
|
||||
if !tc.expectInternalObjErr {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
if !genericprinters.IsInternalObjectError(err) {
|
||||
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.expectInternalObjErr {
|
||||
t.Fatalf("expected internal object printer error, but got no error")
|
||||
}
|
||||
|
||||
if len(tc.expectedOutput) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.expectedOutput != output.String() {
|
||||
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func internalPod() *api.Pod {
|
||||
return &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodRunning,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func externalPod() *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
308
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_test.go
generated
vendored
Normal file
308
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_test.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
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"
|
||||
"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/kubernetes/pkg/api/testapi"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
// This init should be removed after switching this command and its tests to user external types.
|
||||
func init() {
|
||||
api.AddToScheme(scheme.Scheme)
|
||||
}
|
||||
|
||||
func initTestErrorHandler(t *testing.T) {
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
t.Errorf("Error running command (exit code %d): %s", code, str)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
func newAllPhasePodList() *api.PodList {
|
||||
nodeName := "kubernetes-node-abcd"
|
||||
return &api.PodList{
|
||||
Items: []api.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test1",
|
||||
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: make([]api.Container, 2),
|
||||
NodeName: nodeName,
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodPending,
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
|
||||
{RestartCount: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test2",
|
||||
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: make([]api.Container, 2),
|
||||
NodeName: nodeName,
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodRunning,
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
|
||||
{RestartCount: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test3",
|
||||
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: make([]api.Container, 2),
|
||||
NodeName: nodeName,
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodSucceeded,
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
|
||||
{RestartCount: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test4",
|
||||
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: make([]api.Container, 2),
|
||||
NodeName: nodeName,
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodFailed,
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
|
||||
{RestartCount: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test5",
|
||||
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: make([]api.Container, 2),
|
||||
NodeName: nodeName,
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodUnknown,
|
||||
ContainerStatuses: []api.ContainerStatus{
|
||||
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
|
||||
{RestartCount: 3},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizationFuncGlobalExistence(t *testing.T) {
|
||||
// This test can be safely deleted when we will not support multiple flag formats
|
||||
root := NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
310
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/completion.go
generated
vendored
Normal file
310
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/completion.go
generated
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
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 evaluated 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",
|
||||
DisableFlagsInUseLine: true,
|
||||
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
|
||||
|
||||
__kubectl_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="," __kubectl_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
|
||||
}
|
||||
94
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/BUILD
generated
vendored
Normal file
94
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/BUILD
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
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",
|
||||
"flags.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/api/legacyscheme:go_default_library",
|
||||
"//pkg/kubectl/cmd/templates:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/printers:go_default_library",
|
||||
"//pkg/kubectl/util/i18n:go_default_library",
|
||||
"//pkg/printers:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api/latest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
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",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
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",
|
||||
],
|
||||
)
|
||||
92
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config.go
generated
vendored
Normal file
92
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
// NewCmdConfig creates a command object for the "config" action, and adds all child commands to it.
|
||||
func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
if len(pathOptions.ExplicitFileFlag) == 0 {
|
||||
pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "config SUBCOMMAND",
|
||||
DisableFlagsInUseLine: true,
|
||||
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(streams.ErrOut),
|
||||
}
|
||||
|
||||
// file paths are common to all sub commands
|
||||
cmd.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
|
||||
|
||||
// TODO(juanvallejo): update all subcommands to work with genericclioptions.IOStreams
|
||||
cmd.AddCommand(NewCmdConfigView(f, streams, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetCluster(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetAuthInfo(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetContext(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSet(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigUnset(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigCurrentContext(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigUseContext(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigGetContexts(streams, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigGetClusters(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigDeleteCluster(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigDeleteContext(streams.Out, streams.ErrOut, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigRenameContext(streams.Out, pathOptions))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
949
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config_test.go
generated
vendored
Normal file
949
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config_test.go
generated
vendored
Normal file
@@ -0,0 +1,949 @@
|
||||
/*
|
||||
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/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"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
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...)
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdConfig(cmdutil.NewFactory(genericclioptions.NewTestConfigFlags()), clientcmd.NewDefaultPathOptions(), streams)
|
||||
// "context" is a global flag, inherited from base kubectl command in the real world
|
||||
cmd.PersistentFlags().String("context", "", "The name of the kubeconfig context to use")
|
||||
cmd.SetArgs(argsToUse)
|
||||
cmd.Execute()
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
299
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo.go
generated
vendored
Normal file
299
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo.go
generated
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/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),
|
||||
DisableFlagsInUseLine: true,
|
||||
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
|
||||
}
|
||||
260
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo_test.go
generated
vendored
Normal file
260
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
179
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster.go
generated
vendored
Normal file
179
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
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),
|
||||
DisableFlagsInUseLine: true,
|
||||
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
|
||||
}
|
||||
119
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster_test.go
generated
vendored
Normal file
119
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
138
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context.go
generated
vendored
Normal file
138
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
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),
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Sets a context entry in kubeconfig"),
|
||||
Long: create_context_long,
|
||||
Example: create_context_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.complete(cmd))
|
||||
exists, err := options.run()
|
||||
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
|
||||
}
|
||||
118
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context_test.go
generated
vendored
Normal file
118
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
74
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context.go
generated
vendored
Normal file
74
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context.go
generated
vendored
Normal 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
|
||||
}
|
||||
93
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context_test.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
84
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_cluster.go
generated
vendored
Normal file
84
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_cluster.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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",
|
||||
DisableFlagsInUseLine: true,
|
||||
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
|
||||
}
|
||||
97
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_cluster_test.go
generated
vendored
Normal file
97
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_cluster_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
88
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_context.go
generated
vendored
Normal file
88
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_context.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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",
|
||||
DisableFlagsInUseLine: true,
|
||||
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
|
||||
}
|
||||
98
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_context_test.go
generated
vendored
Normal file
98
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/delete_context_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
101
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/flags.go
generated
vendored
Normal file
101
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/flags.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
// kubectlConfigPrintFlags composes common printer flag structs
|
||||
// used across all config commands, and provides a method
|
||||
// of retrieving a known printer based on flag values provided.
|
||||
type kubectlConfigPrintFlags struct {
|
||||
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
|
||||
NamePrintFlags *genericclioptions.NamePrintFlags
|
||||
TemplateFlags *printers.KubeTemplatePrintFlags
|
||||
|
||||
TypeSetter *genericprinters.TypeSetterPrinter
|
||||
|
||||
OutputFormat *string
|
||||
}
|
||||
|
||||
func (f *kubectlConfigPrintFlags) Complete(successTemplate string) error {
|
||||
return f.NamePrintFlags.Complete(successTemplate)
|
||||
}
|
||||
|
||||
func (f *kubectlConfigPrintFlags) AllowedFormats() []string {
|
||||
formats := f.JSONYamlPrintFlags.AllowedFormats()
|
||||
formats = append(formats, f.NamePrintFlags.AllowedFormats()...)
|
||||
formats = append(formats, f.TemplateFlags.AllowedFormats()...)
|
||||
return formats
|
||||
}
|
||||
|
||||
func (f *kubectlConfigPrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
|
||||
outputFormat := ""
|
||||
if f.OutputFormat != nil {
|
||||
outputFormat = *f.OutputFormat
|
||||
}
|
||||
|
||||
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetter.WrapToPrinter(p, err)
|
||||
}
|
||||
|
||||
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetter.WrapToPrinter(p, err)
|
||||
}
|
||||
|
||||
if p, err := f.TemplateFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetter.WrapToPrinter(p, err)
|
||||
}
|
||||
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
func (f *kubectlConfigPrintFlags) AddFlags(cmd *cobra.Command) {
|
||||
f.JSONYamlPrintFlags.AddFlags(cmd)
|
||||
f.NamePrintFlags.AddFlags(cmd)
|
||||
f.TemplateFlags.AddFlags(cmd)
|
||||
|
||||
if f.OutputFormat != nil {
|
||||
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, "Output format. One of: json|yaml|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultOutput sets a default output format if one is not provided through a flag value
|
||||
func (f *kubectlConfigPrintFlags) WithDefaultOutput(output string) *kubectlConfigPrintFlags {
|
||||
f.OutputFormat = &output
|
||||
return f
|
||||
}
|
||||
|
||||
func newKubeConfigPrintFlags(scheme runtime.ObjectTyper) *kubectlConfigPrintFlags {
|
||||
outputFormat := ""
|
||||
|
||||
return &kubectlConfigPrintFlags{
|
||||
OutputFormat: &outputFormat,
|
||||
|
||||
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
|
||||
NamePrintFlags: genericclioptions.NewNamePrintFlags(""),
|
||||
TemplateFlags: printers.NewKubeTemplatePrintFlags(),
|
||||
|
||||
TypeSetter: genericprinters.NewTypeSetter(scheme),
|
||||
}
|
||||
}
|
||||
65
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_clusters.go
generated
vendored
Normal file
65
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_clusters.go
generated
vendored
Normal 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
|
||||
}
|
||||
84
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_clusters_test.go
generated
vendored
Normal file
84
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_clusters_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
180
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_contexts.go
generated
vendored
Normal file
180
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_contexts.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
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/genericclioptions"
|
||||
"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
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
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(streams genericclioptions.IOStreams, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
options := &GetContextsOptions{
|
||||
configAccess: configAccess,
|
||||
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "get-contexts [(-o|--output=)name)]",
|
||||
DisableFlagsInUseLine: true,
|
||||
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(options.Out, "--output %v is not available in kubectl config get-contexts; resetting to default output format\n", outputFormat)
|
||||
cmd.Flags().Set("output", "")
|
||||
}
|
||||
cmdutil.CheckErr(options.Complete(cmd, args))
|
||||
cmdutil.CheckErr(options.RunGetContexts())
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers (default print headers).")
|
||||
cmd.Flags().StringP("output", "o", "", "Output format. One of: name")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete assigns GetContextsOptions from the args.
|
||||
func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string) error {
|
||||
o.contextNames = args
|
||||
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
|
||||
}
|
||||
178
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_contexts_test.go
generated
vendored
Normal file
178
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/get_contexts_test.go
generated
vendored
Normal 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
type getContextsTest struct {
|
||||
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 = ""
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
options := GetContextsOptions{
|
||||
configAccess: pathOptions,
|
||||
}
|
||||
cmd := NewCmdConfigGetContexts(streams, 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
|
||||
}
|
||||
}
|
||||
152
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser.go
generated
vendored
Normal file
152
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser.go
generated
vendored
Normal 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
|
||||
}
|
||||
96
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser_test.go
generated
vendored
Normal file
96
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
136
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/rename_context.go
generated
vendored
Normal file
136
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/rename_context.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"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"
|
||||
)
|
||||
|
||||
// RenameContextOptions contains the options for running the rename-context cli command.
|
||||
type RenameContextOptions struct {
|
||||
configAccess clientcmd.ConfigAccess
|
||||
contextName string
|
||||
newName string
|
||||
}
|
||||
|
||||
const (
|
||||
renameContextUse = "rename-context CONTEXT_NAME NEW_NAME"
|
||||
|
||||
renameContextShort = "Renames a context from the kubeconfig file."
|
||||
)
|
||||
|
||||
var (
|
||||
renameContextLong = templates.LongDesc(`
|
||||
Renames a context from the kubeconfig file.
|
||||
|
||||
CONTEXT_NAME is the context name that you wish change.
|
||||
|
||||
NEW_NAME is the new name you wish to set.
|
||||
|
||||
Note: In case the context being renamed is the 'current-context', this field will also be updated.`)
|
||||
|
||||
renameContextExample = templates.Examples(`
|
||||
# Rename the context 'old-name' to 'new-name' in your kubeconfig file
|
||||
kubectl config rename-context old-name new-name`)
|
||||
)
|
||||
|
||||
// NewCmdConfigRenameContext creates a command object for the "rename-context" action
|
||||
func NewCmdConfigRenameContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
options := &RenameContextOptions{configAccess: configAccess}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: renameContextUse,
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: renameContextShort,
|
||||
Long: renameContextLong,
|
||||
Example: renameContextExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := options.Complete(cmd, args, out); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
if err := options.Validate(); err != nil {
|
||||
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error()))
|
||||
}
|
||||
if err := options.RunRenameContext(out); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete assigns RenameContextOptions from the args.
|
||||
func (o *RenameContextOptions) Complete(cmd *cobra.Command, args []string, out io.Writer) error {
|
||||
if len(args) != 2 {
|
||||
return helpErrorf(cmd, "Unexpected args: %v", args)
|
||||
}
|
||||
|
||||
o.contextName = args[0]
|
||||
o.newName = args[1]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o RenameContextOptions) Validate() error {
|
||||
if len(o.newName) == 0 {
|
||||
return errors.New("You must specify a new non-empty context name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o RenameContextOptions) RunRenameContext(out io.Writer) error {
|
||||
config, err := o.configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configFile := o.configAccess.GetDefaultFilename()
|
||||
if o.configAccess.IsExplicitFile() {
|
||||
configFile = o.configAccess.GetExplicitFile()
|
||||
}
|
||||
|
||||
context, exists := config.Contexts[o.contextName]
|
||||
if !exists {
|
||||
return fmt.Errorf("cannot rename the context %q, it's not in %s", o.contextName, configFile)
|
||||
}
|
||||
|
||||
_, newExists := config.Contexts[o.newName]
|
||||
if newExists {
|
||||
return fmt.Errorf("cannot rename the context %q, the context %q already exists in %s", o.contextName, o.newName, configFile)
|
||||
}
|
||||
|
||||
config.Contexts[o.newName] = context
|
||||
delete(config.Contexts, o.contextName)
|
||||
|
||||
if config.CurrentContext == o.contextName {
|
||||
config.CurrentContext = o.newName
|
||||
}
|
||||
|
||||
if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Context %q renamed to %q.\n", o.contextName, o.newName)
|
||||
return nil
|
||||
}
|
||||
156
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/rename_context_test.go
generated
vendored
Normal file
156
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/rename_context_test.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
const (
|
||||
currentContext = "current-context"
|
||||
newContext = "new-context"
|
||||
nonexistentCurrentContext = "nonexistent-current-context"
|
||||
existentNewContext = "existent-new-context"
|
||||
)
|
||||
|
||||
var (
|
||||
contextData *clientcmdapi.Context = clientcmdapi.NewContext()
|
||||
)
|
||||
|
||||
type renameContextTest struct {
|
||||
description string
|
||||
initialConfig clientcmdapi.Config // initial config
|
||||
expectedConfig clientcmdapi.Config // expected config
|
||||
args []string // kubectl rename-context args
|
||||
expectedOut string // expected out message
|
||||
expectedErr string // expected error message
|
||||
}
|
||||
|
||||
func TestRenameContext(t *testing.T) {
|
||||
initialConfig := clientcmdapi.Config{
|
||||
CurrentContext: currentContext,
|
||||
Contexts: map[string]*clientcmdapi.Context{currentContext: contextData}}
|
||||
|
||||
expectedConfig := clientcmdapi.Config{
|
||||
CurrentContext: newContext,
|
||||
Contexts: map[string]*clientcmdapi.Context{newContext: contextData}}
|
||||
|
||||
test := renameContextTest{
|
||||
description: "Testing for kubectl config rename-context whose context to be renamed is the CurrentContext",
|
||||
initialConfig: initialConfig,
|
||||
expectedConfig: expectedConfig,
|
||||
args: []string{currentContext, newContext},
|
||||
expectedOut: fmt.Sprintf("Context %q renamed to %q.\n", currentContext, newContext),
|
||||
expectedErr: "",
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestRenameNonexistentContext(t *testing.T) {
|
||||
initialConfig := clientcmdapi.Config{
|
||||
CurrentContext: currentContext,
|
||||
Contexts: map[string]*clientcmdapi.Context{currentContext: contextData}}
|
||||
|
||||
test := renameContextTest{
|
||||
description: "Testing for kubectl config rename-context whose context to be renamed no exists",
|
||||
initialConfig: initialConfig,
|
||||
expectedConfig: initialConfig,
|
||||
args: []string{nonexistentCurrentContext, newContext},
|
||||
expectedOut: "",
|
||||
expectedErr: fmt.Sprintf("cannot rename the context %q, it's not in", nonexistentCurrentContext),
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestRenameToAlreadyExistingContext(t *testing.T) {
|
||||
initialConfig := clientcmdapi.Config{
|
||||
CurrentContext: currentContext,
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
currentContext: contextData,
|
||||
existentNewContext: contextData}}
|
||||
|
||||
test := renameContextTest{
|
||||
description: "Testing for kubectl config rename-context whose the new name is already in another context.",
|
||||
initialConfig: initialConfig,
|
||||
expectedConfig: initialConfig,
|
||||
args: []string{currentContext, existentNewContext},
|
||||
expectedOut: "",
|
||||
expectedErr: fmt.Sprintf("cannot rename the context %q, the context %q already exists", currentContext, existentNewContext),
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func (test renameContextTest) run(t *testing.T) {
|
||||
fakeKubeFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(fakeKubeFile.Name())
|
||||
err := clientcmd.WriteToFile(test.initialConfig, fakeKubeFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
pathOptions := clientcmd.NewDefaultPathOptions()
|
||||
pathOptions.GlobalFile = fakeKubeFile.Name()
|
||||
pathOptions.EnvVar = ""
|
||||
options := RenameContextOptions{
|
||||
configAccess: pathOptions,
|
||||
contextName: test.args[0],
|
||||
newName: test.args[1],
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
cmd := NewCmdConfigRenameContext(buf, options.configAccess)
|
||||
|
||||
options.Complete(cmd, test.args, buf)
|
||||
options.Validate()
|
||||
err = options.RunRenameContext(buf)
|
||||
|
||||
if len(test.expectedErr) != 0 {
|
||||
if err == nil {
|
||||
t.Errorf("Did not get %v", test.expectedErr)
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Errorf("Expected error %v, but got %v", test.expectedErr, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
|
||||
}
|
||||
|
||||
_, oldExists := config.Contexts[currentContext]
|
||||
_, newExists := config.Contexts[newContext]
|
||||
|
||||
if (!newExists) || (oldExists) || (config.CurrentContext != newContext) {
|
||||
t.Errorf("Failed in: %q\n expected %v\n but got %v", test.description, test.expectedConfig, *config)
|
||||
}
|
||||
|
||||
if len(test.expectedOut) != 0 {
|
||||
if buf.String() != test.expectedOut {
|
||||
t.Errorf("Failed in:%q\n expected out %v\n but got %v", test.description, test.expectedOut, buf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
243
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/set.go
generated
vendored
Normal file
243
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/set.go
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apiserver/pkg/util/flag"
|
||||
"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 setOptions struct {
|
||||
configAccess clientcmd.ConfigAccess
|
||||
propertyName string
|
||||
propertyValue string
|
||||
setRawBytes flag.Tristate
|
||||
}
|
||||
|
||||
var set_long = templates.LongDesc(`
|
||||
Sets an individual value in a kubeconfig file
|
||||
|
||||
PROPERTY_NAME is a dot delimited name where each token represents either an attribute name or a map key. Map keys may not contain dots.
|
||||
|
||||
PROPERTY_VALUE is the new value you wish to set. Binary fields such as 'certificate-authority-data' expect a base64 encoded string unless the --set-raw-bytes flag is used.`)
|
||||
|
||||
func NewCmdConfigSet(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
options := &setOptions{configAccess: configAccess}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "set PROPERTY_NAME PROPERTY_VALUE",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Sets an individual value in a kubeconfig file"),
|
||||
Long: set_long,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.complete(cmd))
|
||||
cmdutil.CheckErr(options.run())
|
||||
fmt.Fprintf(out, "Property %q set.\n", options.propertyName)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags().VarPF(&options.setRawBytes, "set-raw-bytes", "", "When writing a []byte PROPERTY_VALUE, write the given string directly without base64 decoding.")
|
||||
f.NoOptDefVal = "true"
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o setOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := o.configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
steps, err := newNavigationSteps(o.propertyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setRawBytes := false
|
||||
if o.setRawBytes.Provided() {
|
||||
setRawBytes = o.setRawBytes.Value()
|
||||
}
|
||||
|
||||
err = modifyConfig(reflect.ValueOf(config), steps, o.propertyValue, false, setRawBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *setOptions) complete(cmd *cobra.Command) error {
|
||||
endingArgs := cmd.Flags().Args()
|
||||
if len(endingArgs) != 2 {
|
||||
return helpErrorf(cmd, "Unexpected args: %v", endingArgs)
|
||||
}
|
||||
|
||||
o.propertyValue = endingArgs[1]
|
||||
o.propertyName = endingArgs[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o setOptions) validate() error {
|
||||
if len(o.propertyValue) == 0 {
|
||||
return errors.New("you cannot use set to unset a property")
|
||||
}
|
||||
|
||||
if len(o.propertyName) == 0 {
|
||||
return errors.New("you must specify a property")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue string, unset bool, setRawBytes bool) error {
|
||||
currStep := steps.pop()
|
||||
|
||||
actualCurrValue := curr
|
||||
if curr.Kind() == reflect.Ptr {
|
||||
actualCurrValue = curr.Elem()
|
||||
}
|
||||
|
||||
switch actualCurrValue.Kind() {
|
||||
case reflect.Map:
|
||||
if !steps.moreStepsRemaining() && !unset {
|
||||
return fmt.Errorf("can't set a map to a value: %v", actualCurrValue)
|
||||
}
|
||||
|
||||
mapKey := reflect.ValueOf(currStep.stepValue)
|
||||
mapValueType := curr.Type().Elem().Elem()
|
||||
|
||||
if !steps.moreStepsRemaining() && unset {
|
||||
actualCurrValue.SetMapIndex(mapKey, reflect.Value{})
|
||||
return nil
|
||||
}
|
||||
|
||||
currMapValue := actualCurrValue.MapIndex(mapKey)
|
||||
|
||||
needToSetNewMapValue := currMapValue.Kind() == reflect.Invalid
|
||||
if needToSetNewMapValue {
|
||||
if unset {
|
||||
return fmt.Errorf("current map key `%v` is invalid", mapKey.Interface())
|
||||
}
|
||||
currMapValue = reflect.New(mapValueType.Elem()).Elem().Addr()
|
||||
actualCurrValue.SetMapIndex(mapKey, currMapValue)
|
||||
}
|
||||
|
||||
err := modifyConfig(currMapValue, steps, propertyValue, unset, setRawBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case reflect.String:
|
||||
if steps.moreStepsRemaining() {
|
||||
return fmt.Errorf("can't have more steps after a string. %v", steps)
|
||||
}
|
||||
actualCurrValue.SetString(propertyValue)
|
||||
return nil
|
||||
|
||||
case reflect.Slice:
|
||||
if steps.moreStepsRemaining() {
|
||||
return fmt.Errorf("can't have more steps after bytes. %v", steps)
|
||||
}
|
||||
innerKind := actualCurrValue.Type().Elem().Kind()
|
||||
if innerKind != reflect.Uint8 {
|
||||
return fmt.Errorf("unrecognized slice type. %v", innerKind)
|
||||
}
|
||||
|
||||
if unset {
|
||||
actualCurrValue.Set(reflect.Zero(actualCurrValue.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
if setRawBytes {
|
||||
actualCurrValue.SetBytes([]byte(propertyValue))
|
||||
} else {
|
||||
val, err := base64.StdEncoding.DecodeString(propertyValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding input value: %v", err)
|
||||
}
|
||||
actualCurrValue.SetBytes(val)
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Bool:
|
||||
if steps.moreStepsRemaining() {
|
||||
return fmt.Errorf("can't have more steps after a bool. %v", steps)
|
||||
}
|
||||
boolValue, err := toBool(propertyValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actualCurrValue.SetBool(boolValue)
|
||||
return nil
|
||||
|
||||
case reflect.Struct:
|
||||
for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
|
||||
currFieldValue := actualCurrValue.Field(fieldIndex)
|
||||
currFieldType := actualCurrValue.Type().Field(fieldIndex)
|
||||
currYamlTag := currFieldType.Tag.Get("json")
|
||||
currFieldTypeYamlName := strings.Split(currYamlTag, ",")[0]
|
||||
|
||||
if currFieldTypeYamlName == currStep.stepValue {
|
||||
thisMapHasNoValue := (currFieldValue.Kind() == reflect.Map && currFieldValue.IsNil())
|
||||
|
||||
if thisMapHasNoValue {
|
||||
newValue := reflect.MakeMap(currFieldValue.Type())
|
||||
currFieldValue.Set(newValue)
|
||||
|
||||
if !steps.moreStepsRemaining() && unset {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !steps.moreStepsRemaining() && unset {
|
||||
// if we're supposed to unset the value or if the value is a map that doesn't exist, create a new value and overwrite
|
||||
newValue := reflect.New(currFieldValue.Type()).Elem()
|
||||
currFieldValue.Set(newValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
return modifyConfig(currFieldValue.Addr(), steps, propertyValue, unset, setRawBytes)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to locate path %#v under %v", currStep, actualCurrValue)
|
||||
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unrecognized type: %v", actualCurrValue))
|
||||
}
|
||||
88
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/set_test.go
generated
vendored
Normal file
88
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/set_test.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"reflect"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
type setConfigTest struct {
|
||||
description string
|
||||
config clientcmdapi.Config
|
||||
args []string
|
||||
expected string
|
||||
expectedConfig clientcmdapi.Config
|
||||
}
|
||||
|
||||
func TestSetConfigCurrentContext(t *testing.T) {
|
||||
conf := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
CurrentContext: "minikube",
|
||||
}
|
||||
expectedConfig := *clientcmdapi.NewConfig()
|
||||
expectedConfig.CurrentContext = "my-cluster"
|
||||
test := setConfigTest{
|
||||
description: "Testing for kubectl config set current-context",
|
||||
config: conf,
|
||||
args: []string{"current-context", "my-cluster"},
|
||||
expected: `Property "current-context" set.` + "\n",
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func (test setConfigTest) 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 := NewCmdConfigSet(buf, pathOptions)
|
||||
cmd.SetArgs(test.args)
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("unexpected error executing command: %v", err)
|
||||
}
|
||||
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("Failded in:%q\n expected %v\n but got %v", test.description, test.expected, buf.String())
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(*config, test.expectedConfig) {
|
||||
t.Errorf("Failed in: %q\n expected %v\n but got %v", test.description, *config, test.expectedConfig)
|
||||
}
|
||||
}
|
||||
115
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/unset.go
generated
vendored
Normal file
115
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/unset.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
type unsetOptions struct {
|
||||
configAccess clientcmd.ConfigAccess
|
||||
propertyName string
|
||||
}
|
||||
|
||||
var (
|
||||
unsetLong = templates.LongDesc(`
|
||||
Unsets an individual value in a kubeconfig file
|
||||
|
||||
PROPERTY_NAME is a dot delimited name where each token represents either an attribute name or a map key. Map keys may not contain dots.`)
|
||||
|
||||
unsetExample = templates.Examples(`
|
||||
# Unset the current-context.
|
||||
kubectl config unset current-context
|
||||
|
||||
# Unset namespace in foo context.
|
||||
kubectl config unset contexts.foo.namespace`)
|
||||
)
|
||||
|
||||
func NewCmdConfigUnset(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
options := &unsetOptions{configAccess: configAccess}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "unset PROPERTY_NAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Unsets an individual value in a kubeconfig file"),
|
||||
Long: unsetLong,
|
||||
Example: unsetExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.complete(cmd, args))
|
||||
cmdutil.CheckErr(options.run(out))
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o unsetOptions) run(out io.Writer) error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := o.configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
steps, err := newNavigationSteps(o.propertyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = modifyConfig(reflect.ValueOf(config), steps, "", true, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(out, "Property %q unset.\n", o.propertyName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *unsetOptions) complete(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return helpErrorf(cmd, "Unexpected args: %v", args)
|
||||
}
|
||||
|
||||
o.propertyName = args[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o unsetOptions) validate() error {
|
||||
if len(o.propertyName) == 0 {
|
||||
return errors.New("you must specify a property")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
150
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/unset_test.go
generated
vendored
Normal file
150
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/unset_test.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
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 unsetConfigTest struct {
|
||||
description string
|
||||
config clientcmdapi.Config
|
||||
args []string
|
||||
expected string
|
||||
expectedErr string
|
||||
}
|
||||
|
||||
func TestUnsetConfigString(t *testing.T) {
|
||||
conf := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"minikube": {Server: "https://192.168.99.100:8443"},
|
||||
"my-cluster": {Server: "https://192.168.0.1:3434"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
|
||||
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
|
||||
},
|
||||
CurrentContext: "minikube",
|
||||
}
|
||||
test := unsetConfigTest{
|
||||
description: "Testing for kubectl config unset a value",
|
||||
config: conf,
|
||||
args: []string{"current-context"},
|
||||
expected: `Property "current-context" unset.` + "\n",
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestUnsetConfigMap(t *testing.T) {
|
||||
conf := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"minikube": {Server: "https://192.168.99.100:8443"},
|
||||
"my-cluster": {Server: "https://192.168.0.1:3434"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
|
||||
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
|
||||
},
|
||||
CurrentContext: "minikube",
|
||||
}
|
||||
test := unsetConfigTest{
|
||||
description: "Testing for kubectl config unset a map",
|
||||
config: conf,
|
||||
args: []string{"clusters"},
|
||||
expected: `Property "clusters" unset.` + "\n",
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestUnsetUnexistConfig(t *testing.T) {
|
||||
conf := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"minikube": {Server: "https://192.168.99.100:8443"},
|
||||
"my-cluster": {Server: "https://192.168.0.1:3434"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
|
||||
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
|
||||
},
|
||||
CurrentContext: "minikube",
|
||||
}
|
||||
|
||||
test := unsetConfigTest{
|
||||
description: "Testing for kubectl config unset a unexist map key",
|
||||
config: conf,
|
||||
args: []string{"contexts.foo.namespace"},
|
||||
expectedErr: "current map key `foo` is invalid",
|
||||
}
|
||||
test.run(t)
|
||||
|
||||
}
|
||||
|
||||
func (test unsetConfigTest) 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 := NewCmdConfigUnset(buf, pathOptions)
|
||||
opts := &unsetOptions{configAccess: pathOptions}
|
||||
err = opts.complete(cmd, test.args)
|
||||
if err == nil {
|
||||
err = opts.run(buf)
|
||||
}
|
||||
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
|
||||
}
|
||||
|
||||
if err != nil && err.Error() != test.expectedErr {
|
||||
t.Fatalf("expected error:\n %v\nbut got error:\n%v", test.expectedErr, 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 test.args[0] == "current-context" {
|
||||
if config.CurrentContext != "" {
|
||||
t.Errorf("Failed in :%q\n expected current-context nil,but got %v", test.description, config.CurrentContext)
|
||||
}
|
||||
} else if test.args[0] == "clusters" {
|
||||
if len(config.Clusters) != 0 {
|
||||
t.Errorf("Failed in :%q\n expected clusters nil map, but got %v", test.description, config.Clusters)
|
||||
}
|
||||
}
|
||||
}
|
||||
102
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/use_context.go
generated
vendored
Normal file
102
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/use_context.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
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/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"
|
||||
)
|
||||
|
||||
var (
|
||||
use_context_example = templates.Examples(`
|
||||
# Use the context for the minikube cluster
|
||||
kubectl config use-context minikube`)
|
||||
)
|
||||
|
||||
type useContextOptions struct {
|
||||
configAccess clientcmd.ConfigAccess
|
||||
contextName string
|
||||
}
|
||||
|
||||
func NewCmdConfigUseContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
options := &useContextOptions{configAccess: configAccess}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "use-context CONTEXT_NAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Sets the current-context in a kubeconfig file"),
|
||||
Aliases: []string{"use"},
|
||||
Long: `Sets the current-context in a kubeconfig file`,
|
||||
Example: use_context_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.complete(cmd))
|
||||
cmdutil.CheckErr(options.run())
|
||||
fmt.Fprintf(out, "Switched to context %q.\n", options.contextName)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o useContextOptions) run() error {
|
||||
config, err := o.configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = o.validate(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.CurrentContext = o.contextName
|
||||
|
||||
return clientcmd.ModifyConfig(o.configAccess, *config, true)
|
||||
}
|
||||
|
||||
func (o *useContextOptions) complete(cmd *cobra.Command) error {
|
||||
endingArgs := cmd.Flags().Args()
|
||||
if len(endingArgs) != 1 {
|
||||
return helpErrorf(cmd, "Unexpected args: %v", endingArgs)
|
||||
}
|
||||
|
||||
o.contextName = endingArgs[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o useContextOptions) validate(config *clientcmdapi.Config) error {
|
||||
if len(o.contextName) == 0 {
|
||||
return errors.New("empty context names are not allowed")
|
||||
}
|
||||
|
||||
for name := range config.Contexts {
|
||||
if name == o.contextName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("no context exists with the name: %q.", o.contextName)
|
||||
}
|
||||
104
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/use_context_test.go
generated
vendored
Normal file
104
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/use_context_test.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
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 useContextTest struct {
|
||||
description string
|
||||
config clientcmdapi.Config //initiate kubectl config
|
||||
args []string //kubectl config use-context args
|
||||
expected string //expect out
|
||||
expectedConfig clientcmdapi.Config //expect kubectl config
|
||||
}
|
||||
|
||||
func TestUseContext(t *testing.T) {
|
||||
conf := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"minikube": {Server: "https://192.168.99.100:8443"},
|
||||
"my-cluster": {Server: "https://192.168.0.1:3434"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
|
||||
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
|
||||
},
|
||||
CurrentContext: "minikube",
|
||||
}
|
||||
test := useContextTest{
|
||||
description: "Testing for kubectl config use-context",
|
||||
config: conf,
|
||||
args: []string{"my-cluster"},
|
||||
expected: `Switched to context "my-cluster".` + "\n",
|
||||
expectedConfig: clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"minikube": {Server: "https://192.168.99.100:8443"},
|
||||
"my-cluster": {Server: "https://192.168.0.1:3434"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
|
||||
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
|
||||
},
|
||||
CurrentContext: "my-cluster",
|
||||
},
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func (test useContextTest) 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 := NewCmdConfigUseContext(buf, pathOptions)
|
||||
cmd.SetArgs(test.args)
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("unexpected error executing command: %v,kubectl config use-context args: %v", err, test.args)
|
||||
}
|
||||
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\n", test.description, test.expected, buf.String())
|
||||
}
|
||||
}
|
||||
if test.expectedConfig.CurrentContext != config.CurrentContext {
|
||||
t.Errorf("Failed in :%q\n expected config %v, but found %v\n in kubeconfig\n", test.description, test.expectedConfig, config)
|
||||
}
|
||||
}
|
||||
177
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view.go
generated
vendored
Normal file
177
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"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/client-go/tools/clientcmd/api/latest"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
type ViewOptions struct {
|
||||
PrintFlags *kubectlConfigPrintFlags
|
||||
PrintObject printers.ResourcePrinterFunc
|
||||
|
||||
ConfigAccess clientcmd.ConfigAccess
|
||||
Merge flag.Tristate
|
||||
Flatten bool
|
||||
Minify bool
|
||||
RawByteData bool
|
||||
|
||||
Context string
|
||||
OutputFormat string
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
var (
|
||||
view_long = templates.LongDesc(`
|
||||
Display merged kubeconfig settings or a specified kubeconfig file.
|
||||
|
||||
You can use --output jsonpath={...} to extract specific values using a jsonpath expression.`)
|
||||
|
||||
view_example = templates.Examples(`
|
||||
# Show merged kubeconfig settings.
|
||||
kubectl config view
|
||||
|
||||
# Show merged kubeconfig settings and raw certificate data.
|
||||
kubectl config view --raw
|
||||
|
||||
# Get the password for the e2e user
|
||||
kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'`)
|
||||
|
||||
defaultOutputFormat = "yaml"
|
||||
)
|
||||
|
||||
func NewCmdConfigView(f cmdutil.Factory, streams genericclioptions.IOStreams, ConfigAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
o := &ViewOptions{
|
||||
PrintFlags: newKubeConfigPrintFlags(legacyscheme.Scheme).WithDefaultOutput("yaml"),
|
||||
ConfigAccess: ConfigAccess,
|
||||
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "view",
|
||||
Short: i18n.T("Display merged kubeconfig settings or a specified kubeconfig file"),
|
||||
Long: view_long,
|
||||
Example: view_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(cmd))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
o.Merge.Default(true)
|
||||
mergeFlag := cmd.Flags().VarPF(&o.Merge, "merge", "", "Merge the full hierarchy of kubeconfig files")
|
||||
mergeFlag.NoOptDefVal = "true"
|
||||
cmd.Flags().BoolVar(&o.RawByteData, "raw", o.RawByteData, "Display raw byte data")
|
||||
cmd.Flags().BoolVar(&o.Flatten, "flatten", o.Flatten, "Flatten the resulting kubeconfig file into self-contained output (useful for creating portable kubeconfig files)")
|
||||
cmd.Flags().BoolVar(&o.Minify, "minify", o.Minify, "Remove all information not used by current-context from the output")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ViewOptions) Complete(cmd *cobra.Command) error {
|
||||
if o.ConfigAccess.IsExplicitFile() {
|
||||
if !o.Merge.Provided() {
|
||||
o.Merge.Set("false")
|
||||
}
|
||||
}
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObject = printer.PrintObj
|
||||
o.Context = cmdutil.GetFlagString(cmd, "context")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o ViewOptions) Validate() error {
|
||||
if !o.Merge.Value() && !o.ConfigAccess.IsExplicitFile() {
|
||||
return errors.New("if merge==false a precise file must to specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o ViewOptions) Run() error {
|
||||
config, err := o.loadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.Minify {
|
||||
if len(o.Context) > 0 {
|
||||
config.CurrentContext = o.Context
|
||||
}
|
||||
if err := clientcmdapi.MinifyConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Flatten {
|
||||
if err := clientcmdapi.FlattenConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !o.RawByteData {
|
||||
clientcmdapi.ShortenConfig(config)
|
||||
}
|
||||
|
||||
convertedObj, err := latest.Scheme.ConvertToVersion(config, latest.ExternalVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return o.PrintObject(convertedObj, o.Out)
|
||||
}
|
||||
|
||||
func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) {
|
||||
err := o.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := o.getStartingConfig()
|
||||
return config, err
|
||||
}
|
||||
|
||||
// getStartingConfig returns the Config object built from the sources specified by the options, the filename read (only if it was a single file), and an error if something goes wrong
|
||||
func (o *ViewOptions) getStartingConfig() (*clientcmdapi.Config, error) {
|
||||
switch {
|
||||
case !o.Merge.Value():
|
||||
return clientcmd.LoadFromFile(o.ConfigAccess.GetExplicitFile())
|
||||
|
||||
default:
|
||||
return o.ConfigAccess.GetStartingConfig()
|
||||
}
|
||||
}
|
||||
201
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view_test.go
generated
vendored
Normal file
201
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view_test.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
type viewClusterTest struct {
|
||||
description string
|
||||
config clientcmdapi.Config //initiate kubectl config
|
||||
flags []string //kubectl config viw flags
|
||||
expected string //expect out
|
||||
}
|
||||
|
||||
func TestViewCluster(t *testing.T) {
|
||||
conf := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"minikube": {Server: "https://192.168.99.100:8443"},
|
||||
"my-cluster": {Server: "https://192.168.0.1:3434"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
|
||||
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
|
||||
},
|
||||
CurrentContext: "minikube",
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"minikube": {Token: "minikube-token"},
|
||||
"mu-cluster": {Token: "minikube-token"},
|
||||
},
|
||||
}
|
||||
|
||||
test := viewClusterTest{
|
||||
description: "Testing for kubectl config view",
|
||||
config: conf,
|
||||
expected: `apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://192.168.99.100:8443
|
||||
name: minikube
|
||||
- cluster:
|
||||
server: https://192.168.0.1:3434
|
||||
name: my-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: minikube
|
||||
user: minikube
|
||||
name: minikube
|
||||
- context:
|
||||
cluster: my-cluster
|
||||
user: mu-cluster
|
||||
name: my-cluster
|
||||
current-context: minikube
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: minikube
|
||||
user:
|
||||
token: minikube-token
|
||||
- name: mu-cluster
|
||||
user:
|
||||
token: minikube-token` + "\n",
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
|
||||
}
|
||||
|
||||
func TestViewClusterMinify(t *testing.T) {
|
||||
conf := clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: "v1",
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"minikube": {Server: "https://192.168.99.100:8443"},
|
||||
"my-cluster": {Server: "https://192.168.0.1:3434"},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
|
||||
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
|
||||
},
|
||||
CurrentContext: "minikube",
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"minikube": {Token: "minikube-token"},
|
||||
"mu-cluster": {Token: "minikube-token"},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
config clientcmdapi.Config
|
||||
flags []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
description: "Testing for kubectl config view --minify=true",
|
||||
config: conf,
|
||||
flags: []string{"--minify=true"},
|
||||
expected: `apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://192.168.99.100:8443
|
||||
name: minikube
|
||||
contexts:
|
||||
- context:
|
||||
cluster: minikube
|
||||
user: minikube
|
||||
name: minikube
|
||||
current-context: minikube
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: minikube
|
||||
user:
|
||||
token: minikube-token` + "\n",
|
||||
},
|
||||
{
|
||||
description: "Testing for kubectl config view --minify=true --context=my-cluster",
|
||||
config: conf,
|
||||
flags: []string{"--minify=true", "--context=my-cluster"},
|
||||
expected: `apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://192.168.0.1:3434
|
||||
name: my-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: my-cluster
|
||||
user: mu-cluster
|
||||
name: my-cluster
|
||||
current-context: my-cluster
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: mu-cluster
|
||||
user:
|
||||
token: minikube-token` + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
cmdTest := viewClusterTest{
|
||||
description: test.description,
|
||||
config: test.config,
|
||||
flags: test.flags,
|
||||
expected: test.expected,
|
||||
}
|
||||
cmdTest.run(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (test viewClusterTest) run(t *testing.T) {
|
||||
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 = ""
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdConfigView(cmdutil.NewFactory(genericclioptions.NewTestConfigFlags()), streams, pathOptions)
|
||||
// "context" is a global flag, inherited from base kubectl command in the real world
|
||||
cmd.Flags().String("context", "", "The name of the kubeconfig context to use")
|
||||
cmd.Flags().Parse(test.flags)
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("unexpected error executing command: %v,kubectl config view flags: %v", err, test.flags)
|
||||
}
|
||||
if len(test.expected) != 0 {
|
||||
if buf.String() != test.expected {
|
||||
t.Errorf("Failed in %q\n expected %v\n but got %v\n", test.description, test.expected, buf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
310
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert.go
generated
vendored
Normal file
310
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert.go
generated
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
scheme "k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
convert_long = templates.LongDesc(i18n.T(`
|
||||
Convert config files between different API versions. Both YAML
|
||||
and JSON formats are accepted.
|
||||
|
||||
The command takes filename, directory, or URL as input, and convert it into format
|
||||
of version specified by --output-version flag. If target version is not specified or
|
||||
not supported, convert to latest version.
|
||||
|
||||
The default output will be printed to stdout in YAML format. One can use -o option
|
||||
to change to output destination.`))
|
||||
|
||||
convert_example = templates.Examples(i18n.T(`
|
||||
# Convert 'pod.yaml' to latest version and print to stdout.
|
||||
kubectl convert -f pod.yaml
|
||||
|
||||
# Convert the live state of the resource specified by 'pod.yaml' to the latest version
|
||||
# and print to stdout in JSON format.
|
||||
kubectl convert -f pod.yaml --local -o json
|
||||
|
||||
# Convert all files under current directory to latest version and create them all.
|
||||
kubectl convert -f . | kubectl create -f -`))
|
||||
)
|
||||
|
||||
// NewCmdConvert creates a command object for the generic "convert" action, which
|
||||
// translates the config file into a given version.
|
||||
func NewCmdConvert(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := NewConvertOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "convert -f FILENAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Convert config files between different API versions"),
|
||||
Long: convert_long,
|
||||
Example: convert_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd))
|
||||
cmdutil.CheckErr(options.RunConvert())
|
||||
},
|
||||
}
|
||||
|
||||
options.PrintFlags.AddFlags(cmd)
|
||||
|
||||
usage := "to need to get converted."
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
|
||||
cmd.MarkFlagRequired("filename")
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, convert will NOT try to contact api-server but run locally.")
|
||||
cmd.Flags().String("output-version", "", i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)"))
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ConvertOptions have the data required to perform the convert operation
|
||||
type ConvertOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
resource.FilenameOptions
|
||||
|
||||
builder *resource.Builder
|
||||
local bool
|
||||
|
||||
genericclioptions.IOStreams
|
||||
specifiedOutputVersion schema.GroupVersion
|
||||
}
|
||||
|
||||
func NewConvertOptions(ioStreams genericclioptions.IOStreams) *ConvertOptions {
|
||||
return &ConvertOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("converted").WithTypeSetter(scheme.Scheme).WithDefaultOutput("yaml"),
|
||||
local: true,
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
|
||||
// defaultVersion is never mutated. Nil simply allows clean passing in common usage from client.Config
|
||||
func outputVersion(cmd *cobra.Command) (schema.GroupVersion, error) {
|
||||
outputVersionString := cmdutil.GetFlagString(cmd, "output-version")
|
||||
if len(outputVersionString) == 0 {
|
||||
return schema.GroupVersion{}, nil
|
||||
}
|
||||
|
||||
return schema.ParseGroupVersion(outputVersionString)
|
||||
}
|
||||
|
||||
// Complete collects information required to run Convert command from command line.
|
||||
func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err error) {
|
||||
o.specifiedOutputVersion, err = outputVersion(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build the builder
|
||||
o.builder = f.NewBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
LocalParam(o.local)
|
||||
if !o.local {
|
||||
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.builder.Schema(schema)
|
||||
}
|
||||
|
||||
cmdNamespace, _, err := f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.builder.NamespaceParam(cmdNamespace).
|
||||
ContinueOnError().
|
||||
FilenameParam(false, &o.FilenameOptions).
|
||||
Flatten()
|
||||
|
||||
// build the printer
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = printer.PrintObj
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunConvert implements the generic Convert command
|
||||
func (o *ConvertOptions) RunConvert() error {
|
||||
r := o.builder.Do()
|
||||
err := r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
singleItemImplied := false
|
||||
infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(infos) == 0 {
|
||||
return fmt.Errorf("no objects passed to convert")
|
||||
}
|
||||
|
||||
objects, err := asVersionedObject(infos, !singleItemImplied, o.specifiedOutputVersion, cmdutil.InternalVersionJSONEncoder())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if meta.IsListType(objects) {
|
||||
obj, err := objectListToVersionedObject([]runtime.Object{objects}, o.specifiedOutputVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return o.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
return o.PrintObj(objects, o.Out)
|
||||
}
|
||||
|
||||
// objectListToVersionedObject receives a list of api objects and a group version
|
||||
// and squashes the list's items into a single versioned runtime.Object.
|
||||
func objectListToVersionedObject(objects []runtime.Object, specifiedOutputVersion schema.GroupVersion) (runtime.Object, error) {
|
||||
objectList := &api.List{Items: objects}
|
||||
targetVersions := []schema.GroupVersion{}
|
||||
if !specifiedOutputVersion.Empty() {
|
||||
targetVersions = append(targetVersions, specifiedOutputVersion)
|
||||
}
|
||||
targetVersions = append(targetVersions, schema.GroupVersion{Group: "", Version: "v1"})
|
||||
converted, err := tryConvert(scheme.Scheme, objectList, targetVersions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
// asVersionedObject converts a list of infos into a single object - either a List containing
|
||||
// the objects as children, or if only a single Object is present, as that object. The provided
|
||||
// version will be preferred as the conversion target, but the Object's mapping version will be
|
||||
// used if that version is not present.
|
||||
func asVersionedObject(infos []*resource.Info, forceList bool, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder) (runtime.Object, error) {
|
||||
objects, err := asVersionedObjects(infos, specifiedOutputVersion, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var object runtime.Object
|
||||
if len(objects) == 1 && !forceList {
|
||||
object = objects[0]
|
||||
} else {
|
||||
object = &api.List{Items: objects}
|
||||
targetVersions := []schema.GroupVersion{}
|
||||
if !specifiedOutputVersion.Empty() {
|
||||
targetVersions = append(targetVersions, specifiedOutputVersion)
|
||||
}
|
||||
targetVersions = append(targetVersions, schema.GroupVersion{Group: "", Version: "v1"})
|
||||
|
||||
converted, err := tryConvert(scheme.Scheme, object, targetVersions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object = converted
|
||||
}
|
||||
|
||||
actualVersion := object.GetObjectKind().GroupVersionKind()
|
||||
if actualVersion.Version != specifiedOutputVersion.Version {
|
||||
defaultVersionInfo := ""
|
||||
if len(actualVersion.Version) > 0 {
|
||||
defaultVersionInfo = fmt.Sprintf("Defaulting to %q", actualVersion.Version)
|
||||
}
|
||||
glog.V(1).Infof("info: the output version specified is invalid. %s\n", defaultVersionInfo)
|
||||
}
|
||||
return object, nil
|
||||
}
|
||||
|
||||
// asVersionedObjects converts a list of infos into versioned objects. The provided
|
||||
// version will be preferred as the conversion target, but the Object's mapping version will be
|
||||
// used if that version is not present.
|
||||
func asVersionedObjects(infos []*resource.Info, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) {
|
||||
objects := []runtime.Object{}
|
||||
for _, info := range infos {
|
||||
if info.Object == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
targetVersions := []schema.GroupVersion{}
|
||||
// objects that are not part of api.Scheme must be converted to JSON
|
||||
// TODO: convert to map[string]interface{}, attach to runtime.Unknown?
|
||||
if !specifiedOutputVersion.Empty() {
|
||||
if _, _, err := scheme.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) {
|
||||
// TODO: ideally this would encode to version, but we don't expose multiple codecs here.
|
||||
data, err := runtime.Encode(encoder, info.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Set ContentEncoding and ContentType.
|
||||
objects = append(objects, &runtime.Unknown{Raw: data})
|
||||
continue
|
||||
}
|
||||
targetVersions = append(targetVersions, specifiedOutputVersion)
|
||||
} else {
|
||||
gvks, _, err := scheme.Scheme.ObjectKinds(info.Object)
|
||||
if err == nil {
|
||||
for _, gvk := range gvks {
|
||||
for _, version := range scheme.Scheme.PrioritizedVersionsForGroup(gvk.Group) {
|
||||
targetVersions = append(targetVersions, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
converted, err := tryConvert(scheme.Scheme, info.Object, targetVersions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objects = append(objects, converted)
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// tryConvert attempts to convert the given object to the provided versions in order. This function assumes
|
||||
// the object is in internal version.
|
||||
func tryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...schema.GroupVersion) (runtime.Object, error) {
|
||||
var last error
|
||||
for _, version := range versions {
|
||||
if version.Empty() {
|
||||
return object, nil
|
||||
}
|
||||
obj, err := converter.ConvertToVersion(object, version)
|
||||
if err != nil {
|
||||
last = err
|
||||
continue
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
return nil, last
|
||||
}
|
||||
121
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert_test.go
generated
vendored
Normal file
121
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert_test.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/rest/fake"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
type testcase struct {
|
||||
name string
|
||||
file string
|
||||
outputVersion string
|
||||
fields []checkField
|
||||
}
|
||||
|
||||
type checkField struct {
|
||||
expected string
|
||||
}
|
||||
|
||||
func TestConvertObject(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "apps deployment to extensions deployment",
|
||||
file: "../../../test/fixtures/pkg/kubectl/cmd/convert/appsdeployment.yaml",
|
||||
outputVersion: "extensions/v1beta1",
|
||||
fields: []checkField{
|
||||
{
|
||||
expected: "apiVersion: extensions/v1beta1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extensions deployment to apps deployment",
|
||||
file: "../../../test/fixtures/pkg/kubectl/cmd/convert/extensionsdeployment.yaml",
|
||||
outputVersion: "apps/v1beta2",
|
||||
fields: []checkField{
|
||||
{
|
||||
expected: "apiVersion: apps/v1beta2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v1 HPA to v2beta1 HPA",
|
||||
file: "../../../test/fixtures/pkg/kubectl/cmd/convert/v1HPA.yaml",
|
||||
outputVersion: "autoscaling/v2beta1",
|
||||
fields: []checkField{
|
||||
{
|
||||
expected: "apiVersion: autoscaling/v2beta1",
|
||||
},
|
||||
{
|
||||
expected: "name: cpu",
|
||||
},
|
||||
{
|
||||
expected: "targetAverageUtilization: 50",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v2beta1 HPA to v1 HPA",
|
||||
file: "../../../test/fixtures/pkg/kubectl/cmd/convert/v2beta1HPA.yaml",
|
||||
outputVersion: "autoscaling/v1",
|
||||
fields: []checkField{
|
||||
{
|
||||
expected: "apiVersion: autoscaling/v1",
|
||||
},
|
||||
{
|
||||
expected: "targetCPUUtilizationPercentage: 50",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
for _, field := range tc.fields {
|
||||
t.Run(fmt.Sprintf("%s %s", tc.name, field), func(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}),
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
cmd := NewCmdConvert(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf})
|
||||
cmd.Flags().Set("filename", tc.file)
|
||||
cmd.Flags().Set("output-version", tc.outputVersion)
|
||||
cmd.Flags().Set("local", "true")
|
||||
cmd.Flags().Set("output", "yaml")
|
||||
cmd.Run(cmd, []string{})
|
||||
if !strings.Contains(buf.String(), field.expected) {
|
||||
t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
492
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cp.go
generated
vendored
Normal file
492
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cp.go
generated
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
|
||||
"bytes"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
cpExample = templates.Examples(i18n.T(`
|
||||
# !!!Important Note!!!
|
||||
# Requires that the 'tar' binary is present in your container
|
||||
# image. If 'tar' is not present, 'kubectl cp' will fail.
|
||||
|
||||
# Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod in the default namespace
|
||||
kubectl cp /tmp/foo_dir <some-pod>:/tmp/bar_dir
|
||||
|
||||
# Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container
|
||||
kubectl cp /tmp/foo <some-pod>:/tmp/bar -c <specific-container>
|
||||
|
||||
# Copy /tmp/foo local file to /tmp/bar in a remote pod in namespace <some-namespace>
|
||||
kubectl cp /tmp/foo <some-namespace>/<some-pod>:/tmp/bar
|
||||
|
||||
# Copy /tmp/foo from a remote pod to /tmp/bar locally
|
||||
kubectl cp <some-namespace>/<some-pod>:/tmp/foo /tmp/bar`))
|
||||
|
||||
cpUsageStr = dedent.Dedent(`
|
||||
expected 'cp <file-spec-src> <file-spec-dest> [-c container]'.
|
||||
<file-spec> is:
|
||||
[namespace/]pod-name:/file/path for a remote file
|
||||
/file/path for a local file`)
|
||||
)
|
||||
|
||||
type CopyOptions struct {
|
||||
Container string
|
||||
Namespace string
|
||||
|
||||
ClientConfig *restclient.Config
|
||||
Clientset internalclientset.Interface
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewCopyOptions(ioStreams genericclioptions.IOStreams) *CopyOptions {
|
||||
return &CopyOptions{
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCp creates a new Copy command.
|
||||
func NewCmdCp(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewCopyOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "cp <file-spec-src> <file-spec-dest>",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Copy files and directories to and from containers."),
|
||||
Long: "Copy files and directories to and from containers.",
|
||||
Example: cpExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd))
|
||||
cmdutil.CheckErr(o.Run(args))
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&o.Container, "container", "c", o.Container, "Container name. If omitted, the first container in the pod will be chosen")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type fileSpec struct {
|
||||
PodNamespace string
|
||||
PodName string
|
||||
File string
|
||||
}
|
||||
|
||||
var (
|
||||
errFileSpecDoesntMatchFormat = errors.New("Filespec must match the canonical format: [[namespace/]pod:]file/path")
|
||||
errFileCannotBeEmpty = errors.New("Filepath can not be empty")
|
||||
)
|
||||
|
||||
func extractFileSpec(arg string) (fileSpec, error) {
|
||||
if i := strings.Index(arg, ":"); i == -1 {
|
||||
return fileSpec{File: arg}, nil
|
||||
} else if i > 0 {
|
||||
file := arg[i+1:]
|
||||
pod := arg[:i]
|
||||
pieces := strings.Split(pod, "/")
|
||||
if len(pieces) == 1 {
|
||||
return fileSpec{
|
||||
PodName: pieces[0],
|
||||
File: file,
|
||||
}, nil
|
||||
}
|
||||
if len(pieces) == 2 {
|
||||
return fileSpec{
|
||||
PodNamespace: pieces[0],
|
||||
PodName: pieces[1],
|
||||
File: file,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return fileSpec{}, errFileSpecDoesntMatchFormat
|
||||
}
|
||||
|
||||
func (o *CopyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
var err error
|
||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Clientset, err = f.ClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.ClientConfig, err = f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CopyOptions) Validate(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 2 {
|
||||
return cmdutil.UsageErrorf(cmd, cpUsageStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CopyOptions) Run(args []string) error {
|
||||
srcSpec, err := extractFileSpec(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destSpec, err := extractFileSpec(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(srcSpec.PodName) != 0 && len(destSpec.PodName) != 0 {
|
||||
if _, err := os.Stat(args[0]); err == nil {
|
||||
return o.copyToPod(fileSpec{File: args[0]}, destSpec)
|
||||
}
|
||||
return fmt.Errorf("src doesn't exist in local filesystem")
|
||||
}
|
||||
|
||||
if len(srcSpec.PodName) != 0 {
|
||||
return o.copyFromPod(srcSpec, destSpec)
|
||||
}
|
||||
if len(destSpec.PodName) != 0 {
|
||||
return o.copyToPod(srcSpec, destSpec)
|
||||
}
|
||||
return fmt.Errorf("one of src or dest must be a remote file specification")
|
||||
}
|
||||
|
||||
// checkDestinationIsDir receives a destination fileSpec and
|
||||
// determines if the provided destination path exists on the
|
||||
// pod. If the destination path does not exist or is _not_ a
|
||||
// directory, an error is returned with the exit code received.
|
||||
func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
|
||||
options := &ExecOptions{
|
||||
StreamOptions: StreamOptions{
|
||||
IOStreams: genericclioptions.IOStreams{
|
||||
Out: bytes.NewBuffer([]byte{}),
|
||||
ErrOut: bytes.NewBuffer([]byte{}),
|
||||
},
|
||||
|
||||
Namespace: dest.PodNamespace,
|
||||
PodName: dest.PodName,
|
||||
},
|
||||
|
||||
Command: []string{"test", "-d", dest.File},
|
||||
Executor: &DefaultRemoteExecutor{},
|
||||
}
|
||||
|
||||
return o.execute(options)
|
||||
}
|
||||
|
||||
func (o *CopyOptions) copyToPod(src, dest fileSpec) error {
|
||||
if len(src.File) == 0 || len(dest.File) == 0 {
|
||||
return errFileCannotBeEmpty
|
||||
}
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
// strip trailing slash (if any)
|
||||
if dest.File != "/" && strings.HasSuffix(string(dest.File[len(dest.File)-1]), "/") {
|
||||
dest.File = dest.File[:len(dest.File)-1]
|
||||
}
|
||||
|
||||
if err := o.checkDestinationIsDir(dest); err == nil {
|
||||
// If no error, dest.File was found to be a directory.
|
||||
// Copy specified src into it
|
||||
dest.File = dest.File + "/" + path.Base(src.File)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer writer.Close()
|
||||
err := makeTar(src.File, dest.File, writer)
|
||||
cmdutil.CheckErr(err)
|
||||
}()
|
||||
|
||||
// TODO: Improve error messages by first testing if 'tar' is present in the container?
|
||||
cmdArr := []string{"tar", "xf", "-"}
|
||||
destDir := path.Dir(dest.File)
|
||||
if len(destDir) > 0 {
|
||||
cmdArr = append(cmdArr, "-C", destDir)
|
||||
}
|
||||
|
||||
options := &ExecOptions{
|
||||
StreamOptions: StreamOptions{
|
||||
IOStreams: genericclioptions.IOStreams{
|
||||
In: reader,
|
||||
Out: o.Out,
|
||||
ErrOut: o.ErrOut,
|
||||
},
|
||||
Stdin: true,
|
||||
|
||||
Namespace: dest.PodNamespace,
|
||||
PodName: dest.PodName,
|
||||
},
|
||||
|
||||
Command: cmdArr,
|
||||
Executor: &DefaultRemoteExecutor{},
|
||||
}
|
||||
return o.execute(options)
|
||||
}
|
||||
|
||||
func (o *CopyOptions) copyFromPod(src, dest fileSpec) error {
|
||||
if len(src.File) == 0 || len(dest.File) == 0 {
|
||||
return errFileCannotBeEmpty
|
||||
}
|
||||
|
||||
reader, outStream := io.Pipe()
|
||||
options := &ExecOptions{
|
||||
StreamOptions: StreamOptions{
|
||||
IOStreams: genericclioptions.IOStreams{
|
||||
In: nil,
|
||||
Out: outStream,
|
||||
ErrOut: o.Out,
|
||||
},
|
||||
|
||||
Namespace: src.PodNamespace,
|
||||
PodName: src.PodName,
|
||||
},
|
||||
|
||||
// TODO: Improve error messages by first testing if 'tar' is present in the container?
|
||||
Command: []string{"tar", "cf", "-", src.File},
|
||||
Executor: &DefaultRemoteExecutor{},
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer outStream.Close()
|
||||
o.execute(options)
|
||||
}()
|
||||
prefix := getPrefix(src.File)
|
||||
prefix = path.Clean(prefix)
|
||||
// remove extraneous path shortcuts - these could occur if a path contained extra "../"
|
||||
// and attempted to navigate beyond "/" in a remote filesystem
|
||||
prefix = stripPathShortcuts(prefix)
|
||||
return untarAll(reader, dest.File, prefix)
|
||||
}
|
||||
|
||||
// stripPathShortcuts removes any leading or trailing "../" from a given path
|
||||
func stripPathShortcuts(p string) string {
|
||||
newPath := path.Clean(p)
|
||||
if len(newPath) > 0 && string(newPath[0]) == "/" {
|
||||
return newPath[1:]
|
||||
}
|
||||
|
||||
return newPath
|
||||
}
|
||||
|
||||
func makeTar(srcPath, destPath string, writer io.Writer) error {
|
||||
// TODO: use compression here?
|
||||
tarWriter := tar.NewWriter(writer)
|
||||
defer tarWriter.Close()
|
||||
|
||||
srcPath = path.Clean(srcPath)
|
||||
destPath = path.Clean(destPath)
|
||||
return recursiveTar(path.Dir(srcPath), path.Base(srcPath), path.Dir(destPath), path.Base(destPath), tarWriter)
|
||||
}
|
||||
|
||||
func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error {
|
||||
filepath := path.Join(srcBase, srcFile)
|
||||
stat, err := os.Lstat(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
files, err := ioutil.ReadDir(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(files) == 0 {
|
||||
//case empty directory
|
||||
hdr, _ := tar.FileInfoHeader(stat, filepath)
|
||||
hdr.Name = destFile
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else if stat.Mode()&os.ModeSymlink != 0 {
|
||||
//case soft link
|
||||
hdr, _ := tar.FileInfoHeader(stat, filepath)
|
||||
target, err := os.Readlink(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hdr.Linkname = target
|
||||
hdr.Name = destFile
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
//case regular file or other file type like pipe
|
||||
hdr, err := tar.FileInfoHeader(stat, filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = destFile
|
||||
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(tw, f); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// clean prevents path traversals by stripping them out.
|
||||
// This is adapted from https://golang.org/src/net/http/fs.go#L74
|
||||
func clean(fileName string) string {
|
||||
return path.Clean(string(os.PathSeparator) + fileName)
|
||||
}
|
||||
|
||||
func untarAll(reader io.Reader, destFile, prefix string) error {
|
||||
entrySeq := -1
|
||||
|
||||
// TODO: use compression here?
|
||||
tarReader := tar.NewReader(reader)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
entrySeq++
|
||||
mode := header.FileInfo().Mode()
|
||||
outFileName := path.Join(destFile, clean(header.Name[len(prefix):]))
|
||||
baseName := path.Dir(outFileName)
|
||||
if err := os.MkdirAll(baseName, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if header.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(outFileName, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// handle coping remote file into local directory
|
||||
if entrySeq == 0 && !header.FileInfo().IsDir() {
|
||||
exists, err := dirExists(outFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
outFileName = filepath.Join(outFileName, path.Base(clean(header.Name)))
|
||||
}
|
||||
}
|
||||
|
||||
if mode&os.ModeSymlink != 0 {
|
||||
err := os.Symlink(header.Linkname, outFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
outFile, err := os.Create(outFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := outFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if entrySeq == -1 {
|
||||
//if no file was copied
|
||||
errInfo := fmt.Sprintf("error: %s no such file or directory", prefix)
|
||||
return errors.New(errInfo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPrefix(file string) string {
|
||||
// tar strips the leading '/' if it's there, so we will too
|
||||
return strings.TrimLeft(file, "/")
|
||||
}
|
||||
|
||||
func (o *CopyOptions) execute(options *ExecOptions) error {
|
||||
if len(options.Namespace) == 0 {
|
||||
options.Namespace = o.Namespace
|
||||
}
|
||||
|
||||
if len(o.Container) > 0 {
|
||||
options.ContainerName = o.Container
|
||||
}
|
||||
|
||||
options.Config = o.ClientConfig
|
||||
options.PodClient = o.Clientset.Core()
|
||||
|
||||
if err := options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := options.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dirExists checks if a path exists and is a directory.
|
||||
func dirExists(path string) (bool, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err == nil && fi.IsDir() {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
621
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cp_test.go
generated
vendored
Normal file
621
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cp_test.go
generated
vendored
Normal file
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
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 (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
type FileType int
|
||||
|
||||
const (
|
||||
RegularFile FileType = 0
|
||||
SymLink FileType = 1
|
||||
)
|
||||
|
||||
func TestExtractFileSpec(t *testing.T) {
|
||||
tests := []struct {
|
||||
spec string
|
||||
expectedPod string
|
||||
expectedNamespace string
|
||||
expectedFile string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
spec: "namespace/pod:/some/file",
|
||||
expectedPod: "pod",
|
||||
expectedNamespace: "namespace",
|
||||
expectedFile: "/some/file",
|
||||
},
|
||||
{
|
||||
spec: "pod:/some/file",
|
||||
expectedPod: "pod",
|
||||
expectedFile: "/some/file",
|
||||
},
|
||||
{
|
||||
spec: "/some/file",
|
||||
expectedFile: "/some/file",
|
||||
},
|
||||
{
|
||||
spec: ":file:not:exist:in:local:filesystem",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
spec: "namespace/pod/invalid:/some/file",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
spec: "pod:/some/filenamewith:in",
|
||||
expectedPod: "pod",
|
||||
expectedFile: "/some/filenamewith:in",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
spec, err := extractFileSpec(test.spec)
|
||||
if test.expectErr && err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
continue
|
||||
}
|
||||
if err != nil && !test.expectErr {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if spec.PodName != test.expectedPod {
|
||||
t.Errorf("expected: %s, saw: %s", test.expectedPod, spec.PodName)
|
||||
}
|
||||
if spec.PodNamespace != test.expectedNamespace {
|
||||
t.Errorf("expected: %s, saw: %s", test.expectedNamespace, spec.PodNamespace)
|
||||
}
|
||||
if spec.File != test.expectedFile {
|
||||
t.Errorf("expected: %s, saw: %s", test.expectedFile, spec.File)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "/foo/bar",
|
||||
expected: "foo/bar",
|
||||
},
|
||||
{
|
||||
input: "foo/bar",
|
||||
expected: "foo/bar",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
out := getPrefix(test.input)
|
||||
if out != test.expected {
|
||||
t.Errorf("expected: %s, saw: %s", test.expected, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarUntar(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "input")
|
||||
dir2, err2 := ioutil.TempDir("", "output")
|
||||
if err != nil || err2 != nil {
|
||||
t.Errorf("unexpected error: %v | %v", err, err2)
|
||||
t.FailNow()
|
||||
}
|
||||
dir = dir + "/"
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Errorf("Unexpected error cleaning up: %v", err)
|
||||
}
|
||||
if err := os.RemoveAll(dir2); err != nil {
|
||||
t.Errorf("Unexpected error cleaning up: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
data string
|
||||
fileType FileType
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
data: "foobarbaz",
|
||||
fileType: RegularFile,
|
||||
},
|
||||
{
|
||||
name: "dir/blah",
|
||||
data: "bazblahfoo",
|
||||
fileType: RegularFile,
|
||||
},
|
||||
{
|
||||
name: "some/other/directory/",
|
||||
data: "with more data here",
|
||||
fileType: RegularFile,
|
||||
},
|
||||
{
|
||||
name: "blah",
|
||||
data: "same file name different data",
|
||||
fileType: RegularFile,
|
||||
},
|
||||
{
|
||||
name: "gakki",
|
||||
data: "/tmp/gakki",
|
||||
fileType: SymLink,
|
||||
},
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
filepath := path.Join(dir, file.name)
|
||||
if err := os.MkdirAll(path.Dir(filepath), 0755); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if file.fileType == RegularFile {
|
||||
f, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(f, bytes.NewBuffer([]byte(file.data))); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if file.fileType == SymLink {
|
||||
err := os.Symlink(file.data, filepath)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("unexpected file type: %v", file)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
writer := &bytes.Buffer{}
|
||||
if err := makeTar(dir, dir, writer); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
reader := bytes.NewBuffer(writer.Bytes())
|
||||
if err := untarAll(reader, dir2, ""); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
absPath := filepath.Join(dir2, strings.TrimPrefix(dir, os.TempDir()))
|
||||
filePath := filepath.Join(absPath, file.name)
|
||||
|
||||
if file.fileType == RegularFile {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
buff := &bytes.Buffer{}
|
||||
if _, err := io.Copy(buff, f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file.data != string(buff.Bytes()) {
|
||||
t.Fatalf("expected: %s, saw: %s", file.data, string(buff.Bytes()))
|
||||
}
|
||||
} else if file.fileType == SymLink {
|
||||
dest, err := os.Readlink(filePath)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if file.data != dest {
|
||||
t.Fatalf("expected: %s, saw: %s", file.data, dest)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("unexpected file type: %v", file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCopyToLocalFileOrDir tests untarAll in two cases :
|
||||
// 1: copy pod file to local file
|
||||
// 2: copy pod file into local directory
|
||||
func TestCopyToLocalFileOrDir(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "input")
|
||||
dir2, err2 := ioutil.TempDir(os.TempDir(), "output")
|
||||
if err != nil || err2 != nil {
|
||||
t.Errorf("unexpected error: %v | %v", err, err2)
|
||||
t.FailNow()
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Errorf("Unexpected error cleaning up: %v", err)
|
||||
}
|
||||
if err := os.RemoveAll(dir2); err != nil {
|
||||
t.Errorf("Unexpected error cleaning up: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
data string
|
||||
dest string
|
||||
destDirExists bool
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
data: "foobarbaz",
|
||||
dest: "path/to/dest",
|
||||
destDirExists: false,
|
||||
},
|
||||
{
|
||||
name: "dir/blah",
|
||||
data: "bazblahfoo",
|
||||
dest: "dest/file/path",
|
||||
destDirExists: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
func() {
|
||||
// setup
|
||||
srcFilePath := filepath.Join(dir, file.name)
|
||||
destPath := filepath.Join(dir2, file.dest)
|
||||
if err := os.MkdirAll(filepath.Dir(srcFilePath), 0755); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
srcFile, err := os.Create(srcFilePath)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
if _, err := io.Copy(srcFile, bytes.NewBuffer([]byte(file.data))); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
if file.destDirExists {
|
||||
if err := os.MkdirAll(destPath, 0755); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// start tests
|
||||
srcTarFilePath := filepath.Join(dir, file.name+".tar")
|
||||
// here use tar command to create tar file instead of calling makeTar func
|
||||
// because makeTar func can not generate correct header name
|
||||
err = exec.Command("tar", "cf", srcTarFilePath, srcFilePath).Run()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
srcTarFile, err := os.Open(srcTarFilePath)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer srcTarFile.Close()
|
||||
|
||||
if err := untarAll(srcTarFile, destPath, getPrefix(srcFilePath)); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
actualDestFilePath := destPath
|
||||
if file.destDirExists {
|
||||
actualDestFilePath = filepath.Join(destPath, filepath.Base(srcFilePath))
|
||||
}
|
||||
_, err = os.Stat(actualDestFilePath)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
t.Errorf("expecting %s exists, but actually it's missing", actualDestFilePath)
|
||||
}
|
||||
destFile, err := os.Open(actualDestFilePath)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer destFile.Close()
|
||||
buff := &bytes.Buffer{}
|
||||
io.Copy(buff, destFile)
|
||||
if file.data != string(buff.Bytes()) {
|
||||
t.Errorf("expected: %s, actual: %s", file.data, string(buff.Bytes()))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTarDestinationName(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "input")
|
||||
dir2, err2 := ioutil.TempDir(os.TempDir(), "output")
|
||||
if err != nil || err2 != nil {
|
||||
t.Errorf("unexpected error: %v | %v", err, err2)
|
||||
t.FailNow()
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Errorf("Unexpected error cleaning up: %v", err)
|
||||
}
|
||||
if err := os.RemoveAll(dir2); err != nil {
|
||||
t.Errorf("Unexpected error cleaning up: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
data string
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
data: "foobarbaz",
|
||||
},
|
||||
{
|
||||
name: "dir/blah",
|
||||
data: "bazblahfoo",
|
||||
},
|
||||
{
|
||||
name: "some/other/directory",
|
||||
data: "with more data here",
|
||||
},
|
||||
{
|
||||
name: "blah",
|
||||
data: "same file name different data",
|
||||
},
|
||||
}
|
||||
|
||||
// ensure files exist on disk
|
||||
for _, file := range files {
|
||||
filepath := path.Join(dir, file.name)
|
||||
if err := os.MkdirAll(path.Dir(filepath), 0755); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
f, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(f, bytes.NewBuffer([]byte(file.data))); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
go func() {
|
||||
if err := makeTar(dir, dir2, writer); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tarReader := tar.NewReader(reader)
|
||||
for {
|
||||
hdr, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(hdr.Name, path.Base(dir2)) {
|
||||
t.Errorf("expected %q as destination filename prefix, saw: %q", path.Base(dir2), hdr.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadTar(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "dest")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v ", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// More or less cribbed from https://golang.org/pkg/archive/tar/#example__minimal
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
var files = []struct {
|
||||
name string
|
||||
body string
|
||||
}{
|
||||
{"/prefix/../../../tmp/foo", "Up to temp"},
|
||||
{"/prefix/foo/bar/../../home/bburns/names.txt", "Down and back"},
|
||||
}
|
||||
for _, file := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: file.name,
|
||||
Mode: 0600,
|
||||
Size: int64(len(file.body)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
t.Errorf("unexpected error: %v ", err)
|
||||
t.FailNow()
|
||||
}
|
||||
if _, err := tw.Write([]byte(file.body)); err != nil {
|
||||
t.Errorf("unexpected error: %v ", err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Errorf("unexpected error: %v ", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if err := untarAll(&buf, dir, "/prefix"); err != nil {
|
||||
t.Errorf("unexpected error: %v ", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
_, err := os.Stat(path.Join(dir, path.Clean(file.name[len("/prefix"):])))
|
||||
if err != nil {
|
||||
t.Errorf("Error finding file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
cleaned string
|
||||
}{
|
||||
{
|
||||
"../../../tmp/foo",
|
||||
"/tmp/foo",
|
||||
},
|
||||
{
|
||||
"/../../../tmp/foo",
|
||||
"/tmp/foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
out := clean(test.input)
|
||||
if out != test.cleaned {
|
||||
t.Errorf("Expected: %s, saw %s", test.cleaned, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyToPod(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
ns := legacyscheme.Codecs
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
responsePod := &v1.Pod{}
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, responsePod))))}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
tf.ClientConfigVal = defaultClientConfig()
|
||||
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdCp(tf, ioStreams)
|
||||
|
||||
srcFile, err := ioutil.TempDir("", "test")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.RemoveAll(srcFile)
|
||||
|
||||
tests := map[string]struct {
|
||||
dest string
|
||||
expectedErr bool
|
||||
}{
|
||||
"copy to directory": {
|
||||
dest: "/tmp/",
|
||||
expectedErr: false,
|
||||
},
|
||||
"copy to root": {
|
||||
dest: "/",
|
||||
expectedErr: false,
|
||||
},
|
||||
"copy to empty file name": {
|
||||
dest: "",
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
opts := NewCopyOptions(ioStreams)
|
||||
src := fileSpec{
|
||||
File: srcFile,
|
||||
}
|
||||
dest := fileSpec{
|
||||
PodNamespace: "pod-ns",
|
||||
PodName: "pod-name",
|
||||
File: test.dest,
|
||||
}
|
||||
opts.Complete(tf, cmd)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err = opts.copyToPod(src, dest)
|
||||
//If error is NotFound error , it indicates that the
|
||||
//request has been sent correctly.
|
||||
//Treat this as no error.
|
||||
if test.expectedErr && errors.IsNotFound(err) {
|
||||
t.Errorf("expected error but didn't get one")
|
||||
}
|
||||
if !test.expectedErr && !errors.IsNotFound(err) {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "Validate Succeed",
|
||||
args: []string{"1", "2"},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "Validate Fail",
|
||||
args: []string{"1", "2", "3"},
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
|
||||
opts := NewCopyOptions(ioStreams)
|
||||
cmd := NewCmdCp(tf, ioStreams)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := opts.Validate(cmd, test.args)
|
||||
if (err != nil) != test.expectedErr {
|
||||
t.Errorf("expected error: %v, saw: %v, error: %v", test.expectedErr, err != nil, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
119
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/BUILD
generated
vendored
Normal file
119
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/BUILD
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"create.go",
|
||||
"create_clusterrole.go",
|
||||
"create_clusterrolebinding.go",
|
||||
"create_configmap.go",
|
||||
"create_deployment.go",
|
||||
"create_job.go",
|
||||
"create_namespace.go",
|
||||
"create_pdb.go",
|
||||
"create_priorityclass.go",
|
||||
"create_quota.go",
|
||||
"create_role.go",
|
||||
"create_rolebinding.go",
|
||||
"create_secret.go",
|
||||
"create_service.go",
|
||||
"create_serviceaccount.go",
|
||||
"flags.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/create",
|
||||
visibility = ["//build/visible_to:pkg_kubectl_cmd_create_CONSUMERS"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/kubectl:go_default_library",
|
||||
"//pkg/kubectl/cmd/templates:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/cmd/util/editor:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/printers:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/resource:go_default_library",
|
||||
"//pkg/kubectl/scheme:go_default_library",
|
||||
"//pkg/kubectl/util/i18n:go_default_library",
|
||||
"//pkg/printers:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/k8s.io/api/batch/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
||||
"//vendor/k8s.io/client-go/dynamic:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"create_clusterrole_test.go",
|
||||
"create_clusterrolebinding_test.go",
|
||||
"create_configmap_test.go",
|
||||
"create_deployment_test.go",
|
||||
"create_job_test.go",
|
||||
"create_namespace_test.go",
|
||||
"create_pdb_test.go",
|
||||
"create_priorityclass_test.go",
|
||||
"create_quota_test.go",
|
||||
"create_role_test.go",
|
||||
"create_rolebinding_test.go",
|
||||
"create_secret_test.go",
|
||||
"create_service_test.go",
|
||||
"create_serviceaccount_test.go",
|
||||
"create_test.go",
|
||||
],
|
||||
data = [
|
||||
"//test/e2e/testing-manifests:all-srcs",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/api/testing:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/kubectl:go_default_library",
|
||||
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/resource:go_default_library",
|
||||
"//pkg/kubectl/scheme:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/batch/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
451
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create.go
generated
vendored
Normal file
451
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create.go
generated
vendored
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
kruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
type CreateOptions struct {
|
||||
PrintFlags *PrintFlags
|
||||
RecordFlags *genericclioptions.RecordFlags
|
||||
|
||||
DryRun bool
|
||||
|
||||
FilenameOptions resource.FilenameOptions
|
||||
Selector string
|
||||
EditBeforeCreate bool
|
||||
Raw string
|
||||
|
||||
Recorder genericclioptions.Recorder
|
||||
PrintObj func(obj kruntime.Object) error
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
var (
|
||||
createLong = templates.LongDesc(i18n.T(`
|
||||
Create a resource from a file or from stdin.
|
||||
|
||||
JSON and YAML formats are accepted.`))
|
||||
|
||||
createExample = templates.Examples(i18n.T(`
|
||||
# Create a pod using the data in pod.json.
|
||||
kubectl create -f ./pod.json
|
||||
|
||||
# Create a pod based on the JSON passed into stdin.
|
||||
cat pod.json | kubectl create -f -
|
||||
|
||||
# Edit the data in docker-registry.yaml in JSON then create the resource using the edited data.
|
||||
kubectl create -f docker-registry.yaml --edit -o json`))
|
||||
)
|
||||
|
||||
func NewCreateOptions(ioStreams genericclioptions.IOStreams) *CreateOptions {
|
||||
return &CreateOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||
|
||||
Recorder: genericclioptions.NoopRecorder{},
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create -f FILENAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a resource from a file or from stdin."),
|
||||
Long: createLong,
|
||||
Example: createExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) {
|
||||
defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut)
|
||||
defaultRunFunc(cmd, args)
|
||||
return
|
||||
}
|
||||
cmdutil.CheckErr(o.Complete(f, cmd))
|
||||
cmdutil.CheckErr(o.ValidateArgs(cmd, args))
|
||||
cmdutil.CheckErr(o.RunCreate(f, cmd))
|
||||
},
|
||||
}
|
||||
|
||||
// bind flag structs
|
||||
o.RecordFlags.AddFlags(cmd)
|
||||
|
||||
usage := "to use to create the resource"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
|
||||
cmd.MarkFlagRequired("filename")
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating")
|
||||
cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows",
|
||||
"Only relevant if --edit=true. Defaults to the line ending native to your platform.")
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||
cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.")
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
// create subcommands
|
||||
cmd.AddCommand(NewCmdCreateNamespace(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateQuota(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateSecret(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateConfigMap(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceAccount(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateService(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateDeployment(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateClusterRole(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateRole(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateRoleBinding(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreatePriorityClass(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateJob(f, ioStreams))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *CreateOptions) ValidateArgs(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
|
||||
}
|
||||
if len(o.Raw) > 0 {
|
||||
if o.EditBeforeCreate {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw and --edit are mutually exclusive")
|
||||
}
|
||||
if len(o.FilenameOptions.Filenames) != 1 {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw can only use a single local file or stdin")
|
||||
}
|
||||
if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw cannot read from a url")
|
||||
}
|
||||
if o.FilenameOptions.Recursive {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw and --recursive are mutually exclusive")
|
||||
}
|
||||
if len(o.Selector) > 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw and --selector (-l) are mutually exclusive")
|
||||
}
|
||||
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw and --output are mutually exclusive")
|
||||
}
|
||||
if _, err := url.ParseRequestURI(o.Raw); err != nil {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw must be a valid URL path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
var err error
|
||||
|
||||
o.RecordFlags.Complete(cmd)
|
||||
o.Recorder, err = o.RecordFlags.ToRecorder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DryRun = cmdutil.GetDryRunFlag(cmd)
|
||||
|
||||
if o.DryRun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj kruntime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
// raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
|
||||
// the validator enforces this, so
|
||||
if len(o.Raw) > 0 {
|
||||
return o.raw(f)
|
||||
}
|
||||
|
||||
if o.EditBeforeCreate {
|
||||
return RunEditOnCreate(f, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions)
|
||||
}
|
||||
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := f.NewBuilder().
|
||||
Unstructured().
|
||||
Schema(schema).
|
||||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
LabelSelectorParam(o.Selector).
|
||||
Flatten().
|
||||
Do()
|
||||
err = r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count := 0
|
||||
err = r.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil {
|
||||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||
}
|
||||
|
||||
if err := o.Recorder.Record(info.Object); err != nil {
|
||||
glog.V(4).Infof("error recording current command: %v", err)
|
||||
}
|
||||
|
||||
if !o.DryRun {
|
||||
if err := createAndRefresh(info); err != nil {
|
||||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||
}
|
||||
}
|
||||
|
||||
count++
|
||||
|
||||
return o.PrintObj(info.Object)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return fmt.Errorf("no objects passed to create")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// raw makes a simple HTTP request to the provided path on the server using the default
|
||||
// credentials.
|
||||
func (o *CreateOptions) raw(f cmdutil.Factory) error {
|
||||
restClient, err := f.RESTClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data io.ReadCloser
|
||||
if o.FilenameOptions.Filenames[0] == "-" {
|
||||
data = os.Stdin
|
||||
} else {
|
||||
data, err = os.Open(o.FilenameOptions.Filenames[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// TODO post content with stream. Right now it ignores body content
|
||||
result := restClient.Post().RequestURI(o.Raw).Body(data).Do()
|
||||
if err := result.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := result.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.Out, "%v", string(body))
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunEditOnCreate(f cmdutil.Factory, recordFlags *genericclioptions.RecordFlags, ioStreams genericclioptions.IOStreams, cmd *cobra.Command, options *resource.FilenameOptions) error {
|
||||
editOptions := editor.NewEditOptions(editor.EditBeforeCreateMode, ioStreams)
|
||||
editOptions.FilenameOptions = *options
|
||||
editOptions.ValidateOptions = cmdutil.ValidateOptions{
|
||||
EnableValidation: cmdutil.GetFlagBool(cmd, "validate"),
|
||||
}
|
||||
editOptions.Output = cmdutil.GetFlagString(cmd, "output")
|
||||
editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
editOptions.RecordFlags = recordFlags
|
||||
|
||||
err := editOptions.Complete(f, []string{}, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return editOptions.Run()
|
||||
}
|
||||
|
||||
// createAndRefresh creates an object from input info and refreshes info with that object
|
||||
func createAndRefresh(info *resource.Info) error {
|
||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.Refresh(obj, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name
|
||||
func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) {
|
||||
if len(args) != 1 {
|
||||
return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", len(args))
|
||||
}
|
||||
return args[0], nil
|
||||
}
|
||||
|
||||
// CreateSubcommandOptions is an options struct to support create subcommands
|
||||
type CreateSubcommandOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *PrintFlags
|
||||
// Name of resource being created
|
||||
Name string
|
||||
// StructuredGenerator is the resource generator for the object being created
|
||||
StructuredGenerator kubectl.StructuredGenerator
|
||||
// DryRun is true if the command should be simulated but not run against the server
|
||||
DryRun bool
|
||||
CreateAnnotation bool
|
||||
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
DynamicClient dynamic.Interface
|
||||
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewCreateSubcommandOptions(ioStreams genericclioptions.IOStreams) *CreateSubcommandOptions {
|
||||
return &CreateSubcommandOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, generator kubectl.StructuredGenerator) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Name = name
|
||||
o.StructuredGenerator = generator
|
||||
o.DryRun = cmdutil.GetDryRunFlag(cmd)
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
if o.DryRun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj kruntime.Object, out io.Writer) error {
|
||||
return printer.PrintObj(obj, out)
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DynamicClient, err = f.DynamicClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCreateSubcommand executes a create subcommand using the specified options
|
||||
func (o *CreateSubcommandOptions) Run() error {
|
||||
obj, err := o.StructuredGenerator.StructuredGenerate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !o.DryRun {
|
||||
// create subcommands have compiled knowledge of things they create, so type them directly
|
||||
gvks, _, err := legacyscheme.Scheme.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gvk := gvks[0]
|
||||
mapping, err := o.Mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := kubectl.CreateOrUpdateAnnotation(o.CreateAnnotation, obj, cmdutil.InternalVersionJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
asUnstructured := &unstructured.Unstructured{}
|
||||
|
||||
if err := legacyscheme.Scheme.Convert(obj, asUnstructured, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if mapping.Scope.Name() == meta.RESTScopeNameRoot {
|
||||
o.Namespace = ""
|
||||
}
|
||||
actualObject, err := o.DynamicClient.Resource(mapping.Resource).Namespace(o.Namespace).Create(asUnstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure we pass a versioned object to the printer
|
||||
obj = actualObject
|
||||
} else {
|
||||
if meta, err := meta.Accessor(obj); err == nil && o.EnforceNamespace {
|
||||
meta.SetNamespace(o.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(obj, o.Out)
|
||||
}
|
||||
206
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrole.go
generated
vendored
Normal file
206
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrole.go
generated
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
clusterRoleLong = templates.LongDesc(i18n.T(`
|
||||
Create a ClusterRole.`))
|
||||
|
||||
clusterRoleExample = templates.Examples(i18n.T(`
|
||||
# Create a ClusterRole named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
|
||||
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods
|
||||
|
||||
# Create a ClusterRole named "pod-reader" with ResourceName specified
|
||||
kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
|
||||
|
||||
# Create a ClusterRole named "foo" with API Group specified
|
||||
kubectl create clusterrole foo --verb=get,list,watch --resource=rs.extensions
|
||||
|
||||
# Create a ClusterRole named "foo" with SubResource specified
|
||||
kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
|
||||
|
||||
# Create a ClusterRole name "foo" with NonResourceURL specified
|
||||
kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*
|
||||
|
||||
# Create a ClusterRole name "monitoring" with AggregationRule specified
|
||||
kubectl create clusterrole monitoring --aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"`))
|
||||
|
||||
// Valid nonResource verb list for validation.
|
||||
validNonResourceVerbs = []string{"*", "get", "post", "put", "delete", "patch", "head", "options"}
|
||||
)
|
||||
|
||||
type CreateClusterRoleOptions struct {
|
||||
*CreateRoleOptions
|
||||
NonResourceURLs []string
|
||||
AggregationRule map[string]string
|
||||
}
|
||||
|
||||
// ClusterRole is a command to ease creating ClusterRoles.
|
||||
func NewCmdCreateClusterRole(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
c := &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: NewCreateRoleOptions(ioStreams),
|
||||
AggregationRule: map[string]string{},
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "clusterrole NAME --verb=verb --resource=resource.group [--resource-name=resourcename] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: clusterRoleLong,
|
||||
Long: clusterRoleLong,
|
||||
Example: clusterRoleExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(c.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(c.Validate())
|
||||
cmdutil.CheckErr(c.RunCreateRole())
|
||||
},
|
||||
}
|
||||
|
||||
c.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringSliceVar(&c.Verbs, "verb", c.Verbs, "Verb that applies to the resources contained in the rule")
|
||||
cmd.Flags().StringSliceVar(&c.NonResourceURLs, "non-resource-url", c.NonResourceURLs, "A partial url that user should have access to.")
|
||||
cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
|
||||
cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", c.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
|
||||
cmd.Flags().Var(utilflag.NewMapStringString(&c.AggregationRule), "aggregation-rule", "An aggregation label selector for combining ClusterRoles.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *CreateClusterRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
// Remove duplicate nonResourceURLs
|
||||
nonResourceURLs := []string{}
|
||||
for _, n := range c.NonResourceURLs {
|
||||
if !arrayContains(nonResourceURLs, n) {
|
||||
nonResourceURLs = append(nonResourceURLs, n)
|
||||
}
|
||||
}
|
||||
c.NonResourceURLs = nonResourceURLs
|
||||
|
||||
return c.CreateRoleOptions.Complete(f, cmd, args)
|
||||
}
|
||||
|
||||
func (c *CreateClusterRoleOptions) Validate() error {
|
||||
if c.Name == "" {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
|
||||
if len(c.AggregationRule) > 0 {
|
||||
if len(c.NonResourceURLs) > 0 || len(c.Verbs) > 0 || len(c.Resources) > 0 || len(c.ResourceNames) > 0 {
|
||||
return fmt.Errorf("aggregation rule must be specified without nonResourceURLs, verbs, resources or resourceNames")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate verbs.
|
||||
if len(c.Verbs) == 0 {
|
||||
return fmt.Errorf("at least one verb must be specified")
|
||||
}
|
||||
|
||||
if len(c.Resources) == 0 && len(c.NonResourceURLs) == 0 {
|
||||
return fmt.Errorf("one of resource or nonResourceURL must be specified")
|
||||
}
|
||||
|
||||
// validate resources
|
||||
if len(c.Resources) > 0 {
|
||||
for _, v := range c.Verbs {
|
||||
if !arrayContains(validResourceVerbs, v) {
|
||||
return fmt.Errorf("invalid verb: '%s'", v)
|
||||
}
|
||||
}
|
||||
if err := c.validateResource(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//validate non-resource-url
|
||||
if len(c.NonResourceURLs) > 0 {
|
||||
for _, v := range c.Verbs {
|
||||
if !arrayContains(validNonResourceVerbs, v) {
|
||||
return fmt.Errorf("invalid verb: '%s' for nonResourceURL", v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, nonResourceURL := range c.NonResourceURLs {
|
||||
if nonResourceURL == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
if nonResourceURL == "" || !strings.HasPrefix(nonResourceURL, "/") {
|
||||
return fmt.Errorf("nonResourceURL should start with /")
|
||||
}
|
||||
|
||||
if strings.ContainsRune(nonResourceURL[:len(nonResourceURL)-1], '*') {
|
||||
return fmt.Errorf("nonResourceURL only supports wildcard matches when '*' is at the end")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *CreateClusterRoleOptions) RunCreateRole() error {
|
||||
clusterRole := &rbacv1.ClusterRole{
|
||||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRole"},
|
||||
}
|
||||
clusterRole.Name = c.Name
|
||||
|
||||
var err error
|
||||
if len(c.AggregationRule) == 0 {
|
||||
rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clusterRole.Rules = rules
|
||||
} else {
|
||||
clusterRole.AggregationRule = &rbacv1.AggregationRule{
|
||||
ClusterRoleSelectors: []metav1.LabelSelector{
|
||||
{
|
||||
MatchLabels: c.AggregationRule,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create ClusterRole.
|
||||
if !c.DryRun {
|
||||
clusterRole, err = c.Client.ClusterRoles().Create(clusterRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.PrintObj(clusterRole)
|
||||
}
|
||||
528
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrole_test.go
generated
vendored
Normal file
528
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrole_test.go
generated
vendored
Normal file
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func TestCreateClusterRole(t *testing.T) {
|
||||
clusterRoleName := "my-cluster-role"
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.Client = &fake.RESTClient{}
|
||||
tf.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
tests := map[string]struct {
|
||||
verbs string
|
||||
resources string
|
||||
nonResourceURL string
|
||||
resourceNames string
|
||||
aggregationRule string
|
||||
expectedClusterRole *rbac.ClusterRole
|
||||
}{
|
||||
"test-duplicate-resources": {
|
||||
verbs: "get,watch,list",
|
||||
resources: "pods,pods",
|
||||
expectedClusterRole: &rbac.ClusterRole{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterRoleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"pods"},
|
||||
APIGroups: []string{""},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-valid-case-with-multiple-apigroups": {
|
||||
verbs: "get,watch,list",
|
||||
resources: "pods,deployments.extensions",
|
||||
expectedClusterRole: &rbac.ClusterRole{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterRoleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"pods"},
|
||||
APIGroups: []string{""},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"deployments"},
|
||||
APIGroups: []string{"extensions"},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-non-resource-url": {
|
||||
verbs: "get",
|
||||
nonResourceURL: "/logs/,/healthz",
|
||||
expectedClusterRole: &rbac.ClusterRole{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterRoleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{"/logs/", "/healthz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-resource-and-non-resource-url": {
|
||||
verbs: "get",
|
||||
nonResourceURL: "/logs/,/healthz",
|
||||
resources: "pods",
|
||||
expectedClusterRole: &rbac.ClusterRole{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterRoleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get"},
|
||||
Resources: []string{"pods"},
|
||||
APIGroups: []string{""},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{"/logs/", "/healthz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-aggregation-rules": {
|
||||
aggregationRule: "foo1=foo2,foo3=foo4",
|
||||
expectedClusterRole: &rbac.ClusterRole{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: clusterRoleName,
|
||||
},
|
||||
AggregationRule: &rbac.AggregationRule{
|
||||
ClusterRoleSelectors: []metav1.LabelSelector{
|
||||
{
|
||||
MatchLabels: map[string]string{
|
||||
"foo1": "foo2",
|
||||
"foo3": "foo4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateClusterRole(tf, ioStreams)
|
||||
cmd.Flags().Set("dry-run", "true")
|
||||
cmd.Flags().Set("output", "yaml")
|
||||
cmd.Flags().Set("verb", test.verbs)
|
||||
cmd.Flags().Set("resource", test.resources)
|
||||
cmd.Flags().Set("non-resource-url", test.nonResourceURL)
|
||||
cmd.Flags().Set("aggregation-rule", test.aggregationRule)
|
||||
if test.resourceNames != "" {
|
||||
cmd.Flags().Set("resource-name", test.resourceNames)
|
||||
}
|
||||
cmd.Run(cmd, []string{clusterRoleName})
|
||||
actual := &rbac.ClusterRole{}
|
||||
if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), buf.Bytes(), actual); err != nil {
|
||||
t.Log(string(buf.Bytes()))
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(test.expectedClusterRole, actual) {
|
||||
t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", name, test.expectedClusterRole, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterRoleValidate(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tests := map[string]struct {
|
||||
clusterRoleOptions *CreateClusterRoleOptions
|
||||
expectErr bool
|
||||
}{
|
||||
"test-missing-name": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-verb": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-resource": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get"},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-resource-existing-apigroup": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-resource-existing-subresource": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
SubResource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-verb": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"invalid-verb"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-nonresource-verb": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"post"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-special-verb": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"use"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-mix-verbs": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"impersonate", "use"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "userextras",
|
||||
SubResource: "scopes",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-special-verb-with-wrong-apigroup": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"impersonate"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "userextras",
|
||||
SubResource: "scopes",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-resource": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "invalid-resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-resource-name-with-multiple-resources": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
{
|
||||
Resource: "deployments",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-case": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "role-binder",
|
||||
Verbs: []string{"get", "list", "bind"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "roles",
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-case-with-subresource": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get", "list"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "replicasets",
|
||||
SubResource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-case-with-additional-resource": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"impersonate"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "userextras",
|
||||
SubResource: "scopes",
|
||||
Group: "authentication.k8s.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-invalid-empty-non-resource-url": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"create"},
|
||||
},
|
||||
NonResourceURLs: []string{""},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-non-resource-url": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"create"},
|
||||
},
|
||||
NonResourceURLs: []string{"logs"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-non-resource-url-with-*": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"create"},
|
||||
},
|
||||
NonResourceURLs: []string{"/logs/*/"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-non-resource-url-with-multiple-*": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"create"},
|
||||
},
|
||||
NonResourceURLs: []string{"/logs*/*"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-verb-for-non-resource-url": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"create"},
|
||||
},
|
||||
NonResourceURLs: []string{"/logs/"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-resource-and-non-resource-url-specified-together": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "replicasets",
|
||||
SubResource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
NonResourceURLs: []string{"/logs/", "/logs/*"},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-aggregation-rule-with-verb": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Verbs: []string{"get"},
|
||||
},
|
||||
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-aggregation-rule-with-resource": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "replicasets",
|
||||
SubResource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-aggregation-rule-with-no-resource-url": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
},
|
||||
NonResourceURLs: []string{"/logs/"},
|
||||
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-aggregation-rule": {
|
||||
clusterRoleOptions: &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: &CreateRoleOptions{
|
||||
Name: "my-clusterrole",
|
||||
},
|
||||
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var err error
|
||||
test.clusterRoleOptions.Mapper, err = tf.ToRESTMapper()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = test.clusterRoleOptions.Validate()
|
||||
if test.expectErr && err == nil {
|
||||
t.Errorf("%s: expect error happens, but validate passes.", name)
|
||||
}
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func defaultClientConfig() *restclient.Config {
|
||||
return &restclient.Config{
|
||||
APIPath: "/api",
|
||||
ContentConfig: restclient.ContentConfig{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
ContentType: runtime.ContentTypeJSON,
|
||||
GroupVersion: &schema.GroupVersion{Version: "v1"},
|
||||
},
|
||||
}
|
||||
}
|
||||
99
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrolebinding.go
generated
vendored
Normal file
99
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrolebinding.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
clusterRoleBindingLong = templates.LongDesc(i18n.T(`
|
||||
Create a ClusterRoleBinding for a particular ClusterRole.`))
|
||||
|
||||
clusterRoleBindingExample = templates.Examples(i18n.T(`
|
||||
# Create a ClusterRoleBinding for user1, user2, and group1 using the cluster-admin ClusterRole
|
||||
kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1`))
|
||||
)
|
||||
|
||||
type ClusterRoleBindingOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// ClusterRoleBinding is a command to ease creating ClusterRoleBindings.
|
||||
func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &ClusterRoleBindingOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "clusterrolebinding NAME --clusterrole=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a ClusterRoleBinding for a particular ClusterRole"),
|
||||
Long: clusterRoleBindingLong,
|
||||
Example: clusterRoleBindingExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ClusterRoleBindingV1GeneratorName)
|
||||
cmd.Flags().String("clusterrole", "", i18n.T("ClusterRole this ClusterRoleBinding should reference"))
|
||||
cmd.MarkFlagCustom("clusterrole", "__kubectl_get_resource_clusterrole")
|
||||
cmd.Flags().StringArray("user", []string{}, "Usernames to bind to the role")
|
||||
cmd.Flags().StringArray("group", []string{}, "Groups to bind to the role")
|
||||
cmd.Flags().StringArray("serviceaccount", []string{}, "Service accounts to bind to the role, in the format <namespace>:<name>")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ClusterRoleBindingOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ClusterRoleBindingV1GeneratorName:
|
||||
generator = &kubectl.ClusterRoleBindingGeneratorV1{
|
||||
Name: name,
|
||||
ClusterRole: cmdutil.GetFlagString(cmd, "clusterrole"),
|
||||
Users: cmdutil.GetFlagStringArray(cmd, "user"),
|
||||
Groups: cmdutil.GetFlagStringArray(cmd, "group"),
|
||||
ServiceAccounts: cmdutil.GetFlagStringArray(cmd, "serviceaccount"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateClusterRoleBinding is the implementation of the create clusterrolebinding command.
|
||||
func (o *ClusterRoleBindingOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
153
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrolebinding_test.go
generated
vendored
Normal file
153
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_clusterrolebinding_test.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
rbac "k8s.io/api/rbac/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func TestCreateClusterRoleBinding(t *testing.T) {
|
||||
expectBinding := &rbac.ClusterRoleBinding{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "fake-binding",
|
||||
},
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ClusterRoleBinding",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1beta1",
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbac.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: "fake-clusterrole",
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: rbac.UserKind,
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: "fake-user",
|
||||
},
|
||||
{
|
||||
Kind: rbac.GroupKind,
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: "fake-group",
|
||||
},
|
||||
{
|
||||
Kind: rbac.ServiceAccountKind,
|
||||
Namespace: "fake-namespace",
|
||||
Name: "fake-account",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
|
||||
decoder := ns.DecoderToVersion(info.Serializer, groupVersion)
|
||||
|
||||
tf.Client = &ClusterRoleBindingRESTClient{
|
||||
RESTClient: &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/clusterrolebindings" && m == "POST":
|
||||
bodyBits, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("TestCreateClusterRoleBinding error: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if obj, _, err := decoder.Decode(bodyBits, nil, &rbac.ClusterRoleBinding{}); err == nil {
|
||||
if !reflect.DeepEqual(obj.(*rbac.ClusterRoleBinding), expectBinding) {
|
||||
t.Fatalf("TestCreateClusterRoleBinding: expected:\n%#v\nsaw:\n%#v", expectBinding, obj.(*rbac.ClusterRoleBinding))
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("TestCreateClusterRoleBinding error, could not decode the request body into rbac.ClusterRoleBinding object: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
responseBinding := &rbac.ClusterRoleBinding{}
|
||||
responseBinding.Name = "fake-binding"
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseBinding))))}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
expectedOutput := "clusterrolebinding.rbac.authorization.k8s.io/" + expectBinding.Name + "\n"
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateClusterRoleBinding(tf, ioStreams)
|
||||
cmd.Flags().Set("clusterrole", "fake-clusterrole")
|
||||
cmd.Flags().Set("user", "fake-user")
|
||||
cmd.Flags().Set("group", "fake-group")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Flags().Set("serviceaccount", "fake-namespace:fake-account")
|
||||
cmd.Run(cmd, []string{"fake-binding"})
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("TestCreateClusterRoleBinding: expected %v\n but got %v\n", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
type ClusterRoleBindingRESTClient struct {
|
||||
*fake.RESTClient
|
||||
}
|
||||
|
||||
func (c *ClusterRoleBindingRESTClient) Post() *restclient.Request {
|
||||
config := restclient.ContentConfig{
|
||||
ContentType: runtime.ContentTypeJSON,
|
||||
NegotiatedSerializer: c.NegotiatedSerializer,
|
||||
}
|
||||
|
||||
info, _ := runtime.SerializerInfoForMediaType(c.NegotiatedSerializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
serializers := restclient.Serializers{
|
||||
Encoder: c.NegotiatedSerializer.EncoderForVersion(info.Serializer, schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1beta1"}),
|
||||
Decoder: c.NegotiatedSerializer.DecoderToVersion(info.Serializer, schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1beta1"}),
|
||||
}
|
||||
if info.StreamSerializer != nil {
|
||||
serializers.StreamingSerializer = info.StreamSerializer.Serializer
|
||||
serializers.Framer = info.StreamSerializer.Framer
|
||||
}
|
||||
return restclient.NewRequest(c, "POST", &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
|
||||
}
|
||||
|
||||
func defaultHeader() http.Header {
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", runtime.ContentTypeJSON)
|
||||
return header
|
||||
}
|
||||
120
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_configmap.go
generated
vendored
Normal file
120
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_configmap.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
configMapLong = templates.LongDesc(i18n.T(`
|
||||
Create a configmap based on a file, directory, or specified literal value.
|
||||
|
||||
A single configmap may package one or more key/value pairs.
|
||||
|
||||
When creating a configmap based on a file, the key will default to the basename of the file, and the value will
|
||||
default to the file content. If the basename is an invalid key, you may specify an alternate key.
|
||||
|
||||
When creating a configmap based on a directory, each file whose basename is a valid key in the directory will be
|
||||
packaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories,
|
||||
symlinks, devices, pipes, etc).`))
|
||||
|
||||
configMapExample = templates.Examples(i18n.T(`
|
||||
# Create a new configmap named my-config based on folder bar
|
||||
kubectl create configmap my-config --from-file=path/to/bar
|
||||
|
||||
# Create a new configmap named my-config with specified keys instead of file basenames on disk
|
||||
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
|
||||
|
||||
# Create a new configmap named my-config with key1=config1 and key2=config2
|
||||
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
|
||||
|
||||
# Create a new configmap named my-config from the key=value pairs in the file
|
||||
kubectl create configmap my-config --from-file=path/to/bar
|
||||
|
||||
# Create a new configmap named my-config from an env file
|
||||
kubectl create configmap my-config --from-env-file=path/to/bar.env`))
|
||||
)
|
||||
|
||||
type ConfigMapOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// ConfigMap is a command to ease creating ConfigMaps.
|
||||
func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &ConfigMapOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"cm"},
|
||||
Short: i18n.T("Create a configmap from a local file, directory or literal value"),
|
||||
Long: configMapLong,
|
||||
Example: configMapExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ConfigMapV1GeneratorName)
|
||||
cmd.Flags().StringSlice("from-file", []string{}, "Key file can be specified using its file path, in which case file basename will be used as configmap key, or optionally with a key and file path, in which case the given key will be used. Specifying a directory will iterate each named file in the directory whose basename is a valid configmap key.")
|
||||
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
|
||||
cmd.Flags().String("from-env-file", "", "Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the configmap to its name.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ConfigMapOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ConfigMapV1GeneratorName:
|
||||
generator = &kubectl.ConfigMapGeneratorV1{
|
||||
Name: name,
|
||||
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
|
||||
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
|
||||
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateConfigMap is the implementation of the create configmap command.
|
||||
func (o *ConfigMapOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
70
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_configmap_test.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_configmap_test.go
generated
vendored
Normal 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 create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func TestCreateConfigMap(t *testing.T) {
|
||||
configMap := &v1.ConfigMap{}
|
||||
configMap.Name = "my-configmap"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/configmaps" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, configMap)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateConfigMap(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{configMap.Name})
|
||||
expectedOutput := "configmap/" + configMap.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
|
||||
}
|
||||
155
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_deployment.go
generated
vendored
Normal file
155
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_deployment.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
deploymentLong = templates.LongDesc(i18n.T(`
|
||||
Create a deployment with the specified name.`))
|
||||
|
||||
deploymentExample = templates.Examples(i18n.T(`
|
||||
# Create a new deployment named my-dep that runs the busybox image.
|
||||
kubectl create deployment my-dep --image=busybox`))
|
||||
)
|
||||
|
||||
type DeploymentOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateDeployment is a macro command to create a new deployment.
|
||||
// This command is better known to users as `kubectl create deployment`.
|
||||
// Note that this command overlaps significantly with the `kubectl run` command.
|
||||
func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &DeploymentOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "deployment NAME --image=image [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"deploy"},
|
||||
Short: i18n.T("Create a deployment with the specified name."),
|
||||
Long: deploymentLong,
|
||||
Example: deploymentExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, "")
|
||||
cmd.Flags().StringSlice("image", []string{}, "Image name to run.")
|
||||
cmd.MarkFlagRequired("image")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// generatorFromName returns the appropriate StructuredGenerator based on the
|
||||
// generatorName. If the generatorName is unrecognized, then return (nil,
|
||||
// false).
|
||||
func generatorFromName(
|
||||
generatorName string,
|
||||
imageNames []string,
|
||||
deploymentName string,
|
||||
) (kubectl.StructuredGenerator, bool) {
|
||||
|
||||
switch generatorName {
|
||||
case cmdutil.DeploymentBasicAppsV1GeneratorName:
|
||||
generator := &kubectl.DeploymentBasicAppsGeneratorV1{
|
||||
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
|
||||
Name: deploymentName,
|
||||
Images: imageNames,
|
||||
},
|
||||
}
|
||||
return generator, true
|
||||
|
||||
case cmdutil.DeploymentBasicAppsV1Beta1GeneratorName:
|
||||
generator := &kubectl.DeploymentBasicAppsGeneratorV1Beta1{
|
||||
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
|
||||
Name: deploymentName,
|
||||
Images: imageNames,
|
||||
},
|
||||
}
|
||||
return generator, true
|
||||
|
||||
case cmdutil.DeploymentBasicV1Beta1GeneratorName:
|
||||
generator := &kubectl.DeploymentBasicGeneratorV1{
|
||||
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
|
||||
Name: deploymentName,
|
||||
Images: imageNames,
|
||||
},
|
||||
}
|
||||
return generator, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (o *DeploymentOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientset, err := f.ClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
generatorName := cmdutil.GetFlagString(cmd, "generator")
|
||||
|
||||
if len(generatorName) == 0 {
|
||||
generatorName = cmdutil.DeploymentBasicAppsV1GeneratorName
|
||||
generatorNameTemp, err := cmdutil.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), o.CreateSubcommandOptions.ErrOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if generatorNameTemp != generatorName {
|
||||
cmdutil.Warning(o.CreateSubcommandOptions.ErrOut, generatorName, generatorNameTemp)
|
||||
} else {
|
||||
generatorName = generatorNameTemp
|
||||
}
|
||||
}
|
||||
|
||||
imageNames := cmdutil.GetFlagStringSlice(cmd, "image")
|
||||
generator, ok := generatorFromName(generatorName, imageNames, name)
|
||||
if !ok {
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// createDeployment
|
||||
// 1. Reads user config values from Cobra.
|
||||
// 2. Sets up the correct Generator object.
|
||||
// 3. Calls RunCreateSubcommand.
|
||||
func (o *DeploymentOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
156
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_deployment_test.go
generated
vendored
Normal file
156
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_deployment_test.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func Test_generatorFromName(t *testing.T) {
|
||||
const (
|
||||
nonsenseName = "not-a-real-generator-name"
|
||||
basicName = cmdutil.DeploymentBasicV1Beta1GeneratorName
|
||||
basicAppsV1Beta1Name = cmdutil.DeploymentBasicAppsV1Beta1GeneratorName
|
||||
basicAppsV1Name = cmdutil.DeploymentBasicAppsV1GeneratorName
|
||||
deploymentName = "deployment-name"
|
||||
)
|
||||
imageNames := []string{"image-1", "image-2"}
|
||||
|
||||
generator, ok := generatorFromName(nonsenseName, imageNames, deploymentName)
|
||||
assert.Nil(t, generator)
|
||||
assert.False(t, ok)
|
||||
|
||||
generator, ok = generatorFromName(basicName, imageNames, deploymentName)
|
||||
assert.True(t, ok)
|
||||
|
||||
{
|
||||
expectedGenerator := &kubectl.DeploymentBasicGeneratorV1{
|
||||
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
|
||||
Name: deploymentName,
|
||||
Images: imageNames,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedGenerator, generator)
|
||||
}
|
||||
|
||||
generator, ok = generatorFromName(basicAppsV1Beta1Name, imageNames, deploymentName)
|
||||
assert.True(t, ok)
|
||||
|
||||
{
|
||||
expectedGenerator := &kubectl.DeploymentBasicAppsGeneratorV1Beta1{
|
||||
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
|
||||
Name: deploymentName,
|
||||
Images: imageNames,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedGenerator, generator)
|
||||
}
|
||||
|
||||
generator, ok = generatorFromName(basicAppsV1Name, imageNames, deploymentName)
|
||||
assert.True(t, ok)
|
||||
|
||||
{
|
||||
expectedGenerator := &kubectl.DeploymentBasicAppsGeneratorV1{
|
||||
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
|
||||
Name: deploymentName,
|
||||
Images: imageNames,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedGenerator, generator)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDeployment(t *testing.T) {
|
||||
depName := "jonny-dep"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := legacyscheme.Codecs
|
||||
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer([]byte(fakeDiscovery))),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = &restclient.Config{}
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateDeployment(tf, ioStreams)
|
||||
cmd.Flags().Set("dry-run", "true")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Flags().Set("image", "hollywood/jonny.depp:v2")
|
||||
cmd.Run(cmd, []string{depName})
|
||||
expectedOutput := "deployment.apps/" + depName + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDeploymentNoImage(t *testing.T) {
|
||||
depName := "jonny-dep"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := legacyscheme.Codecs
|
||||
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer([]byte(fakeDiscovery))),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = &restclient.Config{}
|
||||
|
||||
ioStreams := genericclioptions.NewTestIOStreamsDiscard()
|
||||
cmd := NewCmdCreateDeployment(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
options := &DeploymentOpts{
|
||||
CreateSubcommandOptions: &CreateSubcommandOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
DryRun: true,
|
||||
IOStreams: ioStreams,
|
||||
},
|
||||
}
|
||||
|
||||
err := options.Complete(tf, cmd, []string{depName})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = options.Run()
|
||||
assert.Error(t, err, "at least one image must be specified")
|
||||
}
|
||||
186
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_job.go
generated
vendored
Normal file
186
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_job.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
jobLong = templates.LongDesc(i18n.T(`
|
||||
Create a job with the specified name.`))
|
||||
|
||||
jobExample = templates.Examples(i18n.T(`
|
||||
# Create a job from a CronJob named "a-cronjob"
|
||||
kubectl create job test-job --from=cronjob/a-cronjob`))
|
||||
)
|
||||
|
||||
type CreateJobOptions struct {
|
||||
PrintFlags *PrintFlags
|
||||
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
From string
|
||||
|
||||
Namespace string
|
||||
OutputFormat string
|
||||
Client clientbatchv1.BatchV1Interface
|
||||
DryRun bool
|
||||
Builder *resource.Builder
|
||||
Cmd *cobra.Command
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewCreateJobOptions(ioStreams genericclioptions.IOStreams) *CreateJobOptions {
|
||||
return &CreateJobOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateJob is a command to ease creating Jobs from CronJobs.
|
||||
func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateJobOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "job NAME [--from=CRONJOB]",
|
||||
Short: jobLong,
|
||||
Long: jobLong,
|
||||
Example: jobExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.RunCreateJob())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) {
|
||||
if len(args) == 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "NAME is required")
|
||||
}
|
||||
o.Name = args[0]
|
||||
|
||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientset, err := f.KubernetesClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client = clientset.BatchV1()
|
||||
o.Builder = f.NewBuilder()
|
||||
o.DryRun = cmdutil.GetDryRunFlag(cmd)
|
||||
o.Cmd = cmd
|
||||
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
|
||||
|
||||
if o.DryRun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateJobOptions) RunCreateJob() error {
|
||||
infos, err := o.Builder.
|
||||
Unstructured().
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().
|
||||
ResourceTypeOrNameArgs(false, o.From).
|
||||
Flatten().
|
||||
Latest().
|
||||
Do().
|
||||
Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(infos) != 1 {
|
||||
return fmt.Errorf("from must be an existing cronjob")
|
||||
}
|
||||
|
||||
uncastVersionedObj, err := scheme.Scheme.ConvertToVersion(infos[0].Object, batchv1beta1.SchemeGroupVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("from must be an existing cronjob: %v", err)
|
||||
}
|
||||
cronJob, ok := uncastVersionedObj.(*batchv1beta1.CronJob)
|
||||
if !ok {
|
||||
return fmt.Errorf("from must be an existing cronjob")
|
||||
}
|
||||
|
||||
return o.createJob(cronJob)
|
||||
}
|
||||
|
||||
func (o *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error {
|
||||
annotations := make(map[string]string)
|
||||
annotations["cronjob.kubernetes.io/instantiate"] = "manual"
|
||||
for k, v := range cronJob.Spec.JobTemplate.Annotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Namespace: o.Namespace,
|
||||
Annotations: annotations,
|
||||
Labels: cronJob.Spec.JobTemplate.Labels,
|
||||
},
|
||||
Spec: cronJob.Spec.JobTemplate.Spec,
|
||||
}
|
||||
|
||||
if !o.DryRun {
|
||||
var err error
|
||||
job, err = o.Client.Jobs(o.Namespace).Create(job)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create job: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(job)
|
||||
}
|
||||
137
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_job_test.go
generated
vendored
Normal file
137
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_job_test.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
fake "k8s.io/client-go/kubernetes/fake"
|
||||
clienttesting "k8s.io/client-go/testing"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func TestCreateJobFromCronJob(t *testing.T) {
|
||||
var submittedJob *batchv1.Job
|
||||
testNamespaceName := "test"
|
||||
testCronJobName := "test-cronjob"
|
||||
testJobName := "test-job"
|
||||
testImageName := "fake"
|
||||
|
||||
expectedLabels := make(map[string]string)
|
||||
expectedAnnotations := make(map[string]string)
|
||||
expectedLabels["test-label"] = "test-value"
|
||||
|
||||
expectJob := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: testNamespaceName,
|
||||
Labels: expectedLabels,
|
||||
Annotations: expectedAnnotations,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Image: testImageName},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cronJob := &batchv1beta1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: testCronJobName,
|
||||
Namespace: testNamespaceName,
|
||||
},
|
||||
Spec: batchv1beta1.CronJobSpec{
|
||||
Schedule: "* * * * *",
|
||||
JobTemplate: batchv1beta1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: testNamespaceName,
|
||||
Labels: expectedLabels,
|
||||
},
|
||||
Spec: expectJob.Spec,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientset := fake.Clientset{}
|
||||
clientset.PrependReactor("create", "jobs", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
ca := action.(clienttesting.CreateAction)
|
||||
submittedJob = ca.GetObject().(*batchv1.Job)
|
||||
return true, expectJob, nil
|
||||
})
|
||||
f := cmdtesting.NewTestFactory()
|
||||
defer f.Cleanup()
|
||||
|
||||
printFlags := NewPrintFlags("created", legacyscheme.Scheme)
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmdOptions := &CreateJobOptions{
|
||||
PrintFlags: printFlags,
|
||||
Name: testJobName,
|
||||
Namespace: testNamespaceName,
|
||||
Client: clientset.BatchV1(),
|
||||
Cmd: NewCmdCreateJob(f, ioStreams),
|
||||
PrintObj: func(obj runtime.Object) error {
|
||||
p, err := printFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.PrintObj(obj, buf)
|
||||
},
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
|
||||
err := cmdOptions.createJob(cronJob)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if submittedJob.ObjectMeta.Name != testJobName {
|
||||
t.Errorf("expected '%s', got '%s'", testJobName, submittedJob.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
if l := len(submittedJob.Annotations); l != 1 {
|
||||
t.Errorf("expected length of annotations array to be 1, got %d", l)
|
||||
}
|
||||
if v, ok := submittedJob.Annotations["cronjob.kubernetes.io/instantiate"]; !ok || v != "manual" {
|
||||
t.Errorf("expected annotation cronjob.kubernetes.io/instantiate=manual to exist, got '%s'", v)
|
||||
}
|
||||
|
||||
if l := len(submittedJob.Labels); l != 1 {
|
||||
t.Errorf("expected length of labels array to be 1, got %d", l)
|
||||
}
|
||||
if v, ok := submittedJob.Labels["test-label"]; !ok || v != "test-value" {
|
||||
t.Errorf("expected label test-label=test-value to to exist, got '%s'", v)
|
||||
}
|
||||
|
||||
if l := len(submittedJob.Spec.Template.Spec.Containers); l != 1 {
|
||||
t.Errorf("expected length of container array to be 1, got %d", l)
|
||||
}
|
||||
if submittedJob.Spec.Template.Spec.Containers[0].Image != testImageName {
|
||||
t.Errorf("expected '%s', got '%s'", testImageName, submittedJob.Spec.Template.Spec.Containers[0].Image)
|
||||
}
|
||||
}
|
||||
90
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_namespace.go
generated
vendored
Normal file
90
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_namespace.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
namespaceLong = templates.LongDesc(i18n.T(`
|
||||
Create a namespace with the specified name.`))
|
||||
|
||||
namespaceExample = templates.Examples(i18n.T(`
|
||||
# Create a new namespace named my-namespace
|
||||
kubectl create namespace my-namespace`))
|
||||
)
|
||||
|
||||
type NamespaceOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateNamespace is a macro command to create a new namespace
|
||||
func NewCmdCreateNamespace(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &NamespaceOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "namespace NAME [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"ns"},
|
||||
Short: i18n.T("Create a namespace with the specified name"),
|
||||
Long: namespaceLong,
|
||||
Example: namespaceExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.NamespaceV1GeneratorName)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *NamespaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.NamespaceV1GeneratorName:
|
||||
generator = &kubectl.NamespaceGeneratorV1{Name: name}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateNamespace implements the behavior to run the create namespace command
|
||||
func (o *NamespaceOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
62
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_namespace_test.go
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_namespace_test.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func TestCreateNamespace(t *testing.T) {
|
||||
namespaceObject := &v1.Namespace{}
|
||||
namespaceObject.Name = "my-namespace"
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, namespaceObject)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateNamespace(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{namespaceObject.Name})
|
||||
expectedOutput := "namespace/" + namespaceObject.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
109
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_pdb.go
generated
vendored
Normal file
109
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_pdb.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
pdbLong = templates.LongDesc(i18n.T(`
|
||||
Create a pod disruption budget with the specified name, selector, and desired minimum available pods`))
|
||||
|
||||
pdbExample = templates.Examples(i18n.T(`
|
||||
# Create a pod disruption budget named my-pdb that will select all pods with the app=rails label
|
||||
# and require at least one of them being available at any point in time.
|
||||
kubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1
|
||||
|
||||
# Create a pod disruption budget named my-pdb that will select all pods with the app=nginx label
|
||||
# and require at least half of the pods selected to be available at any point in time.
|
||||
kubectl create pdb my-pdb --selector=app=nginx --min-available=50%`))
|
||||
)
|
||||
|
||||
type PodDisruptionBudgetOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreatePodDisruptionBudget is a macro command to create a new pod disruption budget.
|
||||
func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &PodDisruptionBudgetOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "poddisruptionbudget NAME --selector=SELECTOR --min-available=N [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"pdb"},
|
||||
Short: i18n.T("Create a pod disruption budget with the specified name."),
|
||||
Long: pdbLong,
|
||||
Example: pdbExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.PodDisruptionBudgetV2GeneratorName)
|
||||
|
||||
cmd.Flags().String("min-available", "", i18n.T("The minimum number or percentage of available pods this budget requires."))
|
||||
cmd.Flags().String("max-unavailable", "", i18n.T("The maximum number or percentage of unavailable pods this budget requires."))
|
||||
cmd.Flags().String("selector", "", i18n.T("A label selector to use for this budget. Only equality-based selector requirements are supported."))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *PodDisruptionBudgetOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.PodDisruptionBudgetV1GeneratorName:
|
||||
generator = &kubectl.PodDisruptionBudgetV1Generator{
|
||||
Name: name,
|
||||
MinAvailable: cmdutil.GetFlagString(cmd, "min-available"),
|
||||
Selector: cmdutil.GetFlagString(cmd, "selector"),
|
||||
}
|
||||
case cmdutil.PodDisruptionBudgetV2GeneratorName:
|
||||
generator = &kubectl.PodDisruptionBudgetV2Generator{
|
||||
Name: name,
|
||||
MinAvailable: cmdutil.GetFlagString(cmd, "min-available"),
|
||||
MaxUnavailable: cmdutil.GetFlagString(cmd, "max-unavailable"),
|
||||
Selector: cmdutil.GetFlagString(cmd, "selector"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreatePodDisruptionBudget implements the behavior to run the create pdb command.
|
||||
func (o *PodDisruptionBudgetOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
85
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_pdb_test.go
generated
vendored
Normal file
85
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_pdb_test.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func TestCreatePdb(t *testing.T) {
|
||||
pdbName := "my-pdb"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "policy", Version: "v1beta1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: ioutil.NopCloser(&bytes.Buffer{}),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = &restclient.Config{}
|
||||
|
||||
outputFormat := "name"
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreatePodDisruptionBudget(tf, ioStreams)
|
||||
cmd.Flags().Set("min-available", "1")
|
||||
cmd.Flags().Set("selector", "app=rails")
|
||||
cmd.Flags().Set("dry-run", "true")
|
||||
cmd.Flags().Set("output", outputFormat)
|
||||
|
||||
printFlags := NewPrintFlags("created", legacyscheme.Scheme)
|
||||
printFlags.OutputFormat = &outputFormat
|
||||
|
||||
options := &PodDisruptionBudgetOpts{
|
||||
CreateSubcommandOptions: &CreateSubcommandOptions{
|
||||
PrintFlags: printFlags,
|
||||
Name: pdbName,
|
||||
IOStreams: ioStreams,
|
||||
},
|
||||
}
|
||||
err := options.Complete(tf, cmd, []string{pdbName})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = options.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedOutput := "poddisruptionbudget.policy/" + pdbName + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
101
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_priorityclass.go
generated
vendored
Normal file
101
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_priorityclass.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
pcLong = templates.LongDesc(i18n.T(`
|
||||
Create a priorityclass with the specified name, value, globalDefault and description`))
|
||||
|
||||
pcExample = templates.Examples(i18n.T(`
|
||||
# Create a priorityclass named high-priority
|
||||
kubectl create priorityclass default-priority --value=1000 --description="high priority"
|
||||
|
||||
# Create a priorityclass named default-priority that considered as the global default priority
|
||||
kubectl create priorityclass default-priority --value=1000 --global-default=true --description="default priority"`))
|
||||
)
|
||||
|
||||
type PriorityClassOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreatePriorityClass is a macro command to create a new priorityClass.
|
||||
func NewCmdCreatePriorityClass(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &PriorityClassOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "priorityclass NAME --value=VALUE --global-default=BOOL [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"pc"},
|
||||
Short: i18n.T("Create a priorityclass with the specified name."),
|
||||
Long: pcLong,
|
||||
Example: pcExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.PriorityClassV1Alpha1GeneratorName)
|
||||
|
||||
cmd.Flags().Int32("value", 0, i18n.T("the value of this priority class."))
|
||||
cmd.Flags().Bool("global-default", false, i18n.T("global-default specifies whether this PriorityClass should be considered as the default priority."))
|
||||
cmd.Flags().String("description", "", i18n.T("description is an arbitrary string that usually provides guidelines on when this priority class should be used."))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *PriorityClassOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.PriorityClassV1Alpha1GeneratorName:
|
||||
generator = &kubectl.PriorityClassV1Generator{
|
||||
Name: name,
|
||||
Value: cmdutil.GetFlagInt32(cmd, "value"),
|
||||
GlobalDefault: cmdutil.GetFlagBool(cmd, "global-default"),
|
||||
Description: cmdutil.GetFlagString(cmd, "description"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreatePriorityClass implements the behavior to run the create priorityClass command.
|
||||
func (o *PriorityClassOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
86
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_priorityclass_test.go
generated
vendored
Normal file
86
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_priorityclass_test.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func TestCreatePriorityClass(t *testing.T) {
|
||||
pcName := "my-pc"
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Group: "scheduling.k8s.io", Version: "v1beta1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: ioutil.NopCloser(&bytes.Buffer{}),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = &restclient.Config{}
|
||||
|
||||
outputFormat := "name"
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreatePriorityClass(tf, ioStreams)
|
||||
cmd.Flags().Set("value", "1000")
|
||||
cmd.Flags().Set("global-default", "true")
|
||||
cmd.Flags().Set("description", "my priority")
|
||||
cmd.Flags().Set("dry-run", "true")
|
||||
cmd.Flags().Set("output", outputFormat)
|
||||
|
||||
printFlags := NewPrintFlags("created", legacyscheme.Scheme)
|
||||
printFlags.OutputFormat = &outputFormat
|
||||
|
||||
options := &PriorityClassOpts{
|
||||
CreateSubcommandOptions: &CreateSubcommandOptions{
|
||||
PrintFlags: printFlags,
|
||||
Name: pcName,
|
||||
IOStreams: ioStreams,
|
||||
},
|
||||
}
|
||||
err := options.Complete(tf, cmd, []string{pcName})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = options.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedOutput := "priorityclass.scheduling.k8s.io/" + pcName + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
98
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_quota.go
generated
vendored
Normal file
98
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_quota.go
generated
vendored
Normal 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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
quotaLong = templates.LongDesc(i18n.T(`
|
||||
Create a resourcequota with the specified name, hard limits and optional scopes`))
|
||||
|
||||
quotaExample = templates.Examples(i18n.T(`
|
||||
# Create a new resourcequota named my-quota
|
||||
kubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10
|
||||
|
||||
# Create a new resourcequota named best-effort
|
||||
kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`))
|
||||
)
|
||||
|
||||
type QuotaOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateQuota is a macro command to create a new quota
|
||||
func NewCmdCreateQuota(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &QuotaOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "quota NAME [--hard=key1=value1,key2=value2] [--scopes=Scope1,Scope2] [--dry-run=bool]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"resourcequota"},
|
||||
Short: i18n.T("Create a quota with the specified name."),
|
||||
Long: quotaLong,
|
||||
Example: quotaExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ResourceQuotaV1GeneratorName)
|
||||
cmd.Flags().String("hard", "", i18n.T("A comma-delimited set of resource=quantity pairs that define a hard limit."))
|
||||
cmd.Flags().String("scopes", "", i18n.T("A comma-delimited set of quota scopes that must all match each object tracked by the quota."))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *QuotaOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ResourceQuotaV1GeneratorName:
|
||||
generator = &kubectl.ResourceQuotaGeneratorV1{
|
||||
Name: name,
|
||||
Hard: cmdutil.GetFlagString(cmd, "hard"),
|
||||
Scopes: cmdutil.GetFlagString(cmd, "scopes"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateQuota implements the behavior to run the create quota command
|
||||
func (o *QuotaOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
68
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_quota_test.go
generated
vendored
Normal file
68
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_quota_test.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func TestCreateQuota(t *testing.T) {
|
||||
resourceQuotaObject := &v1.ResourceQuota{}
|
||||
resourceQuotaObject.Name = "my-quota"
|
||||
|
||||
tests := map[string]struct {
|
||||
flags []string
|
||||
expectedOutput string
|
||||
}{
|
||||
"single resource": {
|
||||
flags: []string{"--hard=cpu=1"},
|
||||
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
|
||||
},
|
||||
"single resource with a scope": {
|
||||
flags: []string{"--hard=cpu=1", "--scopes=BestEffort"},
|
||||
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
|
||||
},
|
||||
"multiple resources": {
|
||||
flags: []string{"--hard=cpu=1,pods=42", "--scopes=BestEffort"},
|
||||
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
|
||||
},
|
||||
"single resource with multiple scopes": {
|
||||
flags: []string{"--hard=cpu=1", "--scopes=BestEffort,NotTerminating"},
|
||||
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
|
||||
},
|
||||
}
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateQuota(tf, ioStreams)
|
||||
cmd.Flags().Parse(test.flags)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{resourceQuotaObject.Name})
|
||||
|
||||
if buf.String() != test.expectedOutput {
|
||||
t.Errorf("%s: expected output: %s, but got: %s", name, test.expectedOutput, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
378
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_role.go
generated
vendored
Normal file
378
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_role.go
generated
vendored
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
roleLong = templates.LongDesc(i18n.T(`
|
||||
Create a role with single rule.`))
|
||||
|
||||
roleExample = templates.Examples(i18n.T(`
|
||||
# Create a Role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
|
||||
kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
|
||||
|
||||
# Create a Role named "pod-reader" with ResourceName specified
|
||||
kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
|
||||
|
||||
# Create a Role named "foo" with API Group specified
|
||||
kubectl create role foo --verb=get,list,watch --resource=rs.extensions
|
||||
|
||||
# Create a Role named "foo" with SubResource specified
|
||||
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status`))
|
||||
|
||||
// Valid resource verb list for validation.
|
||||
validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "impersonate"}
|
||||
|
||||
// Specialized verbs and GroupResources
|
||||
specialVerbs = map[string][]schema.GroupResource{
|
||||
"use": {
|
||||
{
|
||||
Group: "extensions",
|
||||
Resource: "podsecuritypolicies",
|
||||
},
|
||||
},
|
||||
"bind": {
|
||||
{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resource: "roles",
|
||||
},
|
||||
{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resource: "clusterroles",
|
||||
},
|
||||
},
|
||||
"impersonate": {
|
||||
{
|
||||
Group: "",
|
||||
Resource: "users",
|
||||
},
|
||||
{
|
||||
Group: "",
|
||||
Resource: "serviceaccounts",
|
||||
},
|
||||
{
|
||||
Group: "",
|
||||
Resource: "groups",
|
||||
},
|
||||
{
|
||||
Group: "authentication.k8s.io",
|
||||
Resource: "userextras",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type ResourceOptions struct {
|
||||
Group string
|
||||
Resource string
|
||||
SubResource string
|
||||
}
|
||||
|
||||
type CreateRoleOptions struct {
|
||||
PrintFlags *PrintFlags
|
||||
|
||||
Name string
|
||||
Verbs []string
|
||||
Resources []ResourceOptions
|
||||
ResourceNames []string
|
||||
|
||||
DryRun bool
|
||||
OutputFormat string
|
||||
Namespace string
|
||||
Client clientgorbacv1.RbacV1Interface
|
||||
Mapper meta.RESTMapper
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewCreateRoleOptions(ioStreams genericclioptions.IOStreams) *CreateRoleOptions {
|
||||
return &CreateRoleOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// Role is a command to ease creating Roles.
|
||||
func NewCmdCreateRole(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateRoleOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: roleLong,
|
||||
Long: roleLong,
|
||||
Example: roleExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.RunCreateRole())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringSliceVar(&o.Verbs, "verb", o.Verbs, "Verb that applies to the resources contained in the rule")
|
||||
cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
|
||||
cmd.Flags().StringArrayVar(&o.ResourceNames, "resource-name", o.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name = name
|
||||
|
||||
// Remove duplicate verbs.
|
||||
verbs := []string{}
|
||||
for _, v := range o.Verbs {
|
||||
// VerbAll respresents all kinds of verbs.
|
||||
if v == "*" {
|
||||
verbs = []string{"*"}
|
||||
break
|
||||
}
|
||||
if !arrayContains(verbs, v) {
|
||||
verbs = append(verbs, v)
|
||||
}
|
||||
}
|
||||
o.Verbs = verbs
|
||||
|
||||
// Support resource.group pattern. If no API Group specified, use "" as core API Group.
|
||||
// e.g. --resource=pods,deployments.extensions
|
||||
resources := cmdutil.GetFlagStringSlice(cmd, "resource")
|
||||
for _, r := range resources {
|
||||
sections := strings.SplitN(r, "/", 2)
|
||||
|
||||
resource := &ResourceOptions{}
|
||||
if len(sections) == 2 {
|
||||
resource.SubResource = sections[1]
|
||||
}
|
||||
|
||||
parts := strings.SplitN(sections[0], ".", 2)
|
||||
if len(parts) == 2 {
|
||||
resource.Group = parts[1]
|
||||
}
|
||||
resource.Resource = parts[0]
|
||||
|
||||
o.Resources = append(o.Resources, *resource)
|
||||
}
|
||||
|
||||
// Remove duplicate resource names.
|
||||
resourceNames := []string{}
|
||||
for _, n := range o.ResourceNames {
|
||||
if !arrayContains(resourceNames, n) {
|
||||
resourceNames = append(resourceNames, n)
|
||||
}
|
||||
}
|
||||
o.ResourceNames = resourceNames
|
||||
|
||||
// Complete other options for Run.
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DryRun = cmdutil.GetDryRunFlag(cmd)
|
||||
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
|
||||
|
||||
if o.DryRun {
|
||||
o.PrintFlags.Complete("%s (dry run)")
|
||||
}
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientset, err := f.KubernetesClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client = clientset.RbacV1()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateRoleOptions) Validate() error {
|
||||
if o.Name == "" {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
|
||||
// validate verbs.
|
||||
if len(o.Verbs) == 0 {
|
||||
return fmt.Errorf("at least one verb must be specified")
|
||||
}
|
||||
|
||||
for _, v := range o.Verbs {
|
||||
if !arrayContains(validResourceVerbs, v) {
|
||||
return fmt.Errorf("invalid verb: '%s'", v)
|
||||
}
|
||||
}
|
||||
|
||||
// validate resources.
|
||||
if len(o.Resources) == 0 {
|
||||
return fmt.Errorf("at least one resource must be specified")
|
||||
}
|
||||
|
||||
return o.validateResource()
|
||||
}
|
||||
|
||||
func (o *CreateRoleOptions) validateResource() error {
|
||||
for _, r := range o.Resources {
|
||||
if len(r.Resource) == 0 {
|
||||
return fmt.Errorf("resource must be specified if apiGroup/subresource specified")
|
||||
}
|
||||
|
||||
resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
|
||||
groupVersionResource, err := o.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
|
||||
if err == nil {
|
||||
resource = groupVersionResource
|
||||
}
|
||||
|
||||
for _, v := range o.Verbs {
|
||||
if groupResources, ok := specialVerbs[v]; ok {
|
||||
match := false
|
||||
for _, extra := range groupResources {
|
||||
if resource.Resource == extra.Resource && resource.Group == extra.Group {
|
||||
match = true
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return fmt.Errorf("can not perform '%s' on '%s' in group '%s'", v, resource.Resource, resource.Group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateRoleOptions) RunCreateRole() error {
|
||||
role := &rbacv1.Role{
|
||||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "Role"},
|
||||
}
|
||||
role.Name = o.Name
|
||||
rules, err := generateResourcePolicyRules(o.Mapper, o.Verbs, o.Resources, o.ResourceNames, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
role.Rules = rules
|
||||
|
||||
// Create role.
|
||||
if !o.DryRun {
|
||||
role, err = o.Client.Roles(o.Namespace).Create(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(role)
|
||||
}
|
||||
|
||||
func arrayContains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbacv1.PolicyRule, error) {
|
||||
// groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value
|
||||
// is a string array of resources under this api group.
|
||||
// E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]}
|
||||
groupResourceMapping := map[string][]string{}
|
||||
|
||||
// This loop does the following work:
|
||||
// 1. Constructs groupResourceMapping based on input resources.
|
||||
// 2. Prevents pointing to non-existent resources.
|
||||
// 3. Transfers resource short name to long name. E.g. rs.extensions is transferred to replicasets.extensions
|
||||
for _, r := range resources {
|
||||
resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
|
||||
groupVersionResource, err := mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
|
||||
if err == nil {
|
||||
resource = groupVersionResource
|
||||
}
|
||||
|
||||
if len(r.SubResource) > 0 {
|
||||
resource.Resource = resource.Resource + "/" + r.SubResource
|
||||
}
|
||||
if !arrayContains(groupResourceMapping[resource.Group], resource.Resource) {
|
||||
groupResourceMapping[resource.Group] = append(groupResourceMapping[resource.Group], resource.Resource)
|
||||
}
|
||||
}
|
||||
|
||||
// Create separate rule for each of the api group.
|
||||
rules := []rbacv1.PolicyRule{}
|
||||
for _, g := range sets.StringKeySet(groupResourceMapping).List() {
|
||||
rule := rbacv1.PolicyRule{}
|
||||
rule.Verbs = verbs
|
||||
rule.Resources = groupResourceMapping[g]
|
||||
rule.APIGroups = []string{g}
|
||||
rule.ResourceNames = resourceNames
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
if len(nonResourceURLs) > 0 {
|
||||
rule := rbacv1.PolicyRule{}
|
||||
rule.Verbs = verbs
|
||||
rule.NonResourceURLs = nonResourceURLs
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
523
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_role_test.go
generated
vendored
Normal file
523
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_role_test.go
generated
vendored
Normal file
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
func TestCreateRole(t *testing.T) {
|
||||
roleName := "my-role"
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.Client = &fake.RESTClient{}
|
||||
tf.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
tests := map[string]struct {
|
||||
verbs string
|
||||
resources string
|
||||
resourceNames string
|
||||
expectedRole *rbac.Role
|
||||
}{
|
||||
"test-duplicate-resources": {
|
||||
verbs: "get,watch,list",
|
||||
resources: "pods,pods",
|
||||
expectedRole: &rbac.Role{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: roleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"pods"},
|
||||
APIGroups: []string{""},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-subresources": {
|
||||
verbs: "get,watch,list",
|
||||
resources: "replicasets/scale",
|
||||
expectedRole: &rbac.Role{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: roleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"replicasets/scale"},
|
||||
APIGroups: []string{"extensions"},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-subresources-with-apigroup": {
|
||||
verbs: "get,watch,list",
|
||||
resources: "replicasets.extensions/scale",
|
||||
expectedRole: &rbac.Role{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: roleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"replicasets/scale"},
|
||||
APIGroups: []string{"extensions"},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-valid-case-with-multiple-apigroups": {
|
||||
verbs: "get,watch,list",
|
||||
resources: "pods,deployments.extensions",
|
||||
expectedRole: &rbac.Role{
|
||||
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: roleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"pods"},
|
||||
APIGroups: []string{""},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "watch", "list"},
|
||||
Resources: []string{"deployments"},
|
||||
APIGroups: []string{"extensions"},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateRole(tf, ioStreams)
|
||||
cmd.Flags().Set("dry-run", "true")
|
||||
cmd.Flags().Set("output", "yaml")
|
||||
cmd.Flags().Set("verb", test.verbs)
|
||||
cmd.Flags().Set("resource", test.resources)
|
||||
if test.resourceNames != "" {
|
||||
cmd.Flags().Set("resource-name", test.resourceNames)
|
||||
}
|
||||
cmd.Run(cmd, []string{roleName})
|
||||
actual := &rbac.Role{}
|
||||
if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), buf.Bytes(), actual); err != nil {
|
||||
t.Log(string(buf.Bytes()))
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(test.expectedRole, actual) {
|
||||
t.Errorf("%s", diff.ObjectReflectDiff(test.expectedRole, actual))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tests := map[string]struct {
|
||||
roleOptions *CreateRoleOptions
|
||||
expectErr bool
|
||||
}{
|
||||
"test-missing-name": {
|
||||
roleOptions: &CreateRoleOptions{},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-verb": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-resource": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"get"},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-resource-existing-apigroup": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-missing-resource-existing-subresource": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
SubResource: "scale",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-verb": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"invalid-verb"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-nonresource-verb": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"post"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-special-verb": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"use"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-mix-verbs": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"impersonate", "use"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "userextras",
|
||||
SubResource: "scopes",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-special-verb-with-wrong-apigroup": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"impersonate"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "userextras",
|
||||
SubResource: "scopes",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-invalid-resource": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "invalid-resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-resource-name-with-multiple-resources": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"get"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
},
|
||||
{
|
||||
Resource: "deployments",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
ResourceNames: []string{"foo"},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-case": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "role-binder",
|
||||
Verbs: []string{"get", "list", "bind"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "roles",
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
},
|
||||
},
|
||||
ResourceNames: []string{"foo"},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-case-with-subresource": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"get", "list"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "replicasets",
|
||||
SubResource: "scale",
|
||||
},
|
||||
},
|
||||
ResourceNames: []string{"bar"},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-case-with-additional-resource": {
|
||||
roleOptions: &CreateRoleOptions{
|
||||
Name: "my-role",
|
||||
Verbs: []string{"impersonate"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "userextras",
|
||||
SubResource: "scopes",
|
||||
Group: "authentication.k8s.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
var err error
|
||||
test.roleOptions.Mapper, err = tf.ToRESTMapper()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = test.roleOptions.Validate()
|
||||
if test.expectErr && err == nil {
|
||||
t.Errorf("%s: expect error happens but validate passes.", name)
|
||||
}
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplete(t *testing.T) {
|
||||
roleName := "my-role"
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.Client = &fake.RESTClient{}
|
||||
tf.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
cmd := NewCmdCreateRole(tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd.Flags().Set("resource", "pods,deployments.extensions")
|
||||
|
||||
tests := map[string]struct {
|
||||
params []string
|
||||
roleOptions *CreateRoleOptions
|
||||
expected *CreateRoleOptions
|
||||
expectErr bool
|
||||
}{
|
||||
"test-missing-name": {
|
||||
params: []string{},
|
||||
roleOptions: &CreateRoleOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
"test-duplicate-verbs": {
|
||||
params: []string{roleName},
|
||||
roleOptions: &CreateRoleOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
Name: roleName,
|
||||
Verbs: []string{
|
||||
"get",
|
||||
"watch",
|
||||
"list",
|
||||
"get",
|
||||
},
|
||||
},
|
||||
expected: &CreateRoleOptions{
|
||||
Name: roleName,
|
||||
Verbs: []string{
|
||||
"get",
|
||||
"watch",
|
||||
"list",
|
||||
},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
Group: "",
|
||||
},
|
||||
{
|
||||
Resource: "deployments",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-verball": {
|
||||
params: []string{roleName},
|
||||
roleOptions: &CreateRoleOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
Name: roleName,
|
||||
Verbs: []string{
|
||||
"get",
|
||||
"watch",
|
||||
"list",
|
||||
"*",
|
||||
},
|
||||
},
|
||||
expected: &CreateRoleOptions{
|
||||
Name: roleName,
|
||||
Verbs: []string{"*"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
Group: "",
|
||||
},
|
||||
{
|
||||
Resource: "deployments",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
ResourceNames: []string{},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-duplicate-resourcenames": {
|
||||
params: []string{roleName},
|
||||
roleOptions: &CreateRoleOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
Name: roleName,
|
||||
Verbs: []string{"*"},
|
||||
ResourceNames: []string{"foo", "foo"},
|
||||
},
|
||||
expected: &CreateRoleOptions{
|
||||
Name: roleName,
|
||||
Verbs: []string{"*"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
Group: "",
|
||||
},
|
||||
{
|
||||
Resource: "deployments",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
ResourceNames: []string{"foo"},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-complete-case": {
|
||||
params: []string{roleName},
|
||||
roleOptions: &CreateRoleOptions{
|
||||
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
|
||||
Name: roleName,
|
||||
Verbs: []string{"*"},
|
||||
ResourceNames: []string{"foo"},
|
||||
},
|
||||
expected: &CreateRoleOptions{
|
||||
Name: roleName,
|
||||
Verbs: []string{"*"},
|
||||
Resources: []ResourceOptions{
|
||||
{
|
||||
Resource: "pods",
|
||||
Group: "",
|
||||
},
|
||||
{
|
||||
Resource: "deployments",
|
||||
Group: "extensions",
|
||||
},
|
||||
},
|
||||
ResourceNames: []string{"foo"},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
err := test.roleOptions.Complete(tf, cmd, test.params)
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", name, err)
|
||||
}
|
||||
|
||||
if test.expectErr {
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
t.Errorf("%s: expect error happens but test passes.", name)
|
||||
}
|
||||
}
|
||||
|
||||
if test.roleOptions.Name != test.expected.Name {
|
||||
t.Errorf("%s:\nexpected name:\n%#v\nsaw name:\n%#v", name, test.expected.Name, test.roleOptions.Name)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.roleOptions.Verbs, test.expected.Verbs) {
|
||||
t.Errorf("%s:\nexpected verbs:\n%#v\nsaw verbs:\n%#v", name, test.expected.Verbs, test.roleOptions.Verbs)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.roleOptions.Resources, test.expected.Resources) {
|
||||
t.Errorf("%s:\nexpected resources:\n%#v\nsaw resources:\n%#v", name, test.expected.Resources, test.roleOptions.Resources)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.roleOptions.ResourceNames, test.expected.ResourceNames) {
|
||||
t.Errorf("%s:\nexpected resource names:\n%#v\nsaw resource names:\n%#v", name, test.expected.ResourceNames, test.roleOptions.ResourceNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
99
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_rolebinding.go
generated
vendored
Normal file
99
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_rolebinding.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
roleBindingLong = templates.LongDesc(i18n.T(`
|
||||
Create a RoleBinding for a particular Role or ClusterRole.`))
|
||||
|
||||
roleBindingExample = templates.Examples(i18n.T(`
|
||||
# Create a RoleBinding for user1, user2, and group1 using the admin ClusterRole
|
||||
kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1`))
|
||||
)
|
||||
|
||||
type RoleBindingOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// RoleBinding is a command to ease creating RoleBindings.
|
||||
func NewCmdCreateRoleBinding(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &RoleBindingOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a RoleBinding for a particular Role or ClusterRole"),
|
||||
Long: roleBindingLong,
|
||||
Example: roleBindingExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.RoleBindingV1GeneratorName)
|
||||
cmd.Flags().String("clusterrole", "", i18n.T("ClusterRole this RoleBinding should reference"))
|
||||
cmd.Flags().String("role", "", i18n.T("Role this RoleBinding should reference"))
|
||||
cmd.Flags().StringArray("user", []string{}, "Usernames to bind to the role")
|
||||
cmd.Flags().StringArray("group", []string{}, "Groups to bind to the role")
|
||||
cmd.Flags().StringArray("serviceaccount", []string{}, "Service accounts to bind to the role, in the format <namespace>:<name>")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *RoleBindingOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.RoleBindingV1GeneratorName:
|
||||
generator = &kubectl.RoleBindingGeneratorV1{
|
||||
Name: name,
|
||||
ClusterRole: cmdutil.GetFlagString(cmd, "clusterrole"),
|
||||
Role: cmdutil.GetFlagString(cmd, "role"),
|
||||
Users: cmdutil.GetFlagStringArray(cmd, "user"),
|
||||
Groups: cmdutil.GetFlagStringArray(cmd, "group"),
|
||||
ServiceAccounts: cmdutil.GetFlagStringArray(cmd, "serviceaccount"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
func (o *RoleBindingOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
144
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_rolebinding_test.go
generated
vendored
Normal file
144
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_rolebinding_test.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
var groupVersion = schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}
|
||||
|
||||
func TestCreateRoleBinding(t *testing.T) {
|
||||
expectBinding := &rbac.RoleBinding{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
Kind: "RoleBinding",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "fake-binding",
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbac.GroupName,
|
||||
Kind: "Role",
|
||||
Name: "fake-role",
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: rbac.UserKind,
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: "fake-user",
|
||||
},
|
||||
{
|
||||
Kind: rbac.GroupKind,
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: "fake-group",
|
||||
},
|
||||
{
|
||||
Kind: rbac.ServiceAccountKind,
|
||||
Namespace: "fake-namespace",
|
||||
Name: "fake-account",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
|
||||
decoder := ns.DecoderToVersion(info.Serializer, groupVersion)
|
||||
|
||||
tf.Client = &RoleBindingRESTClient{
|
||||
RESTClient: &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/rolebindings" && m == "POST":
|
||||
bodyBits, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("TestCreateRoleBinding error: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if obj, _, err := decoder.Decode(bodyBits, nil, &rbac.RoleBinding{}); err == nil {
|
||||
if !reflect.DeepEqual(obj.(*rbac.RoleBinding), expectBinding) {
|
||||
t.Fatalf("TestCreateRoleBinding: expected:\n%#v\nsaw:\n%#v", expectBinding, obj.(*rbac.RoleBinding))
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("TestCreateRoleBinding error, could not decode the request body into rbac.RoleBinding object: %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
responseBinding := &rbac.RoleBinding{}
|
||||
responseBinding.Name = "fake-binding"
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseBinding))))}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
cmd := NewCmdCreateRoleBinding(tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd.Flags().Set("role", "fake-role")
|
||||
cmd.Flags().Set("user", "fake-user")
|
||||
cmd.Flags().Set("group", "fake-group")
|
||||
cmd.Flags().Set("serviceaccount", "fake-namespace:fake-account")
|
||||
cmd.Run(cmd, []string{"fake-binding"})
|
||||
}
|
||||
|
||||
type RoleBindingRESTClient struct {
|
||||
*fake.RESTClient
|
||||
}
|
||||
|
||||
func (c *RoleBindingRESTClient) Post() *restclient.Request {
|
||||
config := restclient.ContentConfig{
|
||||
ContentType: runtime.ContentTypeJSON,
|
||||
GroupVersion: &groupVersion,
|
||||
NegotiatedSerializer: c.NegotiatedSerializer,
|
||||
}
|
||||
|
||||
info, _ := runtime.SerializerInfoForMediaType(c.NegotiatedSerializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
serializers := restclient.Serializers{
|
||||
Encoder: c.NegotiatedSerializer.EncoderForVersion(info.Serializer, groupVersion),
|
||||
Decoder: c.NegotiatedSerializer.DecoderToVersion(info.Serializer, groupVersion),
|
||||
}
|
||||
if info.StreamSerializer != nil {
|
||||
serializers.StreamingSerializer = info.StreamSerializer.Serializer
|
||||
serializers.Framer = info.StreamSerializer.Framer
|
||||
}
|
||||
return restclient.NewRequest(c, "POST", &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
|
||||
}
|
||||
315
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_secret.go
generated
vendored
Normal file
315
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_secret.go
generated
vendored
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
// NewCmdCreateSecret groups subcommands to create various types of secrets
|
||||
func NewCmdCreateSecret(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "secret",
|
||||
Short: i18n.T("Create a secret using specified subcommand"),
|
||||
Long: "Create a secret using specified subcommand.",
|
||||
Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
|
||||
}
|
||||
cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateSecretTLS(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateSecretGeneric(f, ioStreams))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
var (
|
||||
secretLong = templates.LongDesc(i18n.T(`
|
||||
Create a secret based on a file, directory, or specified literal value.
|
||||
|
||||
A single secret may package one or more key/value pairs.
|
||||
|
||||
When creating a secret based on a file, the key will default to the basename of the file, and the value will
|
||||
default to the file content. If the basename is an invalid key or you wish to chose your own, you may specify
|
||||
an alternate key.
|
||||
|
||||
When creating a secret based on a directory, each file whose basename is a valid key in the directory will be
|
||||
packaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,
|
||||
symlinks, devices, pipes, etc).`))
|
||||
|
||||
secretExample = templates.Examples(i18n.T(`
|
||||
# Create a new secret named my-secret with keys for each file in folder bar
|
||||
kubectl create secret generic my-secret --from-file=path/to/bar
|
||||
|
||||
# Create a new secret named my-secret with specified keys instead of names on disk
|
||||
kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub
|
||||
|
||||
# Create a new secret named my-secret with key1=supersecret and key2=topsecret
|
||||
kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
|
||||
|
||||
# Create a new secret named my-secret using a combination of a file and a literal
|
||||
kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-literal=passphrase=topsecret
|
||||
|
||||
# Create a new secret named my-secret from an env file
|
||||
kubectl create secret generic my-secret --from-env-file=path/to/bar.env`))
|
||||
)
|
||||
|
||||
type SecretGenericOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
|
||||
func NewCmdCreateSecretGeneric(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &SecretGenericOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a secret from a local file, directory or literal value"),
|
||||
Long: secretLong,
|
||||
Example: secretExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretV1GeneratorName)
|
||||
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
|
||||
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
|
||||
cmd.Flags().String("from-env-file", "", "Specify the path to a file to read lines of key=val pairs to create a secret (i.e. a Docker .env file).")
|
||||
cmd.Flags().String("type", "", i18n.T("The type of secret to create"))
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *SecretGenericOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.SecretV1GeneratorName:
|
||||
generator = &kubectl.SecretGeneratorV1{
|
||||
Name: name,
|
||||
Type: cmdutil.GetFlagString(cmd, "type"),
|
||||
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
|
||||
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
|
||||
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateSecretGeneric is the implementation of the create secret generic command
|
||||
func (o *SecretGenericOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
|
||||
var (
|
||||
secretForDockerRegistryLong = templates.LongDesc(i18n.T(`
|
||||
Create a new secret for use with Docker registries.
|
||||
|
||||
Dockercfg secrets are used to authenticate against Docker registries.
|
||||
|
||||
When using the Docker command line to push images, you can authenticate to a given registry by running:
|
||||
'$ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
|
||||
|
||||
That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
|
||||
authenticate to the registry. The email address is optional.
|
||||
|
||||
When creating applications, you may have a Docker registry that requires authentication. In order for the
|
||||
nodes to pull images on your behalf, they have to have the credentials. You can provide this information
|
||||
by creating a dockercfg secret and attaching it to your service account.`))
|
||||
|
||||
secretForDockerRegistryExample = templates.Examples(i18n.T(`
|
||||
# If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using:
|
||||
kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL`))
|
||||
)
|
||||
|
||||
type SecretDockerRegistryOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries
|
||||
func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &SecretDockerRegistryOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-literal=key1=value1] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a secret for use with a Docker registry"),
|
||||
Long: secretForDockerRegistryLong,
|
||||
Example: secretForDockerRegistryExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForDockerRegistryV1GeneratorName)
|
||||
cmd.Flags().String("docker-username", "", i18n.T("Username for Docker registry authentication"))
|
||||
cmd.MarkFlagRequired("docker-username")
|
||||
cmd.Flags().String("docker-password", "", i18n.T("Password for Docker registry authentication"))
|
||||
cmd.MarkFlagRequired("docker-password")
|
||||
cmd.Flags().String("docker-email", "", i18n.T("Email for Docker registry"))
|
||||
cmd.Flags().String("docker-server", "https://index.docker.io/v1/", i18n.T("Server location for Docker registry"))
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.")
|
||||
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *SecretDockerRegistryOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromFileFlag := cmdutil.GetFlagStringSlice(cmd, "from-file")
|
||||
if len(fromFileFlag) == 0 {
|
||||
requiredFlags := []string{"docker-username", "docker-password", "docker-server"}
|
||||
for _, requiredFlag := range requiredFlags {
|
||||
if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "flag %s is required", requiredFlag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.SecretForDockerRegistryV1GeneratorName:
|
||||
generator = &kubectl.SecretForDockerRegistryGeneratorV1{
|
||||
Name: name,
|
||||
Username: cmdutil.GetFlagString(cmd, "docker-username"),
|
||||
Email: cmdutil.GetFlagString(cmd, "docker-email"),
|
||||
Password: cmdutil.GetFlagString(cmd, "docker-password"),
|
||||
Server: cmdutil.GetFlagString(cmd, "docker-server"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateSecretDockerRegistry is the implementation of the create secret docker-registry command
|
||||
func (o *SecretDockerRegistryOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
|
||||
var (
|
||||
secretForTLSLong = templates.LongDesc(i18n.T(`
|
||||
Create a TLS secret from the given public/private key pair.
|
||||
|
||||
The public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match
|
||||
the given private key.`))
|
||||
|
||||
secretForTLSExample = templates.Examples(i18n.T(`
|
||||
# Create a new TLS secret named tls-secret with the given key pair:
|
||||
kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key`))
|
||||
)
|
||||
|
||||
type SecretTLSOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateSecretTLS is a macro command for creating secrets to work with Docker registries
|
||||
func NewCmdCreateSecretTLS(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &SecretTLSOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a TLS secret"),
|
||||
Long: secretForTLSLong,
|
||||
Example: secretForTLSExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForTLSV1GeneratorName)
|
||||
cmd.Flags().String("cert", "", i18n.T("Path to PEM encoded public key certificate."))
|
||||
cmd.Flags().String("key", "", i18n.T("Path to private key associated with given certificate."))
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *SecretTLSOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requiredFlags := []string{"cert", "key"}
|
||||
for _, requiredFlag := range requiredFlags {
|
||||
if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "flag %s is required", requiredFlag)
|
||||
}
|
||||
}
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.SecretForTLSV1GeneratorName:
|
||||
generator = &kubectl.SecretForTLSGeneratorV1{
|
||||
Name: name,
|
||||
Key: cmdutil.GetFlagString(cmd, "key"),
|
||||
Cert: cmdutil.GetFlagString(cmd, "cert"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateSecretTLS is the implementation of the create secret tls command
|
||||
func (o *SecretTLSOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
102
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_secret_test.go
generated
vendored
Normal file
102
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_secret_test.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func TestCreateSecretGeneric(t *testing.T) {
|
||||
secretObject := &v1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"password": []byte("includes,comma"),
|
||||
"username": []byte("test_user"),
|
||||
},
|
||||
}
|
||||
secretObject.Name = "my-secret"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/secrets" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, secretObject)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateSecretGeneric(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Flags().Set("from-literal", "password=includes,comma")
|
||||
cmd.Flags().Set("from-literal", "username=test_user")
|
||||
cmd.Run(cmd, []string{secretObject.Name})
|
||||
expectedOutput := "secret/" + secretObject.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSecretDockerRegistry(t *testing.T) {
|
||||
secretObject := &v1.Secret{}
|
||||
secretObject.Name = "my-secret"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/secrets" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, secretObject)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateSecretDockerRegistry(tf, ioStreams)
|
||||
cmd.Flags().Set("docker-username", "test-user")
|
||||
cmd.Flags().Set("docker-password", "test-pass")
|
||||
cmd.Flags().Set("docker-email", "test-email")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{secretObject.Name})
|
||||
expectedOutput := "secret/" + secretObject.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", buf.String(), expectedOutput)
|
||||
}
|
||||
}
|
||||
333
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_service.go
generated
vendored
Normal file
333
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_service.go
generated
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
// NewCmdCreateService is a macro command to create a new service
|
||||
func NewCmdCreateService(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "service",
|
||||
Aliases: []string{"svc"},
|
||||
Short: i18n.T("Create a service using specified subcommand."),
|
||||
Long: "Create a service using specified subcommand.",
|
||||
Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
|
||||
}
|
||||
cmd.AddCommand(NewCmdCreateServiceClusterIP(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceNodePort(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceExternalName(f, ioStreams))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
var (
|
||||
serviceClusterIPLong = templates.LongDesc(i18n.T(`
|
||||
Create a ClusterIP service with the specified name.`))
|
||||
|
||||
serviceClusterIPExample = templates.Examples(i18n.T(`
|
||||
# Create a new ClusterIP service named my-cs
|
||||
kubectl create service clusterip my-cs --tcp=5678:8080
|
||||
|
||||
# Create a new ClusterIP service named my-cs (in headless mode)
|
||||
kubectl create service clusterip my-cs --clusterip="None"`))
|
||||
)
|
||||
|
||||
func addPortFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringSlice("tcp", []string{}, "Port pairs can be specified as '<port>:<targetPort>'.")
|
||||
}
|
||||
|
||||
type ServiceClusterIPOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateServiceClusterIP is a command to create a ClusterIP service
|
||||
func NewCmdCreateServiceClusterIP(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &ServiceClusterIPOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "clusterip NAME [--tcp=<port>:<targetPort>] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a ClusterIP service."),
|
||||
Long: serviceClusterIPLong,
|
||||
Example: serviceClusterIPExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceClusterIPGeneratorV1Name)
|
||||
addPortFlags(cmd)
|
||||
cmd.Flags().String("clusterip", "", i18n.T("Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing)."))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func errUnsupportedGenerator(cmd *cobra.Command, generatorName string) error {
|
||||
return cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName)
|
||||
}
|
||||
|
||||
func (o *ServiceClusterIPOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ServiceClusterIPGeneratorV1Name:
|
||||
generator = &kubectl.ServiceCommonGeneratorV1{
|
||||
Name: name,
|
||||
TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"),
|
||||
Type: v1.ServiceTypeClusterIP,
|
||||
ClusterIP: cmdutil.GetFlagString(cmd, "clusterip"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateServiceClusterIP is the implementation of the create service clusterip command
|
||||
func (o *ServiceClusterIPOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
|
||||
var (
|
||||
serviceNodePortLong = templates.LongDesc(i18n.T(`
|
||||
Create a NodePort service with the specified name.`))
|
||||
|
||||
serviceNodePortExample = templates.Examples(i18n.T(`
|
||||
# Create a new NodePort service named my-ns
|
||||
kubectl create service nodeport my-ns --tcp=5678:8080`))
|
||||
)
|
||||
|
||||
type ServiceNodePortOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateServiceNodePort is a macro command for creating a NodePort service
|
||||
func NewCmdCreateServiceNodePort(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &ServiceNodePortOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "nodeport NAME [--tcp=port:targetPort] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a NodePort service."),
|
||||
Long: serviceNodePortLong,
|
||||
Example: serviceNodePortExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceNodePortGeneratorV1Name)
|
||||
cmd.Flags().Int("node-port", 0, "Port used to expose the service on each node in a cluster.")
|
||||
addPortFlags(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ServiceNodePortOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ServiceNodePortGeneratorV1Name:
|
||||
generator = &kubectl.ServiceCommonGeneratorV1{
|
||||
Name: name,
|
||||
TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"),
|
||||
Type: v1.ServiceTypeNodePort,
|
||||
ClusterIP: "",
|
||||
NodePort: cmdutil.GetFlagInt(cmd, "node-port"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateServiceNodePort is the implementation of the create service nodeport command
|
||||
func (o *ServiceNodePortOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
|
||||
var (
|
||||
serviceLoadBalancerLong = templates.LongDesc(i18n.T(`
|
||||
Create a LoadBalancer service with the specified name.`))
|
||||
|
||||
serviceLoadBalancerExample = templates.Examples(i18n.T(`
|
||||
# Create a new LoadBalancer service named my-lbs
|
||||
kubectl create service loadbalancer my-lbs --tcp=5678:8080`))
|
||||
)
|
||||
|
||||
type ServiceLoadBalancerOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateServiceLoadBalancer is a macro command for creating a LoadBalancer service
|
||||
func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &ServiceLoadBalancerOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "loadbalancer NAME [--tcp=port:targetPort] [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a LoadBalancer service."),
|
||||
Long: serviceLoadBalancerLong,
|
||||
Example: serviceLoadBalancerExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceLoadBalancerGeneratorV1Name)
|
||||
addPortFlags(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ServiceLoadBalancerOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ServiceLoadBalancerGeneratorV1Name:
|
||||
generator = &kubectl.ServiceCommonGeneratorV1{
|
||||
Name: name,
|
||||
TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"),
|
||||
Type: v1.ServiceTypeLoadBalancer,
|
||||
ClusterIP: "",
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateServiceLoadBalancer is the implementation of the create service loadbalancer command
|
||||
func (o *ServiceLoadBalancerOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
|
||||
var (
|
||||
serviceExternalNameLong = templates.LongDesc(i18n.T(`
|
||||
Create an ExternalName service with the specified name.
|
||||
|
||||
ExternalName service references to an external DNS address instead of
|
||||
only pods, which will allow application authors to reference services
|
||||
that exist off platform, on other clusters, or locally.`))
|
||||
|
||||
serviceExternalNameExample = templates.Examples(i18n.T(`
|
||||
# Create a new ExternalName service named my-ns
|
||||
kubectl create service externalname my-ns --external-name bar.com`))
|
||||
)
|
||||
|
||||
type ServiceExternalNameOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateServiceExternalName is a macro command for creating an ExternalName service
|
||||
func NewCmdCreateServiceExternalName(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &ServiceExternalNameOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "externalname NAME --external-name external.name [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create an ExternalName service."),
|
||||
Long: serviceExternalNameLong,
|
||||
Example: serviceExternalNameExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceExternalNameGeneratorV1Name)
|
||||
addPortFlags(cmd)
|
||||
cmd.Flags().String("external-name", "", i18n.T("External name of service"))
|
||||
cmd.MarkFlagRequired("external-name")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ServiceExternalNameOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ServiceExternalNameGeneratorV1Name:
|
||||
generator = &kubectl.ServiceCommonGeneratorV1{
|
||||
Name: name,
|
||||
Type: v1.ServiceTypeExternalName,
|
||||
ExternalName: cmdutil.GetFlagString(cmd, "external-name"),
|
||||
ClusterIP: "",
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateExternalNameService is the implementation of the create service externalname command
|
||||
func (o *ServiceExternalNameOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
129
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_service_test.go
generated
vendored
Normal file
129
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_service_test.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func TestCreateService(t *testing.T) {
|
||||
service := &v1.Service{}
|
||||
service.Name = "my-service"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
negSer := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: negSer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/services" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, service)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateServiceClusterIP(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Flags().Set("tcp", "8080:8000")
|
||||
cmd.Run(cmd, []string{service.Name})
|
||||
expectedOutput := "service/" + service.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateServiceNodePort(t *testing.T) {
|
||||
service := &v1.Service{}
|
||||
service.Name = "my-node-port-service"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
negSer := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: negSer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/services" && m == http.MethodPost:
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, service)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateServiceNodePort(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Flags().Set("tcp", "30000:8000")
|
||||
cmd.Run(cmd, []string{service.Name})
|
||||
expectedOutput := "service/" + service.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateServiceExternalName(t *testing.T) {
|
||||
service := &v1.Service{}
|
||||
service.Name = "my-external-name-service"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
negSer := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: negSer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/services" && m == http.MethodPost:
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, service)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateServiceExternalName(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Flags().Set("external-name", "name")
|
||||
cmd.Run(cmd, []string{service.Name})
|
||||
expectedOutput := "service/" + service.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
89
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_serviceaccount.go
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_serviceaccount.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceAccountLong = templates.LongDesc(i18n.T(`
|
||||
Create a service account with the specified name.`))
|
||||
|
||||
serviceAccountExample = templates.Examples(i18n.T(`
|
||||
# Create a new service account named my-service-account
|
||||
kubectl create serviceaccount my-service-account`))
|
||||
)
|
||||
|
||||
type ServiceAccountOpts struct {
|
||||
CreateSubcommandOptions *CreateSubcommandOptions
|
||||
}
|
||||
|
||||
// NewCmdCreateServiceAccount is a macro command to create a new service account
|
||||
func NewCmdCreateServiceAccount(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
options := &ServiceAccountOpts{
|
||||
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "serviceaccount NAME [--dry-run]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"sa"},
|
||||
Short: i18n.T("Create a service account with the specified name"),
|
||||
Long: serviceAccountLong,
|
||||
Example: serviceAccountExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceAccountV1GeneratorName)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ServiceAccountOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var generator kubectl.StructuredGenerator
|
||||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.ServiceAccountV1GeneratorName:
|
||||
generator = &kubectl.ServiceAccountGeneratorV1{Name: name}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
}
|
||||
|
||||
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
|
||||
}
|
||||
|
||||
// CreateServiceAccount implements the behavior to run the create service account command
|
||||
func (o *ServiceAccountOpts) Run() error {
|
||||
return o.CreateSubcommandOptions.Run()
|
||||
}
|
||||
62
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_serviceaccount_test.go
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_serviceaccount_test.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func TestCreateServiceAccount(t *testing.T) {
|
||||
serviceAccountObject := &v1.ServiceAccount{}
|
||||
serviceAccountObject.Name = "my-service-account"
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
ns := legacyscheme.Codecs
|
||||
|
||||
tf.Client = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/serviceaccounts" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, serviceAccountObject)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateServiceAccount(tf, ioStreams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{serviceAccountObject.Name})
|
||||
expectedOutput := "serviceaccount/" + serviceAccountObject.Name + "\n"
|
||||
if buf.String() != expectedOutput {
|
||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
210
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_test.go
generated
vendored
Normal file
210
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/create_test.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
func TestExtraArgsFail(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
|
||||
f := cmdtesting.NewTestFactory()
|
||||
defer f.Cleanup()
|
||||
|
||||
c := NewCmdCreate(f, genericclioptions.NewTestIOStreamsDiscard())
|
||||
options := CreateOptions{}
|
||||
if options.ValidateArgs(c, []string{"rc"}) == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateObject(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, _, rc := testData()
|
||||
rc.Items[0].Name = "redis-master-controller"
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreate(tf, ioStreams)
|
||||
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// uses the name from the file, not the response
|
||||
if buf.String() != "replicationcontroller/redis-master-controller\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateMultipleObject(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, svc, rc := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/services" && m == http.MethodPost:
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreate(tf, ioStreams)
|
||||
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
|
||||
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// Names should come from the REST response, NOT the files
|
||||
if buf.String() != "replicationcontroller/rc1\nservice/baz\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDirectory(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, _, rc := testData()
|
||||
rc.Items[0].Name = "name"
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreate(tf, ioStreams)
|
||||
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if buf.String() != "replicationcontroller/name\nreplicationcontroller/name\nreplicationcontroller/name\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
|
||||
|
||||
func initTestErrorHandler(t *testing.T) {
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
t.Errorf("Error running command (exit code %d): %s", code, str)
|
||||
})
|
||||
}
|
||||
|
||||
func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) {
|
||||
pods := &api.PodList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "15",
|
||||
},
|
||||
Items: []api.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
|
||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
|
||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||
},
|
||||
},
|
||||
}
|
||||
svc := &api.ServiceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "16",
|
||||
},
|
||||
Items: []api.Service{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
|
||||
Spec: api.ServiceSpec{
|
||||
SessionAffinity: "None",
|
||||
Type: api.ServiceTypeClusterIP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rc := &api.ReplicationControllerList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "17",
|
||||
},
|
||||
Items: []api.ReplicationController{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
|
||||
Spec: api.ReplicationControllerSpec{
|
||||
Replicas: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return pods, svc, rc
|
||||
}
|
||||
93
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/flags.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/flags.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
// PrintFlags composes common printer flag structs
|
||||
// used across all create commands, and provides a method
|
||||
// of retrieving a known printer based on flag values provided.
|
||||
type PrintFlags struct {
|
||||
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
|
||||
NamePrintFlags *genericclioptions.NamePrintFlags
|
||||
TemplateFlags *printers.KubeTemplatePrintFlags
|
||||
|
||||
TypeSetter *genericprinters.TypeSetterPrinter
|
||||
|
||||
OutputFormat *string
|
||||
}
|
||||
|
||||
func (f *PrintFlags) AllowedFormats() []string {
|
||||
return append(append(f.JSONYamlPrintFlags.AllowedFormats(), f.NamePrintFlags.AllowedFormats()...),
|
||||
f.TemplateFlags.AllowedFormats()...)
|
||||
}
|
||||
|
||||
func (f *PrintFlags) Complete(successTemplate string) error {
|
||||
return f.NamePrintFlags.Complete(successTemplate)
|
||||
}
|
||||
|
||||
func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
|
||||
outputFormat := ""
|
||||
if f.OutputFormat != nil {
|
||||
outputFormat = *f.OutputFormat
|
||||
}
|
||||
|
||||
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetter.WrapToPrinter(p, err)
|
||||
}
|
||||
|
||||
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetter.WrapToPrinter(p, err)
|
||||
}
|
||||
|
||||
if p, err := f.TemplateFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetter.WrapToPrinter(p, err)
|
||||
}
|
||||
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
|
||||
f.JSONYamlPrintFlags.AddFlags(cmd)
|
||||
f.NamePrintFlags.AddFlags(cmd)
|
||||
f.TemplateFlags.AddFlags(cmd)
|
||||
|
||||
if f.OutputFormat != nil {
|
||||
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
|
||||
}
|
||||
}
|
||||
|
||||
func NewPrintFlags(operation string, scheme runtime.ObjectTyper) *PrintFlags {
|
||||
outputFormat := ""
|
||||
|
||||
return &PrintFlags{
|
||||
OutputFormat: &outputFormat,
|
||||
|
||||
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
|
||||
NamePrintFlags: genericclioptions.NewNamePrintFlags(operation),
|
||||
TemplateFlags: printers.NewKubeTemplatePrintFlags(),
|
||||
|
||||
TypeSetter: genericprinters.NewTypeSetter(scheme),
|
||||
}
|
||||
}
|
||||
386
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete.go
generated
vendored
Normal file
386
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete.go
generated
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
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"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
kubectlwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
delete_long = templates.LongDesc(i18n.T(`
|
||||
Delete resources by filenames, stdin, resources and names, or by resources and label selector.
|
||||
|
||||
JSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,
|
||||
resources and names, or resources and label selector.
|
||||
|
||||
Some resources, such as pods, support graceful deletion. These resources define a default period
|
||||
before they are forcibly terminated (the grace period) but you may override that value with
|
||||
the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
|
||||
represent entities in the cluster, deletion may not be acknowledged immediately. If the node
|
||||
hosting a pod is down or cannot reach the API server, termination may take significantly longer
|
||||
than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
|
||||
the --force flag.
|
||||
|
||||
IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
|
||||
terminated, which can leave those processes running until the node detects the deletion and
|
||||
completes graceful deletion. If your processes use shared storage or talk to a remote API and
|
||||
depend on the name of the pod to identify themselves, force deleting those pods may result in
|
||||
multiple processes running on different machines using the same identification which may lead
|
||||
to data corruption or inconsistency. Only force delete pods when you are sure the pod is
|
||||
terminated, or if your application can tolerate multiple copies of the same pod running at once.
|
||||
Also, if you force delete pods the scheduler may place new pods on those nodes before the node
|
||||
has released those resources and causing those pods to be evicted immediately.
|
||||
|
||||
Note that the delete command does NOT do resource version checks, so if someone submits an
|
||||
update to a resource right when you submit a delete, their update will be lost along with the
|
||||
rest of the resource.`))
|
||||
|
||||
delete_example = templates.Examples(i18n.T(`
|
||||
# Delete a pod using the type and name specified in pod.json.
|
||||
kubectl delete -f ./pod.json
|
||||
|
||||
# Delete a pod based on the type and name in the JSON passed into stdin.
|
||||
cat pod.json | kubectl delete -f -
|
||||
|
||||
# Delete pods and services with same names "baz" and "foo"
|
||||
kubectl delete pod,service baz foo
|
||||
|
||||
# Delete pods and services with label name=myLabel.
|
||||
kubectl delete pods,services -l name=myLabel
|
||||
|
||||
# Delete a pod with minimal delay
|
||||
kubectl delete pod foo --now
|
||||
|
||||
# Force delete a pod on a dead node
|
||||
kubectl delete pod foo --grace-period=0 --force
|
||||
|
||||
# Delete all pods
|
||||
kubectl delete pods --all`))
|
||||
)
|
||||
|
||||
type DeleteOptions struct {
|
||||
resource.FilenameOptions
|
||||
|
||||
LabelSelector string
|
||||
FieldSelector string
|
||||
DeleteAll bool
|
||||
IgnoreNotFound bool
|
||||
Cascade bool
|
||||
DeleteNow bool
|
||||
ForceDeletion bool
|
||||
WaitForDeletion bool
|
||||
|
||||
GracePeriod int
|
||||
Timeout time.Duration
|
||||
|
||||
Output string
|
||||
|
||||
DynamicClient dynamic.Interface
|
||||
Mapper meta.RESTMapper
|
||||
Result *resource.Result
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"),
|
||||
Long: delete_long,
|
||||
Example: delete_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
o := deleteFlags.ToOptions(nil, streams)
|
||||
cmdutil.CheckErr(o.Complete(f, args, cmd))
|
||||
cmdutil.CheckErr(o.Validate(cmd))
|
||||
cmdutil.CheckErr(o.RunDelete())
|
||||
},
|
||||
SuggestFor: []string{"rm"},
|
||||
}
|
||||
|
||||
deleteFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddIncludeUninitializedFlag(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
|
||||
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
|
||||
if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
|
||||
// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
|
||||
o.IgnoreNotFound = true
|
||||
}
|
||||
}
|
||||
if o.DeleteNow {
|
||||
if o.GracePeriod != -1 {
|
||||
return fmt.Errorf("--now and --grace-period cannot be specified together")
|
||||
}
|
||||
o.GracePeriod = 1
|
||||
}
|
||||
if o.GracePeriod == 0 && !o.ForceDeletion {
|
||||
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
|
||||
// into --grace-period=1. Users may provide --force to bypass this conversion.
|
||||
o.GracePeriod = 1
|
||||
}
|
||||
|
||||
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
|
||||
r := f.NewBuilder().
|
||||
Unstructured().
|
||||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
LabelSelectorParam(o.LabelSelector).
|
||||
FieldSelectorParam(o.FieldSelector).
|
||||
IncludeUninitialized(includeUninitialized).
|
||||
SelectAllParam(o.DeleteAll).
|
||||
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
|
||||
Flatten().
|
||||
Do()
|
||||
err = r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Result = r
|
||||
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DynamicClient, err = f.DynamicClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) Validate(cmd *cobra.Command) error {
|
||||
if o.Output != "" && o.Output != "name" {
|
||||
return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", o.Output)
|
||||
}
|
||||
|
||||
if o.DeleteAll && len(o.LabelSelector) > 0 {
|
||||
return fmt.Errorf("cannot set --all and --selector at the same time")
|
||||
}
|
||||
if o.DeleteAll && len(o.FieldSelector) > 0 {
|
||||
return fmt.Errorf("cannot set --all and --field-selector at the same time")
|
||||
}
|
||||
|
||||
if o.GracePeriod == 0 && !o.ForceDeletion && !o.WaitForDeletion {
|
||||
// With the explicit --wait flag we need extra validation for backward compatibility
|
||||
return fmt.Errorf("--grace-period=0 must have either --force specified, or --wait to be set to true")
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.GracePeriod == 0 && o.ForceDeletion:
|
||||
fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
|
||||
case o.ForceDeletion:
|
||||
fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) RunDelete() error {
|
||||
return o.DeleteResult(o.Result)
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
|
||||
found := 0
|
||||
if o.IgnoreNotFound {
|
||||
r = r.IgnoreErrors(errors.IsNotFound)
|
||||
}
|
||||
deletedInfos := []*resource.Info{}
|
||||
err := r.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deletedInfos = append(deletedInfos, info)
|
||||
found++
|
||||
|
||||
options := &metav1.DeleteOptions{}
|
||||
if o.GracePeriod >= 0 {
|
||||
options = metav1.NewDeleteOptions(int64(o.GracePeriod))
|
||||
}
|
||||
policy := metav1.DeletePropagationForeground
|
||||
if !o.Cascade {
|
||||
policy = metav1.DeletePropagationOrphan
|
||||
}
|
||||
options.PropagationPolicy = &policy
|
||||
|
||||
return o.deleteResource(info, options)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if found == 0 {
|
||||
fmt.Fprintf(o.Out, "No resources found\n")
|
||||
return nil
|
||||
}
|
||||
if !o.WaitForDeletion {
|
||||
return nil
|
||||
}
|
||||
// if we don't have a dynamic client, we don't want to wait. Eventually when delete is cleaned up, this will likely
|
||||
// drop out.
|
||||
if o.DynamicClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
effectiveTimeout := o.Timeout
|
||||
if effectiveTimeout == 0 {
|
||||
// if we requested to wait forever, set it to a week.
|
||||
effectiveTimeout = 168 * time.Hour
|
||||
}
|
||||
waitOptions := kubectlwait.WaitOptions{
|
||||
ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
|
||||
DynamicClient: o.DynamicClient,
|
||||
Timeout: effectiveTimeout,
|
||||
|
||||
Printer: printers.NewDiscardingPrinter(),
|
||||
ConditionFn: kubectlwait.IsDeleted,
|
||||
IOStreams: o.IOStreams,
|
||||
}
|
||||
err = waitOptions.RunWait()
|
||||
if errors.IsForbidden(err) {
|
||||
// if we're forbidden from waiting, we shouldn't fail.
|
||||
glog.V(1).Info(err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
|
||||
// TODO: Remove this in or after 1.12 release.
|
||||
// Server version >= 1.11 no longer needs this hack.
|
||||
mapping := info.ResourceMapping()
|
||||
if mapping.Resource.GroupResource() == (schema.GroupResource{Group: "extensions", Resource: "daemonsets"}) ||
|
||||
mapping.Resource.GroupResource() == (schema.GroupResource{Group: "apps", Resource: "daemonsets"}) {
|
||||
if err := updateDaemonSet(info.Namespace, info.Name, o.DynamicClient); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
|
||||
return cmdutil.AddSourceToErr("deleting", info.Source, err)
|
||||
}
|
||||
|
||||
o.PrintObj(info)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Remove this in or after 1.12 release.
|
||||
// Server version >= 1.11 no longer needs this hack.
|
||||
func updateDaemonSet(namespace, name string, dynamicClient dynamic.Interface) error {
|
||||
dsClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace(namespace)
|
||||
obj, err := dsClient.Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ds := &appsv1.DaemonSet{}
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We set the nodeSelector to a random label. This label is nearly guaranteed
|
||||
// to not be set on any node so the DameonSetController will start deleting
|
||||
// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
|
||||
// the DaemonSet.
|
||||
ds.Spec.Template.Spec.NodeSelector = map[string]string{
|
||||
string(uuid.NewUUID()): string(uuid.NewUUID()),
|
||||
}
|
||||
// force update to avoid version conflict
|
||||
ds.ResourceVersion = ""
|
||||
|
||||
out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = dsClient.Update(&unstructured.Unstructured{Object: out}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the daemon set controller to kill all the daemon pods.
|
||||
if err := wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
|
||||
updatedObj, err := dsClient.Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
updatedDS := &appsv1.DaemonSet{}
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedObj.Object, ds); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deletion printing is special because we do not have an object to print.
|
||||
// This mirrors name printer behavior
|
||||
func (o *DeleteOptions) PrintObj(info *resource.Info) {
|
||||
operation := "deleted"
|
||||
groupKind := info.Mapping.GroupVersionKind
|
||||
kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
|
||||
if len(groupKind.Group) == 0 {
|
||||
kindString = strings.ToLower(groupKind.Kind)
|
||||
}
|
||||
|
||||
if o.GracePeriod == 0 {
|
||||
operation = "force deleted"
|
||||
}
|
||||
|
||||
if o.Output == "name" {
|
||||
// -o name: prints resource/name
|
||||
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// understandable output by default
|
||||
fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
|
||||
}
|
||||
193
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_flags.go
generated
vendored
Normal file
193
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_flags.go
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
)
|
||||
|
||||
// PrintFlags composes common printer flag structs
|
||||
// used for commands requiring deletion logic.
|
||||
type DeleteFlags struct {
|
||||
FileNameFlags *genericclioptions.FileNameFlags
|
||||
LabelSelector *string
|
||||
FieldSelector *string
|
||||
|
||||
All *bool
|
||||
Cascade *bool
|
||||
Force *bool
|
||||
GracePeriod *int
|
||||
IgnoreNotFound *bool
|
||||
Now *bool
|
||||
Timeout *time.Duration
|
||||
Wait *bool
|
||||
Output *string
|
||||
}
|
||||
|
||||
func (f *DeleteFlags) ToOptions(dynamicClient dynamic.Interface, streams genericclioptions.IOStreams) *DeleteOptions {
|
||||
options := &DeleteOptions{
|
||||
DynamicClient: dynamicClient,
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
// add filename options
|
||||
if f.FileNameFlags != nil {
|
||||
options.FilenameOptions = f.FileNameFlags.ToOptions()
|
||||
}
|
||||
if f.LabelSelector != nil {
|
||||
options.LabelSelector = *f.LabelSelector
|
||||
}
|
||||
if f.FieldSelector != nil {
|
||||
options.FieldSelector = *f.FieldSelector
|
||||
}
|
||||
|
||||
// add output format
|
||||
if f.Output != nil {
|
||||
options.Output = *f.Output
|
||||
}
|
||||
|
||||
if f.All != nil {
|
||||
options.DeleteAll = *f.All
|
||||
}
|
||||
if f.Cascade != nil {
|
||||
options.Cascade = *f.Cascade
|
||||
}
|
||||
if f.Force != nil {
|
||||
options.ForceDeletion = *f.Force
|
||||
}
|
||||
if f.GracePeriod != nil {
|
||||
options.GracePeriod = *f.GracePeriod
|
||||
}
|
||||
if f.IgnoreNotFound != nil {
|
||||
options.IgnoreNotFound = *f.IgnoreNotFound
|
||||
}
|
||||
if f.Now != nil {
|
||||
options.DeleteNow = *f.Now
|
||||
}
|
||||
if f.Timeout != nil {
|
||||
options.Timeout = *f.Timeout
|
||||
}
|
||||
if f.Wait != nil {
|
||||
options.WaitForDeletion = *f.Wait
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (f *DeleteFlags) AddFlags(cmd *cobra.Command) {
|
||||
f.FileNameFlags.AddFlags(cmd.Flags())
|
||||
if f.LabelSelector != nil {
|
||||
cmd.Flags().StringVarP(f.LabelSelector, "selector", "l", *f.LabelSelector, "Selector (label query) to filter on, not including uninitialized ones.")
|
||||
}
|
||||
if f.FieldSelector != nil {
|
||||
cmd.Flags().StringVarP(f.FieldSelector, "field-selector", "", *f.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
|
||||
}
|
||||
if f.All != nil {
|
||||
cmd.Flags().BoolVar(f.All, "all", *f.All, "Delete all resources, including uninitialized ones, in the namespace of the specified resource types.")
|
||||
}
|
||||
if f.Force != nil {
|
||||
cmd.Flags().BoolVar(f.Force, "force", *f.Force, "Only used when grace-period=0. If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
|
||||
}
|
||||
if f.Cascade != nil {
|
||||
cmd.Flags().BoolVar(f.Cascade, "cascade", *f.Cascade, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
|
||||
}
|
||||
if f.Now != nil {
|
||||
cmd.Flags().BoolVar(f.Now, "now", *f.Now, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
|
||||
}
|
||||
if f.GracePeriod != nil {
|
||||
cmd.Flags().IntVar(f.GracePeriod, "grace-period", *f.GracePeriod, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
|
||||
}
|
||||
if f.Timeout != nil {
|
||||
cmd.Flags().DurationVar(f.Timeout, "timeout", *f.Timeout, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
|
||||
}
|
||||
if f.IgnoreNotFound != nil {
|
||||
cmd.Flags().BoolVar(f.IgnoreNotFound, "ignore-not-found", *f.IgnoreNotFound, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.")
|
||||
}
|
||||
if f.Wait != nil {
|
||||
cmd.Flags().BoolVar(f.Wait, "wait", *f.Wait, "If true, wait for resources to be gone before returning. This waits for finalizers.")
|
||||
}
|
||||
if f.Output != nil {
|
||||
cmd.Flags().StringVarP(f.Output, "output", "o", *f.Output, "Output mode. Use \"-o name\" for shorter output (resource/name).")
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeleteCommandFlags provides default flags and values for use with the "delete" command
|
||||
func NewDeleteCommandFlags(usage string) *DeleteFlags {
|
||||
cascade := true
|
||||
gracePeriod := -1
|
||||
|
||||
// setup command defaults
|
||||
all := false
|
||||
force := false
|
||||
ignoreNotFound := false
|
||||
now := false
|
||||
output := ""
|
||||
labelSelector := ""
|
||||
fieldSelector := ""
|
||||
timeout := time.Duration(0)
|
||||
wait := true
|
||||
|
||||
filenames := []string{}
|
||||
recursive := false
|
||||
|
||||
return &DeleteFlags{
|
||||
FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive},
|
||||
LabelSelector: &labelSelector,
|
||||
FieldSelector: &fieldSelector,
|
||||
|
||||
Cascade: &cascade,
|
||||
GracePeriod: &gracePeriod,
|
||||
|
||||
All: &all,
|
||||
Force: &force,
|
||||
IgnoreNotFound: &ignoreNotFound,
|
||||
Now: &now,
|
||||
Timeout: &timeout,
|
||||
Wait: &wait,
|
||||
Output: &output,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeleteFlags provides default flags and values for use in commands outside of "delete"
|
||||
func NewDeleteFlags(usage string) *DeleteFlags {
|
||||
cascade := true
|
||||
gracePeriod := -1
|
||||
|
||||
force := false
|
||||
timeout := time.Duration(0)
|
||||
wait := false
|
||||
|
||||
filenames := []string{}
|
||||
recursive := false
|
||||
|
||||
return &DeleteFlags{
|
||||
FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive},
|
||||
|
||||
Cascade: &cascade,
|
||||
GracePeriod: &gracePeriod,
|
||||
|
||||
// add non-defaults
|
||||
Force: &force,
|
||||
Timeout: &timeout,
|
||||
Wait: &wait,
|
||||
}
|
||||
}
|
||||
716
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_test.go
generated
vendored
Normal file
716
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_test.go
generated
vendored
Normal file
@@ -0,0 +1,716 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
)
|
||||
|
||||
var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
|
||||
|
||||
func fakecmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
|
||||
DisableFlagsInUseLine: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func TestDeleteObjectByTuple(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, _, rc := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
|
||||
// replication controller with cascade off
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
|
||||
// secret with cascade on, but no client-side reaper
|
||||
case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
|
||||
default:
|
||||
// Ensures no GET is performed when deleting by name
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("namespace", "test")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"})
|
||||
if buf.String() != "replicationcontroller/redis-master-controller\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
|
||||
// Test cascading delete of object without client-side reaper doesn't make GET requests
|
||||
streams, _, buf, _ = genericclioptions.NewTestIOStreams()
|
||||
cmd = NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("namespace", "test")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"secrets/mysecret"})
|
||||
if buf.String() != "secret/mysecret\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func hasExpectedPropagationPolicy(body io.ReadCloser, policy *metav1.DeletionPropagation) bool {
|
||||
if body == nil || policy == nil {
|
||||
return body == nil && policy == nil
|
||||
}
|
||||
var parsedBody metav1.DeleteOptions
|
||||
rawBody, _ := ioutil.ReadAll(body)
|
||||
json.Unmarshal(rawBody, &parsedBody)
|
||||
if parsedBody.PropagationPolicy == nil {
|
||||
return false
|
||||
}
|
||||
return *policy == *parsedBody.PropagationPolicy
|
||||
}
|
||||
|
||||
// Tests that DeleteOptions.OrphanDependents is appropriately set while deleting objects.
|
||||
func TestOrphanDependentsInDeleteObject(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, _, rc := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
var policy *metav1.DeletionPropagation
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m, b := req.URL.Path, req.Method, req.Body; {
|
||||
|
||||
case p == "/namespaces/test/secrets/mysecret" && m == "DELETE" && hasExpectedPropagationPolicy(b, policy):
|
||||
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
// DeleteOptions.PropagationPolicy should be Foreground, when cascade is true (default).
|
||||
foregroundPolicy := metav1.DeletePropagationForeground
|
||||
policy = &foregroundPolicy
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("namespace", "test")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"secrets/mysecret"})
|
||||
if buf.String() != "secret/mysecret\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
|
||||
// Test that delete options should be set to orphan when cascade is false.
|
||||
orphanPolicy := metav1.DeletePropagationOrphan
|
||||
policy = &orphanPolicy
|
||||
streams, _, buf, _ = genericclioptions.NewTestIOStreams()
|
||||
cmd = NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("namespace", "test")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"secrets/mysecret"})
|
||||
if buf.String() != "secret/mysecret\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNamedObject(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
initTestErrorHandler(t)
|
||||
_, _, rc := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
|
||||
// replication controller with cascade off
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
|
||||
// secret with cascade on, but no client-side reaper
|
||||
case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
|
||||
default:
|
||||
// Ensures no GET is performed when deleting by name
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("namespace", "test")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"})
|
||||
if buf.String() != "replicationcontroller/redis-master-controller\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
|
||||
// Test cascading delete of object without client-side reaper doesn't make GET requests
|
||||
streams, _, buf, _ = genericclioptions.NewTestIOStreams()
|
||||
cmd = NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("namespace", "test")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"secrets", "mysecret"})
|
||||
if buf.String() != "secret/mysecret\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteObject(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, _, rc := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
// uses the name from the file, not the response
|
||||
if buf.String() != "replicationcontroller/redis-master\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteObjectGraceZero(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
pods, _, _ := testData()
|
||||
|
||||
count := 0
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
t.Logf("got request %s %s", req.Method, req.URL.Path)
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods/nginx" && m == "GET":
|
||||
count++
|
||||
switch count {
|
||||
case 1, 2, 3:
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
|
||||
default:
|
||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &metav1.Status{})}, nil
|
||||
}
|
||||
case p == "/api/v1/namespaces/test" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
|
||||
case p == "/namespaces/test/pods/nginx" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
streams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Flags().Set("grace-period", "0")
|
||||
cmd.Run(cmd, []string{"pods/nginx"})
|
||||
|
||||
// uses the name from the file, not the response
|
||||
if buf.String() != "pod/nginx\n" {
|
||||
t.Errorf("unexpected output: %s\n---\n%s", buf.String(), errBuf.String())
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("unexpected calls to GET: %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteObjectNotFound(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
options := &DeleteOptions{
|
||||
FilenameOptions: resource.FilenameOptions{
|
||||
Filenames: []string{"../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml"},
|
||||
},
|
||||
GracePeriod: -1,
|
||||
Cascade: false,
|
||||
Output: "name",
|
||||
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
|
||||
}
|
||||
err := options.Complete(tf, []string{}, fakecmd())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = options.RunDelete()
|
||||
if err == nil || !errors.IsNotFound(err) {
|
||||
t.Errorf("unexpected error: expected NotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteObjectIgnoreNotFound(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("ignore-not-found", "true")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if buf.String() != "" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAllNotFound(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, svc, _ := testData()
|
||||
// Add an item to the list which will result in a 404 on delete
|
||||
svc.Items = append(svc.Items, api.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
|
||||
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/services" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
|
||||
case p == "/namespaces/test/services/foo" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, notFoundError)}, nil
|
||||
case p == "/namespaces/test/services/baz" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
// Make sure we can explicitly choose to fail on NotFound errors, even with --all
|
||||
options := &DeleteOptions{
|
||||
FilenameOptions: resource.FilenameOptions{},
|
||||
GracePeriod: -1,
|
||||
Cascade: false,
|
||||
DeleteAll: true,
|
||||
IgnoreNotFound: false,
|
||||
Output: "name",
|
||||
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
|
||||
}
|
||||
err := options.Complete(tf, []string{"services"}, fakecmd())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = options.RunDelete()
|
||||
if err == nil || !errors.IsNotFound(err) {
|
||||
t.Errorf("unexpected error: expected NotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAllIgnoreNotFound(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, svc, _ := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
// Add an item to the list which will result in a 404 on delete
|
||||
svc.Items = append(svc.Items, api.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
|
||||
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/services" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
|
||||
case p == "/namespaces/test/services/foo" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, notFoundError)}, nil
|
||||
case p == "/namespaces/test/services/baz" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("all", "true")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"services"})
|
||||
|
||||
if buf.String() != "service/baz\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMultipleObject(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, svc, rc := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
case p == "/namespaces/test/services/frontend" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
|
||||
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if buf.String() != "replicationcontroller/redis-master\nservice/frontend\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, svc, _ := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
|
||||
case p == "/namespaces/test/services/frontend" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
options := &DeleteOptions{
|
||||
FilenameOptions: resource.FilenameOptions{
|
||||
Filenames: []string{"../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml", "../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml"},
|
||||
},
|
||||
GracePeriod: -1,
|
||||
Cascade: false,
|
||||
Output: "name",
|
||||
IOStreams: streams,
|
||||
}
|
||||
err := options.Complete(tf, []string{}, fakecmd())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = options.RunDelete()
|
||||
if err == nil || !errors.IsNotFound(err) {
|
||||
t.Errorf("unexpected error: expected NotFound, got %v", err)
|
||||
}
|
||||
|
||||
if buf.String() != "service/frontend\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, svc, rc := testData()
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers/baz" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
case p == "/namespaces/test/replicationcontrollers/foo" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
case p == "/namespaces/test/services/baz" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
case p == "/namespaces/test/services/foo" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
// Ensures no GET is performed when deleting by name
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("namespace", "test")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"replicationcontrollers,services", "baz", "foo"})
|
||||
if buf.String() != "replicationcontroller/baz\nreplicationcontroller/foo\nservice/baz\nservice/foo\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteDirectory(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
_, _, rc := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMultipleSelector(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
pods, svc, _ := testData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods" && m == "GET":
|
||||
if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
|
||||
case p == "/namespaces/test/services" && m == "GET":
|
||||
if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
|
||||
case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
cmd.Flags().Set("selector", "a=b")
|
||||
cmd.Flags().Set("cascade", "false")
|
||||
cmd.Flags().Set("output", "name")
|
||||
cmd.Run(cmd, []string{"pods,services"})
|
||||
|
||||
if buf.String() != "pod/foo\npod/bar\nservice/baz\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceErrors(t *testing.T) {
|
||||
initTestErrorHandler(t)
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
errFn func(error) bool
|
||||
}{
|
||||
"no args": {
|
||||
args: []string{},
|
||||
errFn: func(err error) bool { return strings.Contains(err.Error(), "You must provide one or more resources") },
|
||||
},
|
||||
"resources but no selectors": {
|
||||
args: []string{"pods"},
|
||||
errFn: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
|
||||
},
|
||||
},
|
||||
"multiple resources but no selectors": {
|
||||
args: []string{"pods,deployments"},
|
||||
errFn: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, testCase := range testCases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
tf.ClientConfigVal = defaultClientConfig()
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
options := &DeleteOptions{
|
||||
FilenameOptions: resource.FilenameOptions{},
|
||||
GracePeriod: -1,
|
||||
Cascade: false,
|
||||
Output: "name",
|
||||
IOStreams: streams,
|
||||
}
|
||||
err := options.Complete(tf, testCase.args, fakecmd())
|
||||
if !testCase.errFn(err) {
|
||||
t.Errorf("%s: unexpected error: %v", k, err)
|
||||
return
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
250
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe.go
generated
vendored
Normal file
250
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
var (
|
||||
describeLong = templates.LongDesc(`
|
||||
Show details of a specific resource or group of resources
|
||||
|
||||
Print a detailed description of the selected resources, including related resources such
|
||||
as events or controllers. You may select a single object by name, all objects of that
|
||||
type, provide a name prefix, or label selector. For example:
|
||||
|
||||
$ kubectl describe TYPE NAME_PREFIX
|
||||
|
||||
will first check for an exact match on TYPE and NAME_PREFIX. If no such resource
|
||||
exists, it will output details for every resource that has a name prefixed with NAME_PREFIX.`)
|
||||
|
||||
describeExample = templates.Examples(i18n.T(`
|
||||
# Describe a node
|
||||
kubectl describe nodes kubernetes-node-emt8.c.myproject.internal
|
||||
|
||||
# Describe a pod
|
||||
kubectl describe pods/nginx
|
||||
|
||||
# Describe a pod identified by type and name in "pod.json"
|
||||
kubectl describe -f pod.json
|
||||
|
||||
# Describe all pods
|
||||
kubectl describe pods
|
||||
|
||||
# Describe pods by label name=myLabel
|
||||
kubectl describe po -l name=myLabel
|
||||
|
||||
# Describe all pods managed by the 'frontend' replication controller (rc-created pods
|
||||
# get the name of the rc as a prefix in the pod the name).
|
||||
kubectl describe pods frontend`))
|
||||
)
|
||||
|
||||
type DescribeOptions struct {
|
||||
CmdParent string
|
||||
Selector string
|
||||
Namespace string
|
||||
|
||||
Describer func(*meta.RESTMapping) (printers.Describer, error)
|
||||
NewBuilder func() *resource.Builder
|
||||
|
||||
BuilderArgs []string
|
||||
|
||||
EnforceNamespace bool
|
||||
AllNamespaces bool
|
||||
IncludeUninitialized bool
|
||||
|
||||
DescriberSettings *printers.DescriberSettings
|
||||
FilenameOptions *resource.FilenameOptions
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
func NewCmdDescribe(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := &DescribeOptions{
|
||||
FilenameOptions: &resource.FilenameOptions{},
|
||||
DescriberSettings: &printers.DescriberSettings{
|
||||
ShowEvents: true,
|
||||
},
|
||||
|
||||
CmdParent: parent,
|
||||
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Show details of a specific resource or group of resources"),
|
||||
Long: describeLong + "\n\n" + cmdutil.SuggestApiResources(parent),
|
||||
Example: describeExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
usage := "containing the resource to describe"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage)
|
||||
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
|
||||
cmd.Flags().BoolVar(&o.DescriberSettings.ShowEvents, "show-events", o.DescriberSettings.ShowEvents, "If true, display events related to the described object.")
|
||||
cmdutil.AddIncludeUninitializedFlag(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *DescribeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.AllNamespaces {
|
||||
o.EnforceNamespace = false
|
||||
}
|
||||
|
||||
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) {
|
||||
return fmt.Errorf("You must specify the type of resource to describe. %s\n", cmdutil.SuggestApiResources(o.CmdParent))
|
||||
}
|
||||
|
||||
o.BuilderArgs = args
|
||||
|
||||
o.Describer = func(mapping *meta.RESTMapping) (printers.Describer, error) {
|
||||
return cmdutil.DescriberFn(f, mapping)
|
||||
}
|
||||
|
||||
o.NewBuilder = f.NewBuilder
|
||||
|
||||
// include the uninitialized objects by default
|
||||
// unless user explicitly set --include-uninitialized=false
|
||||
o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DescribeOptions) Validate(args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DescribeOptions) Run() error {
|
||||
r := o.NewBuilder().
|
||||
Unstructured().
|
||||
ContinueOnError().
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
|
||||
FilenameParam(o.EnforceNamespace, o.FilenameOptions).
|
||||
LabelSelectorParam(o.Selector).
|
||||
IncludeUninitialized(o.IncludeUninitialized).
|
||||
ResourceTypeOrNameArgs(true, o.BuilderArgs...).
|
||||
Flatten().
|
||||
Do()
|
||||
err := r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allErrs := []error{}
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) && len(o.BuilderArgs) == 2 {
|
||||
return o.DescribeMatchingResources(err, o.BuilderArgs[0], o.BuilderArgs[1])
|
||||
}
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
|
||||
errs := sets.NewString()
|
||||
first := true
|
||||
for _, info := range infos {
|
||||
mapping := info.ResourceMapping()
|
||||
describer, err := o.Describer(mapping)
|
||||
if err != nil {
|
||||
if errs.Has(err.Error()) {
|
||||
continue
|
||||
}
|
||||
allErrs = append(allErrs, err)
|
||||
errs.Insert(err.Error())
|
||||
continue
|
||||
}
|
||||
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
|
||||
if err != nil {
|
||||
if errs.Has(err.Error()) {
|
||||
continue
|
||||
}
|
||||
allErrs = append(allErrs, err)
|
||||
errs.Insert(err.Error())
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
first = false
|
||||
fmt.Fprint(o.Out, s)
|
||||
} else {
|
||||
fmt.Fprintf(o.Out, "\n\n%s", s)
|
||||
}
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
func (o *DescribeOptions) DescribeMatchingResources(originalError error, resource, prefix string) error {
|
||||
r := o.NewBuilder().
|
||||
Unstructured().
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().
|
||||
ResourceTypeOrNameArgs(true, resource).
|
||||
SingleResourceType().
|
||||
Flatten().
|
||||
Do()
|
||||
mapping, err := r.ResourceMapping()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
describer, err := o.Describer(mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isFound := false
|
||||
for ix := range infos {
|
||||
info := infos[ix]
|
||||
if strings.HasPrefix(info.Name, prefix) {
|
||||
isFound = true
|
||||
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(o.Out, "%s\n", s)
|
||||
}
|
||||
}
|
||||
if !isFound {
|
||||
return originalError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
262
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe_test.go
generated
vendored
Normal file
262
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe_test.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
|
||||
func TestDescribeUnknownSchemaObject(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := cmdutil.DescriberFn
|
||||
defer func() {
|
||||
cmdutil.DescriberFn = oldFn
|
||||
}()
|
||||
cmdutil.DescriberFn = d.describerFor
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("non-default")
|
||||
defer tf.Cleanup()
|
||||
_, _, codec := cmdtesting.NewExternalScheme()
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))},
|
||||
}
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
cmd.Run(cmd, []string{"type", "foo"})
|
||||
|
||||
if d.Name != "foo" || d.Namespace != "" {
|
||||
t.Errorf("unexpected describer: %#v", d)
|
||||
}
|
||||
|
||||
if buf.String() != fmt.Sprintf("%s", d.Output) {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
|
||||
func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := cmdutil.DescriberFn
|
||||
defer func() {
|
||||
cmdutil.DescriberFn = oldFn
|
||||
}()
|
||||
cmdutil.DescriberFn = d.describerFor
|
||||
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
_, _, codec := cmdtesting.NewExternalScheme()
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalNamespacedType("", "", "foo", "non-default"))},
|
||||
}
|
||||
tf.WithNamespace("non-default")
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
cmd.Run(cmd, []string{"namespacedtype", "foo"})
|
||||
|
||||
if d.Name != "foo" || d.Namespace != "non-default" {
|
||||
t.Errorf("unexpected describer: %#v", d)
|
||||
}
|
||||
|
||||
if buf.String() != fmt.Sprintf("%s", d.Output) {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeObject(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := cmdutil.DescriberFn
|
||||
defer func() {
|
||||
cmdutil.DescriberFn = oldFn
|
||||
}()
|
||||
cmdutil.DescriberFn = d.describerFor
|
||||
|
||||
_, _, rc := testData()
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if d.Name != "redis-master" || d.Namespace != "test" {
|
||||
t.Errorf("unexpected describer: %#v", d)
|
||||
}
|
||||
|
||||
if buf.String() != fmt.Sprintf("%s", d.Output) {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeListObjects(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := cmdutil.DescriberFn
|
||||
defer func() {
|
||||
cmdutil.DescriberFn = oldFn
|
||||
}()
|
||||
cmdutil.DescriberFn = d.describerFor
|
||||
|
||||
pods, _, _ := testData()
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
|
||||
}
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
if buf.String() != fmt.Sprintf("%s\n\n%s", d.Output, d.Output) {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeObjectShowEvents(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := cmdutil.DescriberFn
|
||||
defer func() {
|
||||
cmdutil.DescriberFn = oldFn
|
||||
}()
|
||||
cmdutil.DescriberFn = d.describerFor
|
||||
|
||||
pods, _, _ := testData()
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
|
||||
}
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd.Flags().Set("show-events", "true")
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
if d.Settings.ShowEvents != true {
|
||||
t.Errorf("ShowEvents = true expected, got ShowEvents = %v", d.Settings.ShowEvents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeObjectSkipEvents(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := cmdutil.DescriberFn
|
||||
defer func() {
|
||||
cmdutil.DescriberFn = oldFn
|
||||
}()
|
||||
cmdutil.DescriberFn = d.describerFor
|
||||
|
||||
pods, _, _ := testData()
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
|
||||
}
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd.Flags().Set("show-events", "false")
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
if d.Settings.ShowEvents != false {
|
||||
t.Errorf("ShowEvents = false expected, got ShowEvents = %v", d.Settings.ShowEvents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeHelpMessage(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
cmd.SetArgs([]string{"-h"})
|
||||
cmd.SetOutput(buf)
|
||||
_, err := cmd.ExecuteC()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got := buf.String()
|
||||
|
||||
expected := `describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)`
|
||||
if !strings.Contains(got, expected) {
|
||||
t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
|
||||
}
|
||||
|
||||
unexpected := `describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME) [flags]`
|
||||
if strings.Contains(got, unexpected) {
|
||||
t.Errorf("Expected not to contain: \n %v\nGot:\n %v\n", unexpected, got)
|
||||
}
|
||||
}
|
||||
|
||||
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 (t *testDescriber) describerFor(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (printers.Describer, error) {
|
||||
return t, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user