Add generated file

This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
xing-yang
2018-07-12 10:55:15 -07:00
parent 36b1de0341
commit e213d1890d
17729 changed files with 5090889 additions and 0 deletions

94
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/BUILD generated vendored Normal file
View 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",
],
)

View File

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

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

View File

@@ -0,0 +1,299 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"k8s.io/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
}

View File

@@ -0,0 +1,260 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/apiserver/pkg/util/flag"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
func stringFlagFor(s string) flag.StringFlag {
var f flag.StringFlag
f.Set(s)
return f
}
func TestCreateAuthInfoOptions(t *testing.T) {
tests := []struct {
flags []string
wantParseErr bool
wantCompleteErr bool
wantValidateErr bool
wantOptions *createAuthInfoOptions
}{
{
flags: []string{
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
},
},
{
flags: []string{
"me",
"--token=foo",
},
wantOptions: &createAuthInfoOptions{
name: "me",
token: stringFlagFor("foo"),
},
},
{
flags: []string{
"me",
"--username=jane",
"--password=bar",
},
wantOptions: &createAuthInfoOptions{
name: "me",
username: stringFlagFor("jane"),
password: stringFlagFor("bar"),
},
},
{
// Cannot provide both token and basic auth.
flags: []string{
"me",
"--token=foo",
"--username=jane",
"--password=bar",
},
wantValidateErr: true,
},
{
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id=foo",
"--auth-provider-arg=client-secret=bar",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
authProvider: stringFlagFor("oidc"),
authProviderArgs: map[string]string{
"client-id": "foo",
"client-secret": "bar",
},
authProviderArgsToRemove: []string{},
},
},
{
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id-",
"--auth-provider-arg=client-secret-",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
authProvider: stringFlagFor("oidc"),
authProviderArgs: map[string]string{},
authProviderArgsToRemove: []string{
"client-id",
"client-secret",
},
},
},
{
flags: []string{
"--auth-provider-arg=client-id-", // auth provider name not required
"--auth-provider-arg=client-secret-",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
authProviderArgs: map[string]string{},
authProviderArgsToRemove: []string{
"client-id",
"client-secret",
},
},
},
{
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id", // values must be of form 'key=value' or 'key-'
"me",
},
wantCompleteErr: true,
},
{
flags: []string{
// No name for authinfo provided.
},
wantCompleteErr: true,
},
}
for i, test := range tests {
buff := new(bytes.Buffer)
opts := new(createAuthInfoOptions)
cmd := newCmdConfigSetAuthInfo(buff, opts)
if err := cmd.ParseFlags(test.flags); err != nil {
if !test.wantParseErr {
t.Errorf("case %d: parsing error for flags %q: %v: %s", i, test.flags, err, buff)
}
continue
}
if test.wantParseErr {
t.Errorf("case %d: expected parsing error for flags %q: %s", i, test.flags, buff)
continue
}
if err := opts.complete(cmd, buff); err != nil {
if !test.wantCompleteErr {
t.Errorf("case %d: complete() error for flags %q: %s", i, test.flags, buff)
}
continue
}
if test.wantCompleteErr {
t.Errorf("case %d: complete() expected errors for flags %q: %s", i, test.flags, buff)
continue
}
if err := opts.validate(); err != nil {
if !test.wantValidateErr {
t.Errorf("case %d: flags %q: validate failed: %v", i, test.flags, err)
}
continue
}
if test.wantValidateErr {
t.Errorf("case %d: flags %q: expected validate to fail", i, test.flags)
continue
}
if !reflect.DeepEqual(opts, test.wantOptions) {
t.Errorf("case %d: flags %q: mis-matched options,\nwanted=%#v\ngot= %#v", i, test.flags, test.wantOptions, opts)
}
}
}
type createAuthInfoTest struct {
description string
config clientcmdapi.Config
args []string
flags []string
expected string
expectedConfig clientcmdapi.Config
}
func TestCreateAuthInfo(t *testing.T) {
conf := clientcmdapi.Config{}
test := createAuthInfoTest{
description: "Testing for create aythinfo",
config: conf,
args: []string{"cluster-admin"},
flags: []string{
"--username=admin",
"--password=uXFGweU9l35qcif",
},
expected: `User "cluster-admin" set.` + "\n",
expectedConfig: clientcmdapi.Config{
AuthInfos: map[string]*clientcmdapi.AuthInfo{
"cluster-admin": {Username: "admin", Password: "uXFGweU9l35qcif"}},
},
}
test.run(t)
}
func (test createAuthInfoTest) run(t *testing.T) {
fakeKubeFile, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.Remove(fakeKubeFile.Name())
err = clientcmd.WriteToFile(test.config, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigSetAuthInfo(buf, pathOptions)
cmd.SetArgs(test.args)
cmd.Flags().Parse(test.flags)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v,kubectl config set-credentials args: %v,flags: %v", err, test.args, test.flags)
}
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
}
if len(test.expected) != 0 {
if buf.String() != test.expected {
t.Errorf("Fail in %q:\n expected %v\n but got %v\n", test.description, test.expected, buf.String())
}
}
if test.expectedConfig.AuthInfos != nil {
expectAuthInfo := test.expectedConfig.AuthInfos[test.args[0]]
actualAuthInfo := config.AuthInfos[test.args[0]]
if expectAuthInfo.Username != actualAuthInfo.Username || expectAuthInfo.Password != actualAuthInfo.Password {
t.Errorf("Fail in %q:\n expected AuthInfo%v\n but found %v in kubeconfig\n", test.description, expectAuthInfo, actualAuthInfo)
}
}
}

View File

@@ -0,0 +1,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
}

View File

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

View File

@@ -0,0 +1,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
}

View File

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

View File

@@ -0,0 +1,74 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
type CurrentContextOptions struct {
ConfigAccess clientcmd.ConfigAccess
}
var (
current_context_long = templates.LongDesc(`
Displays the current-context`)
current_context_example = templates.Examples(`
# Display the current-context
kubectl config current-context`)
)
func NewCmdConfigCurrentContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &CurrentContextOptions{ConfigAccess: configAccess}
cmd := &cobra.Command{
Use: "current-context",
Short: i18n.T("Displays the current-context"),
Long: current_context_long,
Example: current_context_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunCurrentContext(out, options)
cmdutil.CheckErr(err)
},
}
return cmd
}
func RunCurrentContext(out io.Writer, options *CurrentContextOptions) error {
config, err := options.ConfigAccess.GetStartingConfig()
if err != nil {
return err
}
if config.CurrentContext == "" {
err = fmt.Errorf("current-context is not set\n")
return err
}
fmt.Fprintf(out, "%s\n", config.CurrentContext)
return nil
}

View File

@@ -0,0 +1,93 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
type currentContextTest struct {
startingConfig clientcmdapi.Config
expectedError string
}
func newFederalContextConfig() clientcmdapi.Config {
return clientcmdapi.Config{
CurrentContext: "federal-context",
}
}
func TestCurrentContextWithSetContext(t *testing.T) {
test := currentContextTest{
startingConfig: newFederalContextConfig(),
expectedError: "",
}
test.run(t)
}
func TestCurrentContextWithUnsetContext(t *testing.T) {
test := currentContextTest{
startingConfig: *clientcmdapi.NewConfig(),
expectedError: "current-context is not set",
}
test.run(t)
}
func (test currentContextTest) run(t *testing.T) {
fakeKubeFile, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.Remove(fakeKubeFile.Name())
err = clientcmd.WriteToFile(test.startingConfig, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
options := CurrentContextOptions{
ConfigAccess: pathOptions,
}
buf := bytes.NewBuffer([]byte{})
err = RunCurrentContext(buf, &options)
if len(test.expectedError) != 0 {
if err == nil {
t.Errorf("Did not get %v", test.expectedError)
} else {
if !strings.Contains(err.Error(), test.expectedError) {
t.Errorf("Expected %v, but got %v", test.expectedError, err)
}
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}

View File

@@ -0,0 +1,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
}

View File

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

View File

@@ -0,0 +1,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
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"io/ioutil"
"os"
"testing"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
type getClustersTest struct {
config clientcmdapi.Config
expected string
}
func TestGetClusters(t *testing.T) {
conf := clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"minikube": {Server: "https://192.168.0.99"},
},
}
test := getClustersTest{
config: conf,
expected: `NAME
minikube
`,
}
test.run(t)
}
func TestGetClustersEmpty(t *testing.T) {
test := getClustersTest{
config: clientcmdapi.Config{},
expected: "NAME\n",
}
test.run(t)
}
func (test getClustersTest) run(t *testing.T) {
fakeKubeFile, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.Remove(fakeKubeFile.Name())
err = clientcmd.WriteToFile(test.config, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigGetClusters(buf, pathOptions)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v", err)
}
if len(test.expected) != 0 {
if buf.String() != test.expected {
t.Errorf("expected %v, but got %v", test.expected, buf.String())
}
return
}
}

View File

@@ -0,0 +1,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
}

View File

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

View File

@@ -0,0 +1,152 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
type navigationSteps struct {
steps []navigationStep
currentStepIndex int
}
type navigationStep struct {
stepValue string
stepType reflect.Type
}
func newNavigationSteps(path string) (*navigationSteps, error) {
steps := []navigationStep{}
individualParts := strings.Split(path, ".")
currType := reflect.TypeOf(clientcmdapi.Config{})
currPartIndex := 0
for currPartIndex < len(individualParts) {
switch currType.Kind() {
case reflect.Map:
// if we're in a map, we need to locate a name. That name may contain dots, so we need to know what tokens are legal for the map's value type
// for example, we could have a set request like: `set clusters.10.10.12.56.insecure-skip-tls-verify true`. We enter this case with
// steps representing 10, 10, 12, 56, insecure-skip-tls-verify. The name is "10.10.12.56", so we want to collect all those parts together and
// store them as a single step. In order to do that, we need to determine what set of tokens is a legal step AFTER the name of the map key
// This set of reflective code pulls the type of the map values, uses that type to look up the set of legal tags. Those legal tags are used to
// walk the list of remaining parts until we find a match to a legal tag or the end of the string. That name is used to burn all the used parts.
mapValueType := currType.Elem().Elem()
mapValueOptions, err := getPotentialTypeValues(mapValueType)
if err != nil {
return nil, err
}
nextPart := findNameStep(individualParts[currPartIndex:], sets.StringKeySet(mapValueOptions))
steps = append(steps, navigationStep{nextPart, mapValueType})
currPartIndex += len(strings.Split(nextPart, "."))
currType = mapValueType
case reflect.Struct:
nextPart := individualParts[currPartIndex]
options, err := getPotentialTypeValues(currType)
if err != nil {
return nil, err
}
fieldType, exists := options[nextPart]
if !exists {
return nil, fmt.Errorf("unable to parse %v after %v at %v", path, steps, currType)
}
steps = append(steps, navigationStep{nextPart, fieldType})
currPartIndex += len(strings.Split(nextPart, "."))
currType = fieldType
}
}
return &navigationSteps{steps, 0}, nil
}
func (s *navigationSteps) pop() navigationStep {
if s.moreStepsRemaining() {
s.currentStepIndex++
return s.steps[s.currentStepIndex-1]
}
return navigationStep{}
}
func (s *navigationSteps) peek() navigationStep {
if s.moreStepsRemaining() {
return s.steps[s.currentStepIndex]
}
return navigationStep{}
}
func (s *navigationSteps) moreStepsRemaining() bool {
return len(s.steps) > s.currentStepIndex
}
// findNameStep takes the list of parts and a set of valid tags that can be used after the name. It then walks the list of parts
// until it find a valid "next" tag or until it reaches the end of the parts and then builds the name back up out of the individual parts
func findNameStep(parts []string, typeOptions sets.String) string {
if len(parts) == 0 {
return ""
}
numberOfPartsInStep := findKnownValue(parts[1:], typeOptions) + 1
// if we didn't find a known value, then the entire thing must be a name
if numberOfPartsInStep == 0 {
numberOfPartsInStep = len(parts)
}
nextParts := parts[0:numberOfPartsInStep]
return strings.Join(nextParts, ".")
}
// getPotentialTypeValues takes a type and looks up the tags used to represent its fields when serialized.
func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, error) {
if typeValue.Kind() == reflect.Ptr {
typeValue = typeValue.Elem()
}
if typeValue.Kind() != reflect.Struct {
return nil, fmt.Errorf("%v is not of type struct", typeValue)
}
ret := make(map[string]reflect.Type)
for fieldIndex := 0; fieldIndex < typeValue.NumField(); fieldIndex++ {
fieldType := typeValue.Field(fieldIndex)
yamlTag := fieldType.Tag.Get("json")
yamlTagName := strings.Split(yamlTag, ",")[0]
ret[yamlTagName] = fieldType.Type
}
return ret, nil
}
func findKnownValue(parts []string, valueOptions sets.String) int {
for i := range parts {
if valueOptions.Has(parts[i]) {
return i
}
}
return -1
}

View File

@@ -0,0 +1,96 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
type stepParserTest struct {
path string
expectedNavigationSteps navigationSteps
expectedError string
}
func TestParseWithDots(t *testing.T) {
test := stepParserTest{
path: "clusters.my.dot.delimited.name.server",
expectedNavigationSteps: navigationSteps{
steps: []navigationStep{
{"clusters", reflect.TypeOf(make(map[string]*clientcmdapi.Cluster))},
{"my.dot.delimited.name", reflect.TypeOf(clientcmdapi.Cluster{})},
{"server", reflect.TypeOf("")},
},
},
}
test.run(t)
}
func TestParseWithDotsEndingWithName(t *testing.T) {
test := stepParserTest{
path: "contexts.10.12.12.12",
expectedNavigationSteps: navigationSteps{
steps: []navigationStep{
{"contexts", reflect.TypeOf(make(map[string]*clientcmdapi.Context))},
{"10.12.12.12", reflect.TypeOf(clientcmdapi.Context{})},
},
},
}
test.run(t)
}
func TestParseWithBadValue(t *testing.T) {
test := stepParserTest{
path: "user.bad",
expectedNavigationSteps: navigationSteps{
steps: []navigationStep{},
},
expectedError: "unable to parse user.bad after [] at api.Config",
}
test.run(t)
}
func (test stepParserTest) run(t *testing.T) {
actualSteps, err := newNavigationSteps(test.path)
if len(test.expectedError) != 0 {
if err == nil {
t.Errorf("Did not get %v", test.expectedError)
} else {
if !strings.Contains(err.Error(), test.expectedError) {
t.Errorf("Expected %v, but got %v", test.expectedError, err)
}
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expectedNavigationSteps, *actualSteps) {
t.Errorf("diff: %v", diff.ObjectDiff(test.expectedNavigationSteps, *actualSteps))
t.Errorf("expected: %#v\n actual: %#v", test.expectedNavigationSteps, *actualSteps)
}
}

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

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

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

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

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

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

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

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