Switch to github.com/golang/dep for vendoring
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
parent
d6ab91be27
commit
8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions
70
vendor/k8s.io/kubernetes/pkg/client/unversioned/BUILD
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/pkg/client/unversioned/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"conditions.go",
|
||||
"helper.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/apps:go_default_library",
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//plugin/pkg/client/auth:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["helper_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/client/unversioned/auth:all-srcs",
|
||||
"//pkg/client/unversioned/clientcmd:all-srcs",
|
||||
"//pkg/client/unversioned/portforward:all-srcs",
|
||||
"//pkg/client/unversioned/remotecommand:all-srcs",
|
||||
"//pkg/client/unversioned/testclient/simple:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
40
vendor/k8s.io/kubernetes/pkg/client/unversioned/OWNERS
generated
vendored
Executable file
40
vendor/k8s.io/kubernetes/pkg/client/unversioned/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,40 @@
|
|||
reviewers:
|
||||
- thockin
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- vishh
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- nikhiljindal
|
||||
- bprashanth
|
||||
- ixdy
|
||||
- gmarek
|
||||
- erictune
|
||||
- davidopp
|
||||
- pmorie
|
||||
- sttts
|
||||
- kargakis
|
||||
- saad-ali
|
||||
- zmerlynn
|
||||
- janetkuo
|
||||
- ncdc
|
||||
- timstclair
|
||||
- mwielgus
|
||||
- timothysc
|
||||
- feiskyer
|
||||
- jlowdermilk
|
||||
- soltysh
|
||||
- piosz
|
||||
- dims
|
||||
- errordeveloper
|
||||
- madhusudancs
|
||||
- hongchaodeng
|
||||
- krousey
|
||||
- jayunit100
|
||||
- jszczepkowski
|
||||
- jdef
|
||||
- mml
|
36
vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/BUILD
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["clientauth.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//pkg/client/restclient:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_xtest",
|
||||
srcs = ["clientauth_test.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//pkg/client/unversioned/auth:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
125
vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth.go
generated
vendored
Normal file
125
vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth.go
generated
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package auth defines a file format for holding authentication
|
||||
information needed by clients of Kubernetes. Typically,
|
||||
a Kubernetes cluster will put auth info for the admin in a known
|
||||
location when it is created, and will (soon) put it in a known
|
||||
location within a Container's file tree for Containers that
|
||||
need access to the Kubernetes API.
|
||||
|
||||
Having a defined format allows:
|
||||
- clients to be implmented in multiple languages
|
||||
- applications which link clients to be portable across
|
||||
clusters with different authentication styles (e.g.
|
||||
some may use SSL Client certs, others may not, etc)
|
||||
- when the format changes, applications only
|
||||
need to update this code.
|
||||
|
||||
The file format is json, marshalled from a struct authcfg.Info.
|
||||
|
||||
Clinet libraries in other languages should use the same format.
|
||||
|
||||
It is not intended to store general preferences, such as default
|
||||
namespace, output options, etc. CLIs (such as kubectl) and UIs should
|
||||
develop their own format and may wish to inline the authcfg.Info type.
|
||||
|
||||
The authcfg.Info is just a file format. It is distinct from
|
||||
client.Config which holds options for creating a client.Client.
|
||||
Helper functions are provided in this package to fill in a
|
||||
client.Client from an authcfg.Info.
|
||||
|
||||
Example:
|
||||
|
||||
import (
|
||||
"pkg/client"
|
||||
"pkg/client/auth"
|
||||
)
|
||||
|
||||
info, err := auth.LoadFromFile(filename)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
clientConfig = client.Config{}
|
||||
clientConfig.Host = "example.com:4901"
|
||||
clientConfig = info.MergeWithConfig()
|
||||
client := client.New(clientConfig)
|
||||
client.Pods(ns).List()
|
||||
*/
|
||||
package auth
|
||||
|
||||
// TODO: need a way to rotate Tokens. Therefore, need a way for client object to be reset when the authcfg is updated.
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
)
|
||||
|
||||
// Info holds Kubernetes API authorization config. It is intended
|
||||
// to be read/written from a file as a JSON object.
|
||||
type Info struct {
|
||||
User string
|
||||
Password string
|
||||
CAFile string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
BearerToken string
|
||||
Insecure *bool
|
||||
}
|
||||
|
||||
// LoadFromFile parses an Info object from a file path.
|
||||
// If the file does not exist, then os.IsNotExist(err) == true
|
||||
func LoadFromFile(path string) (*Info, error) {
|
||||
var info Info
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(data, &info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, err
|
||||
}
|
||||
|
||||
// MergeWithConfig returns a copy of a client.Config with values from the Info.
|
||||
// The fields of client.Config with a corresponding field in the Info are set
|
||||
// with the value from the Info.
|
||||
func (info Info) MergeWithConfig(c restclient.Config) (restclient.Config, error) {
|
||||
var config restclient.Config = c
|
||||
config.Username = info.User
|
||||
config.Password = info.Password
|
||||
config.CAFile = info.CAFile
|
||||
config.CertFile = info.CertFile
|
||||
config.KeyFile = info.KeyFile
|
||||
config.BearerToken = info.BearerToken
|
||||
if info.Insecure != nil {
|
||||
config.Insecure = *info.Insecure
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (info Info) Complete() bool {
|
||||
return len(info.User) > 0 ||
|
||||
len(info.CertFile) > 0 ||
|
||||
len(info.BearerToken) > 0
|
||||
}
|
69
vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth_test.go
generated
vendored
Normal file
69
vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth_test.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
clientauth "k8s.io/kubernetes/pkg/client/unversioned/auth"
|
||||
)
|
||||
|
||||
func TestLoadFromFile(t *testing.T) {
|
||||
loadAuthInfoTests := []struct {
|
||||
authData string
|
||||
authInfo *clientauth.Info
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
`{"user": "user", "password": "pass"}`,
|
||||
&clientauth.Info{User: "user", Password: "pass"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"", nil, true,
|
||||
},
|
||||
}
|
||||
for _, loadAuthInfoTest := range loadAuthInfoTests {
|
||||
tt := loadAuthInfoTest
|
||||
aifile, err := ioutil.TempFile("", "testAuthInfo")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if tt.authData != "missing" {
|
||||
defer os.Remove(aifile.Name())
|
||||
defer aifile.Close()
|
||||
_, err = aifile.WriteString(tt.authData)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
} else {
|
||||
aifile.Close()
|
||||
os.Remove(aifile.Name())
|
||||
}
|
||||
authInfo, err := clientauth.LoadFromFile(aifile.Name())
|
||||
gotErr := err != nil
|
||||
if gotErr != tt.expectErr {
|
||||
t.Errorf("expected errorness: %v, actual errorness: %v", tt.expectErr, gotErr)
|
||||
}
|
||||
if !reflect.DeepEqual(authInfo, tt.authInfo) {
|
||||
t.Errorf("Expected %v, got %v", tt.authInfo, authInfo)
|
||||
}
|
||||
}
|
||||
}
|
78
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/BUILD
generated
vendored
Normal file
78
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"auth_loaders.go",
|
||||
"client_config.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
"helpers.go",
|
||||
"loader.go",
|
||||
"merged_client_builder.go",
|
||||
"overrides.go",
|
||||
"validation.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/client/unversioned/auth:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api/latest:go_default_library",
|
||||
"//pkg/util/homedir:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/howeyc/gopass",
|
||||
"//vendor:github.com/imdario/mergo",
|
||||
"//vendor:github.com/spf13/pflag",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/validation",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"client_config_test.go",
|
||||
"loader_test.go",
|
||||
"merged_client_builder_test.go",
|
||||
"validation_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api/latest:go_default_library",
|
||||
"//vendor:github.com/ghodss/yaml",
|
||||
"//vendor:github.com/imdario/mergo",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/client/unversioned/clientcmd/api:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
51
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/BUILD
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"helpers.go",
|
||||
"register.go",
|
||||
"types.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"helpers_test.go",
|
||||
"types_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor:github.com/ghodss/yaml"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/client/unversioned/clientcmd/api/latest:all-srcs",
|
||||
"//pkg/client/unversioned/clientcmd/api/v1:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
183
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers.go
generated
vendored
Normal file
183
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sDec, _ := base64.StdEncoding.DecodeString("REDACTED+")
|
||||
redactedBytes = []byte(string(sDec))
|
||||
}
|
||||
|
||||
// IsConfigEmpty returns true if the config is empty.
|
||||
func IsConfigEmpty(config *Config) bool {
|
||||
return len(config.AuthInfos) == 0 && len(config.Clusters) == 0 && len(config.Contexts) == 0 &&
|
||||
len(config.CurrentContext) == 0 &&
|
||||
len(config.Preferences.Extensions) == 0 && !config.Preferences.Colors &&
|
||||
len(config.Extensions) == 0
|
||||
}
|
||||
|
||||
// MinifyConfig read the current context and uses that to keep only the relevant pieces of config
|
||||
// This is useful for making secrets based on kubeconfig files
|
||||
func MinifyConfig(config *Config) error {
|
||||
if len(config.CurrentContext) == 0 {
|
||||
return errors.New("current-context must exist in order to minify")
|
||||
}
|
||||
|
||||
currContext, exists := config.Contexts[config.CurrentContext]
|
||||
if !exists {
|
||||
return fmt.Errorf("cannot locate context %v", config.CurrentContext)
|
||||
}
|
||||
|
||||
newContexts := map[string]*Context{}
|
||||
newContexts[config.CurrentContext] = currContext
|
||||
|
||||
newClusters := map[string]*Cluster{}
|
||||
if len(currContext.Cluster) > 0 {
|
||||
if _, exists := config.Clusters[currContext.Cluster]; !exists {
|
||||
return fmt.Errorf("cannot locate cluster %v", currContext.Cluster)
|
||||
}
|
||||
|
||||
newClusters[currContext.Cluster] = config.Clusters[currContext.Cluster]
|
||||
}
|
||||
|
||||
newAuthInfos := map[string]*AuthInfo{}
|
||||
if len(currContext.AuthInfo) > 0 {
|
||||
if _, exists := config.AuthInfos[currContext.AuthInfo]; !exists {
|
||||
return fmt.Errorf("cannot locate user %v", currContext.AuthInfo)
|
||||
}
|
||||
|
||||
newAuthInfos[currContext.AuthInfo] = config.AuthInfos[currContext.AuthInfo]
|
||||
}
|
||||
|
||||
config.AuthInfos = newAuthInfos
|
||||
config.Clusters = newClusters
|
||||
config.Contexts = newContexts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var redactedBytes []byte
|
||||
|
||||
// Flatten redacts raw data entries from the config object for a human-readable view.
|
||||
func ShortenConfig(config *Config) {
|
||||
// trick json encoder into printing a human readable string in the raw data
|
||||
// by base64 decoding what we want to print. Relies on implementation of
|
||||
// http://golang.org/pkg/encoding/json/#Marshal using base64 to encode []byte
|
||||
for key, authInfo := range config.AuthInfos {
|
||||
if len(authInfo.ClientKeyData) > 0 {
|
||||
authInfo.ClientKeyData = redactedBytes
|
||||
}
|
||||
if len(authInfo.ClientCertificateData) > 0 {
|
||||
authInfo.ClientCertificateData = redactedBytes
|
||||
}
|
||||
config.AuthInfos[key] = authInfo
|
||||
}
|
||||
for key, cluster := range config.Clusters {
|
||||
if len(cluster.CertificateAuthorityData) > 0 {
|
||||
cluster.CertificateAuthorityData = redactedBytes
|
||||
}
|
||||
config.Clusters[key] = cluster
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten changes the config object into a self contained config (useful for making secrets)
|
||||
func FlattenConfig(config *Config) error {
|
||||
for key, authInfo := range config.AuthInfos {
|
||||
baseDir, err := MakeAbs(path.Dir(authInfo.LocationOfOrigin), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := FlattenContent(&authInfo.ClientCertificate, &authInfo.ClientCertificateData, baseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := FlattenContent(&authInfo.ClientKey, &authInfo.ClientKeyData, baseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.AuthInfos[key] = authInfo
|
||||
}
|
||||
for key, cluster := range config.Clusters {
|
||||
baseDir, err := MakeAbs(path.Dir(cluster.LocationOfOrigin), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := FlattenContent(&cluster.CertificateAuthority, &cluster.CertificateAuthorityData, baseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Clusters[key] = cluster
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func FlattenContent(path *string, contents *[]byte, baseDir string) error {
|
||||
if len(*path) != 0 {
|
||||
if len(*contents) > 0 {
|
||||
return errors.New("cannot have values for both path and contents")
|
||||
}
|
||||
|
||||
var err error
|
||||
absPath := ResolvePath(*path, baseDir)
|
||||
*contents, err = ioutil.ReadFile(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*path = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolvePath returns the path as an absolute paths, relative to the given base directory
|
||||
func ResolvePath(path string, base string) string {
|
||||
// Don't resolve empty paths
|
||||
if len(path) > 0 {
|
||||
// Don't resolve absolute paths
|
||||
if !filepath.IsAbs(path) {
|
||||
return filepath.Join(base, path)
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func MakeAbs(path, base string) (string, error) {
|
||||
if filepath.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
if len(base) == 0 {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
base = cwd
|
||||
}
|
||||
return filepath.Join(base, path), nil
|
||||
}
|
301
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers_test.go
generated
vendored
Normal file
301
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers_test.go
generated
vendored
Normal file
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caContent string, t *testing.T) Config {
|
||||
if err := ioutil.WriteFile(certFile, []byte(certContent), 0644); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if err := ioutil.WriteFile(keyFile, []byte(keyContent), 0600); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if err := ioutil.WriteFile(caFile, []byte(caContent), 0644); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
return Config{
|
||||
AuthInfos: map[string]*AuthInfo{
|
||||
"red-user": {Token: "red-token", ClientCertificateData: []byte(certContent), ClientKeyData: []byte(keyContent)},
|
||||
"blue-user": {Token: "blue-token", ClientCertificate: certFile, ClientKey: keyFile}},
|
||||
Clusters: map[string]*Cluster{
|
||||
"cow-cluster": {Server: "http://cow.org:8080", CertificateAuthorityData: []byte(caContent)},
|
||||
"chicken-cluster": {Server: "http://chicken.org:8080", CertificateAuthority: caFile}},
|
||||
Contexts: map[string]*Context{
|
||||
"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster"},
|
||||
"shaker-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster"}},
|
||||
CurrentContext: "federal-context",
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinifySuccess(t *testing.T) {
|
||||
certFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
|
||||
if err := MinifyConfig(&mutatingConfig); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(mutatingConfig.Contexts) > 1 {
|
||||
t.Errorf("unexpected contexts: %v", mutatingConfig.Contexts)
|
||||
}
|
||||
if _, exists := mutatingConfig.Contexts["federal-context"]; !exists {
|
||||
t.Errorf("missing context")
|
||||
}
|
||||
|
||||
if len(mutatingConfig.Clusters) > 1 {
|
||||
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
|
||||
}
|
||||
if _, exists := mutatingConfig.Clusters["cow-cluster"]; !exists {
|
||||
t.Errorf("missing cluster")
|
||||
}
|
||||
|
||||
if len(mutatingConfig.AuthInfos) > 1 {
|
||||
t.Errorf("unexpected users: %v", mutatingConfig.AuthInfos)
|
||||
}
|
||||
if _, exists := mutatingConfig.AuthInfos["red-user"]; !exists {
|
||||
t.Errorf("missing user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinifyMissingContext(t *testing.T) {
|
||||
certFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
mutatingConfig.CurrentContext = "missing"
|
||||
|
||||
errMsg := "cannot locate context missing"
|
||||
|
||||
if err := MinifyConfig(&mutatingConfig); err == nil || err.Error() != errMsg {
|
||||
t.Errorf("expected %v, got %v", errMsg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinifyMissingCluster(t *testing.T) {
|
||||
certFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
delete(mutatingConfig.Clusters, mutatingConfig.Contexts[mutatingConfig.CurrentContext].Cluster)
|
||||
|
||||
errMsg := "cannot locate cluster cow-cluster"
|
||||
|
||||
if err := MinifyConfig(&mutatingConfig); err == nil || err.Error() != errMsg {
|
||||
t.Errorf("expected %v, got %v", errMsg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinifyMissingAuthInfo(t *testing.T) {
|
||||
certFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
delete(mutatingConfig.AuthInfos, mutatingConfig.Contexts[mutatingConfig.CurrentContext].AuthInfo)
|
||||
|
||||
errMsg := "cannot locate user red-user"
|
||||
|
||||
if err := MinifyConfig(&mutatingConfig); err == nil || err.Error() != errMsg {
|
||||
t.Errorf("expected %v, got %v", errMsg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlattenSuccess(t *testing.T) {
|
||||
certFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
certData := "cert"
|
||||
keyData := "key"
|
||||
caData := "ca"
|
||||
|
||||
unchangingCluster := "cow-cluster"
|
||||
unchangingAuthInfo := "red-user"
|
||||
changingCluster := "chicken-cluster"
|
||||
changingAuthInfo := "blue-user"
|
||||
|
||||
startingConfig := newMergedConfig(certFile.Name(), certData, keyFile.Name(), keyData, caFile.Name(), caData, t)
|
||||
mutatingConfig := startingConfig
|
||||
|
||||
if err := FlattenConfig(&mutatingConfig); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(mutatingConfig.Contexts) != 2 {
|
||||
t.Errorf("unexpected contexts: %v", mutatingConfig.Contexts)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.Contexts, mutatingConfig.Contexts) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.Contexts, mutatingConfig.Contexts)
|
||||
}
|
||||
|
||||
if len(mutatingConfig.Clusters) != 2 {
|
||||
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster]) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster])
|
||||
}
|
||||
if len(mutatingConfig.Clusters[changingCluster].CertificateAuthority) != 0 {
|
||||
t.Errorf("unexpected caFile")
|
||||
}
|
||||
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != caData {
|
||||
t.Errorf("expected %v, got %v", caData, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
|
||||
}
|
||||
|
||||
if len(mutatingConfig.AuthInfos) != 2 {
|
||||
t.Errorf("unexpected users: %v", mutatingConfig.AuthInfos)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo]) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo])
|
||||
}
|
||||
if len(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificate) != 0 {
|
||||
t.Errorf("unexpected caFile")
|
||||
}
|
||||
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData) != certData {
|
||||
t.Errorf("expected %v, got %v", certData, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData))
|
||||
}
|
||||
if len(mutatingConfig.AuthInfos[changingAuthInfo].ClientKey) != 0 {
|
||||
t.Errorf("unexpected caFile")
|
||||
}
|
||||
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData) != keyData {
|
||||
t.Errorf("expected %v, got %v", keyData, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Example_minifyAndShorten() {
|
||||
certFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
certData := "cert"
|
||||
keyData := "key"
|
||||
caData := "ca"
|
||||
|
||||
config := newMergedConfig(certFile.Name(), certData, keyFile.Name(), keyData, caFile.Name(), caData, nil)
|
||||
|
||||
MinifyConfig(&config)
|
||||
ShortenConfig(&config)
|
||||
|
||||
output, _ := yaml.Marshal(config)
|
||||
fmt.Printf("%s", string(output))
|
||||
// Output:
|
||||
// clusters:
|
||||
// cow-cluster:
|
||||
// LocationOfOrigin: ""
|
||||
// certificate-authority-data: REDACTED
|
||||
// server: http://cow.org:8080
|
||||
// contexts:
|
||||
// federal-context:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: cow-cluster
|
||||
// user: red-user
|
||||
// current-context: federal-context
|
||||
// preferences: {}
|
||||
// users:
|
||||
// red-user:
|
||||
// LocationOfOrigin: ""
|
||||
// client-certificate-data: REDACTED
|
||||
// client-key-data: REDACTED
|
||||
// token: red-token
|
||||
}
|
||||
|
||||
func TestShortenSuccess(t *testing.T) {
|
||||
certFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
|
||||
certData := "cert"
|
||||
keyData := "key"
|
||||
caData := "ca"
|
||||
|
||||
unchangingCluster := "chicken-cluster"
|
||||
unchangingAuthInfo := "blue-user"
|
||||
changingCluster := "cow-cluster"
|
||||
changingAuthInfo := "red-user"
|
||||
|
||||
startingConfig := newMergedConfig(certFile.Name(), certData, keyFile.Name(), keyData, caFile.Name(), caData, t)
|
||||
mutatingConfig := startingConfig
|
||||
|
||||
ShortenConfig(&mutatingConfig)
|
||||
|
||||
if len(mutatingConfig.Contexts) != 2 {
|
||||
t.Errorf("unexpected contexts: %v", mutatingConfig.Contexts)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.Contexts, mutatingConfig.Contexts) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.Contexts, mutatingConfig.Contexts)
|
||||
}
|
||||
|
||||
redacted := string(redactedBytes)
|
||||
if len(mutatingConfig.Clusters) != 2 {
|
||||
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster]) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster])
|
||||
}
|
||||
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != redacted {
|
||||
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
|
||||
}
|
||||
|
||||
if len(mutatingConfig.AuthInfos) != 2 {
|
||||
t.Errorf("unexpected users: %v", mutatingConfig.AuthInfos)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo]) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo])
|
||||
}
|
||||
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData) != redacted {
|
||||
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData))
|
||||
}
|
||||
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData) != redacted {
|
||||
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData))
|
||||
}
|
||||
}
|
35
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/BUILD
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["latest.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/client/unversioned/clientcmd/api:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api/v1:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/json",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/versioning",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
66
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
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 latest
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
// Version is the string that represents the current external default version.
|
||||
const Version = "v1"
|
||||
|
||||
var ExternalVersion = schema.GroupVersion{Group: "", Version: "v1"}
|
||||
|
||||
// OldestVersion is the string that represents the oldest server version supported,
|
||||
// for client code that wants to hardcode the lowest common denominator.
|
||||
const OldestVersion = "v1"
|
||||
|
||||
// Versions is the list of versions that are recognized in code. The order provided
|
||||
// may be assumed to be least feature rich to most feature rich, and clients may
|
||||
// choose to prefer the latter items in the list over the former items when presented
|
||||
// with a set of versions to choose.
|
||||
var Versions = []string{"v1"}
|
||||
|
||||
var (
|
||||
Codec runtime.Codec
|
||||
Scheme *runtime.Scheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
Scheme = runtime.NewScheme()
|
||||
if err := api.AddToScheme(Scheme); err != nil {
|
||||
// Programmer error, detect immediately
|
||||
panic(err)
|
||||
}
|
||||
if err := v1.AddToScheme(Scheme); err != nil {
|
||||
// Programmer error, detect immediately
|
||||
panic(err)
|
||||
}
|
||||
yamlSerializer := json.NewYAMLSerializer(json.DefaultMetaFactory, Scheme, Scheme)
|
||||
Codec = versioning.NewDefaultingCodecForScheme(
|
||||
Scheme,
|
||||
yamlSerializer,
|
||||
yamlSerializer,
|
||||
schema.GroupVersion{Version: Version},
|
||||
runtime.InternalGroupVersioner,
|
||||
)
|
||||
}
|
46
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/register.go
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/register.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
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 api
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
// TODO this should be in the "kubeconfig" group
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Config{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Config) GetObjectKind() schema.ObjectKind { return obj }
|
||||
func (obj *Config) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
||||
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
||||
}
|
||||
func (obj *Config) GroupVersionKind() schema.GroupVersionKind {
|
||||
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
||||
}
|
178
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go
generated
vendored
Normal file
178
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go
generated
vendored
Normal file
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Where possible, json tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
// IMPORTANT if you add fields to this struct, please update IsConfigEmpty()
|
||||
type Config struct {
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// DEPRECATED: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
|
||||
// Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify
|
||||
// a single value for the cluster version.
|
||||
// This field isn't really needed anyway, so we are deprecating it without replacement.
|
||||
// It will be ignored if it is present.
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `json:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters map[string]*Cluster `json:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos map[string]*AuthInfo `json:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts map[string]*Context `json:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// IMPORTANT if you add fields to this struct, please update IsConfigEmpty()
|
||||
type Preferences struct {
|
||||
// +optional
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
LocationOfOrigin string
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
|
||||
// +optional
|
||||
APIVersion string `json:"api-version,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
// +optional
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
// +optional
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
LocationOfOrigin string
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
// +optional
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||
// +optional
|
||||
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
// +optional
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||
// +optional
|
||||
ClientKeyData []byte `json:"client-key-data,omitempty"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Token string `json:"token,omitempty"`
|
||||
// TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence.
|
||||
// +optional
|
||||
TokenFile string `json:"tokenFile,omitempty"`
|
||||
// Impersonate is the username to act-as.
|
||||
// +optional
|
||||
Impersonate string `json:"act-as,omitempty"`
|
||||
// Username is the username for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password is the password for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Password string `json:"password,omitempty"`
|
||||
// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
LocationOfOrigin string
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `json:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthProviderConfig holds the configuration for a specified auth provider.
|
||||
type AuthProviderConfig struct {
|
||||
Name string `json:"name"`
|
||||
// +optional
|
||||
Config map[string]string `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Preferences: *NewPreferences(),
|
||||
Clusters: make(map[string]*Cluster),
|
||||
AuthInfos: make(map[string]*AuthInfo),
|
||||
Contexts: make(map[string]*Context),
|
||||
Extensions: make(map[string]runtime.Object),
|
||||
}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewContext() *Context {
|
||||
return &Context{Extensions: make(map[string]runtime.Object)}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewCluster() *Cluster {
|
||||
return &Cluster{Extensions: make(map[string]runtime.Object)}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewAuthInfo() *AuthInfo {
|
||||
return &AuthInfo{Extensions: make(map[string]runtime.Object)}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewPreferences() *Preferences {
|
||||
return &Preferences{Extensions: make(map[string]runtime.Object)}
|
||||
}
|
135
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types_test.go
generated
vendored
Normal file
135
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types_test.go
generated
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func Example_emptyConfig() {
|
||||
defaultConfig := NewConfig()
|
||||
|
||||
output, err := yaml.Marshal(defaultConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v", string(output))
|
||||
// Output:
|
||||
// clusters: {}
|
||||
// contexts: {}
|
||||
// current-context: ""
|
||||
// preferences: {}
|
||||
// users: {}
|
||||
}
|
||||
|
||||
func Example_ofOptionsConfig() {
|
||||
defaultConfig := NewConfig()
|
||||
defaultConfig.Preferences.Colors = true
|
||||
defaultConfig.Clusters["alfa"] = &Cluster{
|
||||
Server: "https://alfa.org:8080",
|
||||
InsecureSkipTLSVerify: true,
|
||||
CertificateAuthority: "path/to/my/cert-ca-filename",
|
||||
}
|
||||
defaultConfig.Clusters["bravo"] = &Cluster{
|
||||
Server: "https://bravo.org:8080",
|
||||
InsecureSkipTLSVerify: false,
|
||||
}
|
||||
defaultConfig.AuthInfos["white-mage-via-cert"] = &AuthInfo{
|
||||
ClientCertificate: "path/to/my/client-cert-filename",
|
||||
ClientKey: "path/to/my/client-key-filename",
|
||||
}
|
||||
defaultConfig.AuthInfos["red-mage-via-token"] = &AuthInfo{
|
||||
Token: "my-secret-token",
|
||||
}
|
||||
defaultConfig.AuthInfos["black-mage-via-auth-provider"] = &AuthInfo{
|
||||
AuthProvider: &AuthProviderConfig{
|
||||
Name: "gcp",
|
||||
Config: map[string]string{
|
||||
"foo": "bar",
|
||||
"token": "s3cr3t-t0k3n",
|
||||
},
|
||||
},
|
||||
}
|
||||
defaultConfig.Contexts["bravo-as-black-mage"] = &Context{
|
||||
Cluster: "bravo",
|
||||
AuthInfo: "black-mage-via-auth-provider",
|
||||
Namespace: "yankee",
|
||||
}
|
||||
defaultConfig.Contexts["alfa-as-black-mage"] = &Context{
|
||||
Cluster: "alfa",
|
||||
AuthInfo: "black-mage-via-auth-provider",
|
||||
Namespace: "zulu",
|
||||
}
|
||||
defaultConfig.Contexts["alfa-as-white-mage"] = &Context{
|
||||
Cluster: "alfa",
|
||||
AuthInfo: "white-mage-via-cert",
|
||||
}
|
||||
defaultConfig.CurrentContext = "alfa-as-white-mage"
|
||||
|
||||
output, err := yaml.Marshal(defaultConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v", string(output))
|
||||
// Output:
|
||||
// clusters:
|
||||
// alfa:
|
||||
// LocationOfOrigin: ""
|
||||
// certificate-authority: path/to/my/cert-ca-filename
|
||||
// insecure-skip-tls-verify: true
|
||||
// server: https://alfa.org:8080
|
||||
// bravo:
|
||||
// LocationOfOrigin: ""
|
||||
// server: https://bravo.org:8080
|
||||
// contexts:
|
||||
// alfa-as-black-mage:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: alfa
|
||||
// namespace: zulu
|
||||
// user: black-mage-via-auth-provider
|
||||
// alfa-as-white-mage:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: alfa
|
||||
// user: white-mage-via-cert
|
||||
// bravo-as-black-mage:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: bravo
|
||||
// namespace: yankee
|
||||
// user: black-mage-via-auth-provider
|
||||
// current-context: alfa-as-white-mage
|
||||
// preferences:
|
||||
// colors: true
|
||||
// users:
|
||||
// black-mage-via-auth-provider:
|
||||
// LocationOfOrigin: ""
|
||||
// auth-provider:
|
||||
// config:
|
||||
// foo: bar
|
||||
// token: s3cr3t-t0k3n
|
||||
// name: gcp
|
||||
// red-mage-via-token:
|
||||
// LocationOfOrigin: ""
|
||||
// token: my-secret-token
|
||||
// white-mage-via-cert:
|
||||
// LocationOfOrigin: ""
|
||||
// client-certificate: path/to/my/client-cert-filename
|
||||
// client-key: path/to/my/client-key-filename
|
||||
}
|
37
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/BUILD
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"conversion.go",
|
||||
"register.go",
|
||||
"types.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/client/unversioned/clientcmd/api:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/conversion",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
227
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/conversion.go
generated
vendored
Normal file
227
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/conversion.go
generated
vendored
Normal file
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
func addConversionFuncs(scheme *runtime.Scheme) error {
|
||||
return scheme.AddConversionFuncs(
|
||||
func(in *Cluster, out *api.Cluster, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
func(in *api.Cluster, out *Cluster, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
func(in *Preferences, out *api.Preferences, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
func(in *api.Preferences, out *Preferences, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
func(in *AuthInfo, out *api.AuthInfo, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
func(in *api.AuthInfo, out *AuthInfo, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
func(in *Context, out *api.Context, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
func(in *api.Context, out *Context, s conversion.Scope) error {
|
||||
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
|
||||
},
|
||||
|
||||
func(in *Config, out *api.Config, s conversion.Scope) error {
|
||||
out.CurrentContext = in.CurrentContext
|
||||
if err := s.Convert(&in.Preferences, &out.Preferences, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Clusters = make(map[string]*api.Cluster)
|
||||
if err := s.Convert(&in.Clusters, &out.Clusters, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.AuthInfos = make(map[string]*api.AuthInfo)
|
||||
if err := s.Convert(&in.AuthInfos, &out.AuthInfos, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Contexts = make(map[string]*api.Context)
|
||||
if err := s.Convert(&in.Contexts, &out.Contexts, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Extensions = make(map[string]runtime.Object)
|
||||
if err := s.Convert(&in.Extensions, &out.Extensions, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *api.Config, out *Config, s conversion.Scope) error {
|
||||
out.CurrentContext = in.CurrentContext
|
||||
if err := s.Convert(&in.Preferences, &out.Preferences, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Clusters = make([]NamedCluster, 0, 0)
|
||||
if err := s.Convert(&in.Clusters, &out.Clusters, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.AuthInfos = make([]NamedAuthInfo, 0, 0)
|
||||
if err := s.Convert(&in.AuthInfos, &out.AuthInfos, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Contexts = make([]NamedContext, 0, 0)
|
||||
if err := s.Convert(&in.Contexts, &out.Contexts, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Extensions = make([]NamedExtension, 0, 0)
|
||||
if err := s.Convert(&in.Extensions, &out.Extensions, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedCluster, out *map[string]*api.Cluster, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newCluster := api.NewCluster()
|
||||
if err := s.Convert(&curr.Cluster, newCluster, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = newCluster
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]*api.Cluster, out *[]NamedCluster, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newCluster := (*in)[key]
|
||||
oldCluster := &Cluster{}
|
||||
if err := s.Convert(newCluster, oldCluster, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedCluster := NamedCluster{key, *oldCluster}
|
||||
*out = append(*out, namedCluster)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedAuthInfo, out *map[string]*api.AuthInfo, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newAuthInfo := api.NewAuthInfo()
|
||||
if err := s.Convert(&curr.AuthInfo, newAuthInfo, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = newAuthInfo
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]*api.AuthInfo, out *[]NamedAuthInfo, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newAuthInfo := (*in)[key]
|
||||
oldAuthInfo := &AuthInfo{}
|
||||
if err := s.Convert(newAuthInfo, oldAuthInfo, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedAuthInfo := NamedAuthInfo{key, *oldAuthInfo}
|
||||
*out = append(*out, namedAuthInfo)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedContext, out *map[string]*api.Context, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newContext := api.NewContext()
|
||||
if err := s.Convert(&curr.Context, newContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = newContext
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]*api.Context, out *[]NamedContext, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newContext := (*in)[key]
|
||||
oldContext := &Context{}
|
||||
if err := s.Convert(newContext, oldContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedContext := NamedContext{key, *oldContext}
|
||||
*out = append(*out, namedContext)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedExtension, out *map[string]runtime.Object, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
var newExtension runtime.Object
|
||||
if err := s.Convert(&curr.Extension, &newExtension, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = newExtension
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]runtime.Object, out *[]NamedExtension, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newExtension := (*in)[key]
|
||||
oldExtension := &runtime.RawExtension{}
|
||||
if err := s.Convert(newExtension, oldExtension, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedExtension := NamedExtension{key, *oldExtension}
|
||||
*out = append(*out, namedExtension)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
46
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/register.go
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/register.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
// TODO this should be in the "kubeconfig" group
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: "", Version: "v1"}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addConversionFuncs)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Config{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Config) GetObjectKind() schema.ObjectKind { return obj }
|
||||
func (obj *Config) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
||||
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
||||
}
|
||||
func (obj *Config) GroupVersionKind() schema.GroupVersionKind {
|
||||
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
||||
}
|
170
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go
generated
vendored
Normal file
170
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go
generated
vendored
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Where possible, json tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
type Config struct {
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// DEPRECATED: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
|
||||
// Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify
|
||||
// a single value for the cluster version.
|
||||
// This field isn't really needed anyway, so we are deprecating it without replacement.
|
||||
// It will be ignored if it is present.
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `json:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters []NamedCluster `json:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos []NamedAuthInfo `json:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts []NamedContext `json:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
// +optional
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
|
||||
// +optional
|
||||
APIVersion string `json:"api-version,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
// +optional
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
// +optional
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
// +optional
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||
// +optional
|
||||
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
// +optional
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||
// +optional
|
||||
ClientKeyData []byte `json:"client-key-data,omitempty"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Token string `json:"token,omitempty"`
|
||||
// TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence.
|
||||
// +optional
|
||||
TokenFile string `json:"tokenFile,omitempty"`
|
||||
// Impersonate is the username to imperonate. The name matches the flag.
|
||||
// +optional
|
||||
Impersonate string `json:"as,omitempty"`
|
||||
// Username is the username for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password is the password for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Password string `json:"password,omitempty"`
|
||||
// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `json:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// NamedCluster relates nicknames to cluster information
|
||||
type NamedCluster struct {
|
||||
// Name is the nickname for this Cluster
|
||||
Name string `json:"name"`
|
||||
// Cluster holds the cluster information
|
||||
Cluster Cluster `json:"cluster"`
|
||||
}
|
||||
|
||||
// NamedContext relates nicknames to context information
|
||||
type NamedContext struct {
|
||||
// Name is the nickname for this Context
|
||||
Name string `json:"name"`
|
||||
// Context holds the context information
|
||||
Context Context `json:"context"`
|
||||
}
|
||||
|
||||
// NamedAuthInfo relates nicknames to auth information
|
||||
type NamedAuthInfo struct {
|
||||
// Name is the nickname for this AuthInfo
|
||||
Name string `json:"name"`
|
||||
// AuthInfo holds the auth information
|
||||
AuthInfo AuthInfo `json:"user"`
|
||||
}
|
||||
|
||||
// NamedExtension relates nicknames to extension information
|
||||
type NamedExtension struct {
|
||||
// Name is the nickname for this Extension
|
||||
Name string `json:"name"`
|
||||
// Extension holds the extension information
|
||||
Extension runtime.RawExtension `json:"extension"`
|
||||
}
|
||||
|
||||
// AuthProviderConfig holds the configuration for a specified auth provider.
|
||||
type AuthProviderConfig struct {
|
||||
Name string `json:"name"`
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
106
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/auth_loaders.go
generated
vendored
Normal file
106
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/auth_loaders.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package clientcmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/howeyc/gopass"
|
||||
clientauth "k8s.io/kubernetes/pkg/client/unversioned/auth"
|
||||
)
|
||||
|
||||
// AuthLoaders are used to build clientauth.Info objects.
|
||||
type AuthLoader interface {
|
||||
// LoadAuth takes a path to a config file and can then do anything it needs in order to return a valid clientauth.Info
|
||||
LoadAuth(path string) (*clientauth.Info, error)
|
||||
}
|
||||
|
||||
// default implementation of an AuthLoader
|
||||
type defaultAuthLoader struct{}
|
||||
|
||||
// LoadAuth for defaultAuthLoader simply delegates to clientauth.LoadFromFile
|
||||
func (*defaultAuthLoader) LoadAuth(path string) (*clientauth.Info, error) {
|
||||
return clientauth.LoadFromFile(path)
|
||||
}
|
||||
|
||||
type PromptingAuthLoader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// LoadAuth parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
|
||||
func (a *PromptingAuthLoader) LoadAuth(path string) (*clientauth.Info, error) {
|
||||
// Prompt for user/pass and write a file if none exists.
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
authPtr, err := a.Prompt()
|
||||
auth := *authPtr
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := json.Marshal(auth)
|
||||
if err != nil {
|
||||
return &auth, err
|
||||
}
|
||||
err = ioutil.WriteFile(path, data, 0600)
|
||||
return &auth, err
|
||||
}
|
||||
authPtr, err := clientauth.LoadFromFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authPtr, nil
|
||||
}
|
||||
|
||||
// Prompt pulls the user and password from a reader
|
||||
func (a *PromptingAuthLoader) Prompt() (*clientauth.Info, error) {
|
||||
var err error
|
||||
auth := &clientauth.Info{}
|
||||
auth.User, err = promptForString("Username", a.reader, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth.Password, err = promptForString("Password", nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func promptForString(field string, r io.Reader, show bool) (result string, err error) {
|
||||
fmt.Printf("Please enter %s: ", field)
|
||||
if show {
|
||||
_, err = fmt.Fscan(r, &result)
|
||||
} else {
|
||||
var data []byte
|
||||
data, err = gopass.GetPasswdMasked()
|
||||
result = string(data)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// NewPromptingAuthLoader is an AuthLoader that parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
|
||||
func NewPromptingAuthLoader(reader io.Reader) *PromptingAuthLoader {
|
||||
return &PromptingAuthLoader{reader}
|
||||
}
|
||||
|
||||
// NewDefaultAuthLoader returns a default implementation of an AuthLoader that only reads from a config file
|
||||
func NewDefaultAuthLoader() AuthLoader {
|
||||
return &defaultAuthLoader{}
|
||||
}
|
537
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go
generated
vendored
Normal file
537
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go
generated
vendored
Normal file
|
@ -0,0 +1,537 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
clientauth "k8s.io/kubernetes/pkg/client/unversioned/auth"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
var (
|
||||
// ClusterDefaults has the same behavior as the old EnvVar and DefaultCluster fields
|
||||
// DEPRECATED will be replaced
|
||||
ClusterDefaults = clientcmdapi.Cluster{Server: getDefaultServer()}
|
||||
// DefaultClientConfig represents the legacy behavior of this package for defaulting
|
||||
// DEPRECATED will be replace
|
||||
DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{
|
||||
ClusterDefaults: ClusterDefaults,
|
||||
}, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
||||
)
|
||||
|
||||
// getDefaultServer returns a default setting for DefaultClientConfig
|
||||
// DEPRECATED
|
||||
func getDefaultServer() string {
|
||||
if server := os.Getenv("KUBERNETES_MASTER"); len(server) > 0 {
|
||||
return server
|
||||
}
|
||||
return "http://localhost:8080"
|
||||
}
|
||||
|
||||
// ClientConfig is used to make it easy to get an api server client
|
||||
type ClientConfig interface {
|
||||
// RawConfig returns the merged result of all overrides
|
||||
RawConfig() (clientcmdapi.Config, error)
|
||||
// ClientConfig returns a complete client config
|
||||
ClientConfig() (*restclient.Config, error)
|
||||
// Namespace returns the namespace resulting from the merged
|
||||
// result of all overrides and a boolean indicating if it was
|
||||
// overridden
|
||||
Namespace() (string, bool, error)
|
||||
// ConfigAccess returns the rules for loading/persisting the config.
|
||||
ConfigAccess() ConfigAccess
|
||||
}
|
||||
|
||||
type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
|
||||
|
||||
type promptedCredentials struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
|
||||
type DirectClientConfig struct {
|
||||
config clientcmdapi.Config
|
||||
contextName string
|
||||
overrides *ConfigOverrides
|
||||
fallbackReader io.Reader
|
||||
configAccess ConfigAccess
|
||||
// promptedCredentials store the credentials input by the user
|
||||
promptedCredentials promptedCredentials
|
||||
}
|
||||
|
||||
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
|
||||
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
|
||||
return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
||||
}
|
||||
|
||||
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
|
||||
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
|
||||
return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
|
||||
}
|
||||
|
||||
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
|
||||
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
|
||||
return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
|
||||
}
|
||||
|
||||
func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return config.config, nil
|
||||
}
|
||||
|
||||
// ClientConfig implements ClientConfig
|
||||
func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
// check that getAuthInfo, getContext, and getCluster do not return an error.
|
||||
// Do this before checking if the curent config is usable in the event that an
|
||||
// AuthInfo, Context, or Cluster config with user-defined names are not found.
|
||||
// This provides a user with the immediate cause for error if one is found
|
||||
configAuthInfo, err := config.getAuthInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = config.getContext()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configClusterInfo, err := config.getCluster()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := config.ConfirmUsable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientConfig := &restclient.Config{}
|
||||
clientConfig.Host = configClusterInfo.Server
|
||||
|
||||
if len(config.overrides.Timeout) > 0 {
|
||||
timeout, err := ParseTimeout(config.overrides.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfig.Timeout = timeout
|
||||
}
|
||||
|
||||
if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
|
||||
u.RawQuery = ""
|
||||
u.Fragment = ""
|
||||
clientConfig.Host = u.String()
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
clientConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
|
||||
}
|
||||
|
||||
// only try to read the auth information if we are secure
|
||||
if restclient.IsConfigTransportTLS(*clientConfig) {
|
||||
var err error
|
||||
|
||||
// mergo is a first write wins for map value and a last writing wins for interface values
|
||||
// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
|
||||
// Our mergo.Merge version is older than this change.
|
||||
var persister restclient.AuthProviderConfigPersister
|
||||
if config.configAccess != nil {
|
||||
authInfoName, _ := config.getAuthInfoName()
|
||||
persister = PersisterForUser(config.configAccess, authInfoName)
|
||||
}
|
||||
userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergo.Merge(clientConfig, userAuthPartialConfig)
|
||||
|
||||
serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergo.Merge(clientConfig, serverAuthPartialConfig)
|
||||
}
|
||||
|
||||
return clientConfig, nil
|
||||
}
|
||||
|
||||
// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
|
||||
// both, so we have to split the objects and merge them separately
|
||||
// we want this order of precedence for the server identification
|
||||
// 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files)
|
||||
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
||||
// 3. load the ~/.kubernetes_auth file as a default
|
||||
func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
|
||||
mergedConfig := &restclient.Config{}
|
||||
|
||||
// configClusterInfo holds the information identify the server provided by .kubeconfig
|
||||
configClientConfig := &restclient.Config{}
|
||||
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
||||
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
|
||||
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
||||
mergo.Merge(mergedConfig, configClientConfig)
|
||||
|
||||
return mergedConfig, nil
|
||||
}
|
||||
|
||||
// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
|
||||
// both, so we have to split the objects and merge them separately
|
||||
// we want this order of precedence for user identifcation
|
||||
// 1. configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
|
||||
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
||||
// 3. if there is not enough information to idenfity the user, load try the ~/.kubernetes_auth file
|
||||
// 4. if there is not enough information to identify the user, prompt if possible
|
||||
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
|
||||
mergedConfig := &restclient.Config{}
|
||||
|
||||
// blindly overwrite existing values based on precedence
|
||||
if len(configAuthInfo.Token) > 0 {
|
||||
mergedConfig.BearerToken = configAuthInfo.Token
|
||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergedConfig.BearerToken = string(tokenBytes)
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
mergedConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
|
||||
}
|
||||
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
||||
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
||||
mergedConfig.CertData = configAuthInfo.ClientCertificateData
|
||||
mergedConfig.KeyFile = configAuthInfo.ClientKey
|
||||
mergedConfig.KeyData = configAuthInfo.ClientKeyData
|
||||
}
|
||||
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
||||
mergedConfig.Username = configAuthInfo.Username
|
||||
mergedConfig.Password = configAuthInfo.Password
|
||||
}
|
||||
if configAuthInfo.AuthProvider != nil {
|
||||
mergedConfig.AuthProvider = configAuthInfo.AuthProvider
|
||||
mergedConfig.AuthConfigPersister = persistAuthConfig
|
||||
}
|
||||
|
||||
// if there still isn't enough information to authenticate the user, try prompting
|
||||
if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
|
||||
if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
|
||||
mergedConfig.Username = config.promptedCredentials.username
|
||||
mergedConfig.Password = config.promptedCredentials.password
|
||||
return mergedConfig, nil
|
||||
}
|
||||
prompter := NewPromptingAuthLoader(fallbackReader)
|
||||
promptedAuthInfo, err := prompter.Prompt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
||||
previouslyMergedConfig := mergedConfig
|
||||
mergedConfig = &restclient.Config{}
|
||||
mergo.Merge(mergedConfig, promptedConfig)
|
||||
mergo.Merge(mergedConfig, previouslyMergedConfig)
|
||||
config.promptedCredentials.username = mergedConfig.Username
|
||||
config.promptedCredentials.password = mergedConfig.Password
|
||||
}
|
||||
|
||||
return mergedConfig, nil
|
||||
}
|
||||
|
||||
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
|
||||
func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
|
||||
config := &restclient.Config{}
|
||||
config.Username = info.User
|
||||
config.Password = info.Password
|
||||
config.CertFile = info.CertFile
|
||||
config.KeyFile = info.KeyFile
|
||||
config.BearerToken = info.BearerToken
|
||||
return config
|
||||
}
|
||||
|
||||
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information
|
||||
func makeServerIdentificationConfig(info clientauth.Info) restclient.Config {
|
||||
config := restclient.Config{}
|
||||
config.CAFile = info.CAFile
|
||||
if info.Insecure != nil {
|
||||
config.Insecure = *info.Insecure
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func canIdentifyUser(config restclient.Config) bool {
|
||||
return len(config.Username) > 0 ||
|
||||
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
|
||||
len(config.BearerToken) > 0 ||
|
||||
config.AuthProvider != nil
|
||||
}
|
||||
|
||||
// Namespace implements ClientConfig
|
||||
func (config *DirectClientConfig) Namespace() (string, bool, error) {
|
||||
if err := config.ConfirmUsable(); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
configContext, err := config.getContext()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if len(configContext.Namespace) == 0 {
|
||||
return api.NamespaceDefault, false, nil
|
||||
}
|
||||
|
||||
overridden := false
|
||||
if config.overrides != nil && config.overrides.Context.Namespace != "" {
|
||||
overridden = true
|
||||
}
|
||||
return configContext.Namespace, overridden, nil
|
||||
}
|
||||
|
||||
// ConfigAccess implements ClientConfig
|
||||
func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
|
||||
return config.configAccess
|
||||
}
|
||||
|
||||
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
|
||||
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
|
||||
func (config *DirectClientConfig) ConfirmUsable() error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
var contextName string
|
||||
if len(config.contextName) != 0 {
|
||||
contextName = config.contextName
|
||||
} else {
|
||||
contextName = config.config.CurrentContext
|
||||
}
|
||||
|
||||
if len(contextName) > 0 {
|
||||
_, exists := config.config.Contexts[contextName]
|
||||
if !exists {
|
||||
validationErrors = append(validationErrors, &errContextNotFound{contextName})
|
||||
}
|
||||
}
|
||||
|
||||
authInfoName, _ := config.getAuthInfoName()
|
||||
authInfo, _ := config.getAuthInfo()
|
||||
validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
|
||||
clusterName, _ := config.getClusterName()
|
||||
cluster, _ := config.getCluster()
|
||||
validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
|
||||
// when direct client config is specified, and our only error is that no server is defined, we should
|
||||
// return a standard "no config" error
|
||||
if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
|
||||
return newErrConfigurationInvalid([]error{ErrEmptyConfig})
|
||||
}
|
||||
return newErrConfigurationInvalid(validationErrors)
|
||||
}
|
||||
|
||||
// getContextName returns the default, or user-set context name, and a boolean that indicates
|
||||
// whether the default context name has been overwritten by a user-set flag, or left as its default value
|
||||
func (config *DirectClientConfig) getContextName() (string, bool) {
|
||||
if len(config.overrides.CurrentContext) != 0 {
|
||||
return config.overrides.CurrentContext, true
|
||||
}
|
||||
if len(config.contextName) != 0 {
|
||||
return config.contextName, false
|
||||
}
|
||||
|
||||
return config.config.CurrentContext, false
|
||||
}
|
||||
|
||||
// getAuthInfoName returns a string containing the current authinfo name for the current context,
|
||||
// and a boolean indicating whether the default authInfo name is overwritten by a user-set flag, or
|
||||
// left as its default value
|
||||
func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
|
||||
if len(config.overrides.Context.AuthInfo) != 0 {
|
||||
return config.overrides.Context.AuthInfo, true
|
||||
}
|
||||
context, _ := config.getContext()
|
||||
return context.AuthInfo, false
|
||||
}
|
||||
|
||||
// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
|
||||
// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
|
||||
// its default value
|
||||
func (config *DirectClientConfig) getClusterName() (string, bool) {
|
||||
if len(config.overrides.Context.Cluster) != 0 {
|
||||
return config.overrides.Context.Cluster, true
|
||||
}
|
||||
context, _ := config.getContext()
|
||||
return context.Cluster, false
|
||||
}
|
||||
|
||||
// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
|
||||
func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
|
||||
contexts := config.config.Contexts
|
||||
contextName, required := config.getContextName()
|
||||
|
||||
var mergedContext clientcmdapi.Context
|
||||
if configContext, exists := contexts[contextName]; exists {
|
||||
mergo.Merge(&mergedContext, configContext)
|
||||
} else if required {
|
||||
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
||||
}
|
||||
mergo.Merge(&mergedContext, config.overrides.Context)
|
||||
|
||||
return mergedContext, nil
|
||||
}
|
||||
|
||||
// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
|
||||
func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
|
||||
authInfos := config.config.AuthInfos
|
||||
authInfoName, required := config.getAuthInfoName()
|
||||
|
||||
var mergedAuthInfo clientcmdapi.AuthInfo
|
||||
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
||||
mergo.Merge(&mergedAuthInfo, configAuthInfo)
|
||||
} else if required {
|
||||
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
||||
}
|
||||
mergo.Merge(&mergedAuthInfo, config.overrides.AuthInfo)
|
||||
|
||||
return mergedAuthInfo, nil
|
||||
}
|
||||
|
||||
// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
|
||||
func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
|
||||
clusterInfos := config.config.Clusters
|
||||
clusterInfoName, required := config.getClusterName()
|
||||
|
||||
var mergedClusterInfo clientcmdapi.Cluster
|
||||
mergo.Merge(&mergedClusterInfo, config.overrides.ClusterDefaults)
|
||||
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
||||
mergo.Merge(&mergedClusterInfo, configClusterInfo)
|
||||
} else if required {
|
||||
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
||||
}
|
||||
mergo.Merge(&mergedClusterInfo, config.overrides.ClusterInfo)
|
||||
// An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
|
||||
// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
|
||||
caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
|
||||
caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
|
||||
if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
|
||||
mergedClusterInfo.CertificateAuthority = ""
|
||||
mergedClusterInfo.CertificateAuthorityData = nil
|
||||
}
|
||||
|
||||
return mergedClusterInfo, nil
|
||||
}
|
||||
|
||||
// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
|
||||
// Can take options overrides for flags explicitly provided to the command inside the cluster container.
|
||||
type inClusterClientConfig struct {
|
||||
overrides *ConfigOverrides
|
||||
inClusterConfigProvider func() (*restclient.Config, error)
|
||||
}
|
||||
|
||||
var _ ClientConfig = &inClusterClientConfig{}
|
||||
|
||||
func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
|
||||
}
|
||||
|
||||
func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
if config.inClusterConfigProvider == nil {
|
||||
config.inClusterConfigProvider = restclient.InClusterConfig
|
||||
}
|
||||
|
||||
icc, err := config.inClusterConfigProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// in-cluster configs only takes a host, token, or CA file
|
||||
// if any of them were individually provided, ovewrite anything else
|
||||
if config.overrides != nil {
|
||||
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
|
||||
icc.Host = server
|
||||
}
|
||||
if token := config.overrides.AuthInfo.Token; len(token) > 0 {
|
||||
icc.BearerToken = token
|
||||
}
|
||||
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
|
||||
icc.TLSClientConfig.CAFile = certificateAuthorityFile
|
||||
}
|
||||
}
|
||||
|
||||
return icc, err
|
||||
}
|
||||
|
||||
func (config *inClusterClientConfig) Namespace() (string, bool, error) {
|
||||
// This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
|
||||
// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
|
||||
if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
|
||||
return ns, true, nil
|
||||
}
|
||||
|
||||
// Fall back to the namespace associated with the service account token, if available
|
||||
if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
|
||||
if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
|
||||
return ns, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "default", false, nil
|
||||
}
|
||||
|
||||
func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
|
||||
return NewDefaultClientConfigLoadingRules()
|
||||
}
|
||||
|
||||
// Possible returns true if loading an inside-kubernetes-cluster is possible.
|
||||
func (config *inClusterClientConfig) Possible() bool {
|
||||
fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
|
||||
return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
|
||||
os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
|
||||
err == nil && !fi.IsDir()
|
||||
}
|
||||
|
||||
// BuildConfigFromFlags is a helper function that builds configs from a master
|
||||
// url or a kubeconfig filepath. These are passed in as command line flags for cluster
|
||||
// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
|
||||
// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
|
||||
// to the default config.
|
||||
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
|
||||
if kubeconfigPath == "" && masterUrl == "" {
|
||||
glog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
|
||||
kubeconfig, err := restclient.InClusterConfig()
|
||||
if err == nil {
|
||||
return kubeconfig, nil
|
||||
}
|
||||
glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
|
||||
}
|
||||
return NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
||||
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
|
||||
}
|
||||
|
||||
// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
|
||||
// url and a kubeconfigGetter.
|
||||
func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
|
||||
// TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
|
||||
cc := NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
|
||||
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
|
||||
return cc.ClientConfig()
|
||||
}
|
506
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config_test.go
generated
vendored
Normal file
506
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config_test.go
generated
vendored
Normal file
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
func TestOldMergoLib(t *testing.T) {
|
||||
type T struct {
|
||||
X string
|
||||
}
|
||||
dst := T{X: "one"}
|
||||
src := T{X: "two"}
|
||||
mergo.Merge(&dst, &src)
|
||||
if dst.X != "two" {
|
||||
// mergo.Merge changed in an incompatible way with
|
||||
//
|
||||
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
|
||||
//
|
||||
// We have to stay with the old version which still does eager
|
||||
// copying from src to dst in structs.
|
||||
t.Errorf("mergo.Merge library found with incompatible, new behavior")
|
||||
}
|
||||
}
|
||||
|
||||
func createValidTestConfig() *clientcmdapi.Config {
|
||||
const (
|
||||
server = "https://anything.com:8080"
|
||||
token = "the-token"
|
||||
)
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: server,
|
||||
}
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
Token: token,
|
||||
}
|
||||
config.Contexts["clean"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
config.CurrentContext = "clean"
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func createCAValidTestConfig() *clientcmdapi.Config {
|
||||
|
||||
config := createValidTestConfig()
|
||||
config.Clusters["clean"].CertificateAuthorityData = []byte{0, 0}
|
||||
return config
|
||||
}
|
||||
|
||||
func TestInsecureOverridesCA(t *testing.T) {
|
||||
config := createCAValidTestConfig()
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
InsecureSkipTLSVerify: true,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
actualCfg, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
matchBoolArg(true, actualCfg.Insecure, t)
|
||||
matchStringArg("", actualCfg.TLSClientConfig.CAFile, t)
|
||||
matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t)
|
||||
}
|
||||
|
||||
func TestMergeContext(t *testing.T) {
|
||||
const namespace = "overriden-namespace"
|
||||
|
||||
config := createValidTestConfig()
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
|
||||
|
||||
_, overridden, err := clientBuilder.Namespace()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if overridden {
|
||||
t.Error("Expected namespace to not be overridden")
|
||||
}
|
||||
|
||||
clientBuilder = NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
|
||||
Context: clientcmdapi.Context{
|
||||
Namespace: namespace,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
actual, overridden, err := clientBuilder.Namespace()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !overridden {
|
||||
t.Error("Expected namespace to be overridden")
|
||||
}
|
||||
|
||||
matchStringArg(namespace, actual, t)
|
||||
}
|
||||
|
||||
func TestCertificateData(t *testing.T) {
|
||||
caData := []byte("ca-data")
|
||||
certData := []byte("cert-data")
|
||||
keyData := []byte("key-data")
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "https://localhost:8443",
|
||||
CertificateAuthorityData: caData,
|
||||
}
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
ClientCertificateData: certData,
|
||||
ClientKeyData: keyData,
|
||||
}
|
||||
config.Contexts["clean"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
config.CurrentContext = "clean"
|
||||
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Make sure cert data gets into config (will override file paths)
|
||||
matchByteArg(caData, clientConfig.TLSClientConfig.CAData, t)
|
||||
matchByteArg(certData, clientConfig.TLSClientConfig.CertData, t)
|
||||
matchByteArg(keyData, clientConfig.TLSClientConfig.KeyData, t)
|
||||
}
|
||||
|
||||
func TestBasicAuthData(t *testing.T) {
|
||||
username := "myuser"
|
||||
password := "mypass"
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "https://localhost:8443",
|
||||
}
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
config.Contexts["clean"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
config.CurrentContext = "clean"
|
||||
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Make sure basic auth data gets into config
|
||||
matchStringArg(username, clientConfig.Username, t)
|
||||
matchStringArg(password, clientConfig.Password, t)
|
||||
}
|
||||
|
||||
func TestBasicTokenFile(t *testing.T) {
|
||||
token := "exampletoken"
|
||||
f, err := ioutil.TempFile("", "tokenfile")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
if err := ioutil.WriteFile(f.Name(), []byte(token), 0644); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "https://localhost:8443",
|
||||
}
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
TokenFile: f.Name(),
|
||||
}
|
||||
config.Contexts["clean"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
config.CurrentContext = "clean"
|
||||
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
matchStringArg(token, clientConfig.BearerToken, t)
|
||||
}
|
||||
|
||||
func TestPrecedenceTokenFile(t *testing.T) {
|
||||
token := "exampletoken"
|
||||
f, err := ioutil.TempFile("", "tokenfile")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
if err := ioutil.WriteFile(f.Name(), []byte(token), 0644); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "https://localhost:8443",
|
||||
}
|
||||
expectedToken := "expected"
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
Token: expectedToken,
|
||||
TokenFile: f.Name(),
|
||||
}
|
||||
config.Contexts["clean"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
config.CurrentContext = "clean"
|
||||
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
matchStringArg(expectedToken, clientConfig.BearerToken, t)
|
||||
}
|
||||
|
||||
func TestCreateClean(t *testing.T) {
|
||||
config := createValidTestConfig()
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t)
|
||||
matchStringArg("", clientConfig.APIPath, t)
|
||||
matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t)
|
||||
matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t)
|
||||
}
|
||||
|
||||
func TestCreateCleanWithPrefix(t *testing.T) {
|
||||
tt := []struct {
|
||||
server string
|
||||
host string
|
||||
}{
|
||||
{"https://anything.com:8080/foo/bar", "https://anything.com:8080/foo/bar"},
|
||||
{"http://anything.com:8080/foo/bar", "http://anything.com:8080/foo/bar"},
|
||||
{"http://anything.com:8080/foo/bar/", "http://anything.com:8080/foo/bar/"},
|
||||
{"http://anything.com:8080/", "http://anything.com:8080/"},
|
||||
{"http://anything.com:8080//", "http://anything.com:8080//"},
|
||||
{"anything.com:8080/foo/bar", "anything.com:8080/foo/bar"},
|
||||
{"anything.com:8080", "anything.com:8080"},
|
||||
{"anything.com", "anything.com"},
|
||||
{"anything", "anything"},
|
||||
}
|
||||
|
||||
tt = append(tt, struct{ server, host string }{"", "http://localhost:8080"})
|
||||
|
||||
for _, tc := range tt {
|
||||
config := createValidTestConfig()
|
||||
|
||||
cleanConfig := config.Clusters["clean"]
|
||||
cleanConfig.Server = tc.server
|
||||
config.Clusters["clean"] = cleanConfig
|
||||
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
|
||||
ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"},
|
||||
}, nil)
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
matchStringArg(tc.host, clientConfig.Host, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCleanDefault(t *testing.T) {
|
||||
config := createValidTestConfig()
|
||||
clientBuilder := NewDefaultClientConfig(*config, &ConfigOverrides{})
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t)
|
||||
matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t)
|
||||
matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t)
|
||||
}
|
||||
|
||||
func TestCreateCleanDefaultCluster(t *testing.T) {
|
||||
config := createValidTestConfig()
|
||||
clientBuilder := NewDefaultClientConfig(*config, &ConfigOverrides{
|
||||
ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"},
|
||||
})
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t)
|
||||
matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t)
|
||||
matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t)
|
||||
}
|
||||
|
||||
func TestCreateMissingContextNoDefault(t *testing.T) {
|
||||
const expectedErrorContains = "Context was not found for specified context"
|
||||
config := createValidTestConfig()
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{}, nil)
|
||||
|
||||
_, err := clientBuilder.ClientConfig()
|
||||
if err == nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateMissingContext(t *testing.T) {
|
||||
const expectedErrorContains = "context was not found for specified context: not-present"
|
||||
config := createValidTestConfig()
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{
|
||||
ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"},
|
||||
}, nil)
|
||||
|
||||
_, err := clientBuilder.ClientConfig()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error: %v", expectedErrorContains)
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedErrorContains) {
|
||||
t.Fatalf("Expected error: %v, but got %v", expectedErrorContains, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInClusterClientConfigPrecedence(t *testing.T) {
|
||||
tt := []struct {
|
||||
overrides *ConfigOverrides
|
||||
}{
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
Server: "https://host-from-overrides.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
Token: "https://host-from-overrides.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
CertificateAuthority: "/path/to/ca-from-overrides.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
Server: "https://host-from-overrides.com",
|
||||
},
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
Token: "https://host-from-overrides.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
Server: "https://host-from-overrides.com",
|
||||
CertificateAuthority: "/path/to/ca-from-overrides.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
CertificateAuthority: "/path/to/ca-from-overrides.crt",
|
||||
},
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
Token: "https://host-from-overrides.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{
|
||||
ClusterInfo: clientcmdapi.Cluster{
|
||||
Server: "https://host-from-overrides.com",
|
||||
CertificateAuthority: "/path/to/ca-from-overrides.crt",
|
||||
},
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
Token: "https://host-from-overrides.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: &ConfigOverrides{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
expectedServer := "https://host-from-cluster.com"
|
||||
expectedToken := "token-from-cluster"
|
||||
expectedCAFile := "/path/to/ca-from-cluster.crt"
|
||||
|
||||
icc := &inClusterClientConfig{
|
||||
inClusterConfigProvider: func() (*restclient.Config, error) {
|
||||
return &restclient.Config{
|
||||
Host: expectedServer,
|
||||
BearerToken: expectedToken,
|
||||
TLSClientConfig: restclient.TLSClientConfig{
|
||||
CAFile: expectedCAFile,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
overrides: tc.overrides,
|
||||
}
|
||||
|
||||
clientConfig, err := icc.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unxpected error: %v", err)
|
||||
}
|
||||
|
||||
if overridenServer := tc.overrides.ClusterInfo.Server; len(overridenServer) > 0 {
|
||||
expectedServer = overridenServer
|
||||
}
|
||||
if overridenToken := tc.overrides.AuthInfo.Token; len(overridenToken) > 0 {
|
||||
expectedToken = overridenToken
|
||||
}
|
||||
if overridenCAFile := tc.overrides.ClusterInfo.CertificateAuthority; len(overridenCAFile) > 0 {
|
||||
expectedCAFile = overridenCAFile
|
||||
}
|
||||
|
||||
if clientConfig.Host != expectedServer {
|
||||
t.Errorf("Expected server %v, got %v", expectedServer, clientConfig.Host)
|
||||
}
|
||||
if clientConfig.BearerToken != expectedToken {
|
||||
t.Errorf("Expected token %v, got %v", expectedToken, clientConfig.BearerToken)
|
||||
}
|
||||
if clientConfig.TLSClientConfig.CAFile != expectedCAFile {
|
||||
t.Errorf("Expected Certificate Authority %v, got %v", expectedCAFile, clientConfig.TLSClientConfig.CAFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchBoolArg(expected, got bool, t *testing.T) {
|
||||
if expected != got {
|
||||
t.Errorf("Expected %v, got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func matchStringArg(expected, got string, t *testing.T) {
|
||||
if expected != got {
|
||||
t.Errorf("Expected %q, got %q", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func matchByteArg(expected, got []byte, t *testing.T) {
|
||||
if !reflect.DeepEqual(expected, got) {
|
||||
t.Errorf("Expected %v, got %v", expected, got)
|
||||
}
|
||||
}
|
472
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/config.go
generated
vendored
Normal file
472
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/config.go
generated
vendored
Normal file
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
// ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files
|
||||
type ConfigAccess interface {
|
||||
// GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config
|
||||
GetLoadingPrecedence() []string
|
||||
// GetStartingConfig returns the config that subcommands should being operating against. It may or may not be merged depending on loading rules
|
||||
GetStartingConfig() (*clientcmdapi.Config, error)
|
||||
// GetDefaultFilename returns the name of the file you should write into (create if necessary), if you're trying to create a new stanza as opposed to updating an existing one.
|
||||
GetDefaultFilename() string
|
||||
// IsExplicitFile indicates whether or not this command is interested in exactly one file. This implementation only ever does that via a flag, but implementations that handle local, global, and flags may have more
|
||||
IsExplicitFile() bool
|
||||
// GetExplicitFile returns the particular file this command is operating against. This implementation only ever has one, but implementations that handle local, global, and flags may have more
|
||||
GetExplicitFile() string
|
||||
}
|
||||
|
||||
type PathOptions struct {
|
||||
// GlobalFile is the full path to the file to load as the global (final) option
|
||||
GlobalFile string
|
||||
// EnvVar is the env var name that points to the list of kubeconfig files to load
|
||||
EnvVar string
|
||||
// ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
|
||||
ExplicitFileFlag string
|
||||
|
||||
// GlobalFileSubpath is an optional value used for displaying help
|
||||
GlobalFileSubpath string
|
||||
|
||||
LoadingRules *ClientConfigLoadingRules
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetEnvVarFiles() []string {
|
||||
if len(o.EnvVar) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
envVarValue := os.Getenv(o.EnvVar)
|
||||
if len(envVarValue) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return filepath.SplitList(envVarValue)
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetLoadingPrecedence() []string {
|
||||
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
|
||||
return envVarFiles
|
||||
}
|
||||
|
||||
return []string{o.GlobalFile}
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
|
||||
// don't mutate the original
|
||||
loadingRules := *o.LoadingRules
|
||||
loadingRules.Precedence = o.GetLoadingPrecedence()
|
||||
|
||||
clientConfig := NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &ConfigOverrides{})
|
||||
rawConfig, err := clientConfig.RawConfig()
|
||||
if os.IsNotExist(err) {
|
||||
return clientcmdapi.NewConfig(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rawConfig, nil
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetDefaultFilename() string {
|
||||
if o.IsExplicitFile() {
|
||||
return o.GetExplicitFile()
|
||||
}
|
||||
|
||||
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
|
||||
if len(envVarFiles) == 1 {
|
||||
return envVarFiles[0]
|
||||
}
|
||||
|
||||
// if any of the envvar files already exists, return it
|
||||
for _, envVarFile := range envVarFiles {
|
||||
if _, err := os.Stat(envVarFile); err == nil {
|
||||
return envVarFile
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, return the last one in the list
|
||||
return envVarFiles[len(envVarFiles)-1]
|
||||
}
|
||||
|
||||
return o.GlobalFile
|
||||
}
|
||||
|
||||
func (o *PathOptions) IsExplicitFile() bool {
|
||||
if len(o.LoadingRules.ExplicitPath) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetExplicitFile() string {
|
||||
return o.LoadingRules.ExplicitPath
|
||||
}
|
||||
|
||||
func NewDefaultPathOptions() *PathOptions {
|
||||
ret := &PathOptions{
|
||||
GlobalFile: RecommendedHomeFile,
|
||||
EnvVar: RecommendedConfigPathEnvVar,
|
||||
ExplicitFileFlag: RecommendedConfigPathFlag,
|
||||
|
||||
GlobalFileSubpath: path.Join(RecommendedHomeDir, RecommendedFileName),
|
||||
|
||||
LoadingRules: NewDefaultClientConfigLoadingRules(),
|
||||
}
|
||||
ret.LoadingRules.DoNotResolvePaths = true
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
|
||||
// uses the default destination file to write the results into. This results in multiple file reads, but it's very easy to follow.
|
||||
// Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values
|
||||
// (no nil strings), we're forced have separate handling for them. In the kubeconfig cases, newConfig should have at most one difference,
|
||||
// that means that this code will only write into a single file. If you want to relativizePaths, you must provide a fully qualified path in any
|
||||
// modified element.
|
||||
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
|
||||
possibleSources := configAccess.GetLoadingPrecedence()
|
||||
// sort the possible kubeconfig files so we always "lock" in the same order
|
||||
// to avoid deadlock (note: this can fail w/ symlinks, but... come on).
|
||||
sort.Strings(possibleSources)
|
||||
for _, filename := range possibleSources {
|
||||
if err := lockFile(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
defer unlockFile(filename)
|
||||
}
|
||||
|
||||
startingConfig, err := configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We need to find all differences, locate their original files, read a partial config to modify only that stanza and write out the file.
|
||||
// Special case the test for current context and preferences since those always write to the default file.
|
||||
if reflect.DeepEqual(*startingConfig, newConfig) {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
if startingConfig.CurrentContext != newConfig.CurrentContext {
|
||||
if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences) {
|
||||
if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Search every cluster, authInfo, and context. First from new to old for differences, then from old to new for deletions
|
||||
for key, cluster := range newConfig.Clusters {
|
||||
startingCluster, exists := startingConfig.Clusters[key]
|
||||
if !reflect.DeepEqual(cluster, startingCluster) || !exists {
|
||||
destinationFile := cluster.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := *cluster
|
||||
|
||||
configToWrite.Clusters[key] = &t
|
||||
configToWrite.Clusters[key].LocationOfOrigin = destinationFile
|
||||
if relativizePaths {
|
||||
if err := RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, context := range newConfig.Contexts {
|
||||
startingContext, exists := startingConfig.Contexts[key]
|
||||
if !reflect.DeepEqual(context, startingContext) || !exists {
|
||||
destinationFile := context.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configToWrite.Contexts[key] = context
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, authInfo := range newConfig.AuthInfos {
|
||||
startingAuthInfo, exists := startingConfig.AuthInfos[key]
|
||||
if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
|
||||
destinationFile := authInfo.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := *authInfo
|
||||
configToWrite.AuthInfos[key] = &t
|
||||
configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
|
||||
if relativizePaths {
|
||||
if err := RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, cluster := range startingConfig.Clusters {
|
||||
if _, exists := newConfig.Clusters[key]; !exists {
|
||||
destinationFile := cluster.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.Clusters, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, context := range startingConfig.Contexts {
|
||||
if _, exists := newConfig.Contexts[key]; !exists {
|
||||
destinationFile := context.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.Contexts, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, authInfo := range startingConfig.AuthInfos {
|
||||
if _, exists := newConfig.AuthInfos[key]; !exists {
|
||||
destinationFile := authInfo.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.AuthInfos, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PersisterForUser(configAccess ConfigAccess, user string) restclient.AuthProviderConfigPersister {
|
||||
return &persister{configAccess, user}
|
||||
}
|
||||
|
||||
type persister struct {
|
||||
configAccess ConfigAccess
|
||||
user string
|
||||
}
|
||||
|
||||
func (p *persister) Persist(config map[string]string) error {
|
||||
newConfig, err := p.configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authInfo, ok := newConfig.AuthInfos[p.user]
|
||||
if ok && authInfo.AuthProvider != nil {
|
||||
authInfo.AuthProvider.Config = config
|
||||
ModifyConfig(p.configAccess, *newConfig, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeCurrentContext takes three possible paths.
|
||||
// If newCurrentContext is the same as the startingConfig's current context, then we exit.
|
||||
// If newCurrentContext has a value, then that value is written into the default destination file.
|
||||
// If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
|
||||
func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
|
||||
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
|
||||
return err
|
||||
} else if startingConfig.CurrentContext == newCurrentContext {
|
||||
return nil
|
||||
}
|
||||
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(newCurrentContext) > 0 {
|
||||
destinationFile := configAccess.GetDefaultFilename()
|
||||
config, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.CurrentContext = newCurrentContext
|
||||
|
||||
if err := WriteToFile(*config, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// we're supposed to be clearing the current context. We need to find the first spot in the chain that is setting it and clear it
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(currConfig.CurrentContext) > 0 {
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("no config found to write context")
|
||||
}
|
||||
|
||||
func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
|
||||
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
|
||||
return err
|
||||
} else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currConfig.Preferences = newPrefs
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
|
||||
currConfig.Preferences = newPrefs
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("no config found to write preferences")
|
||||
}
|
||||
|
||||
// getConfigFromFile tries to read a kubeconfig file and if it can't, returns an error. One exception, missing files result in empty configs, not an error.
|
||||
func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
|
||||
config, err := LoadFromFile(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
config = clientcmdapi.NewConfig()
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit. One exception, missing files result in empty configs, not an exit
|
||||
func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
|
||||
config, err := getConfigFromFile(filename)
|
||||
if err != nil {
|
||||
glog.FatalDepth(1, err)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
37
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/doc.go
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
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 clientcmd provides one stop shopping for building a working client from a fixed config,
|
||||
from a .kubeconfig file, from command line flags, or from any merged combination.
|
||||
|
||||
Sample usage from merged .kubeconfig files (local directory, home directory)
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
// if you want to change the loading rules (which files in which order), you can do so here
|
||||
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
// if you want to change override values or bind them to flags, there are methods to help you
|
||||
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err := kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
// Do something
|
||||
}
|
||||
client, err := metav1.New(config)
|
||||
// ...
|
||||
*/
|
||||
package clientcmd // import "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
35
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/helpers.go
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ParseTimeout returns a parsed duration from a string
|
||||
// A duration string value must be a positive integer, optionally followed by a corresponding time unit (s|m|h).
|
||||
func ParseTimeout(duration string) (time.Duration, error) {
|
||||
if i, err := strconv.ParseInt(duration, 10, 64); err == nil && i >= 0 {
|
||||
return (time.Duration(i) * time.Second), nil
|
||||
}
|
||||
if requestTimeout, err := time.ParseDuration(duration); err == nil {
|
||||
return requestTimeout, nil
|
||||
}
|
||||
return 0, fmt.Errorf("Invalid timeout value. Timeout must be a single integer in seconds, or an integer followed by a corresponding time unit (e.g. 1s | 2m | 3h)")
|
||||
}
|
609
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go
generated
vendored
Normal file
609
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go
generated
vendored
Normal file
|
@ -0,0 +1,609 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
clientcmdlatest "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest"
|
||||
"k8s.io/kubernetes/pkg/util/homedir"
|
||||
)
|
||||
|
||||
const (
|
||||
RecommendedConfigPathFlag = "kubeconfig"
|
||||
RecommendedConfigPathEnvVar = "KUBECONFIG"
|
||||
RecommendedHomeDir = ".kube"
|
||||
RecommendedFileName = "config"
|
||||
RecommendedSchemaName = "schema"
|
||||
)
|
||||
|
||||
var RecommendedHomeFile = path.Join(homedir.HomeDir(), RecommendedHomeDir, RecommendedFileName)
|
||||
var RecommendedSchemaFile = path.Join(homedir.HomeDir(), RecommendedHomeDir, RecommendedSchemaName)
|
||||
|
||||
// currentMigrationRules returns a map that holds the history of recommended home directories used in previous versions.
|
||||
// Any future changes to RecommendedHomeFile and related are expected to add a migration rule here, in order to make
|
||||
// sure existing config files are migrated to their new locations properly.
|
||||
func currentMigrationRules() map[string]string {
|
||||
oldRecommendedHomeFile := path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig")
|
||||
oldRecommendedWindowsHomeFile := path.Join(os.Getenv("HOME"), RecommendedHomeDir, RecommendedFileName)
|
||||
|
||||
migrationRules := map[string]string{}
|
||||
migrationRules[RecommendedHomeFile] = oldRecommendedHomeFile
|
||||
if goruntime.GOOS == "windows" {
|
||||
migrationRules[RecommendedHomeFile] = oldRecommendedWindowsHomeFile
|
||||
}
|
||||
return migrationRules
|
||||
}
|
||||
|
||||
type ClientConfigLoader interface {
|
||||
ConfigAccess
|
||||
// IsDefaultConfig returns true if the returned config matches the defaults.
|
||||
IsDefaultConfig(*restclient.Config) bool
|
||||
// Load returns the latest config
|
||||
Load() (*clientcmdapi.Config, error)
|
||||
}
|
||||
|
||||
type KubeconfigGetter func() (*clientcmdapi.Config, error)
|
||||
|
||||
type ClientConfigGetter struct {
|
||||
kubeconfigGetter KubeconfigGetter
|
||||
}
|
||||
|
||||
// ClientConfigGetter implements the ClientConfigLoader interface.
|
||||
var _ ClientConfigLoader = &ClientConfigGetter{}
|
||||
|
||||
func (g *ClientConfigGetter) Load() (*clientcmdapi.Config, error) {
|
||||
return g.kubeconfigGetter()
|
||||
}
|
||||
|
||||
func (g *ClientConfigGetter) GetLoadingPrecedence() []string {
|
||||
return nil
|
||||
}
|
||||
func (g *ClientConfigGetter) GetStartingConfig() (*clientcmdapi.Config, error) {
|
||||
return g.kubeconfigGetter()
|
||||
}
|
||||
func (g *ClientConfigGetter) GetDefaultFilename() string {
|
||||
return ""
|
||||
}
|
||||
func (g *ClientConfigGetter) IsExplicitFile() bool {
|
||||
return false
|
||||
}
|
||||
func (g *ClientConfigGetter) GetExplicitFile() string {
|
||||
return ""
|
||||
}
|
||||
func (g *ClientConfigGetter) IsDefaultConfig(config *restclient.Config) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ClientConfigLoadingRules is an ExplicitPath and string slice of specific locations that are used for merging together a Config
|
||||
// Callers can put the chain together however they want, but we'd recommend:
|
||||
// EnvVarPathFiles if set (a list of files if set) OR the HomeDirectoryPath
|
||||
// ExplicitPath is special, because if a user specifically requests a certain file be used and error is reported if thie file is not present
|
||||
type ClientConfigLoadingRules struct {
|
||||
ExplicitPath string
|
||||
Precedence []string
|
||||
|
||||
// MigrationRules is a map of destination files to source files. If a destination file is not present, then the source file is checked.
|
||||
// If the source file is present, then it is copied to the destination file BEFORE any further loading happens.
|
||||
MigrationRules map[string]string
|
||||
|
||||
// DoNotResolvePaths indicates whether or not to resolve paths with respect to the originating files. This is phrased as a negative so
|
||||
// that a default object that doesn't set this will usually get the behavior it wants.
|
||||
DoNotResolvePaths bool
|
||||
|
||||
// DefaultClientConfig is an optional field indicating what rules to use to calculate a default configuration.
|
||||
// This should match the overrides passed in to ClientConfig loader.
|
||||
DefaultClientConfig ClientConfig
|
||||
}
|
||||
|
||||
// ClientConfigLoadingRules implements the ClientConfigLoader interface.
|
||||
var _ ClientConfigLoader = &ClientConfigLoadingRules{}
|
||||
|
||||
// NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to
|
||||
// use this constructor
|
||||
func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules {
|
||||
chain := []string{}
|
||||
|
||||
envVarFiles := os.Getenv(RecommendedConfigPathEnvVar)
|
||||
if len(envVarFiles) != 0 {
|
||||
chain = append(chain, filepath.SplitList(envVarFiles)...)
|
||||
|
||||
} else {
|
||||
chain = append(chain, RecommendedHomeFile)
|
||||
}
|
||||
|
||||
return &ClientConfigLoadingRules{
|
||||
Precedence: chain,
|
||||
MigrationRules: currentMigrationRules(),
|
||||
}
|
||||
}
|
||||
|
||||
// Load starts by running the MigrationRules and then
|
||||
// takes the loading rules and returns a Config object based on following rules.
|
||||
// if the ExplicitPath, return the unmerged explicit file
|
||||
// Otherwise, return a merged config based on the Precedence slice
|
||||
// A missing ExplicitPath file produces an error. Empty filenames or other missing files are ignored.
|
||||
// Read errors or files with non-deserializable content produce errors.
|
||||
// The first file to set a particular map key wins and map key's value is never changed.
|
||||
// BUT, if you set a struct value that is NOT contained inside of map, the value WILL be changed.
|
||||
// This results in some odd looking logic to merge in one direction, merge in the other, and then merge the two.
|
||||
// It also means that if two files specify a "red-user", only values from the first file's red-user are used. Even
|
||||
// non-conflicting entries from the second file's "red-user" are discarded.
|
||||
// Relative paths inside of the .kubeconfig files are resolved against the .kubeconfig file's parent folder
|
||||
// and only absolute file paths are returned.
|
||||
func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
if err := rules.Migrate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errlist := []error{}
|
||||
|
||||
kubeConfigFiles := []string{}
|
||||
|
||||
// Make sure a file we were explicitly told to use exists
|
||||
if len(rules.ExplicitPath) > 0 {
|
||||
if _, err := os.Stat(rules.ExplicitPath); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
kubeConfigFiles = append(kubeConfigFiles, rules.ExplicitPath)
|
||||
|
||||
} else {
|
||||
kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...)
|
||||
}
|
||||
|
||||
kubeconfigs := []*clientcmdapi.Config{}
|
||||
// read and cache the config files so that we only look at them once
|
||||
for _, filename := range kubeConfigFiles {
|
||||
if len(filename) == 0 {
|
||||
// no work to do
|
||||
continue
|
||||
}
|
||||
|
||||
config, err := LoadFromFile(filename)
|
||||
if os.IsNotExist(err) {
|
||||
// skip missing files
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
errlist = append(errlist, fmt.Errorf("Error loading config file \"%s\": %v", filename, err))
|
||||
continue
|
||||
}
|
||||
|
||||
kubeconfigs = append(kubeconfigs, config)
|
||||
}
|
||||
|
||||
// first merge all of our maps
|
||||
mapConfig := clientcmdapi.NewConfig()
|
||||
|
||||
for _, kubeconfig := range kubeconfigs {
|
||||
mergo.Merge(mapConfig, kubeconfig)
|
||||
}
|
||||
|
||||
// merge all of the struct values in the reverse order so that priority is given correctly
|
||||
// errors are not added to the list the second time
|
||||
nonMapConfig := clientcmdapi.NewConfig()
|
||||
for i := len(kubeconfigs) - 1; i >= 0; i-- {
|
||||
kubeconfig := kubeconfigs[i]
|
||||
mergo.Merge(nonMapConfig, kubeconfig)
|
||||
}
|
||||
|
||||
// since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
|
||||
// get the values we expect.
|
||||
config := clientcmdapi.NewConfig()
|
||||
mergo.Merge(config, mapConfig)
|
||||
mergo.Merge(config, nonMapConfig)
|
||||
|
||||
if rules.ResolvePaths() {
|
||||
if err := ResolveLocalPaths(config); err != nil {
|
||||
errlist = append(errlist, err)
|
||||
}
|
||||
}
|
||||
return config, utilerrors.NewAggregate(errlist)
|
||||
}
|
||||
|
||||
// Migrate uses the MigrationRules map. If a destination file is not present, then the source file is checked.
|
||||
// If the source file is present, then it is copied to the destination file BEFORE any further loading happens.
|
||||
func (rules *ClientConfigLoadingRules) Migrate() error {
|
||||
if rules.MigrationRules == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for destination, source := range rules.MigrationRules {
|
||||
if _, err := os.Stat(destination); err == nil {
|
||||
// if the destination already exists, do nothing
|
||||
continue
|
||||
} else if os.IsPermission(err) {
|
||||
// if we can't access the file, skip it
|
||||
continue
|
||||
} else if !os.IsNotExist(err) {
|
||||
// if we had an error other than non-existence, fail
|
||||
return err
|
||||
}
|
||||
|
||||
if sourceInfo, err := os.Stat(source); err != nil {
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
// if the source file doesn't exist or we can't access it, there's no work to do.
|
||||
continue
|
||||
}
|
||||
|
||||
// if we had an error other than non-existence, fail
|
||||
return err
|
||||
} else if sourceInfo.IsDir() {
|
||||
return fmt.Errorf("cannot migrate %v to %v because it is a directory", source, destination)
|
||||
}
|
||||
|
||||
in, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
out, err := os.Create(destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
if _, err = io.Copy(out, in); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLoadingPrecedence implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetLoadingPrecedence() []string {
|
||||
return rules.Precedence
|
||||
}
|
||||
|
||||
// GetStartingConfig implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetStartingConfig() (*clientcmdapi.Config, error) {
|
||||
clientConfig := NewNonInteractiveDeferredLoadingClientConfig(rules, &ConfigOverrides{})
|
||||
rawConfig, err := clientConfig.RawConfig()
|
||||
if os.IsNotExist(err) {
|
||||
return clientcmdapi.NewConfig(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rawConfig, nil
|
||||
}
|
||||
|
||||
// GetDefaultFilename implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetDefaultFilename() string {
|
||||
// Explicit file if we have one.
|
||||
if rules.IsExplicitFile() {
|
||||
return rules.GetExplicitFile()
|
||||
}
|
||||
// Otherwise, first existing file from precedence.
|
||||
for _, filename := range rules.GetLoadingPrecedence() {
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
// If none exists, use the first from precedence.
|
||||
if len(rules.Precedence) > 0 {
|
||||
return rules.Precedence[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsExplicitFile implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) IsExplicitFile() bool {
|
||||
return len(rules.ExplicitPath) > 0
|
||||
}
|
||||
|
||||
// GetExplicitFile implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetExplicitFile() string {
|
||||
return rules.ExplicitPath
|
||||
}
|
||||
|
||||
// IsDefaultConfig returns true if the provided configuration matches the default
|
||||
func (rules *ClientConfigLoadingRules) IsDefaultConfig(config *restclient.Config) bool {
|
||||
if rules.DefaultClientConfig == nil {
|
||||
return false
|
||||
}
|
||||
defaultConfig, err := rules.DefaultClientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(config, defaultConfig)
|
||||
}
|
||||
|
||||
// LoadFromFile takes a filename and deserializes the contents into Config object
|
||||
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
|
||||
kubeconfigBytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := Load(kubeconfigBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
glog.V(6).Infoln("Config loaded from file", filename)
|
||||
|
||||
// set LocationOfOrigin on every Cluster, User, and Context
|
||||
for key, obj := range config.AuthInfos {
|
||||
obj.LocationOfOrigin = filename
|
||||
config.AuthInfos[key] = obj
|
||||
}
|
||||
for key, obj := range config.Clusters {
|
||||
obj.LocationOfOrigin = filename
|
||||
config.Clusters[key] = obj
|
||||
}
|
||||
for key, obj := range config.Contexts {
|
||||
obj.LocationOfOrigin = filename
|
||||
config.Contexts[key] = obj
|
||||
}
|
||||
|
||||
if config.AuthInfos == nil {
|
||||
config.AuthInfos = map[string]*clientcmdapi.AuthInfo{}
|
||||
}
|
||||
if config.Clusters == nil {
|
||||
config.Clusters = map[string]*clientcmdapi.Cluster{}
|
||||
}
|
||||
if config.Contexts == nil {
|
||||
config.Contexts = map[string]*clientcmdapi.Context{}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Load takes a byte slice and deserializes the contents into Config object.
|
||||
// Encapsulates deserialization without assuming the source is a file.
|
||||
func Load(data []byte) (*clientcmdapi.Config, error) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
// if there's no data in a file, return the default object instead of failing (DecodeInto reject empty input)
|
||||
if len(data) == 0 {
|
||||
return config, nil
|
||||
}
|
||||
decoded, _, err := clientcmdlatest.Codec.Decode(data, &schema.GroupVersionKind{Version: clientcmdlatest.Version, Kind: "Config"}, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decoded.(*clientcmdapi.Config), nil
|
||||
}
|
||||
|
||||
// WriteToFile serializes the config to yaml and writes it out to a file. If not present, it creates the file with the mode 0600. If it is present
|
||||
// it stomps the contents
|
||||
func WriteToFile(config clientcmdapi.Config, filename string) error {
|
||||
content, err := Write(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(filename)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filename, content, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lockFile(filename string) error {
|
||||
// TODO: find a way to do this with actual file locks. Will
|
||||
// probably need seperate solution for windows and linux.
|
||||
|
||||
// Make sure the dir exists before we try to create a lock file.
|
||||
dir := filepath.Dir(filename)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
f, err := os.OpenFile(lockName(filename), os.O_CREATE|os.O_EXCL, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func unlockFile(filename string) error {
|
||||
return os.Remove(lockName(filename))
|
||||
}
|
||||
|
||||
func lockName(filename string) string {
|
||||
return filename + ".lock"
|
||||
}
|
||||
|
||||
// Write serializes the config to yaml.
|
||||
// Encapsulates serialization without assuming the destination is a file.
|
||||
func Write(config clientcmdapi.Config) ([]byte, error) {
|
||||
return runtime.Encode(clientcmdlatest.Codec, &config)
|
||||
}
|
||||
|
||||
func (rules ClientConfigLoadingRules) ResolvePaths() bool {
|
||||
return !rules.DoNotResolvePaths
|
||||
}
|
||||
|
||||
// ResolveLocalPaths resolves all relative paths in the config object with respect to the stanza's LocationOfOrigin
|
||||
// this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without
|
||||
// modification of its contents.
|
||||
func ResolveLocalPaths(config *clientcmdapi.Config) error {
|
||||
for _, cluster := range config.Clusters {
|
||||
if len(cluster.LocationOfOrigin) == 0 {
|
||||
continue
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, authInfo := range config.AuthInfos {
|
||||
if len(authInfo.LocationOfOrigin) == 0 {
|
||||
continue
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelativizeClusterLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already
|
||||
// absolute, but any existing path will be resolved relative to LocationOfOrigin
|
||||
func RelativizeClusterLocalPaths(cluster *clientcmdapi.Cluster) error {
|
||||
if len(cluster.LocationOfOrigin) == 0 {
|
||||
return fmt.Errorf("no location of origin for %s", cluster.Server)
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := RelativizePathWithNoBacksteps(GetClusterFileReferences(cluster), base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelativizeAuthInfoLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already
|
||||
// absolute, but any existing path will be resolved relative to LocationOfOrigin
|
||||
func RelativizeAuthInfoLocalPaths(authInfo *clientcmdapi.AuthInfo) error {
|
||||
if len(authInfo.LocationOfOrigin) == 0 {
|
||||
return fmt.Errorf("no location of origin for %v", authInfo)
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := RelativizePathWithNoBacksteps(GetAuthInfoFileReferences(authInfo), base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RelativizeConfigPaths(config *clientcmdapi.Config, base string) error {
|
||||
return RelativizePathWithNoBacksteps(GetConfigFileReferences(config), base)
|
||||
}
|
||||
|
||||
func ResolveConfigPaths(config *clientcmdapi.Config, base string) error {
|
||||
return ResolvePaths(GetConfigFileReferences(config), base)
|
||||
}
|
||||
|
||||
func GetConfigFileReferences(config *clientcmdapi.Config) []*string {
|
||||
refs := []*string{}
|
||||
|
||||
for _, cluster := range config.Clusters {
|
||||
refs = append(refs, GetClusterFileReferences(cluster)...)
|
||||
}
|
||||
for _, authInfo := range config.AuthInfos {
|
||||
refs = append(refs, GetAuthInfoFileReferences(authInfo)...)
|
||||
}
|
||||
|
||||
return refs
|
||||
}
|
||||
|
||||
func GetClusterFileReferences(cluster *clientcmdapi.Cluster) []*string {
|
||||
return []*string{&cluster.CertificateAuthority}
|
||||
}
|
||||
|
||||
func GetAuthInfoFileReferences(authInfo *clientcmdapi.AuthInfo) []*string {
|
||||
return []*string{&authInfo.ClientCertificate, &authInfo.ClientKey, &authInfo.TokenFile}
|
||||
}
|
||||
|
||||
// ResolvePaths updates the given refs to be absolute paths, relative to the given base directory
|
||||
func ResolvePaths(refs []*string, base string) error {
|
||||
for _, ref := range refs {
|
||||
// Don't resolve empty paths
|
||||
if len(*ref) > 0 {
|
||||
// Don't resolve absolute paths
|
||||
if !filepath.IsAbs(*ref) {
|
||||
*ref = filepath.Join(base, *ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelativizePathWithNoBacksteps updates the given refs to be relative paths, relative to the given base directory as long as they do not require backsteps.
|
||||
// Any path requiring a backstep is left as-is as long it is absolute. Any non-absolute path that can't be relativized produces an error
|
||||
func RelativizePathWithNoBacksteps(refs []*string, base string) error {
|
||||
for _, ref := range refs {
|
||||
// Don't relativize empty paths
|
||||
if len(*ref) > 0 {
|
||||
rel, err := MakeRelative(*ref, base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we have a backstep, don't mess with the path
|
||||
if strings.HasPrefix(rel, "../") {
|
||||
if filepath.IsAbs(*ref) {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v requires backsteps and is not absolute", *ref)
|
||||
}
|
||||
|
||||
*ref = rel
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MakeRelative(path, base string) (string, error) {
|
||||
if len(path) > 0 {
|
||||
rel, err := filepath.Rel(base, path)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
return path, nil
|
||||
}
|
578
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader_test.go
generated
vendored
Normal file
578
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader_test.go
generated
vendored
Normal file
|
@ -0,0 +1,578 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
clientcmdlatest "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest"
|
||||
)
|
||||
|
||||
var (
|
||||
testConfigAlfa = 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", Namespace: "hammer-ns"}},
|
||||
}
|
||||
testConfigBravo = clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"black-user": {Token: "black-token"}},
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"pig-cluster": {Server: "http://pig.org:8080"}},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"queen-anne-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}},
|
||||
}
|
||||
testConfigCharlie = clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"green-user": {Token: "green-token"}},
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"horse-cluster": {Server: "http://horse.org:8080"}},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"shaker-context": {AuthInfo: "green-user", Cluster: "horse-cluster", Namespace: "chisel-ns"}},
|
||||
}
|
||||
testConfigDelta = clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"blue-user": {Token: "blue-token"}},
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"chicken-cluster": {Server: "http://chicken.org:8080"}},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"gothic-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster", Namespace: "plane-ns"}},
|
||||
}
|
||||
|
||||
testConfigConflictAlfa = clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"red-user": {Token: "a-different-red-token"},
|
||||
"yellow-user": {Token: "yellow-token"}},
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"cow-cluster": {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true},
|
||||
"donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}},
|
||||
CurrentContext: "federal-context",
|
||||
}
|
||||
)
|
||||
|
||||
func TestNonExistentCommandLineFile(t *testing.T) {
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
ExplicitPath: "bogus_file",
|
||||
}
|
||||
|
||||
_, err := loadingRules.Load()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for missing command-line file, got none")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bogus_file") {
|
||||
t.Fatalf("Expected error about 'bogus_file', got %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestToleratingMissingFiles(t *testing.T) {
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
Precedence: []string{"bogus1", "bogus2", "bogus3"},
|
||||
}
|
||||
|
||||
_, err := loadingRules.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorReadingFile(t *testing.T) {
|
||||
commandLineFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
|
||||
if err := ioutil.WriteFile(commandLineFile.Name(), []byte("bogus value"), 0644); err != nil {
|
||||
t.Fatalf("Error creating tempfile: %v", err)
|
||||
}
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
ExplicitPath: commandLineFile.Name(),
|
||||
}
|
||||
|
||||
_, err := loadingRules.Load()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for unloadable file, got none")
|
||||
}
|
||||
if !strings.Contains(err.Error(), commandLineFile.Name()) {
|
||||
t.Fatalf("Expected error about '%s', got %s", commandLineFile.Name(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorReadingNonFile(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.Remove(tmpdir)
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
ExplicitPath: tmpdir,
|
||||
}
|
||||
|
||||
_, err = loadingRules.Load()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for non-file, got none")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tmpdir) {
|
||||
t.Fatalf("Expected error about '%s', got %s", tmpdir, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestConflictingCurrentContext(t *testing.T) {
|
||||
commandLineFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
|
||||
mockCommandLineConfig := clientcmdapi.Config{
|
||||
CurrentContext: "any-context-value",
|
||||
}
|
||||
mockEnvVarConfig := clientcmdapi.Config{
|
||||
CurrentContext: "a-different-context",
|
||||
}
|
||||
|
||||
WriteToFile(mockCommandLineConfig, commandLineFile.Name())
|
||||
WriteToFile(mockEnvVarConfig, envVarFile.Name())
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
ExplicitPath: commandLineFile.Name(),
|
||||
Precedence: []string{envVarFile.Name()},
|
||||
}
|
||||
|
||||
mergedConfig, err := loadingRules.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if mergedConfig.CurrentContext != mockCommandLineConfig.CurrentContext {
|
||||
t.Errorf("expected %v, got %v", mockCommandLineConfig.CurrentContext, mergedConfig.CurrentContext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadingEmptyMaps(t *testing.T) {
|
||||
configFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(configFile.Name())
|
||||
|
||||
mockConfig := clientcmdapi.Config{
|
||||
CurrentContext: "any-context-value",
|
||||
}
|
||||
|
||||
WriteToFile(mockConfig, configFile.Name())
|
||||
|
||||
config, err := LoadFromFile(configFile.Name())
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if config.Clusters == nil {
|
||||
t.Error("expected config.Clusters to be non-nil")
|
||||
}
|
||||
if config.AuthInfos == nil {
|
||||
t.Error("expected config.AuthInfos to be non-nil")
|
||||
}
|
||||
if config.Contexts == nil {
|
||||
t.Error("expected config.Contexts to be non-nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveRelativePaths(t *testing.T) {
|
||||
pathResolutionConfig1 := clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key"},
|
||||
"absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key"},
|
||||
},
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"relative-server-1": {CertificateAuthority: "../relative/ca"},
|
||||
"absolute-server-1": {CertificateAuthority: "/absolute/ca"},
|
||||
},
|
||||
}
|
||||
pathResolutionConfig2 := clientcmdapi.Config{
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"relative-user-2": {ClientCertificate: "relative/client/cert2", ClientKey: "../relative/client/key2"},
|
||||
"absolute-user-2": {ClientCertificate: "/absolute/client/cert2", ClientKey: "/absolute/client/key2"},
|
||||
},
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"relative-server-2": {CertificateAuthority: "../relative/ca2"},
|
||||
"absolute-server-2": {CertificateAuthority: "/absolute/ca2"},
|
||||
},
|
||||
}
|
||||
|
||||
configDir1, _ := ioutil.TempDir("", "")
|
||||
configFile1 := path.Join(configDir1, ".kubeconfig")
|
||||
configDir1, _ = filepath.Abs(configDir1)
|
||||
defer os.Remove(configFile1)
|
||||
configDir2, _ := ioutil.TempDir("", "")
|
||||
configDir2, _ = ioutil.TempDir(configDir2, "")
|
||||
configFile2 := path.Join(configDir2, ".kubeconfig")
|
||||
configDir2, _ = filepath.Abs(configDir2)
|
||||
defer os.Remove(configFile2)
|
||||
|
||||
WriteToFile(pathResolutionConfig1, configFile1)
|
||||
WriteToFile(pathResolutionConfig2, configFile2)
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
Precedence: []string{configFile1, configFile2},
|
||||
}
|
||||
|
||||
mergedConfig, err := loadingRules.Load()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
foundClusterCount := 0
|
||||
for key, cluster := range mergedConfig.Clusters {
|
||||
if key == "relative-server-1" {
|
||||
foundClusterCount++
|
||||
matchStringArg(path.Join(configDir1, pathResolutionConfig1.Clusters["relative-server-1"].CertificateAuthority), cluster.CertificateAuthority, t)
|
||||
}
|
||||
if key == "relative-server-2" {
|
||||
foundClusterCount++
|
||||
matchStringArg(path.Join(configDir2, pathResolutionConfig2.Clusters["relative-server-2"].CertificateAuthority), cluster.CertificateAuthority, t)
|
||||
}
|
||||
if key == "absolute-server-1" {
|
||||
foundClusterCount++
|
||||
matchStringArg(pathResolutionConfig1.Clusters["absolute-server-1"].CertificateAuthority, cluster.CertificateAuthority, t)
|
||||
}
|
||||
if key == "absolute-server-2" {
|
||||
foundClusterCount++
|
||||
matchStringArg(pathResolutionConfig2.Clusters["absolute-server-2"].CertificateAuthority, cluster.CertificateAuthority, t)
|
||||
}
|
||||
}
|
||||
if foundClusterCount != 4 {
|
||||
t.Errorf("Expected 4 clusters, found %v: %v", foundClusterCount, mergedConfig.Clusters)
|
||||
}
|
||||
|
||||
foundAuthInfoCount := 0
|
||||
for key, authInfo := range mergedConfig.AuthInfos {
|
||||
if key == "relative-user-1" {
|
||||
foundAuthInfoCount++
|
||||
matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientCertificate), authInfo.ClientCertificate, t)
|
||||
matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientKey), authInfo.ClientKey, t)
|
||||
}
|
||||
if key == "relative-user-2" {
|
||||
foundAuthInfoCount++
|
||||
matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientCertificate), authInfo.ClientCertificate, t)
|
||||
matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientKey), authInfo.ClientKey, t)
|
||||
}
|
||||
if key == "absolute-user-1" {
|
||||
foundAuthInfoCount++
|
||||
matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientCertificate, authInfo.ClientCertificate, t)
|
||||
matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientKey, authInfo.ClientKey, t)
|
||||
}
|
||||
if key == "absolute-user-2" {
|
||||
foundAuthInfoCount++
|
||||
matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientCertificate, authInfo.ClientCertificate, t)
|
||||
matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientKey, authInfo.ClientKey, t)
|
||||
}
|
||||
}
|
||||
if foundAuthInfoCount != 4 {
|
||||
t.Errorf("Expected 4 users, found %v: %v", foundAuthInfoCount, mergedConfig.AuthInfos)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMigratingFile(t *testing.T) {
|
||||
sourceFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(sourceFile.Name())
|
||||
destinationFile, _ := ioutil.TempFile("", "")
|
||||
// delete the file so that we'll write to it
|
||||
os.Remove(destinationFile.Name())
|
||||
|
||||
WriteToFile(testConfigAlfa, sourceFile.Name())
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()},
|
||||
}
|
||||
|
||||
if _, err := loadingRules.Load(); err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
// the load should have recreated this file
|
||||
defer os.Remove(destinationFile.Name())
|
||||
|
||||
sourceContent, err := ioutil.ReadFile(sourceFile.Name())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
destinationContent, err := ioutil.ReadFile(destinationFile.Name())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(sourceContent, destinationContent) {
|
||||
t.Errorf("source and destination do not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigratingFileLeaveExistingFileAlone(t *testing.T) {
|
||||
sourceFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(sourceFile.Name())
|
||||
destinationFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(destinationFile.Name())
|
||||
|
||||
WriteToFile(testConfigAlfa, sourceFile.Name())
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()},
|
||||
}
|
||||
|
||||
if _, err := loadingRules.Load(); err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
destinationContent, err := ioutil.ReadFile(destinationFile.Name())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
if len(destinationContent) > 0 {
|
||||
t.Errorf("destination should not have been touched")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigratingFileSourceMissingSkip(t *testing.T) {
|
||||
sourceFilename := "some-missing-file"
|
||||
destinationFile, _ := ioutil.TempFile("", "")
|
||||
// delete the file so that we'll write to it
|
||||
os.Remove(destinationFile.Name())
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
MigrationRules: map[string]string{destinationFile.Name(): sourceFilename},
|
||||
}
|
||||
|
||||
if _, err := loadingRules.Load(); err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(destinationFile.Name()); !os.IsNotExist(err) {
|
||||
t.Errorf("destination should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileLocking(t *testing.T) {
|
||||
f, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
err := lockFile(f.Name())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error while locking file: %v", err)
|
||||
}
|
||||
defer unlockFile(f.Name())
|
||||
|
||||
err = lockFile(f.Name())
|
||||
if err == nil {
|
||||
t.Error("expected error while locking file.")
|
||||
}
|
||||
}
|
||||
|
||||
func Example_noMergingOnExplicitPaths() {
|
||||
commandLineFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
|
||||
WriteToFile(testConfigAlfa, commandLineFile.Name())
|
||||
WriteToFile(testConfigConflictAlfa, envVarFile.Name())
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
ExplicitPath: commandLineFile.Name(),
|
||||
Precedence: []string{envVarFile.Name()},
|
||||
}
|
||||
|
||||
mergedConfig, err := loadingRules.Load()
|
||||
|
||||
json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
output, err := yaml.JSONToYAML(json)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v", string(output))
|
||||
// Output:
|
||||
// apiVersion: v1
|
||||
// clusters:
|
||||
// - cluster:
|
||||
// server: http://cow.org:8080
|
||||
// name: cow-cluster
|
||||
// contexts:
|
||||
// - context:
|
||||
// cluster: cow-cluster
|
||||
// namespace: hammer-ns
|
||||
// user: red-user
|
||||
// name: federal-context
|
||||
// current-context: ""
|
||||
// kind: Config
|
||||
// preferences: {}
|
||||
// users:
|
||||
// - name: red-user
|
||||
// user:
|
||||
// token: red-token
|
||||
}
|
||||
|
||||
func Example_mergingSomeWithConflict() {
|
||||
commandLineFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
|
||||
WriteToFile(testConfigAlfa, commandLineFile.Name())
|
||||
WriteToFile(testConfigConflictAlfa, envVarFile.Name())
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
Precedence: []string{commandLineFile.Name(), envVarFile.Name()},
|
||||
}
|
||||
|
||||
mergedConfig, err := loadingRules.Load()
|
||||
|
||||
json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
output, err := yaml.JSONToYAML(json)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v", string(output))
|
||||
// Output:
|
||||
// apiVersion: v1
|
||||
// clusters:
|
||||
// - cluster:
|
||||
// server: http://cow.org:8080
|
||||
// name: cow-cluster
|
||||
// - cluster:
|
||||
// insecure-skip-tls-verify: true
|
||||
// server: http://donkey.org:8080
|
||||
// name: donkey-cluster
|
||||
// contexts:
|
||||
// - context:
|
||||
// cluster: cow-cluster
|
||||
// namespace: hammer-ns
|
||||
// user: red-user
|
||||
// name: federal-context
|
||||
// current-context: federal-context
|
||||
// kind: Config
|
||||
// preferences: {}
|
||||
// users:
|
||||
// - name: red-user
|
||||
// user:
|
||||
// token: red-token
|
||||
// - name: yellow-user
|
||||
// user:
|
||||
// token: yellow-token
|
||||
}
|
||||
|
||||
func Example_mergingEverythingNoConflicts() {
|
||||
commandLineFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
currentDirFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(currentDirFile.Name())
|
||||
homeDirFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(homeDirFile.Name())
|
||||
|
||||
WriteToFile(testConfigAlfa, commandLineFile.Name())
|
||||
WriteToFile(testConfigBravo, envVarFile.Name())
|
||||
WriteToFile(testConfigCharlie, currentDirFile.Name())
|
||||
WriteToFile(testConfigDelta, homeDirFile.Name())
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
Precedence: []string{commandLineFile.Name(), envVarFile.Name(), currentDirFile.Name(), homeDirFile.Name()},
|
||||
}
|
||||
|
||||
mergedConfig, err := loadingRules.Load()
|
||||
|
||||
json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
output, err := yaml.JSONToYAML(json)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v", string(output))
|
||||
// Output:
|
||||
// apiVersion: v1
|
||||
// clusters:
|
||||
// - cluster:
|
||||
// server: http://chicken.org:8080
|
||||
// name: chicken-cluster
|
||||
// - cluster:
|
||||
// server: http://cow.org:8080
|
||||
// name: cow-cluster
|
||||
// - cluster:
|
||||
// server: http://horse.org:8080
|
||||
// name: horse-cluster
|
||||
// - cluster:
|
||||
// server: http://pig.org:8080
|
||||
// name: pig-cluster
|
||||
// contexts:
|
||||
// - context:
|
||||
// cluster: cow-cluster
|
||||
// namespace: hammer-ns
|
||||
// user: red-user
|
||||
// name: federal-context
|
||||
// - context:
|
||||
// cluster: chicken-cluster
|
||||
// namespace: plane-ns
|
||||
// user: blue-user
|
||||
// name: gothic-context
|
||||
// - context:
|
||||
// cluster: pig-cluster
|
||||
// namespace: saw-ns
|
||||
// user: black-user
|
||||
// name: queen-anne-context
|
||||
// - context:
|
||||
// cluster: horse-cluster
|
||||
// namespace: chisel-ns
|
||||
// user: green-user
|
||||
// name: shaker-context
|
||||
// current-context: ""
|
||||
// kind: Config
|
||||
// preferences: {}
|
||||
// users:
|
||||
// - name: black-user
|
||||
// user:
|
||||
// token: black-token
|
||||
// - name: blue-user
|
||||
// user:
|
||||
// token: blue-token
|
||||
// - name: green-user
|
||||
// user:
|
||||
// token: green-token
|
||||
// - name: red-user
|
||||
// user:
|
||||
// token: red-token
|
||||
}
|
154
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go
generated
vendored
Normal file
154
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
// DeferredLoadingClientConfig is a ClientConfig interface that is backed by a client config loader.
|
||||
// It is used in cases where the loading rules may change after you've instantiated them and you want to be sure that
|
||||
// the most recent rules are used. This is useful in cases where you bind flags to loading rule parameters before
|
||||
// the parse happens and you want your calling code to be ignorant of how the values are being mutated to avoid
|
||||
// passing extraneous information down a call stack
|
||||
type DeferredLoadingClientConfig struct {
|
||||
loader ClientConfigLoader
|
||||
overrides *ConfigOverrides
|
||||
fallbackReader io.Reader
|
||||
|
||||
clientConfig ClientConfig
|
||||
loadingLock sync.Mutex
|
||||
|
||||
// provided for testing
|
||||
icc InClusterConfig
|
||||
}
|
||||
|
||||
// InClusterConfig abstracts details of whether the client is running in a cluster for testing.
|
||||
type InClusterConfig interface {
|
||||
ClientConfig
|
||||
Possible() bool
|
||||
}
|
||||
|
||||
// NewNonInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name
|
||||
func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig {
|
||||
return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}}
|
||||
}
|
||||
|
||||
// NewInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name and the fallback auth reader
|
||||
func NewInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
|
||||
return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}, fallbackReader: fallbackReader}
|
||||
}
|
||||
|
||||
func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, error) {
|
||||
if config.clientConfig == nil {
|
||||
config.loadingLock.Lock()
|
||||
defer config.loadingLock.Unlock()
|
||||
|
||||
if config.clientConfig == nil {
|
||||
mergedConfig, err := config.loader.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mergedClientConfig ClientConfig
|
||||
if config.fallbackReader != nil {
|
||||
mergedClientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader, config.loader)
|
||||
} else {
|
||||
mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.loader)
|
||||
}
|
||||
|
||||
config.clientConfig = mergedClientConfig
|
||||
}
|
||||
}
|
||||
|
||||
return config.clientConfig, nil
|
||||
}
|
||||
|
||||
func (config *DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
mergedConfig, err := config.createClientConfig()
|
||||
if err != nil {
|
||||
return clientcmdapi.Config{}, err
|
||||
}
|
||||
|
||||
return mergedConfig.RawConfig()
|
||||
}
|
||||
|
||||
// ClientConfig implements ClientConfig
|
||||
func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
mergedClientConfig, err := config.createClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// load the configuration and return on non-empty errors and if the
|
||||
// content differs from the default config
|
||||
mergedConfig, err := mergedClientConfig.ClientConfig()
|
||||
switch {
|
||||
case err != nil:
|
||||
if !IsEmptyConfig(err) {
|
||||
// return on any error except empty config
|
||||
return nil, err
|
||||
}
|
||||
case mergedConfig != nil:
|
||||
// the configuration is valid, but if this is equal to the defaults we should try
|
||||
// in-cluster configuration
|
||||
if !config.loader.IsDefaultConfig(mergedConfig) {
|
||||
return mergedConfig, nil
|
||||
}
|
||||
}
|
||||
|
||||
// check for in-cluster configuration and use it
|
||||
if config.icc.Possible() {
|
||||
glog.V(4).Infof("Using in-cluster configuration")
|
||||
return config.icc.ClientConfig()
|
||||
}
|
||||
|
||||
// return the result of the merged client config
|
||||
return mergedConfig, err
|
||||
}
|
||||
|
||||
// Namespace implements KubeConfig
|
||||
func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
|
||||
mergedKubeConfig, err := config.createClientConfig()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
ns, ok, err := mergedKubeConfig.Namespace()
|
||||
// if we get an error and it is not empty config, or if the merged config defined an explicit namespace, or
|
||||
// if in-cluster config is not possible, return immediately
|
||||
if (err != nil && !IsEmptyConfig(err)) || ok || !config.icc.Possible() {
|
||||
// return on any error except empty config
|
||||
return ns, ok, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Using in-cluster namespace")
|
||||
|
||||
// allow the namespace from the service account token directory to be used.
|
||||
return config.icc.Namespace()
|
||||
}
|
||||
|
||||
// ConfigAccess implements ClientConfig
|
||||
func (config *DeferredLoadingClientConfig) ConfigAccess() ConfigAccess {
|
||||
return config.loader
|
||||
}
|
328
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder_test.go
generated
vendored
Normal file
328
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder_test.go
generated
vendored
Normal file
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
type testLoader struct {
|
||||
ClientConfigLoader
|
||||
|
||||
called bool
|
||||
config *clientcmdapi.Config
|
||||
err error
|
||||
}
|
||||
|
||||
func (l *testLoader) Load() (*clientcmdapi.Config, error) {
|
||||
l.called = true
|
||||
return l.config, l.err
|
||||
}
|
||||
|
||||
type testClientConfig struct {
|
||||
config *restclient.Config
|
||||
namespace string
|
||||
namespaceSpecified bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *testClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return clientcmdapi.Config{}, fmt.Errorf("unexpected call")
|
||||
}
|
||||
func (c *testClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
return c.config, c.err
|
||||
}
|
||||
func (c *testClientConfig) Namespace() (string, bool, error) {
|
||||
return c.namespace, c.namespaceSpecified, c.err
|
||||
}
|
||||
func (c *testClientConfig) ConfigAccess() ConfigAccess {
|
||||
return nil
|
||||
}
|
||||
|
||||
type testICC struct {
|
||||
testClientConfig
|
||||
|
||||
possible bool
|
||||
called bool
|
||||
}
|
||||
|
||||
func (icc *testICC) Possible() bool {
|
||||
icc.called = true
|
||||
return icc.possible
|
||||
}
|
||||
|
||||
func TestInClusterConfig(t *testing.T) {
|
||||
default1 := &DirectClientConfig{
|
||||
config: *createValidTestConfig(),
|
||||
contextName: "clean",
|
||||
overrides: &ConfigOverrides{},
|
||||
}
|
||||
invalidDefaultConfig := clientcmdapi.NewConfig()
|
||||
invalidDefaultConfig.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "http://localhost:8080",
|
||||
}
|
||||
invalidDefaultConfig.Contexts["other"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
}
|
||||
invalidDefaultConfig.CurrentContext = "clean"
|
||||
|
||||
defaultInvalid := &DirectClientConfig{
|
||||
config: *invalidDefaultConfig,
|
||||
overrides: &ConfigOverrides{},
|
||||
}
|
||||
if _, err := defaultInvalid.ClientConfig(); err == nil || !IsConfigurationInvalid(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config1, err := default1.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config2 := &restclient.Config{Host: "config2"}
|
||||
err1 := fmt.Errorf("unique error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
clientConfig *testClientConfig
|
||||
icc *testICC
|
||||
defaultConfig *DirectClientConfig
|
||||
|
||||
checkedICC bool
|
||||
result *restclient.Config
|
||||
err error
|
||||
}{
|
||||
"in-cluster checked on other error": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyConfig},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: true,
|
||||
result: nil,
|
||||
err: ErrEmptyConfig,
|
||||
},
|
||||
|
||||
"in-cluster not checked on non-empty error": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyCluster},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: false,
|
||||
result: nil,
|
||||
err: ErrEmptyCluster,
|
||||
},
|
||||
|
||||
"in-cluster checked when config is default": {
|
||||
defaultConfig: default1,
|
||||
clientConfig: &testClientConfig{config: config1},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: true,
|
||||
result: config1,
|
||||
err: nil,
|
||||
},
|
||||
|
||||
"in-cluster not checked when default config is invalid": {
|
||||
defaultConfig: defaultInvalid,
|
||||
clientConfig: &testClientConfig{config: config1},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: false,
|
||||
result: config1,
|
||||
err: nil,
|
||||
},
|
||||
|
||||
"in-cluster not checked when config is not equal to default": {
|
||||
defaultConfig: default1,
|
||||
clientConfig: &testClientConfig{config: config2},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: false,
|
||||
result: config2,
|
||||
err: nil,
|
||||
},
|
||||
|
||||
"in-cluster checked when config is not equal to default and error is empty": {
|
||||
clientConfig: &testClientConfig{config: config2, err: ErrEmptyConfig},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: true,
|
||||
result: config2,
|
||||
err: ErrEmptyConfig,
|
||||
},
|
||||
|
||||
"in-cluster error returned when config is empty": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyConfig},
|
||||
icc: &testICC{
|
||||
possible: true,
|
||||
testClientConfig: testClientConfig{
|
||||
err: err1,
|
||||
},
|
||||
},
|
||||
|
||||
checkedICC: true,
|
||||
result: nil,
|
||||
err: err1,
|
||||
},
|
||||
|
||||
"in-cluster config returned when config is empty": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyConfig},
|
||||
icc: &testICC{
|
||||
possible: true,
|
||||
testClientConfig: testClientConfig{
|
||||
config: config2,
|
||||
},
|
||||
},
|
||||
|
||||
checkedICC: true,
|
||||
result: config2,
|
||||
err: nil,
|
||||
},
|
||||
|
||||
"in-cluster not checked when standard default is invalid": {
|
||||
defaultConfig: &DefaultClientConfig,
|
||||
clientConfig: &testClientConfig{config: config2},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: false,
|
||||
result: config2,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range testCases {
|
||||
c := &DeferredLoadingClientConfig{icc: test.icc}
|
||||
c.loader = &ClientConfigLoadingRules{DefaultClientConfig: test.defaultConfig}
|
||||
c.clientConfig = test.clientConfig
|
||||
|
||||
cfg, err := c.ClientConfig()
|
||||
if test.icc.called != test.checkedICC {
|
||||
t.Errorf("%s: unexpected in-cluster-config call %t", name, test.icc.called)
|
||||
}
|
||||
if err != test.err || cfg != test.result {
|
||||
t.Errorf("%s: unexpected result: %v %#v", name, err, cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInClusterConfigNamespace(t *testing.T) {
|
||||
err1 := fmt.Errorf("unique error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
clientConfig *testClientConfig
|
||||
icc *testICC
|
||||
|
||||
checkedICC bool
|
||||
result string
|
||||
ok bool
|
||||
err error
|
||||
}{
|
||||
"in-cluster checked on empty error": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyConfig},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: true,
|
||||
err: ErrEmptyConfig,
|
||||
},
|
||||
|
||||
"in-cluster not checked on non-empty error": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyCluster},
|
||||
icc: &testICC{},
|
||||
|
||||
err: ErrEmptyCluster,
|
||||
},
|
||||
|
||||
"in-cluster checked when config is default": {
|
||||
clientConfig: &testClientConfig{},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: true,
|
||||
},
|
||||
|
||||
"in-cluster not checked when config is not equal to default": {
|
||||
clientConfig: &testClientConfig{namespace: "test", namespaceSpecified: true},
|
||||
icc: &testICC{},
|
||||
|
||||
result: "test",
|
||||
ok: true,
|
||||
},
|
||||
|
||||
"in-cluster checked when namespace is not specified, but is defaulted": {
|
||||
clientConfig: &testClientConfig{namespace: "test", namespaceSpecified: false},
|
||||
icc: &testICC{},
|
||||
|
||||
checkedICC: true,
|
||||
result: "test",
|
||||
ok: false,
|
||||
},
|
||||
|
||||
"in-cluster error returned when config is empty": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyConfig},
|
||||
icc: &testICC{
|
||||
possible: true,
|
||||
testClientConfig: testClientConfig{
|
||||
err: err1,
|
||||
},
|
||||
},
|
||||
|
||||
checkedICC: true,
|
||||
err: err1,
|
||||
},
|
||||
|
||||
"in-cluster config returned when config is empty": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyConfig},
|
||||
icc: &testICC{
|
||||
possible: true,
|
||||
testClientConfig: testClientConfig{
|
||||
namespace: "test",
|
||||
namespaceSpecified: true,
|
||||
},
|
||||
},
|
||||
|
||||
checkedICC: true,
|
||||
result: "test",
|
||||
ok: true,
|
||||
},
|
||||
|
||||
"in-cluster config returned when config is empty and namespace is defaulted but not explicitly set": {
|
||||
clientConfig: &testClientConfig{err: ErrEmptyConfig},
|
||||
icc: &testICC{
|
||||
possible: true,
|
||||
testClientConfig: testClientConfig{
|
||||
namespace: "test",
|
||||
namespaceSpecified: false,
|
||||
},
|
||||
},
|
||||
|
||||
checkedICC: true,
|
||||
result: "test",
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range testCases {
|
||||
c := &DeferredLoadingClientConfig{icc: test.icc}
|
||||
c.clientConfig = test.clientConfig
|
||||
|
||||
ns, ok, err := c.Namespace()
|
||||
if test.icc.called != test.checkedICC {
|
||||
t.Errorf("%s: unexpected in-cluster-config call %t", name, test.icc.called)
|
||||
}
|
||||
if err != test.err || ns != test.result || ok != test.ok {
|
||||
t.Errorf("%s: unexpected result: %v %s %t", name, err, ns, ok)
|
||||
}
|
||||
}
|
||||
}
|
206
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go
generated
vendored
Normal file
206
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go
generated
vendored
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
// ConfigOverrides holds values that should override whatever information is pulled from the actual Config object. You can't
|
||||
// simply use an actual Config object, because Configs hold maps, but overrides are restricted to "at most one"
|
||||
type ConfigOverrides struct {
|
||||
AuthInfo clientcmdapi.AuthInfo
|
||||
// ClusterDefaults are applied before the configured cluster info is loaded.
|
||||
ClusterDefaults clientcmdapi.Cluster
|
||||
ClusterInfo clientcmdapi.Cluster
|
||||
Context clientcmdapi.Context
|
||||
CurrentContext string
|
||||
Timeout string
|
||||
}
|
||||
|
||||
// ConfigOverrideFlags holds the flag names to be used for binding command line flags. Notice that this structure tightly
|
||||
// corresponds to ConfigOverrides
|
||||
type ConfigOverrideFlags struct {
|
||||
AuthOverrideFlags AuthOverrideFlags
|
||||
ClusterOverrideFlags ClusterOverrideFlags
|
||||
ContextOverrideFlags ContextOverrideFlags
|
||||
CurrentContext FlagInfo
|
||||
Timeout FlagInfo
|
||||
}
|
||||
|
||||
// AuthOverrideFlags holds the flag names to be used for binding command line flags for AuthInfo objects
|
||||
type AuthOverrideFlags struct {
|
||||
ClientCertificate FlagInfo
|
||||
ClientKey FlagInfo
|
||||
Token FlagInfo
|
||||
Impersonate FlagInfo
|
||||
Username FlagInfo
|
||||
Password FlagInfo
|
||||
}
|
||||
|
||||
// ContextOverrideFlags holds the flag names to be used for binding command line flags for Cluster objects
|
||||
type ContextOverrideFlags struct {
|
||||
ClusterName FlagInfo
|
||||
AuthInfoName FlagInfo
|
||||
Namespace FlagInfo
|
||||
}
|
||||
|
||||
// ClusterOverride holds the flag names to be used for binding command line flags for Cluster objects
|
||||
type ClusterOverrideFlags struct {
|
||||
APIServer FlagInfo
|
||||
APIVersion FlagInfo
|
||||
CertificateAuthority FlagInfo
|
||||
InsecureSkipTLSVerify FlagInfo
|
||||
}
|
||||
|
||||
// FlagInfo contains information about how to register a flag. This struct is useful if you want to provide a way for an extender to
|
||||
// get back a set of recommended flag names, descriptions, and defaults, but allow for customization by an extender. This makes for
|
||||
// coherent extension, without full prescription
|
||||
type FlagInfo struct {
|
||||
// LongName is the long string for a flag. If this is empty, then the flag will not be bound
|
||||
LongName string
|
||||
// ShortName is the single character for a flag. If this is empty, then there will be no short flag
|
||||
ShortName string
|
||||
// Default is the default value for the flag
|
||||
Default string
|
||||
// Description is the description for the flag
|
||||
Description string
|
||||
}
|
||||
|
||||
// BindStringFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||
func (f FlagInfo) BindStringFlag(flags *pflag.FlagSet, target *string) {
|
||||
// you can't register a flag without a long name
|
||||
if len(f.LongName) > 0 {
|
||||
flags.StringVarP(target, f.LongName, f.ShortName, f.Default, f.Description)
|
||||
}
|
||||
}
|
||||
|
||||
// BindBoolFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||
func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) {
|
||||
// you can't register a flag without a long name
|
||||
if len(f.LongName) > 0 {
|
||||
// try to parse Default as a bool. If it fails, assume false
|
||||
boolVal, err := strconv.ParseBool(f.Default)
|
||||
if err != nil {
|
||||
boolVal = false
|
||||
}
|
||||
|
||||
flags.BoolVarP(target, f.LongName, f.ShortName, boolVal, f.Description)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
FlagClusterName = "cluster"
|
||||
FlagAuthInfoName = "user"
|
||||
FlagContext = "context"
|
||||
FlagNamespace = "namespace"
|
||||
FlagAPIServer = "server"
|
||||
FlagAPIVersion = "api-version"
|
||||
FlagInsecure = "insecure-skip-tls-verify"
|
||||
FlagCertFile = "client-certificate"
|
||||
FlagKeyFile = "client-key"
|
||||
FlagCAFile = "certificate-authority"
|
||||
FlagEmbedCerts = "embed-certs"
|
||||
FlagBearerToken = "token"
|
||||
FlagImpersonate = "as"
|
||||
FlagUsername = "username"
|
||||
FlagPassword = "password"
|
||||
FlagTimeout = "request-timeout"
|
||||
)
|
||||
|
||||
// RecommendedConfigOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedConfigOverrideFlags(prefix string) ConfigOverrideFlags {
|
||||
return ConfigOverrideFlags{
|
||||
AuthOverrideFlags: RecommendedAuthOverrideFlags(prefix),
|
||||
ClusterOverrideFlags: RecommendedClusterOverrideFlags(prefix),
|
||||
ContextOverrideFlags: RecommendedContextOverrideFlags(prefix),
|
||||
|
||||
CurrentContext: FlagInfo{prefix + FlagContext, "", "", "The name of the kubeconfig context to use"},
|
||||
Timeout: FlagInfo{prefix + FlagTimeout, "", "0", "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests."},
|
||||
}
|
||||
}
|
||||
|
||||
// RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedAuthOverrideFlags(prefix string) AuthOverrideFlags {
|
||||
return AuthOverrideFlags{
|
||||
ClientCertificate: FlagInfo{prefix + FlagCertFile, "", "", "Path to a client certificate file for TLS"},
|
||||
ClientKey: FlagInfo{prefix + FlagKeyFile, "", "", "Path to a client key file for TLS"},
|
||||
Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server"},
|
||||
Impersonate: FlagInfo{prefix + FlagImpersonate, "", "", "Username to impersonate for the operation"},
|
||||
Username: FlagInfo{prefix + FlagUsername, "", "", "Username for basic authentication to the API server"},
|
||||
Password: FlagInfo{prefix + FlagPassword, "", "", "Password for basic authentication to the API server"},
|
||||
}
|
||||
}
|
||||
|
||||
// RecommendedClusterOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedClusterOverrideFlags(prefix string) ClusterOverrideFlags {
|
||||
return ClusterOverrideFlags{
|
||||
APIServer: FlagInfo{prefix + FlagAPIServer, "", "", "The address and port of the Kubernetes API server"},
|
||||
APIVersion: FlagInfo{prefix + FlagAPIVersion, "", "", "DEPRECATED: The API version to use when talking to the server"},
|
||||
CertificateAuthority: FlagInfo{prefix + FlagCAFile, "", "", "Path to a cert. file for the certificate authority"},
|
||||
InsecureSkipTLSVerify: FlagInfo{prefix + FlagInsecure, "", "false", "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure"},
|
||||
}
|
||||
}
|
||||
|
||||
// RecommendedContextOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedContextOverrideFlags(prefix string) ContextOverrideFlags {
|
||||
return ContextOverrideFlags{
|
||||
ClusterName: FlagInfo{prefix + FlagClusterName, "", "", "The name of the kubeconfig cluster to use"},
|
||||
AuthInfoName: FlagInfo{prefix + FlagAuthInfoName, "", "", "The name of the kubeconfig user to use"},
|
||||
Namespace: FlagInfo{prefix + FlagNamespace, "n", "", "If present, the namespace scope for this CLI request"},
|
||||
}
|
||||
}
|
||||
|
||||
// BindOverrideFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindOverrideFlags(overrides *ConfigOverrides, flags *pflag.FlagSet, flagNames ConfigOverrideFlags) {
|
||||
BindAuthInfoFlags(&overrides.AuthInfo, flags, flagNames.AuthOverrideFlags)
|
||||
BindClusterFlags(&overrides.ClusterInfo, flags, flagNames.ClusterOverrideFlags)
|
||||
BindContextFlags(&overrides.Context, flags, flagNames.ContextOverrideFlags)
|
||||
flagNames.CurrentContext.BindStringFlag(flags, &overrides.CurrentContext)
|
||||
flagNames.Timeout.BindStringFlag(flags, &overrides.Timeout)
|
||||
}
|
||||
|
||||
// BindAuthInfoFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindAuthInfoFlags(authInfo *clientcmdapi.AuthInfo, flags *pflag.FlagSet, flagNames AuthOverrideFlags) {
|
||||
flagNames.ClientCertificate.BindStringFlag(flags, &authInfo.ClientCertificate)
|
||||
flagNames.ClientKey.BindStringFlag(flags, &authInfo.ClientKey)
|
||||
flagNames.Token.BindStringFlag(flags, &authInfo.Token)
|
||||
flagNames.Impersonate.BindStringFlag(flags, &authInfo.Impersonate)
|
||||
flagNames.Username.BindStringFlag(flags, &authInfo.Username)
|
||||
flagNames.Password.BindStringFlag(flags, &authInfo.Password)
|
||||
}
|
||||
|
||||
// BindClusterFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindClusterFlags(clusterInfo *clientcmdapi.Cluster, flags *pflag.FlagSet, flagNames ClusterOverrideFlags) {
|
||||
flagNames.APIServer.BindStringFlag(flags, &clusterInfo.Server)
|
||||
// TODO: remove --api-version flag in 1.3.
|
||||
flagNames.APIVersion.BindStringFlag(flags, &clusterInfo.APIVersion)
|
||||
flags.MarkDeprecated(FlagAPIVersion, "flag is no longer respected and will be deleted in the next release")
|
||||
flagNames.CertificateAuthority.BindStringFlag(flags, &clusterInfo.CertificateAuthority)
|
||||
flagNames.InsecureSkipTLSVerify.BindBoolFlag(flags, &clusterInfo.InsecureSkipTLSVerify)
|
||||
}
|
||||
|
||||
// BindFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindContextFlags(contextInfo *clientcmdapi.Context, flags *pflag.FlagSet, flagNames ContextOverrideFlags) {
|
||||
flagNames.ClusterName.BindStringFlag(flags, &contextInfo.Cluster)
|
||||
flagNames.AuthInfoName.BindStringFlag(flags, &contextInfo.AuthInfo)
|
||||
flagNames.Namespace.BindStringFlag(flags, &contextInfo.Namespace)
|
||||
}
|
270
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/validation.go
generated
vendored
Normal file
270
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/validation.go
generated
vendored
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoContext = errors.New("no context chosen")
|
||||
ErrEmptyConfig = errors.New("no configuration has been provided")
|
||||
// message is for consistency with old behavior
|
||||
ErrEmptyCluster = errors.New("cluster has no server defined")
|
||||
)
|
||||
|
||||
type errContextNotFound struct {
|
||||
ContextName string
|
||||
}
|
||||
|
||||
func (e *errContextNotFound) Error() string {
|
||||
return fmt.Sprintf("context was not found for specified context: %v", e.ContextName)
|
||||
}
|
||||
|
||||
// IsContextNotFound returns a boolean indicating whether the error is known to
|
||||
// report that a context was not found
|
||||
func IsContextNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := err.(*errContextNotFound); ok || err == ErrNoContext {
|
||||
return true
|
||||
}
|
||||
return strings.Contains(err.Error(), "context was not found for specified context")
|
||||
}
|
||||
|
||||
// IsEmptyConfig returns true if the provided error indicates the provided configuration
|
||||
// is empty.
|
||||
func IsEmptyConfig(err error) bool {
|
||||
switch t := err.(type) {
|
||||
case errConfigurationInvalid:
|
||||
return len(t) == 1 && t[0] == ErrEmptyConfig
|
||||
}
|
||||
return err == ErrEmptyConfig
|
||||
}
|
||||
|
||||
// errConfigurationInvalid is a set of errors indicating the configuration is invalid.
|
||||
type errConfigurationInvalid []error
|
||||
|
||||
// errConfigurationInvalid implements error and Aggregate
|
||||
var _ error = errConfigurationInvalid{}
|
||||
var _ utilerrors.Aggregate = errConfigurationInvalid{}
|
||||
|
||||
func newErrConfigurationInvalid(errs []error) error {
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
return nil
|
||||
default:
|
||||
return errConfigurationInvalid(errs)
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e errConfigurationInvalid) Error() string {
|
||||
return fmt.Sprintf("invalid configuration: %v", utilerrors.NewAggregate(e).Error())
|
||||
}
|
||||
|
||||
// Errors implements the AggregateError interface
|
||||
func (e errConfigurationInvalid) Errors() []error {
|
||||
return e
|
||||
}
|
||||
|
||||
// IsConfigurationInvalid returns true if the provided error indicates the configuration is invalid.
|
||||
func IsConfigurationInvalid(err error) bool {
|
||||
switch err.(type) {
|
||||
case *errContextNotFound, errConfigurationInvalid:
|
||||
return true
|
||||
}
|
||||
return IsContextNotFound(err)
|
||||
}
|
||||
|
||||
// Validate checks for errors in the Config. It does not return early so that it can find as many errors as possible.
|
||||
func Validate(config clientcmdapi.Config) error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if clientcmdapi.IsConfigEmpty(&config) {
|
||||
return newErrConfigurationInvalid([]error{ErrEmptyConfig})
|
||||
}
|
||||
|
||||
if len(config.CurrentContext) != 0 {
|
||||
if _, exists := config.Contexts[config.CurrentContext]; !exists {
|
||||
validationErrors = append(validationErrors, &errContextNotFound{config.CurrentContext})
|
||||
}
|
||||
}
|
||||
|
||||
for contextName, context := range config.Contexts {
|
||||
validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
|
||||
}
|
||||
|
||||
for authInfoName, authInfo := range config.AuthInfos {
|
||||
validationErrors = append(validationErrors, validateAuthInfo(authInfoName, *authInfo)...)
|
||||
}
|
||||
|
||||
for clusterName, clusterInfo := range config.Clusters {
|
||||
validationErrors = append(validationErrors, validateClusterInfo(clusterName, *clusterInfo)...)
|
||||
}
|
||||
|
||||
return newErrConfigurationInvalid(validationErrors)
|
||||
}
|
||||
|
||||
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
|
||||
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
|
||||
func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if clientcmdapi.IsConfigEmpty(&config) {
|
||||
return newErrConfigurationInvalid([]error{ErrEmptyConfig})
|
||||
}
|
||||
|
||||
var contextName string
|
||||
if len(passedContextName) != 0 {
|
||||
contextName = passedContextName
|
||||
} else {
|
||||
contextName = config.CurrentContext
|
||||
}
|
||||
|
||||
if len(contextName) == 0 {
|
||||
return ErrNoContext
|
||||
}
|
||||
|
||||
context, exists := config.Contexts[contextName]
|
||||
if !exists {
|
||||
validationErrors = append(validationErrors, &errContextNotFound{contextName})
|
||||
}
|
||||
|
||||
if exists {
|
||||
validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
|
||||
validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, *config.AuthInfos[context.AuthInfo])...)
|
||||
validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, *config.Clusters[context.Cluster])...)
|
||||
}
|
||||
|
||||
return newErrConfigurationInvalid(validationErrors)
|
||||
}
|
||||
|
||||
// validateClusterInfo looks for conflicts and errors in the cluster info
|
||||
func validateClusterInfo(clusterName string, clusterInfo clientcmdapi.Cluster) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if reflect.DeepEqual(clientcmdapi.Cluster{}, clusterInfo) {
|
||||
return []error{ErrEmptyCluster}
|
||||
}
|
||||
|
||||
if len(clusterInfo.Server) == 0 {
|
||||
if len(clusterName) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("default cluster has no server defined"))
|
||||
} else {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("no server found for cluster %q", clusterName))
|
||||
}
|
||||
}
|
||||
// Make sure CA data and CA file aren't both specified
|
||||
if len(clusterInfo.CertificateAuthority) != 0 && len(clusterInfo.CertificateAuthorityData) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("certificate-authority-data and certificate-authority are both specified for %v. certificate-authority-data will override.", clusterName))
|
||||
}
|
||||
if len(clusterInfo.CertificateAuthority) != 0 {
|
||||
clientCertCA, err := os.Open(clusterInfo.CertificateAuthority)
|
||||
defer clientCertCA.Close()
|
||||
if err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read certificate-authority %v for %v due to %v", clusterInfo.CertificateAuthority, clusterName, err))
|
||||
}
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
// validateAuthInfo looks for conflicts and errors in the auth info
|
||||
func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
usingAuthPath := false
|
||||
methods := make([]string, 0, 3)
|
||||
if len(authInfo.Token) != 0 {
|
||||
methods = append(methods, "token")
|
||||
}
|
||||
if len(authInfo.Username) != 0 || len(authInfo.Password) != 0 {
|
||||
methods = append(methods, "basicAuth")
|
||||
}
|
||||
|
||||
if len(authInfo.ClientCertificate) != 0 || len(authInfo.ClientCertificateData) != 0 {
|
||||
// Make sure cert data and file aren't both specified
|
||||
if len(authInfo.ClientCertificate) != 0 && len(authInfo.ClientCertificateData) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("client-cert-data and client-cert are both specified for %v. client-cert-data will override.", authInfoName))
|
||||
}
|
||||
// Make sure key data and file aren't both specified
|
||||
if len(authInfo.ClientKey) != 0 && len(authInfo.ClientKeyData) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("client-key-data and client-key are both specified for %v; client-key-data will override", authInfoName))
|
||||
}
|
||||
// Make sure a key is specified
|
||||
if len(authInfo.ClientKey) == 0 && len(authInfo.ClientKeyData) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("client-key-data or client-key must be specified for %v to use the clientCert authentication method.", authInfoName))
|
||||
}
|
||||
|
||||
if len(authInfo.ClientCertificate) != 0 {
|
||||
clientCertFile, err := os.Open(authInfo.ClientCertificate)
|
||||
defer clientCertFile.Close()
|
||||
if err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-cert %v for %v due to %v", authInfo.ClientCertificate, authInfoName, err))
|
||||
}
|
||||
}
|
||||
if len(authInfo.ClientKey) != 0 {
|
||||
clientKeyFile, err := os.Open(authInfo.ClientKey)
|
||||
defer clientKeyFile.Close()
|
||||
if err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-key %v for %v due to %v", authInfo.ClientKey, authInfoName, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// authPath also provides information for the client to identify the server, so allow multiple auth methods in that case
|
||||
if (len(methods) > 1) && (!usingAuthPath) {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods))
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
// validateContext looks for errors in the context. It is not transitive, so errors in the reference authInfo or cluster configs are not included in this return
|
||||
func validateContext(contextName string, context clientcmdapi.Context, config clientcmdapi.Config) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if len(context.AuthInfo) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("user was not specified for context %q", contextName))
|
||||
} else if _, exists := config.AuthInfos[context.AuthInfo]; !exists {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("user %q was not found for context %q", context.AuthInfo, contextName))
|
||||
}
|
||||
|
||||
if len(context.Cluster) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("cluster was not specified for context %q", contextName))
|
||||
} else if _, exists := config.Clusters[context.Cluster]; !exists {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("cluster %q was not found for context %q", context.Cluster, contextName))
|
||||
}
|
||||
|
||||
if len(context.Namespace) != 0 {
|
||||
if len(validation.IsDNS1123Label(context.Namespace)) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("namespace %q for context %q does not conform to the kubernetes DNS_LABEL rules", context.Namespace, contextName))
|
||||
}
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
432
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/validation_test.go
generated
vendored
Normal file
432
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/validation_test.go
generated
vendored
Normal file
|
@ -0,0 +1,432 @@
|
|||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["missing ca"] = &clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: "missing",
|
||||
}
|
||||
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
||||
Username: "anything",
|
||||
Token: "here",
|
||||
}
|
||||
config.Contexts["dirty"] = &clientcmdapi.Context{
|
||||
Cluster: "missing ca",
|
||||
AuthInfo: "error",
|
||||
}
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
}
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
Token: "here",
|
||||
}
|
||||
config.Contexts["clean"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
|
||||
badValidation := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"unable to read certificate-authority"},
|
||||
}
|
||||
okTest := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
|
||||
okTest.testConfirmUsable("clean", t)
|
||||
badValidation.testConfig(t)
|
||||
}
|
||||
func TestConfirmUsableBadInfoConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["missing ca"] = &clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: "missing",
|
||||
}
|
||||
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
||||
Username: "anything",
|
||||
Token: "here",
|
||||
}
|
||||
config.Contexts["first"] = &clientcmdapi.Context{
|
||||
Cluster: "missing ca",
|
||||
AuthInfo: "error",
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"unable to read certificate-authority"},
|
||||
}
|
||||
|
||||
test.testConfirmUsable("first", t)
|
||||
}
|
||||
func TestConfirmUsableEmptyConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
|
||||
}
|
||||
|
||||
test.testConfirmUsable("", t)
|
||||
}
|
||||
func TestConfirmUsableMissingConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
|
||||
}
|
||||
|
||||
test.testConfirmUsable("not-here", t)
|
||||
}
|
||||
func TestValidateEmptyConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
|
||||
}
|
||||
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateMissingCurrentContextConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"context was not found for specified "},
|
||||
}
|
||||
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestIsContextNotFound(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
|
||||
err := Validate(*config)
|
||||
if !IsContextNotFound(err) {
|
||||
t.Errorf("Expected context not found, but got %v", err)
|
||||
}
|
||||
if !IsConfigurationInvalid(err) {
|
||||
t.Errorf("Expected configuration invalid, but got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmptyConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
|
||||
err := Validate(*config)
|
||||
if !IsEmptyConfig(err) {
|
||||
t.Errorf("Expected context not found, but got %v", err)
|
||||
}
|
||||
if !IsConfigurationInvalid(err) {
|
||||
t.Errorf("Expected configuration invalid, but got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConfigurationInvalid(t *testing.T) {
|
||||
if newErrConfigurationInvalid([]error{}) != nil {
|
||||
t.Errorf("unexpected error")
|
||||
}
|
||||
if newErrConfigurationInvalid([]error{ErrNoContext}) == ErrNoContext {
|
||||
t.Errorf("unexpected error")
|
||||
}
|
||||
if newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext}) == nil {
|
||||
t.Errorf("unexpected error")
|
||||
}
|
||||
if !IsConfigurationInvalid(newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext})) {
|
||||
t.Errorf("unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMissingReferencesConfig(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
config.Contexts["anything"] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""},
|
||||
}
|
||||
|
||||
test.testContext("anything", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateEmptyContext(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
config.Contexts["anything"] = &clientcmdapi.Context{}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""},
|
||||
}
|
||||
|
||||
test.testContext("anything", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
|
||||
func TestValidateEmptyClusterInfo(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["empty"] = &clientcmdapi.Cluster{}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"cluster has no server defined"},
|
||||
}
|
||||
|
||||
test.testCluster("empty", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateMissingCAFileClusterInfo(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["missing ca"] = &clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: "missing",
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"unable to read certificate-authority"},
|
||||
}
|
||||
|
||||
test.testCluster("missing ca", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCleanClusterInfo(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
|
||||
test.testCluster("clean", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCleanWithCAClusterInfo(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: tempFile.Name(),
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
|
||||
test.testCluster("clean", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
|
||||
func TestValidateEmptyAuthInfo(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
|
||||
test.testAuthInfo("error", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
||||
ClientCertificate: "missing",
|
||||
ClientKey: "missing",
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"unable to read client-cert", "unable to read client-key"},
|
||||
}
|
||||
|
||||
test.testAuthInfo("error", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCertDataOverridesFiles(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
ClientCertificate: tempFile.Name(),
|
||||
ClientCertificateData: []byte("certdata"),
|
||||
ClientKey: tempFile.Name(),
|
||||
ClientKeyData: []byte("keydata"),
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"client-cert-data and client-cert are both specified", "client-key-data and client-key are both specified"},
|
||||
}
|
||||
|
||||
test.testAuthInfo("clean", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
ClientCertificate: tempFile.Name(),
|
||||
ClientKey: tempFile.Name(),
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
|
||||
test.testAuthInfo("clean", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCleanTokenAuthInfo(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
Token: "any-value",
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
|
||||
test.testAuthInfo("clean", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
|
||||
func TestValidateMultipleMethodsAuthInfo(t *testing.T) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
||||
Token: "token",
|
||||
Username: "username",
|
||||
}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"more than one authentication method", "token", "basicAuth"},
|
||||
}
|
||||
|
||||
test.testAuthInfo("error", t)
|
||||
test.testConfig(t)
|
||||
}
|
||||
|
||||
type configValidationTest struct {
|
||||
config *clientcmdapi.Config
|
||||
expectedErrorSubstring []string
|
||||
}
|
||||
|
||||
func (c configValidationTest) testContext(contextName string, t *testing.T) {
|
||||
errs := validateContext(contextName, *c.config.Contexts[contextName], *c.config)
|
||||
|
||||
if len(c.expectedErrorSubstring) != 0 {
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
||||
}
|
||||
for _, curr := range c.expectedErrorSubstring {
|
||||
if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
|
||||
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
|
||||
}
|
||||
}
|
||||
}
|
||||
func (c configValidationTest) testConfirmUsable(contextName string, t *testing.T) {
|
||||
err := ConfirmUsable(*c.config, contextName)
|
||||
|
||||
if len(c.expectedErrorSubstring) != 0 {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
||||
} else {
|
||||
for _, curr := range c.expectedErrorSubstring {
|
||||
if err != nil && !strings.Contains(err.Error(), curr) {
|
||||
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (c configValidationTest) testConfig(t *testing.T) {
|
||||
err := Validate(*c.config)
|
||||
|
||||
if len(c.expectedErrorSubstring) != 0 {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
||||
} else {
|
||||
for _, curr := range c.expectedErrorSubstring {
|
||||
if err != nil && !strings.Contains(err.Error(), curr) {
|
||||
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err)
|
||||
}
|
||||
}
|
||||
if !IsConfigurationInvalid(err) {
|
||||
t.Errorf("all errors should be configuration invalid: %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (c configValidationTest) testCluster(clusterName string, t *testing.T) {
|
||||
errs := validateClusterInfo(clusterName, *c.config.Clusters[clusterName])
|
||||
|
||||
if len(c.expectedErrorSubstring) != 0 {
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
||||
}
|
||||
for _, curr := range c.expectedErrorSubstring {
|
||||
if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
|
||||
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) {
|
||||
errs := validateAuthInfo(authInfoName, *c.config.AuthInfos[authInfoName])
|
||||
|
||||
if len(c.expectedErrorSubstring) != 0 {
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
||||
}
|
||||
for _, curr := range c.expectedErrorSubstring {
|
||||
if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
|
||||
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
|
||||
}
|
||||
}
|
||||
}
|
273
vendor/k8s.io/kubernetes/pkg/client/unversioned/conditions.go
generated
vendored
Normal file
273
vendor/k8s.io/kubernetes/pkg/client/unversioned/conditions.go
generated
vendored
Normal file
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
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 unversioned
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/apps"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
appsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion"
|
||||
batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion"
|
||||
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
|
||||
)
|
||||
|
||||
// ControllerHasDesiredReplicas returns a condition that will be true if and only if
|
||||
// the desired replica count for a controller's ReplicaSelector equals the Replicas count.
|
||||
func ControllerHasDesiredReplicas(rcClient coreclient.ReplicationControllersGetter, controller *api.ReplicationController) wait.ConditionFunc {
|
||||
|
||||
// If we're given a controller where the status lags the spec, it either means that the controller is stale,
|
||||
// or that the rc manager hasn't noticed the update yet. Polling status.Replicas is not safe in the latter case.
|
||||
desiredGeneration := controller.Generation
|
||||
|
||||
return func() (bool, error) {
|
||||
ctrl, err := rcClient.ReplicationControllers(controller.Namespace).Get(controller.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// There's a chance a concurrent update modifies the Spec.Replicas causing this check to pass,
|
||||
// or, after this check has passed, a modification causes the rc manager to create more pods.
|
||||
// This will not be an issue once we've implemented graceful delete for rcs, but till then
|
||||
// concurrent stop operations on the same rc might have unintended side effects.
|
||||
return ctrl.Status.ObservedGeneration >= desiredGeneration && ctrl.Status.Replicas == ctrl.Spec.Replicas, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReplicaSetHasDesiredReplicas returns a condition that will be true if and only if
|
||||
// the desired replica count for a ReplicaSet's ReplicaSelector equals the Replicas count.
|
||||
func ReplicaSetHasDesiredReplicas(rsClient extensionsclient.ReplicaSetsGetter, replicaSet *extensions.ReplicaSet) wait.ConditionFunc {
|
||||
|
||||
// If we're given a ReplicaSet where the status lags the spec, it either means that the
|
||||
// ReplicaSet is stale, or that the ReplicaSet manager hasn't noticed the update yet.
|
||||
// Polling status.Replicas is not safe in the latter case.
|
||||
desiredGeneration := replicaSet.Generation
|
||||
|
||||
return func() (bool, error) {
|
||||
rs, err := rsClient.ReplicaSets(replicaSet.Namespace).Get(replicaSet.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// There's a chance a concurrent update modifies the Spec.Replicas causing this check to
|
||||
// pass, or, after this check has passed, a modification causes the ReplicaSet manager to
|
||||
// create more pods. This will not be an issue once we've implemented graceful delete for
|
||||
// ReplicaSets, but till then concurrent stop operations on the same ReplicaSet might have
|
||||
// unintended side effects.
|
||||
return rs.Status.ObservedGeneration >= desiredGeneration && rs.Status.Replicas == rs.Spec.Replicas, nil
|
||||
}
|
||||
}
|
||||
|
||||
// StatefulSetHasDesiredPets returns a conditon that checks the number of petset replicas
|
||||
func StatefulSetHasDesiredPets(psClient appsclient.StatefulSetsGetter, petset *apps.StatefulSet) wait.ConditionFunc {
|
||||
// TODO: Differentiate between 0 pets and a really quick scale down using generation.
|
||||
return func() (bool, error) {
|
||||
ps, err := psClient.StatefulSets(petset.Namespace).Get(petset.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return ps.Status.Replicas == ps.Spec.Replicas, nil
|
||||
}
|
||||
}
|
||||
|
||||
// JobHasDesiredParallelism returns a condition that will be true if the desired parallelism count
|
||||
// for a job equals the current active counts or is less by an appropriate successful/unsuccessful count.
|
||||
func JobHasDesiredParallelism(jobClient batchclient.JobsGetter, job *batch.Job) wait.ConditionFunc {
|
||||
return func() (bool, error) {
|
||||
job, err := jobClient.Jobs(job.Namespace).Get(job.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// desired parallelism can be either the exact number, in which case return immediately
|
||||
if job.Status.Active == *job.Spec.Parallelism {
|
||||
return true, nil
|
||||
}
|
||||
if job.Spec.Completions == nil {
|
||||
// A job without specified completions needs to wait for Active to reach Parallelism.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// otherwise count successful
|
||||
progress := *job.Spec.Completions - job.Status.Active - job.Status.Succeeded
|
||||
return progress == 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeploymentHasDesiredReplicas returns a condition that will be true if and only if
|
||||
// the desired replica count for a deployment equals its updated replicas count.
|
||||
// (non-terminated pods that have the desired template spec).
|
||||
func DeploymentHasDesiredReplicas(dClient extensionsclient.DeploymentsGetter, deployment *extensions.Deployment) wait.ConditionFunc {
|
||||
// If we're given a deployment where the status lags the spec, it either
|
||||
// means that the deployment is stale, or that the deployment manager hasn't
|
||||
// noticed the update yet. Polling status.Replicas is not safe in the latter
|
||||
// case.
|
||||
desiredGeneration := deployment.Generation
|
||||
|
||||
return func() (bool, error) {
|
||||
deployment, err := dClient.Deployments(deployment.Namespace).Get(deployment.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return deployment.Status.ObservedGeneration >= desiredGeneration &&
|
||||
deployment.Status.UpdatedReplicas == deployment.Spec.Replicas, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ErrPodCompleted is returned by PodRunning or PodContainerRunning to indicate that
|
||||
// the pod has already reached completed state.
|
||||
var ErrPodCompleted = fmt.Errorf("pod ran to completion")
|
||||
|
||||
// ErrContainerTerminated is returned by PodContainerRunning in the intermediate
|
||||
// state where the pod indicates it's still running, but its container is already terminated
|
||||
var ErrContainerTerminated = fmt.Errorf("container terminated")
|
||||
|
||||
// PodRunning returns true if the pod is running, false if the pod has not yet reached running state,
|
||||
// returns ErrPodCompleted if the pod has run to completion, or an error in any other case.
|
||||
func PodRunning(event watch.Event) (bool, error) {
|
||||
switch event.Type {
|
||||
case watch.Deleted:
|
||||
return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
|
||||
}
|
||||
switch t := event.Object.(type) {
|
||||
case *api.Pod:
|
||||
switch t.Status.Phase {
|
||||
case api.PodRunning:
|
||||
return true, nil
|
||||
case api.PodFailed, api.PodSucceeded:
|
||||
return false, ErrPodCompleted
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// PodCompleted returns true if the pod has run to completion, false if the pod has not yet
|
||||
// reached running state, or an error in any other case.
|
||||
func PodCompleted(event watch.Event) (bool, error) {
|
||||
switch event.Type {
|
||||
case watch.Deleted:
|
||||
return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
|
||||
}
|
||||
switch t := event.Object.(type) {
|
||||
case *api.Pod:
|
||||
switch t.Status.Phase {
|
||||
case api.PodFailed, api.PodSucceeded:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// PodRunningAndReady returns true if the pod is running and ready, false if the pod has not
|
||||
// yet reached those states, returns ErrPodCompleted if the pod has run to completion, or
|
||||
// an error in any other case.
|
||||
func PodRunningAndReady(event watch.Event) (bool, error) {
|
||||
switch event.Type {
|
||||
case watch.Deleted:
|
||||
return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
|
||||
}
|
||||
switch t := event.Object.(type) {
|
||||
case *api.Pod:
|
||||
switch t.Status.Phase {
|
||||
case api.PodFailed, api.PodSucceeded:
|
||||
return false, ErrPodCompleted
|
||||
case api.PodRunning:
|
||||
return api.IsPodReady(t), nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// PodNotPending returns true if the pod has left the pending state, false if it has not,
|
||||
// or an error in any other case (such as if the pod was deleted).
|
||||
func PodNotPending(event watch.Event) (bool, error) {
|
||||
switch event.Type {
|
||||
case watch.Deleted:
|
||||
return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
|
||||
}
|
||||
switch t := event.Object.(type) {
|
||||
case *api.Pod:
|
||||
switch t.Status.Phase {
|
||||
case api.PodPending:
|
||||
return false, nil
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// PodContainerRunning returns false until the named container has ContainerStatus running (at least once),
|
||||
// and will return an error if the pod is deleted, runs to completion, or the container pod is not available.
|
||||
func PodContainerRunning(containerName string) watch.ConditionFunc {
|
||||
return func(event watch.Event) (bool, error) {
|
||||
switch event.Type {
|
||||
case watch.Deleted:
|
||||
return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
|
||||
}
|
||||
switch t := event.Object.(type) {
|
||||
case *api.Pod:
|
||||
switch t.Status.Phase {
|
||||
case api.PodRunning, api.PodPending:
|
||||
case api.PodFailed, api.PodSucceeded:
|
||||
return false, ErrPodCompleted
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
for _, s := range t.Status.ContainerStatuses {
|
||||
if s.Name != containerName {
|
||||
continue
|
||||
}
|
||||
if s.State.Terminated != nil {
|
||||
return false, ErrContainerTerminated
|
||||
}
|
||||
return s.State.Running != nil, nil
|
||||
}
|
||||
for _, s := range t.Status.InitContainerStatuses {
|
||||
if s.Name != containerName {
|
||||
continue
|
||||
}
|
||||
if s.State.Terminated != nil {
|
||||
return false, ErrContainerTerminated
|
||||
}
|
||||
return s.State.Running != nil, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceAccountHasSecrets returns true if the service account has at least one secret,
|
||||
// false if it does not, or an error.
|
||||
func ServiceAccountHasSecrets(event watch.Event) (bool, error) {
|
||||
switch event.Type {
|
||||
case watch.Deleted:
|
||||
return false, errors.NewNotFound(schema.GroupResource{Resource: "serviceaccounts"}, "")
|
||||
}
|
||||
switch t := event.Object.(type) {
|
||||
case *api.ServiceAccount:
|
||||
return len(t.Secrets) > 0, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
69
vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go
generated
vendored
Normal file
69
vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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 unversioned
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
// Import solely to initialize client auth plugins.
|
||||
_ "k8s.io/kubernetes/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
const (
|
||||
legacyAPIPath = "/api"
|
||||
defaultAPIPath = "/apis"
|
||||
)
|
||||
|
||||
// SetKubernetesDefaults sets default values on the provided client config for accessing the
|
||||
// Kubernetes API or returns an error if any of the defaults are impossible or invalid.
|
||||
// TODO: this method needs to be split into one that sets defaults per group, expected to be fix in PR "Refactoring clientcache.go and helper.go #14592"
|
||||
func SetKubernetesDefaults(config *restclient.Config) error {
|
||||
if config.APIPath == "" {
|
||||
config.APIPath = legacyAPIPath
|
||||
}
|
||||
if config.GroupVersion == nil || config.GroupVersion.Group != api.GroupName {
|
||||
g, err := api.Registry.Group(api.GroupName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copyGroupVersion := g.GroupVersion
|
||||
config.GroupVersion = ©GroupVersion
|
||||
}
|
||||
if config.NegotiatedSerializer == nil {
|
||||
config.NegotiatedSerializer = api.Codecs
|
||||
}
|
||||
return restclient.SetKubernetesDefaults(config)
|
||||
}
|
||||
|
||||
func setGroupDefaults(groupName string, config *restclient.Config) error {
|
||||
config.APIPath = defaultAPIPath
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = restclient.DefaultKubernetesUserAgent()
|
||||
}
|
||||
if config.GroupVersion == nil || config.GroupVersion.Group != groupName {
|
||||
g, err := api.Registry.Group(groupName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copyGroupVersion := g.GroupVersion
|
||||
config.GroupVersion = ©GroupVersion
|
||||
}
|
||||
if config.NegotiatedSerializer == nil {
|
||||
config.NegotiatedSerializer = api.Codecs
|
||||
}
|
||||
return nil
|
||||
}
|
182
vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go
generated
vendored
Normal file
182
vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
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 unversioned
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
)
|
||||
|
||||
func TestSetKubernetesDefaults(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Config restclient.Config
|
||||
After restclient.Config
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
restclient.Config{},
|
||||
restclient.Config{
|
||||
APIPath: "/api",
|
||||
ContentConfig: restclient.ContentConfig{
|
||||
GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion,
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
// Add this test back when we fixed config and SetKubernetesDefaults
|
||||
// {
|
||||
// restclient.Config{
|
||||
// GroupVersion: &schema.GroupVersion{Group: "not.a.group", Version: "not_an_api"},
|
||||
// },
|
||||
// restclient.Config{},
|
||||
// true,
|
||||
// },
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
val := &testCase.Config
|
||||
err := SetKubernetesDefaults(val)
|
||||
val.UserAgent = ""
|
||||
switch {
|
||||
case err == nil && testCase.Err:
|
||||
t.Errorf("expected error but was nil")
|
||||
continue
|
||||
case err != nil && !testCase.Err:
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
case err != nil:
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(*val, testCase.After) {
|
||||
t.Errorf("unexpected result object: %#v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperGetServerAPIVersions(t *testing.T) {
|
||||
expect := []string{"v1", "v2", "v3"}
|
||||
APIVersions := metav1.APIVersions{Versions: expect}
|
||||
expect = append(expect, "group1/v1", "group1/v2", "group2/v1", "group2/v2")
|
||||
APIGroupList := metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{
|
||||
GroupVersion: "group1/v1",
|
||||
},
|
||||
{
|
||||
GroupVersion: "group1/v2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{
|
||||
GroupVersion: "group2/v1",
|
||||
},
|
||||
{
|
||||
GroupVersion: "group2/v2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var output []byte
|
||||
var err error
|
||||
switch req.URL.Path {
|
||||
case "/api":
|
||||
output, err = json.Marshal(APIVersions)
|
||||
|
||||
case "/apis":
|
||||
output, err = json.Marshal(APIGroupList)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected encoding error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
defer server.Close()
|
||||
got, err := restclient.ServerAPIVersions(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "invalid version", Version: "one"}, NegotiatedSerializer: testapi.Default.NegotiatedSerializer()}})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
}
|
||||
if e, a := expect, got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetsCodec(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Err bool
|
||||
Prefix string
|
||||
NegotiatedSerializer runtime.NegotiatedSerializer
|
||||
}{
|
||||
api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version: {
|
||||
Err: false,
|
||||
Prefix: "/api/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version,
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
},
|
||||
// Add this test back when we fixed config and SetKubernetesDefaults
|
||||
// "invalidVersion": {true, "", nil},
|
||||
}
|
||||
for version, expected := range testCases {
|
||||
conf := &restclient.Config{
|
||||
Host: "127.0.0.1",
|
||||
ContentConfig: restclient.ContentConfig{
|
||||
GroupVersion: &schema.GroupVersion{Version: version},
|
||||
},
|
||||
}
|
||||
|
||||
var versionedPath string
|
||||
err := SetKubernetesDefaults(conf)
|
||||
if err == nil {
|
||||
_, versionedPath, err = restclient.DefaultServerURL(conf.Host, conf.APIPath, *conf.GroupVersion, false)
|
||||
}
|
||||
|
||||
switch {
|
||||
case err == nil && expected.Err:
|
||||
t.Errorf("expected error but was nil")
|
||||
continue
|
||||
case err != nil && !expected.Err:
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
case err != nil:
|
||||
continue
|
||||
}
|
||||
if e, a := expected.Prefix, versionedPath; e != a {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
if e, a := expected.NegotiatedSerializer, conf.NegotiatedSerializer; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
}
|
51
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/BUILD
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"portforward.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/kubelet/server/portforward:go_default_library",
|
||||
"//pkg/util/httpstream:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["portforward_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/server/portforward:go_default_library",
|
||||
"//pkg/util/httpstream:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
19
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package portforward adds support for SSH-like port forwarding from the client's
|
||||
// local host to remote containers.
|
||||
package portforward // import "k8s.io/kubernetes/pkg/client/unversioned/portforward"
|
340
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward.go
generated
vendored
Normal file
340
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward.go
generated
vendored
Normal file
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package portforward
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
)
|
||||
|
||||
// PortForwarder knows how to listen for local connections and forward them to
|
||||
// a remote pod via an upgraded HTTP request.
|
||||
type PortForwarder struct {
|
||||
ports []ForwardedPort
|
||||
stopChan <-chan struct{}
|
||||
|
||||
dialer httpstream.Dialer
|
||||
streamConn httpstream.Connection
|
||||
listeners []io.Closer
|
||||
Ready chan struct{}
|
||||
requestIDLock sync.Mutex
|
||||
requestID int
|
||||
out io.Writer
|
||||
errOut io.Writer
|
||||
}
|
||||
|
||||
// ForwardedPort contains a Local:Remote port pairing.
|
||||
type ForwardedPort struct {
|
||||
Local uint16
|
||||
Remote uint16
|
||||
}
|
||||
|
||||
/*
|
||||
valid port specifications:
|
||||
|
||||
5000
|
||||
- forwards from localhost:5000 to pod:5000
|
||||
|
||||
8888:5000
|
||||
- forwards from localhost:8888 to pod:5000
|
||||
|
||||
0:5000
|
||||
:5000
|
||||
- selects a random available local port,
|
||||
forwards from localhost:<random port> to pod:5000
|
||||
*/
|
||||
func parsePorts(ports []string) ([]ForwardedPort, error) {
|
||||
var forwards []ForwardedPort
|
||||
for _, portString := range ports {
|
||||
parts := strings.Split(portString, ":")
|
||||
var localString, remoteString string
|
||||
if len(parts) == 1 {
|
||||
localString = parts[0]
|
||||
remoteString = parts[0]
|
||||
} else if len(parts) == 2 {
|
||||
localString = parts[0]
|
||||
if localString == "" {
|
||||
// support :5000
|
||||
localString = "0"
|
||||
}
|
||||
remoteString = parts[1]
|
||||
} else {
|
||||
return nil, fmt.Errorf("Invalid port format '%s'", portString)
|
||||
}
|
||||
|
||||
localPort, err := strconv.ParseUint(localString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing local port '%s': %s", localString, err)
|
||||
}
|
||||
|
||||
remotePort, err := strconv.ParseUint(remoteString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing remote port '%s': %s", remoteString, err)
|
||||
}
|
||||
if remotePort == 0 {
|
||||
return nil, fmt.Errorf("Remote port must be > 0")
|
||||
}
|
||||
|
||||
forwards = append(forwards, ForwardedPort{uint16(localPort), uint16(remotePort)})
|
||||
}
|
||||
|
||||
return forwards, nil
|
||||
}
|
||||
|
||||
// New creates a new PortForwarder.
|
||||
func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
if len(ports) == 0 {
|
||||
return nil, errors.New("You must specify at least 1 port")
|
||||
}
|
||||
parsedPorts, err := parsePorts(ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PortForwarder{
|
||||
dialer: dialer,
|
||||
ports: parsedPorts,
|
||||
stopChan: stopChan,
|
||||
Ready: readyChan,
|
||||
out: out,
|
||||
errOut: errOut,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ForwardPorts formats and executes a port forwarding request. The connection will remain
|
||||
// open until stopChan is closed.
|
||||
func (pf *PortForwarder) ForwardPorts() error {
|
||||
defer pf.Close()
|
||||
|
||||
var err error
|
||||
pf.streamConn, _, err = pf.dialer.Dial(portforward.PortForwardProtocolV1Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error upgrading connection: %s", err)
|
||||
}
|
||||
defer pf.streamConn.Close()
|
||||
|
||||
return pf.forward()
|
||||
}
|
||||
|
||||
// forward dials the remote host specific in req, upgrades the request, starts
|
||||
// listeners for each port specified in ports, and forwards local connections
|
||||
// to the remote host via streams.
|
||||
func (pf *PortForwarder) forward() error {
|
||||
var err error
|
||||
|
||||
listenSuccess := false
|
||||
for _, port := range pf.ports {
|
||||
err = pf.listenOnPort(&port)
|
||||
switch {
|
||||
case err == nil:
|
||||
listenSuccess = true
|
||||
default:
|
||||
if pf.errOut != nil {
|
||||
fmt.Fprintf(pf.errOut, "Unable to listen on port %d: %v\n", port.Local, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !listenSuccess {
|
||||
return fmt.Errorf("Unable to listen on any of the requested ports: %v", pf.ports)
|
||||
}
|
||||
|
||||
if pf.Ready != nil {
|
||||
close(pf.Ready)
|
||||
}
|
||||
|
||||
// wait for interrupt or conn closure
|
||||
select {
|
||||
case <-pf.stopChan:
|
||||
case <-pf.streamConn.CloseChan():
|
||||
runtime.HandleError(errors.New("lost connection to pod"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenOnPort delegates tcp4 and tcp6 listener creation and waits for connections on both of these addresses.
|
||||
// If both listener creation fail, an error is raised.
|
||||
func (pf *PortForwarder) listenOnPort(port *ForwardedPort) error {
|
||||
errTcp4 := pf.listenOnPortAndAddress(port, "tcp4", "127.0.0.1")
|
||||
errTcp6 := pf.listenOnPortAndAddress(port, "tcp6", "[::1]")
|
||||
if errTcp4 != nil && errTcp6 != nil {
|
||||
return fmt.Errorf("All listeners failed to create with the following errors: %s, %s", errTcp4, errTcp6)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenOnPortAndAddress delegates listener creation and waits for new connections
|
||||
// in the background f
|
||||
func (pf *PortForwarder) listenOnPortAndAddress(port *ForwardedPort, protocol string, address string) error {
|
||||
listener, err := pf.getListener(protocol, address, port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pf.listeners = append(pf.listeners, listener)
|
||||
go pf.waitForConnection(listener, *port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getListener creates a listener on the interface targeted by the given hostname on the given port with
|
||||
// the given protocol. protocol is in net.Listen style which basically admits values like tcp, tcp4, tcp6
|
||||
func (pf *PortForwarder) getListener(protocol string, hostname string, port *ForwardedPort) (net.Listener, error) {
|
||||
listener, err := net.Listen(protocol, fmt.Sprintf("%s:%d", hostname, port.Local))
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("Unable to create listener: Error %s", err))
|
||||
return nil, err
|
||||
}
|
||||
listenerAddress := listener.Addr().String()
|
||||
host, localPort, _ := net.SplitHostPort(listenerAddress)
|
||||
localPortUInt, err := strconv.ParseUint(localPort, 10, 16)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing local port: %s from %s (%s)", err, listenerAddress, host)
|
||||
}
|
||||
port.Local = uint16(localPortUInt)
|
||||
if pf.out != nil {
|
||||
fmt.Fprintf(pf.out, "Forwarding from %s:%d -> %d\n", hostname, localPortUInt, port.Remote)
|
||||
}
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
// waitForConnection waits for new connections to listener and handles them in
|
||||
// the background.
|
||||
func (pf *PortForwarder) waitForConnection(listener net.Listener, port ForwardedPort) {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
// TODO consider using something like https://github.com/hydrogen18/stoppableListener?
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") {
|
||||
runtime.HandleError(fmt.Errorf("Error accepting connection on port %d: %v", port.Local, err))
|
||||
}
|
||||
return
|
||||
}
|
||||
go pf.handleConnection(conn, port)
|
||||
}
|
||||
}
|
||||
|
||||
func (pf *PortForwarder) nextRequestID() int {
|
||||
pf.requestIDLock.Lock()
|
||||
defer pf.requestIDLock.Unlock()
|
||||
id := pf.requestID
|
||||
pf.requestID++
|
||||
return id
|
||||
}
|
||||
|
||||
// handleConnection copies data between the local connection and the stream to
|
||||
// the remote server.
|
||||
func (pf *PortForwarder) handleConnection(conn net.Conn, port ForwardedPort) {
|
||||
defer conn.Close()
|
||||
|
||||
if pf.out != nil {
|
||||
fmt.Fprintf(pf.out, "Handling connection for %d\n", port.Local)
|
||||
}
|
||||
|
||||
requestID := pf.nextRequestID()
|
||||
|
||||
// create error stream
|
||||
headers := http.Header{}
|
||||
headers.Set(api.StreamType, api.StreamTypeError)
|
||||
headers.Set(api.PortHeader, fmt.Sprintf("%d", port.Remote))
|
||||
headers.Set(api.PortForwardRequestIDHeader, strconv.Itoa(requestID))
|
||||
errorStream, err := pf.streamConn.CreateStream(headers)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("error creating error stream for port %d -> %d: %v", port.Local, port.Remote, err))
|
||||
return
|
||||
}
|
||||
// we're not writing to this stream
|
||||
errorStream.Close()
|
||||
|
||||
errorChan := make(chan error)
|
||||
go func() {
|
||||
message, err := ioutil.ReadAll(errorStream)
|
||||
switch {
|
||||
case err != nil:
|
||||
errorChan <- fmt.Errorf("error reading from error stream for port %d -> %d: %v", port.Local, port.Remote, err)
|
||||
case len(message) > 0:
|
||||
errorChan <- fmt.Errorf("an error occurred forwarding %d -> %d: %v", port.Local, port.Remote, string(message))
|
||||
}
|
||||
close(errorChan)
|
||||
}()
|
||||
|
||||
// create data stream
|
||||
headers.Set(api.StreamType, api.StreamTypeData)
|
||||
dataStream, err := pf.streamConn.CreateStream(headers)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("error creating forwarding stream for port %d -> %d: %v", port.Local, port.Remote, err))
|
||||
return
|
||||
}
|
||||
|
||||
localError := make(chan struct{})
|
||||
remoteDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
// Copy from the remote side to the local port.
|
||||
if _, err := io.Copy(conn, dataStream); err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
runtime.HandleError(fmt.Errorf("error copying from remote stream to local connection: %v", err))
|
||||
}
|
||||
|
||||
// inform the select below that the remote copy is done
|
||||
close(remoteDone)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// inform server we're not sending any more data after copy unblocks
|
||||
defer dataStream.Close()
|
||||
|
||||
// Copy from the local port to the remote side.
|
||||
if _, err := io.Copy(dataStream, conn); err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
runtime.HandleError(fmt.Errorf("error copying from local connection to remote stream: %v", err))
|
||||
// break out of the select below without waiting for the other copy to finish
|
||||
close(localError)
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for either a local->remote error or for copying from remote->local to finish
|
||||
select {
|
||||
case <-remoteDone:
|
||||
case <-localError:
|
||||
}
|
||||
|
||||
// always expect something on errorChan (it may be nil)
|
||||
err = <-errorChan
|
||||
if err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (pf *PortForwarder) Close() {
|
||||
// stop all listeners
|
||||
for _, l := range pf.listeners {
|
||||
if err := l.Close(); err != nil {
|
||||
runtime.HandleError(fmt.Errorf("error closing listener: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
398
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward_test.go
generated
vendored
Normal file
398
vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward_test.go
generated
vendored
Normal file
|
@ -0,0 +1,398 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package portforward
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
)
|
||||
|
||||
type fakeDialer struct {
|
||||
dialed bool
|
||||
conn httpstream.Connection
|
||||
err error
|
||||
negotiatedProtocol string
|
||||
}
|
||||
|
||||
func (d *fakeDialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
d.dialed = true
|
||||
return d.conn, d.negotiatedProtocol, d.err
|
||||
}
|
||||
|
||||
func TestParsePortsAndNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []string
|
||||
expected []ForwardedPort
|
||||
expectParseError bool
|
||||
expectNewError bool
|
||||
}{
|
||||
{input: []string{}, expectNewError: true},
|
||||
{input: []string{"a"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{":a"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"-1"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"65536"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"0"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"0:0"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"a:5000"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"5000:a"}, expectParseError: true, expectNewError: true},
|
||||
{
|
||||
input: []string{"5000", "5000:5000", "8888:5000", "5000:8888", ":5000", "0:5000"},
|
||||
expected: []ForwardedPort{
|
||||
{5000, 5000},
|
||||
{5000, 5000},
|
||||
{8888, 5000},
|
||||
{5000, 8888},
|
||||
{0, 5000},
|
||||
{0, 5000},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
parsed, err := parsePorts(test.input)
|
||||
haveError := err != nil
|
||||
if e, a := test.expectParseError, haveError; e != a {
|
||||
t.Fatalf("%d: parsePorts: error expected=%t, got %t: %s", i, e, a, err)
|
||||
}
|
||||
|
||||
dialer := &fakeDialer{}
|
||||
expectedStopChan := make(chan struct{})
|
||||
readyChan := make(chan struct{})
|
||||
pf, err := New(dialer, test.input, expectedStopChan, readyChan, os.Stdout, os.Stderr)
|
||||
haveError = err != nil
|
||||
if e, a := test.expectNewError, haveError; e != a {
|
||||
t.Fatalf("%d: New: error expected=%t, got %t: %s", i, e, a, err)
|
||||
}
|
||||
|
||||
if test.expectParseError || test.expectNewError {
|
||||
continue
|
||||
}
|
||||
|
||||
for pi, expectedPort := range test.expected {
|
||||
if e, a := expectedPort.Local, parsed[pi].Local; e != a {
|
||||
t.Fatalf("%d: local expected: %d, got: %d", i, e, a)
|
||||
}
|
||||
if e, a := expectedPort.Remote, parsed[pi].Remote; e != a {
|
||||
t.Fatalf("%d: remote expected: %d, got: %d", i, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
if dialer.dialed {
|
||||
t.Fatalf("%d: expected not dialed", i)
|
||||
}
|
||||
if e, a := test.expected, pf.ports; !reflect.DeepEqual(e, a) {
|
||||
t.Fatalf("%d: ports: expected %#v, got %#v", i, e, a)
|
||||
}
|
||||
if e, a := expectedStopChan, pf.stopChan; e != a {
|
||||
t.Fatalf("%d: stopChan: expected %#v, got %#v", i, e, a)
|
||||
}
|
||||
if pf.Ready == nil {
|
||||
t.Fatalf("%d: Ready should be non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type GetListenerTestCase struct {
|
||||
Hostname string
|
||||
Protocol string
|
||||
ShouldRaiseError bool
|
||||
ExpectedListenerAddress string
|
||||
}
|
||||
|
||||
func TestGetListener(t *testing.T) {
|
||||
var pf PortForwarder
|
||||
testCases := []GetListenerTestCase{
|
||||
{
|
||||
Hostname: "localhost",
|
||||
Protocol: "tcp4",
|
||||
ShouldRaiseError: false,
|
||||
ExpectedListenerAddress: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
Hostname: "127.0.0.1",
|
||||
Protocol: "tcp4",
|
||||
ShouldRaiseError: false,
|
||||
ExpectedListenerAddress: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
Hostname: "[::1]",
|
||||
Protocol: "tcp6",
|
||||
ShouldRaiseError: false,
|
||||
ExpectedListenerAddress: "::1",
|
||||
},
|
||||
{
|
||||
Hostname: "[::1]",
|
||||
Protocol: "tcp4",
|
||||
ShouldRaiseError: true,
|
||||
},
|
||||
{
|
||||
Hostname: "127.0.0.1",
|
||||
Protocol: "tcp6",
|
||||
ShouldRaiseError: true,
|
||||
},
|
||||
{
|
||||
// IPv6 address must be put into brackets. This test reveals this.
|
||||
Hostname: "::1",
|
||||
Protocol: "tcp6",
|
||||
ShouldRaiseError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
expectedListenerPort := "12345"
|
||||
listener, err := pf.getListener(testCase.Protocol, testCase.Hostname, &ForwardedPort{12345, 12345})
|
||||
if err != nil && strings.Contains(err.Error(), "cannot assign requested address") {
|
||||
t.Logf("Can't test #%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
errorRaised := err != nil
|
||||
|
||||
if testCase.ShouldRaiseError != errorRaised {
|
||||
t.Errorf("Test case #%d failed: Data %v an error has been raised(%t) where it should not (or reciprocally): %v", i, testCase, testCase.ShouldRaiseError, err)
|
||||
continue
|
||||
}
|
||||
if errorRaised {
|
||||
continue
|
||||
}
|
||||
|
||||
if listener == nil {
|
||||
t.Errorf("Test case #%d did not raise an error but failed in initializing listener", i)
|
||||
continue
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(listener.Addr().String())
|
||||
t.Logf("Asked a %s forward for: %s:%v, got listener %s:%s, expected: %s", testCase.Protocol, testCase.Hostname, 12345, host, port, expectedListenerPort)
|
||||
if host != testCase.ExpectedListenerAddress {
|
||||
t.Errorf("Test case #%d failed: Listener does not listen on exepected address: asked %v got %v", i, testCase.ExpectedListenerAddress, host)
|
||||
}
|
||||
if port != expectedListenerPort {
|
||||
t.Errorf("Test case #%d failed: Listener does not listen on exepected port: asked %v got %v", i, expectedListenerPort, port)
|
||||
|
||||
}
|
||||
listener.Close()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// fakePortForwarder simulates port forwarding for testing. It implements
|
||||
// portforward.PortForwarder.
|
||||
type fakePortForwarder struct {
|
||||
lock sync.Mutex
|
||||
// stores data expected from the stream per port
|
||||
expected map[uint16]string
|
||||
// stores data received from the stream per port
|
||||
received map[uint16]string
|
||||
// data to be sent to the stream per port
|
||||
send map[uint16]string
|
||||
}
|
||||
|
||||
var _ portforward.PortForwarder = &fakePortForwarder{}
|
||||
|
||||
func (pf *fakePortForwarder) PortForward(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error {
|
||||
defer stream.Close()
|
||||
|
||||
// read from the client
|
||||
received := make([]byte, len(pf.expected[port]))
|
||||
n, err := stream.Read(received)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading from client for port %d: %v", port, err)
|
||||
}
|
||||
if n != len(pf.expected[port]) {
|
||||
return fmt.Errorf("unexpected length read from client for port %d: got %d, expected %d. data=%q", port, n, len(pf.expected[port]), string(received))
|
||||
}
|
||||
|
||||
// store the received content
|
||||
pf.lock.Lock()
|
||||
pf.received[port] = string(received)
|
||||
pf.lock.Unlock()
|
||||
|
||||
// send the hardcoded data to the client
|
||||
io.Copy(stream, strings.NewReader(pf.send[port]))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fakePortForwardServer creates an HTTP server that can handle port forwarding
|
||||
// requests.
|
||||
func fakePortForwardServer(t *testing.T, testName string, serverSends, expectedFromClient map[uint16]string) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
pf := &fakePortForwarder{
|
||||
expected: expectedFromClient,
|
||||
received: make(map[uint16]string),
|
||||
send: serverSends,
|
||||
}
|
||||
portforward.ServePortForward(w, req, pf, "pod", "uid", 0, 10*time.Second)
|
||||
|
||||
for port, expected := range expectedFromClient {
|
||||
actual, ok := pf.received[port]
|
||||
if !ok {
|
||||
t.Errorf("%s: server didn't receive any data for port %d", testName, port)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected != actual {
|
||||
t.Errorf("%s: server expected to receive %q, got %q for port %d", testName, expected, actual, port)
|
||||
}
|
||||
}
|
||||
|
||||
for port, actual := range pf.received {
|
||||
if _, ok := expectedFromClient[port]; !ok {
|
||||
t.Errorf("%s: server unexpectedly received %q for port %d", testName, actual, port)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestForwardPorts(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
ports []string
|
||||
clientSends map[uint16]string
|
||||
serverSends map[uint16]string
|
||||
}{
|
||||
"forward 1 port with no data either direction": {
|
||||
ports: []string{"5000"},
|
||||
},
|
||||
"forward 2 ports with bidirectional data": {
|
||||
ports: []string{"5001", "6000"},
|
||||
clientSends: map[uint16]string{
|
||||
5001: "abcd",
|
||||
6000: "ghij",
|
||||
},
|
||||
serverSends: map[uint16]string{
|
||||
5001: "1234",
|
||||
6000: "5678",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, test := range tests {
|
||||
server := httptest.NewServer(fakePortForwardServer(t, testName, test.serverSends, test.clientSends))
|
||||
|
||||
url, _ := url.Parse(server.URL)
|
||||
exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stopChan := make(chan struct{}, 1)
|
||||
readyChan := make(chan struct{})
|
||||
|
||||
pf, err := New(exec, test.ports, stopChan, readyChan, os.Stdout, os.Stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: unexpected error calling New: %v", testName, err)
|
||||
}
|
||||
|
||||
doneChan := make(chan error)
|
||||
go func() {
|
||||
doneChan <- pf.ForwardPorts()
|
||||
}()
|
||||
<-pf.Ready
|
||||
|
||||
for port, data := range test.clientSends {
|
||||
clientConn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
|
||||
if err != nil {
|
||||
t.Errorf("%s: error dialing %d: %s", testName, port, err)
|
||||
server.Close()
|
||||
continue
|
||||
}
|
||||
defer clientConn.Close()
|
||||
|
||||
n, err := clientConn.Write([]byte(data))
|
||||
if err != nil && err != io.EOF {
|
||||
t.Errorf("%s: Error sending data '%s': %s", testName, data, err)
|
||||
server.Close()
|
||||
continue
|
||||
}
|
||||
if n == 0 {
|
||||
t.Errorf("%s: unexpected write of 0 bytes", testName)
|
||||
server.Close()
|
||||
continue
|
||||
}
|
||||
b := make([]byte, 4)
|
||||
n, err = clientConn.Read(b)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Errorf("%s: Error reading data: %s", testName, err)
|
||||
server.Close()
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal([]byte(test.serverSends[port]), b) {
|
||||
t.Errorf("%s: expected to read '%s', got '%s'", testName, test.serverSends[port], b)
|
||||
server.Close()
|
||||
continue
|
||||
}
|
||||
}
|
||||
// tell r.ForwardPorts to stop
|
||||
close(stopChan)
|
||||
|
||||
// wait for r.ForwardPorts to actually return
|
||||
err = <-doneChan
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %s", testName, err)
|
||||
}
|
||||
server.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) {
|
||||
server := httptest.NewServer(fakePortForwardServer(t, "allBindsFailed", nil, nil))
|
||||
defer server.Close()
|
||||
|
||||
url, _ := url.Parse(server.URL)
|
||||
exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stopChan1 := make(chan struct{}, 1)
|
||||
defer close(stopChan1)
|
||||
readyChan1 := make(chan struct{})
|
||||
|
||||
pf1, err := New(exec, []string{"5555"}, stopChan1, readyChan1, os.Stdout, os.Stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating pf1: %v", err)
|
||||
}
|
||||
go pf1.ForwardPorts()
|
||||
<-pf1.Ready
|
||||
|
||||
stopChan2 := make(chan struct{}, 1)
|
||||
readyChan2 := make(chan struct{})
|
||||
pf2, err := New(exec, []string{"5555"}, stopChan2, readyChan2, os.Stdout, os.Stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating pf2: %v", err)
|
||||
}
|
||||
if err := pf2.ForwardPorts(); err == nil {
|
||||
t.Fatal("expected non-nil error for pf2.ForwardPorts")
|
||||
}
|
||||
}
|
72
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/BUILD
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"errorstream.go",
|
||||
"remotecommand.go",
|
||||
"v1.go",
|
||||
"v2.go",
|
||||
"v3.go",
|
||||
"v4.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/client/transport:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/util/exec:go_default_library",
|
||||
"//pkg/util/httpstream:go_default_library",
|
||||
"//pkg/util/httpstream/spdy:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"remotecommand_test.go",
|
||||
"v2_test.go",
|
||||
"v4_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/util/httpstream:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/require",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
20
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package remotecommand adds support for executing commands in containers,
|
||||
// with support for separate stdin, stdout, and stderr streams, as well as
|
||||
// TTY.
|
||||
package remotecommand // import "k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
55
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/errorstream.go
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/errorstream.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// errorStreamDecoder interprets the data on the error channel and creates a go error object from it.
|
||||
type errorStreamDecoder interface {
|
||||
decode(message []byte) error
|
||||
}
|
||||
|
||||
// watchErrorStream watches the errorStream for remote command error data,
|
||||
// decodes it with the given errorStreamDecoder, sends the decoded error (or nil if the remote
|
||||
// command exited successfully) to the returned error channel, and closes it.
|
||||
// This function returns immediately.
|
||||
func watchErrorStream(errorStream io.Reader, d errorStreamDecoder) chan error {
|
||||
errorChan := make(chan error)
|
||||
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
message, err := ioutil.ReadAll(errorStream)
|
||||
switch {
|
||||
case err != nil && err != io.EOF:
|
||||
errorChan <- fmt.Errorf("error reading from error stream: %s", err)
|
||||
case len(message) > 0:
|
||||
errorChan <- d.decode(message)
|
||||
default:
|
||||
errorChan <- nil
|
||||
}
|
||||
close(errorChan)
|
||||
}()
|
||||
|
||||
return errorChan
|
||||
}
|
179
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand.go
generated
vendored
Normal file
179
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand.go
generated
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/transport"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream/spdy"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
// StreamOptions holds information pertaining to the current streaming session: supported stream
|
||||
// protocols, input/output streams, if the client is requesting a TTY, and a terminal size queue to
|
||||
// support terminal resizing.
|
||||
type StreamOptions struct {
|
||||
SupportedProtocols []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Tty bool
|
||||
TerminalSizeQueue term.TerminalSizeQueue
|
||||
}
|
||||
|
||||
// Executor is an interface for transporting shell-style streams.
|
||||
type Executor interface {
|
||||
// Stream initiates the transport of the standard shell streams. It will transport any
|
||||
// non-nil stream to a remote system, and return an error if a problem occurs. If tty
|
||||
// is set, the stderr stream is not used (raw TTY manages stdout and stderr over the
|
||||
// stdout stream).
|
||||
Stream(options StreamOptions) error
|
||||
}
|
||||
|
||||
// StreamExecutor supports the ability to dial an httpstream connection and the ability to
|
||||
// run a command line stream protocol over that dialer.
|
||||
type StreamExecutor interface {
|
||||
Executor
|
||||
httpstream.Dialer
|
||||
}
|
||||
|
||||
// streamExecutor handles transporting standard shell streams over an httpstream connection.
|
||||
type streamExecutor struct {
|
||||
upgrader httpstream.UpgradeRoundTripper
|
||||
transport http.RoundTripper
|
||||
|
||||
method string
|
||||
url *url.URL
|
||||
}
|
||||
|
||||
// NewExecutor connects to the provided server and upgrades the connection to
|
||||
// multiplexed bidirectional streams. The current implementation uses SPDY,
|
||||
// but this could be replaced with HTTP/2 once it's available, or something else.
|
||||
// TODO: the common code between this and portforward could be abstracted.
|
||||
func NewExecutor(config *restclient.Config, method string, url *url.URL) (StreamExecutor, error) {
|
||||
tlsConfig, err := restclient.TLSConfigFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig)
|
||||
wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &streamExecutor{
|
||||
upgrader: upgradeRoundTripper,
|
||||
transport: wrapper,
|
||||
method: method,
|
||||
url: url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewStreamExecutor upgrades the request so that it supports multiplexed bidirectional
|
||||
// streams. This method takes a stream upgrader and an optional function that is invoked
|
||||
// to wrap the round tripper. This method may be used by clients that are lower level than
|
||||
// Kubernetes clients or need to provide their own upgrade round tripper.
|
||||
func NewStreamExecutor(upgrader httpstream.UpgradeRoundTripper, fn func(http.RoundTripper) http.RoundTripper, method string, url *url.URL) (StreamExecutor, error) {
|
||||
rt := http.RoundTripper(upgrader)
|
||||
if fn != nil {
|
||||
rt = fn(rt)
|
||||
}
|
||||
return &streamExecutor{
|
||||
upgrader: upgrader,
|
||||
transport: rt,
|
||||
method: method,
|
||||
url: url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Dial opens a connection to a remote server and attempts to negotiate a SPDY
|
||||
// connection. Upon success, it returns the connection and the protocol
|
||||
// selected by the server.
|
||||
func (e *streamExecutor) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
rt := transport.DebugWrappers(e.transport)
|
||||
|
||||
// TODO the client probably shouldn't be created here, as it doesn't allow
|
||||
// flexibility to allow callers to configure it.
|
||||
client := &http.Client{Transport: rt}
|
||||
|
||||
req, err := http.NewRequest(e.method, e.url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error creating request: %v", err)
|
||||
}
|
||||
for i := range protocols {
|
||||
req.Header.Add(httpstream.HeaderProtocolVersion, protocols[i])
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error sending request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
conn, err := e.upgrader.NewConnection(resp)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil
|
||||
}
|
||||
|
||||
type streamCreator interface {
|
||||
CreateStream(headers http.Header) (httpstream.Stream, error)
|
||||
}
|
||||
|
||||
type streamProtocolHandler interface {
|
||||
stream(conn streamCreator) error
|
||||
}
|
||||
|
||||
// Stream opens a protocol streamer to the server and streams until a client closes
|
||||
// the connection or the server disconnects.
|
||||
func (e *streamExecutor) Stream(options StreamOptions) error {
|
||||
conn, protocol, err := e.Dial(options.SupportedProtocols...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var streamer streamProtocolHandler
|
||||
|
||||
switch protocol {
|
||||
case remotecommand.StreamProtocolV4Name:
|
||||
streamer = newStreamProtocolV4(options)
|
||||
case remotecommand.StreamProtocolV3Name:
|
||||
streamer = newStreamProtocolV3(options)
|
||||
case remotecommand.StreamProtocolV2Name:
|
||||
streamer = newStreamProtocolV2(options)
|
||||
case "":
|
||||
glog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case remotecommand.StreamProtocolV1Name:
|
||||
streamer = newStreamProtocolV1(options)
|
||||
}
|
||||
|
||||
return streamer.stream(conn)
|
||||
}
|
369
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand_test.go
generated
vendored
Normal file
369
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand_test.go
generated
vendored
Normal file
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
type fakeExecutor struct {
|
||||
t *testing.T
|
||||
testName string
|
||||
errorData string
|
||||
stdoutData string
|
||||
stderrData string
|
||||
expectStdin bool
|
||||
stdinReceived bytes.Buffer
|
||||
tty bool
|
||||
messageCount int
|
||||
command []string
|
||||
exec bool
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
return ex.run(name, uid, container, cmd, in, out, err, tty)
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
return ex.run(name, uid, container, nil, in, out, err, tty)
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) run(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error {
|
||||
ex.command = cmd
|
||||
ex.tty = tty
|
||||
|
||||
if e, a := "pod", name; e != a {
|
||||
ex.t.Errorf("%s: pod: expected %q, got %q", ex.testName, e, a)
|
||||
}
|
||||
if e, a := "uid", uid; e != string(a) {
|
||||
ex.t.Errorf("%s: uid: expected %q, got %q", ex.testName, e, a)
|
||||
}
|
||||
if ex.exec {
|
||||
if e, a := "ls /", strings.Join(ex.command, " "); e != a {
|
||||
ex.t.Errorf("%s: command: expected %q, got %q", ex.testName, e, a)
|
||||
}
|
||||
} else {
|
||||
if len(ex.command) > 0 {
|
||||
ex.t.Errorf("%s: command: expected nothing, got %v", ex.testName, ex.command)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ex.errorData) > 0 {
|
||||
return errors.New(ex.errorData)
|
||||
}
|
||||
|
||||
if len(ex.stdoutData) > 0 {
|
||||
for i := 0; i < ex.messageCount; i++ {
|
||||
fmt.Fprint(out, ex.stdoutData)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ex.stderrData) > 0 {
|
||||
for i := 0; i < ex.messageCount; i++ {
|
||||
fmt.Fprint(err, ex.stderrData)
|
||||
}
|
||||
}
|
||||
|
||||
if ex.expectStdin {
|
||||
io.Copy(&ex.stdinReceived, in)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fakeServer(t *testing.T, testName string, exec bool, stdinData, stdoutData, stderrData, errorData string, tty bool, messageCount int, serverProtocols []string) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
executor := &fakeExecutor{
|
||||
t: t,
|
||||
testName: testName,
|
||||
errorData: errorData,
|
||||
stdoutData: stdoutData,
|
||||
stderrData: stderrData,
|
||||
expectStdin: len(stdinData) > 0,
|
||||
tty: tty,
|
||||
messageCount: messageCount,
|
||||
exec: exec,
|
||||
}
|
||||
|
||||
opts, err := remotecommand.NewOptions(req)
|
||||
require.NoError(t, err)
|
||||
if exec {
|
||||
cmd := req.URL.Query()[api.ExecCommandParamm]
|
||||
remotecommand.ServeExec(w, req, executor, "pod", "uid", "container", cmd, opts, 0, 10*time.Second, serverProtocols)
|
||||
} else {
|
||||
remotecommand.ServeAttach(w, req, executor, "pod", "uid", "container", opts, 0, 10*time.Second, serverProtocols)
|
||||
}
|
||||
|
||||
if e, a := strings.Repeat(stdinData, messageCount), executor.stdinReceived.String(); e != a {
|
||||
t.Errorf("%s: stdin: expected %q, got %q", testName, e, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
testCases := []struct {
|
||||
TestName string
|
||||
Stdin string
|
||||
Stdout string
|
||||
Stderr string
|
||||
Error string
|
||||
Tty bool
|
||||
MessageCount int
|
||||
ClientProtocols []string
|
||||
ServerProtocols []string
|
||||
}{
|
||||
{
|
||||
TestName: "error",
|
||||
Error: "bail",
|
||||
Stdout: "a",
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
},
|
||||
{
|
||||
TestName: "in/out/err",
|
||||
Stdin: "a",
|
||||
Stdout: "b",
|
||||
Stderr: "c",
|
||||
MessageCount: 100,
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
},
|
||||
{
|
||||
TestName: "in/out/tty",
|
||||
Stdin: "a",
|
||||
Stdout: "b",
|
||||
Tty: true,
|
||||
MessageCount: 100,
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
},
|
||||
{
|
||||
// 1.0 kubectl, 1.0 kubelet
|
||||
TestName: "unversioned client, unversioned server",
|
||||
Stdout: "b",
|
||||
Stderr: "c",
|
||||
MessageCount: 1,
|
||||
ClientProtocols: []string{},
|
||||
ServerProtocols: []string{},
|
||||
},
|
||||
{
|
||||
// 1.0 kubectl, 1.1+ kubelet
|
||||
TestName: "unversioned client, versioned server",
|
||||
Stdout: "b",
|
||||
Stderr: "c",
|
||||
MessageCount: 1,
|
||||
ClientProtocols: []string{},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name},
|
||||
},
|
||||
{
|
||||
// 1.1+ kubectl, 1.0 kubelet
|
||||
TestName: "versioned client, unversioned server",
|
||||
Stdout: "b",
|
||||
Stderr: "c",
|
||||
MessageCount: 1,
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name},
|
||||
ServerProtocols: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
for _, exec := range []bool{true, false} {
|
||||
var name string
|
||||
if exec {
|
||||
name = testCase.TestName + " (exec)"
|
||||
} else {
|
||||
name = testCase.TestName + " (attach)"
|
||||
}
|
||||
var (
|
||||
streamIn io.Reader
|
||||
streamOut, streamErr io.Writer
|
||||
)
|
||||
localOut := &bytes.Buffer{}
|
||||
localErr := &bytes.Buffer{}
|
||||
|
||||
server := httptest.NewServer(fakeServer(t, name, exec, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, testCase.MessageCount, testCase.ServerProtocols))
|
||||
|
||||
url, _ := url.ParseRequestURI(server.URL)
|
||||
config := restclient.ContentConfig{
|
||||
GroupVersion: &schema.GroupVersion{Group: "x"},
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
}
|
||||
c, err := restclient.NewRESTClient(url, "", config, -1, -1, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a client: %v", err)
|
||||
}
|
||||
req := c.Post().Resource("testing")
|
||||
|
||||
if exec {
|
||||
req.Param("command", "ls")
|
||||
req.Param("command", "/")
|
||||
}
|
||||
|
||||
if len(testCase.Stdin) > 0 {
|
||||
req.Param(api.ExecStdinParam, "1")
|
||||
streamIn = strings.NewReader(strings.Repeat(testCase.Stdin, testCase.MessageCount))
|
||||
}
|
||||
|
||||
if len(testCase.Stdout) > 0 {
|
||||
req.Param(api.ExecStdoutParam, "1")
|
||||
streamOut = localOut
|
||||
}
|
||||
|
||||
if testCase.Tty {
|
||||
req.Param(api.ExecTTYParam, "1")
|
||||
} else if len(testCase.Stderr) > 0 {
|
||||
req.Param(api.ExecStderrParam, "1")
|
||||
streamErr = localErr
|
||||
}
|
||||
|
||||
conf := &restclient.Config{
|
||||
Host: server.URL,
|
||||
}
|
||||
e, err := NewExecutor(conf, "POST", req.URL())
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", name, err)
|
||||
continue
|
||||
}
|
||||
err = e.Stream(StreamOptions{
|
||||
SupportedProtocols: testCase.ClientProtocols,
|
||||
Stdin: streamIn,
|
||||
Stdout: streamOut,
|
||||
Stderr: streamErr,
|
||||
Tty: testCase.Tty,
|
||||
})
|
||||
hasErr := err != nil
|
||||
|
||||
if len(testCase.Error) > 0 {
|
||||
if !hasErr {
|
||||
t.Errorf("%s: expected an error", name)
|
||||
} else {
|
||||
if e, a := testCase.Error, err.Error(); !strings.Contains(a, e) {
|
||||
t.Errorf("%s: expected error stream read %q, got %q", name, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
server.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
if hasErr {
|
||||
t.Errorf("%s: unexpected error: %v", name, err)
|
||||
server.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
if len(testCase.Stdout) > 0 {
|
||||
if e, a := strings.Repeat(testCase.Stdout, testCase.MessageCount), localOut; e != a.String() {
|
||||
t.Errorf("%s: expected stdout data %q, got %q", name, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
if testCase.Stderr != "" {
|
||||
if e, a := strings.Repeat(testCase.Stderr, testCase.MessageCount), localErr; e != a.String() {
|
||||
t.Errorf("%s: expected stderr data %q, got %q", name, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
server.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type fakeUpgrader struct {
|
||||
req *http.Request
|
||||
resp *http.Response
|
||||
conn httpstream.Connection
|
||||
err, connErr error
|
||||
checkResponse bool
|
||||
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (u *fakeUpgrader) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
u.req = req
|
||||
return u.resp, u.err
|
||||
}
|
||||
|
||||
func (u *fakeUpgrader) NewConnection(resp *http.Response) (httpstream.Connection, error) {
|
||||
if u.checkResponse && u.resp != resp {
|
||||
u.t.Errorf("response objects passed did not match: %#v", resp)
|
||||
}
|
||||
return u.conn, u.connErr
|
||||
}
|
||||
|
||||
type fakeConnection struct {
|
||||
httpstream.Connection
|
||||
}
|
||||
|
||||
// Dial is the common functionality between any stream based upgrader, regardless of protocol.
|
||||
// This method ensures that someone can use a generic stream executor without being dependent
|
||||
// on the core Kube client config behavior.
|
||||
func TestDial(t *testing.T) {
|
||||
upgrader := &fakeUpgrader{
|
||||
t: t,
|
||||
checkResponse: true,
|
||||
conn: &fakeConnection{},
|
||||
resp: &http.Response{
|
||||
StatusCode: http.StatusSwitchingProtocols,
|
||||
Body: ioutil.NopCloser(&bytes.Buffer{}),
|
||||
},
|
||||
}
|
||||
var called bool
|
||||
testFn := func(rt http.RoundTripper) http.RoundTripper {
|
||||
if rt != upgrader {
|
||||
t.Fatalf("unexpected round tripper: %#v", rt)
|
||||
}
|
||||
called = true
|
||||
return rt
|
||||
}
|
||||
exec, err := NewStreamExecutor(upgrader, testFn, "POST", &url.URL{Host: "something.com", Scheme: "https"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn, protocol, err := exec.Dial("protocol1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conn != upgrader.conn {
|
||||
t.Errorf("unexpected connection: %#v", conn)
|
||||
}
|
||||
if !called {
|
||||
t.Errorf("wrapper not called")
|
||||
}
|
||||
_ = protocol
|
||||
}
|
160
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v1.go
generated
vendored
Normal file
160
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v1.go
generated
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
)
|
||||
|
||||
// streamProtocolV1 implements the first version of the streaming exec & attach
|
||||
// protocol. This version has some bugs, such as not being able to detect when
|
||||
// non-interactive stdin data has ended. See http://issues.k8s.io/13394 and
|
||||
// http://issues.k8s.io/13395 for more details.
|
||||
type streamProtocolV1 struct {
|
||||
StreamOptions
|
||||
|
||||
errorStream httpstream.Stream
|
||||
remoteStdin httpstream.Stream
|
||||
remoteStdout httpstream.Stream
|
||||
remoteStderr httpstream.Stream
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV1{}
|
||||
|
||||
func newStreamProtocolV1(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV1{
|
||||
StreamOptions: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV1) stream(conn streamCreator) error {
|
||||
doneChan := make(chan struct{}, 2)
|
||||
errorChan := make(chan error)
|
||||
|
||||
cp := func(s string, dst io.Writer, src io.Reader) {
|
||||
glog.V(6).Infof("Copying %s", s)
|
||||
defer glog.V(6).Infof("Done copying %s", s)
|
||||
if _, err := io.Copy(dst, src); err != nil && err != io.EOF {
|
||||
glog.Errorf("Error copying %s: %v", s, err)
|
||||
}
|
||||
if s == api.StreamTypeStdout || s == api.StreamTypeStderr {
|
||||
doneChan <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// set up all the streams first
|
||||
var err error
|
||||
headers := http.Header{}
|
||||
headers.Set(api.StreamType, api.StreamTypeError)
|
||||
p.errorStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.errorStream.Reset()
|
||||
|
||||
// Create all the streams first, then start the copy goroutines. The server doesn't start its copy
|
||||
// goroutines until it's received all of the streams. If the client creates the stdin stream and
|
||||
// immediately begins copying stdin data to the server, it's possible to overwhelm and wedge the
|
||||
// spdy frame handler in the server so that it is full of unprocessed frames. The frames aren't
|
||||
// getting processed because the server hasn't started its copying, and it won't do that until it
|
||||
// gets all the streams. By creating all the streams first, we ensure that the server is ready to
|
||||
// process data before the client starts sending any. See https://issues.k8s.io/16373 for more info.
|
||||
if p.Stdin != nil {
|
||||
headers.Set(api.StreamType, api.StreamTypeStdin)
|
||||
p.remoteStdin, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStdin.Reset()
|
||||
}
|
||||
|
||||
if p.Stdout != nil {
|
||||
headers.Set(api.StreamType, api.StreamTypeStdout)
|
||||
p.remoteStdout, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStdout.Reset()
|
||||
}
|
||||
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
headers.Set(api.StreamType, api.StreamTypeStderr)
|
||||
p.remoteStderr, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStderr.Reset()
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
// always read from errorStream
|
||||
go func() {
|
||||
message, err := ioutil.ReadAll(p.errorStream)
|
||||
if err != nil && err != io.EOF {
|
||||
errorChan <- fmt.Errorf("Error reading from error stream: %s", err)
|
||||
return
|
||||
}
|
||||
if len(message) > 0 {
|
||||
errorChan <- fmt.Errorf("Error executing remote command: %s", message)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if p.Stdin != nil {
|
||||
// TODO this goroutine will never exit cleanly (the io.Copy never unblocks)
|
||||
// because stdin is not closed until the process exits. If we try to call
|
||||
// stdin.Close(), it returns no error but doesn't unblock the copy. It will
|
||||
// exit when the process exits, instead.
|
||||
go cp(api.StreamTypeStdin, p.remoteStdin, p.Stdin)
|
||||
}
|
||||
|
||||
waitCount := 0
|
||||
completedStreams := 0
|
||||
|
||||
if p.Stdout != nil {
|
||||
waitCount++
|
||||
go cp(api.StreamTypeStdout, p.Stdout, p.remoteStdout)
|
||||
}
|
||||
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
waitCount++
|
||||
go cp(api.StreamTypeStderr, p.Stderr, p.remoteStderr)
|
||||
}
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case <-doneChan:
|
||||
completedStreams++
|
||||
if completedStreams == waitCount {
|
||||
break Loop
|
||||
}
|
||||
case err := <-errorChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
195
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v2.go
generated
vendored
Normal file
195
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v2.go
generated
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// streamProtocolV2 implements version 2 of the streaming protocol for attach
|
||||
// and exec. The original streaming protocol was metav1. As a result, this
|
||||
// version is referred to as version 2, even though it is the first actual
|
||||
// numbered version.
|
||||
type streamProtocolV2 struct {
|
||||
StreamOptions
|
||||
|
||||
errorStream io.Reader
|
||||
remoteStdin io.ReadWriteCloser
|
||||
remoteStdout io.Reader
|
||||
remoteStderr io.Reader
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV2{}
|
||||
|
||||
func newStreamProtocolV2(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV2{
|
||||
StreamOptions: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) createStreams(conn streamCreator) error {
|
||||
var err error
|
||||
headers := http.Header{}
|
||||
|
||||
// set up error stream
|
||||
headers.Set(api.StreamType, api.StreamTypeError)
|
||||
p.errorStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set up stdin stream
|
||||
if p.Stdin != nil {
|
||||
headers.Set(api.StreamType, api.StreamTypeStdin)
|
||||
p.remoteStdin, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set up stdout stream
|
||||
if p.Stdout != nil {
|
||||
headers.Set(api.StreamType, api.StreamTypeStdout)
|
||||
p.remoteStdout, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set up stderr stream
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
headers.Set(api.StreamType, api.StreamTypeStderr)
|
||||
p.remoteStderr, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStdin() {
|
||||
if p.Stdin != nil {
|
||||
var once sync.Once
|
||||
|
||||
// copy from client's stdin to container's stdin
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
// if p.stdin is noninteractive, p.g. `echo abc | kubectl exec -i <pod> -- cat`, make sure
|
||||
// we close remoteStdin as soon as the copy from p.stdin to remoteStdin finishes. Otherwise
|
||||
// the executed command will remain running.
|
||||
defer once.Do(func() { p.remoteStdin.Close() })
|
||||
|
||||
if _, err := io.Copy(p.remoteStdin, p.Stdin); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// read from remoteStdin until the stream is closed. this is essential to
|
||||
// be able to exit interactive sessions cleanly and not leak goroutines or
|
||||
// hang the client's terminal.
|
||||
//
|
||||
// TODO we aren't using go-dockerclient any more; revisit this to determine if it's still
|
||||
// required by engine-api.
|
||||
//
|
||||
// go-dockerclient's current hijack implementation
|
||||
// (https://github.com/fsouza/go-dockerclient/blob/89f3d56d93788dfe85f864a44f85d9738fca0670/client.go#L564)
|
||||
// waits for all three streams (stdin/stdout/stderr) to finish copying
|
||||
// before returning. When hijack finishes copying stdout/stderr, it calls
|
||||
// Close() on its side of remoteStdin, which allows this copy to complete.
|
||||
// When that happens, we must Close() on our side of remoteStdin, to
|
||||
// allow the copy in hijack to complete, and hijack to return.
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer once.Do(func() { p.remoteStdin.Close() })
|
||||
|
||||
// this "copy" doesn't actually read anything - it's just here to wait for
|
||||
// the server to close remoteStdin.
|
||||
if _, err := io.Copy(ioutil.Discard, p.remoteStdin); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStdout(wg *sync.WaitGroup) {
|
||||
if p.Stdout == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer wg.Done()
|
||||
|
||||
if _, err := io.Copy(p.Stdout, p.remoteStdout); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStderr(wg *sync.WaitGroup) {
|
||||
if p.Stderr == nil || p.Tty {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer wg.Done()
|
||||
|
||||
if _, err := io.Copy(p.Stderr, p.remoteStderr); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV2{})
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
// errorDecoderV2 interprets the error channel data as plain text.
|
||||
type errorDecoderV2 struct{}
|
||||
|
||||
func (d *errorDecoderV2) decode(message []byte) error {
|
||||
return fmt.Errorf("error executing remote command: %s", message)
|
||||
}
|
228
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v2_test.go
generated
vendored
Normal file
228
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v2_test.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
)
|
||||
|
||||
type fakeReader struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *fakeReader) Read([]byte) (int, error) { return 0, r.err }
|
||||
|
||||
type fakeWriter struct{}
|
||||
|
||||
func (*fakeWriter) Write([]byte) (int, error) { return 0, nil }
|
||||
|
||||
type fakeStreamCreator struct {
|
||||
created map[string]bool
|
||||
errors map[string]error
|
||||
}
|
||||
|
||||
var _ streamCreator = &fakeStreamCreator{}
|
||||
|
||||
func (f *fakeStreamCreator) CreateStream(headers http.Header) (httpstream.Stream, error) {
|
||||
streamType := headers.Get(api.StreamType)
|
||||
f.created[streamType] = true
|
||||
return nil, f.errors[streamType]
|
||||
}
|
||||
|
||||
func TestV2CreateStreams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
stdin bool
|
||||
stdinError error
|
||||
stdout bool
|
||||
stdoutError error
|
||||
stderr bool
|
||||
stderrError error
|
||||
errorError error
|
||||
tty bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "stdin error",
|
||||
stdin: true,
|
||||
stdinError: errors.New("stdin error"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "stdout error",
|
||||
stdout: true,
|
||||
stdoutError: errors.New("stdout error"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "stderr error",
|
||||
stderr: true,
|
||||
stderrError: errors.New("stderr error"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "error stream error",
|
||||
stdin: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
errorError: errors.New("error stream error"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "no errors",
|
||||
stdin: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "no errors, stderr & tty set, don't expect stderr",
|
||||
stdin: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
tty: true,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
conn := &fakeStreamCreator{
|
||||
created: make(map[string]bool),
|
||||
errors: map[string]error{
|
||||
api.StreamTypeStdin: test.stdinError,
|
||||
api.StreamTypeStdout: test.stdoutError,
|
||||
api.StreamTypeStderr: test.stderrError,
|
||||
api.StreamTypeError: test.errorError,
|
||||
},
|
||||
}
|
||||
|
||||
opts := StreamOptions{Tty: test.tty}
|
||||
if test.stdin {
|
||||
opts.Stdin = &fakeReader{}
|
||||
}
|
||||
if test.stdout {
|
||||
opts.Stdout = &fakeWriter{}
|
||||
}
|
||||
if test.stderr {
|
||||
opts.Stderr = &fakeWriter{}
|
||||
}
|
||||
|
||||
h := newStreamProtocolV2(opts).(*streamProtocolV2)
|
||||
err := h.createStreams(conn)
|
||||
|
||||
if test.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: expected error", test.name)
|
||||
continue
|
||||
}
|
||||
if e, a := test.stdinError, err; test.stdinError != nil && e != a {
|
||||
t.Errorf("%s: expected %v, got %v", test.name, e, a)
|
||||
}
|
||||
if e, a := test.stdoutError, err; test.stdoutError != nil && e != a {
|
||||
t.Errorf("%s: expected %v, got %v", test.name, e, a)
|
||||
}
|
||||
if e, a := test.stderrError, err; test.stderrError != nil && e != a {
|
||||
t.Errorf("%s: expected %v, got %v", test.name, e, a)
|
||||
}
|
||||
if e, a := test.errorError, err; test.errorError != nil && e != a {
|
||||
t.Errorf("%s: expected %v, got %v", test.name, e, a)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !test.expectError && err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if test.stdin && !conn.created[api.StreamTypeStdin] {
|
||||
t.Errorf("%s: expected stdin stream", test.name)
|
||||
}
|
||||
if test.stdout && !conn.created[api.StreamTypeStdout] {
|
||||
t.Errorf("%s: expected stdout stream", test.name)
|
||||
}
|
||||
if test.stderr {
|
||||
if test.tty && conn.created[api.StreamTypeStderr] {
|
||||
t.Errorf("%s: unexpected stderr stream because tty is set", test.name)
|
||||
} else if !test.tty && !conn.created[api.StreamTypeStderr] {
|
||||
t.Errorf("%s: expected stderr stream", test.name)
|
||||
}
|
||||
}
|
||||
if !conn.created[api.StreamTypeError] {
|
||||
t.Errorf("%s: expected error stream", test.name)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestV2ErrorStreamReading(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
stream io.Reader
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "error reading from stream",
|
||||
stream: &fakeReader{errors.New("foo")},
|
||||
expectedError: errors.New("error reading from error stream: foo"),
|
||||
},
|
||||
{
|
||||
name: "stream returns an error",
|
||||
stream: strings.NewReader("some error"),
|
||||
expectedError: errors.New("error executing remote command: some error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
h := newStreamProtocolV2(StreamOptions{}).(*streamProtocolV2)
|
||||
h.errorStream = test.stream
|
||||
|
||||
ch := watchErrorStream(h.errorStream, &errorDecoderV2{})
|
||||
if ch == nil {
|
||||
t.Fatalf("%s: unexpected nil channel", test.name)
|
||||
}
|
||||
|
||||
var err error
|
||||
select {
|
||||
case err = <-ch:
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Fatalf("%s: timed out", test.name)
|
||||
}
|
||||
|
||||
if test.expectedError != nil {
|
||||
if err == nil {
|
||||
t.Errorf("%s: expected an error", test.name)
|
||||
} else if e, a := test.expectedError, err; e.Error() != a.Error() {
|
||||
t.Errorf("%s: expected %q, got %q", test.name, e, a)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if test.expectedError == nil && err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
112
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v3.go
generated
vendored
Normal file
112
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v3.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// streamProtocolV3 implements version 3 of the streaming protocol for attach
|
||||
// and exec. This version adds support for resizing the container's terminal.
|
||||
type streamProtocolV3 struct {
|
||||
*streamProtocolV2
|
||||
|
||||
resizeStream io.Writer
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV3{}
|
||||
|
||||
func newStreamProtocolV3(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV3{
|
||||
streamProtocolV2: newStreamProtocolV2(options).(*streamProtocolV2),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) createStreams(conn streamCreator) error {
|
||||
// set up the streams from v2
|
||||
if err := p.streamProtocolV2.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set up resize stream
|
||||
if p.Tty {
|
||||
headers := http.Header{}
|
||||
headers.Set(api.StreamType, api.StreamTypeResize)
|
||||
var err error
|
||||
p.resizeStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) handleResizes() {
|
||||
if p.resizeStream == nil || p.TerminalSizeQueue == nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
encoder := json.NewEncoder(p.resizeStream)
|
||||
for {
|
||||
size := p.TerminalSizeQueue.Next()
|
||||
if size == nil {
|
||||
return
|
||||
}
|
||||
if err := encoder.Encode(&size); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV3{})
|
||||
|
||||
p.handleResizes()
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
type errorDecoderV3 struct {
|
||||
errorDecoderV2
|
||||
}
|
119
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v4.go
generated
vendored
Normal file
119
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v4.go
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
)
|
||||
|
||||
// streamProtocolV4 implements version 4 of the streaming protocol for attach
|
||||
// and exec. This version adds support for exit codes on the error stream through
|
||||
// the use of metav1.Status instead of plain text messages.
|
||||
type streamProtocolV4 struct {
|
||||
*streamProtocolV3
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV4{}
|
||||
|
||||
func newStreamProtocolV4(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV4{
|
||||
streamProtocolV3: newStreamProtocolV3(options).(*streamProtocolV3),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) createStreams(conn streamCreator) error {
|
||||
return p.streamProtocolV3.createStreams(conn)
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) handleResizes() {
|
||||
p.streamProtocolV3.handleResizes()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV4{})
|
||||
|
||||
p.handleResizes()
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
// errorDecoderV4 interprets the json-marshaled metav1.Status on the error channel
|
||||
// and creates an exec.ExitError from it.
|
||||
type errorDecoderV4 struct{}
|
||||
|
||||
func (d *errorDecoderV4) decode(message []byte) error {
|
||||
status := metav1.Status{}
|
||||
err := json.Unmarshal(message, &status)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stream protocol error: %v in %q", err, string(message))
|
||||
}
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
return nil
|
||||
case metav1.StatusFailure:
|
||||
if status.Reason == remotecommand.NonZeroExitCodeReason {
|
||||
if status.Details == nil {
|
||||
return errors.New("error stream protocol error: details must be set")
|
||||
}
|
||||
for i := range status.Details.Causes {
|
||||
c := &status.Details.Causes[i]
|
||||
if c.Type != remotecommand.ExitCodeCauseType {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := strconv.ParseUint(c.Message, 10, 8)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stream protocol error: invalid exit code value %q", c.Message)
|
||||
}
|
||||
return exec.CodeExitError{
|
||||
Err: fmt.Errorf("command terminated with exit code %d", rc),
|
||||
Code: int(rc),
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("error stream protocol error: no %s cause given", remotecommand.ExitCodeCauseType)
|
||||
}
|
||||
default:
|
||||
return errors.New("error stream protocol error: unknown error")
|
||||
}
|
||||
|
||||
return fmt.Errorf(status.Message)
|
||||
}
|
71
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v4_test.go
generated
vendored
Normal file
71
vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/v4_test.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestV4ErrorDecoder(t *testing.T) {
|
||||
dec := errorDecoderV4{}
|
||||
|
||||
type Test struct {
|
||||
message string
|
||||
err string
|
||||
}
|
||||
|
||||
for _, test := range []Test{
|
||||
{
|
||||
message: "{}",
|
||||
err: "error stream protocol error: unknown error",
|
||||
},
|
||||
{
|
||||
message: "{",
|
||||
err: "error stream protocol error: unexpected end of JSON input in \"{\"",
|
||||
},
|
||||
{
|
||||
message: `{"status": "Success" }`,
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
message: `{"status": "Failure", "message": "foobar" }`,
|
||||
err: "foobar",
|
||||
},
|
||||
{
|
||||
message: `{"status": "Failure", "message": "foobar", "reason": "NonZeroExitCode", "details": {"causes": [{"reason": "foo"}] } }`,
|
||||
err: "error stream protocol error: no ExitCode cause given",
|
||||
},
|
||||
{
|
||||
message: `{"status": "Failure", "message": "foobar", "reason": "NonZeroExitCode", "details": {"causes": [{"reason": "ExitCode"}] } }`,
|
||||
err: "error stream protocol error: invalid exit code value \"\"",
|
||||
},
|
||||
{
|
||||
message: `{"status": "Failure", "message": "foobar", "reason": "NonZeroExitCode", "details": {"causes": [{"reason": "ExitCode", "message": "42"}] } }`,
|
||||
err: "command terminated with exit code 42",
|
||||
},
|
||||
} {
|
||||
err := dec.decode([]byte(test.message))
|
||||
want := test.err
|
||||
if want == "" {
|
||||
want = "<nil>"
|
||||
}
|
||||
if got := fmt.Sprintf("%v", err); got != want {
|
||||
t.Errorf("wrong error for message %q: want=%q, got=%q", test.message, want, got)
|
||||
}
|
||||
}
|
||||
}
|
38
vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/BUILD
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["simple_testclient.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/util/testing:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/labels",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
222
vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/simple_testclient.go
generated
vendored
Normal file
222
vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/simple_testclient.go
generated
vendored
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package simple
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
utiltesting "k8s.io/kubernetes/pkg/util/testing"
|
||||
)
|
||||
|
||||
const NameRequiredError = "resource name may not be empty"
|
||||
|
||||
type Request struct {
|
||||
Method string
|
||||
Path string
|
||||
Header string
|
||||
Query url.Values
|
||||
Body runtime.Object
|
||||
RawBody *string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
Body runtime.Object
|
||||
RawBody *string
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Clientset *clientset.Clientset
|
||||
Request Request
|
||||
Response Response
|
||||
Error bool
|
||||
Created bool
|
||||
server *httptest.Server
|
||||
handler *utiltesting.FakeHandler
|
||||
// For query args, an optional function to validate the contents
|
||||
// useful when the contents can change but still be correct.
|
||||
// Maps from query arg key to validator.
|
||||
// If no validator is present, string equality is used.
|
||||
QueryValidator map[string]func(string, string) bool
|
||||
|
||||
// If your object could exist in multiple groups, set this to
|
||||
// correspond to the URL you're testing it with.
|
||||
ResourceGroup string
|
||||
}
|
||||
|
||||
func (c *Client) Setup(t *testing.T) *Client {
|
||||
c.handler = &utiltesting.FakeHandler{
|
||||
StatusCode: c.Response.StatusCode,
|
||||
}
|
||||
if responseBody := c.body(t, c.Response.Body, c.Response.RawBody); responseBody != nil {
|
||||
c.handler.ResponseBody = *responseBody
|
||||
}
|
||||
c.server = httptest.NewServer(c.handler)
|
||||
if c.Clientset == nil {
|
||||
c.Clientset = clientset.NewForConfigOrDie(&restclient.Config{Host: c.server.URL})
|
||||
}
|
||||
c.QueryValidator = map[string]func(string, string) bool{}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
if c.server != nil {
|
||||
c.server.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ServerURL() string {
|
||||
return c.server.URL
|
||||
}
|
||||
|
||||
func (c *Client) Validate(t *testing.T, received runtime.Object, err error) {
|
||||
c.ValidateCommon(t, err)
|
||||
|
||||
if c.Response.Body != nil && !api.Semantic.DeepDerivative(c.Response.Body, received) {
|
||||
t.Errorf("bad response for request %#v: \nexpected %#v\ngot %#v\n", c.Request, c.Response.Body, received)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ValidateRaw(t *testing.T, received []byte, err error) {
|
||||
c.ValidateCommon(t, err)
|
||||
|
||||
if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
|
||||
t.Errorf("bad response for request %#v: expected %#v, got %#v", c.Request, c.Response.Body, received)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ValidateCommon(t *testing.T, err error) {
|
||||
if c.Error {
|
||||
if err == nil {
|
||||
t.Errorf("error expected for %#v, got none", c.Request)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("no error expected for %#v, got: %v", c.Request, err)
|
||||
}
|
||||
|
||||
if c.handler.RequestReceived == nil {
|
||||
t.Errorf("handler had an empty request, %#v", c)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody := c.body(t, c.Request.Body, c.Request.RawBody)
|
||||
actualQuery := c.handler.RequestReceived.URL.Query()
|
||||
t.Logf("got query: %v", actualQuery)
|
||||
t.Logf("path: %v", c.Request.Path)
|
||||
// We check the query manually, so blank it out so that FakeHandler.ValidateRequest
|
||||
// won't check it.
|
||||
c.handler.RequestReceived.URL.RawQuery = ""
|
||||
c.handler.ValidateRequest(t, path.Join(c.Request.Path), c.Request.Method, requestBody)
|
||||
for key, values := range c.Request.Query {
|
||||
validator, ok := c.QueryValidator[key]
|
||||
if !ok {
|
||||
switch key {
|
||||
case metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()):
|
||||
validator = ValidateLabels
|
||||
case metav1.FieldSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()):
|
||||
validator = validateFields
|
||||
default:
|
||||
validator = func(a, b string) bool { return a == b }
|
||||
}
|
||||
}
|
||||
observed := actualQuery.Get(key)
|
||||
wanted := strings.Join(values, "")
|
||||
if !validator(wanted, observed) {
|
||||
t.Errorf("Unexpected query arg for key: %s. Expected %s, Received %s", key, wanted, observed)
|
||||
}
|
||||
}
|
||||
if c.Request.Header != "" {
|
||||
if c.handler.RequestReceived.Header.Get(c.Request.Header) == "" {
|
||||
t.Errorf("header %q not found in request %#v", c.Request.Header, c.handler.RequestReceived)
|
||||
}
|
||||
}
|
||||
|
||||
if expected, received := requestBody, c.handler.RequestBody; expected != nil && *expected != received {
|
||||
t.Errorf("bad body for request %#v: expected %s, got %s", c.Request, *expected, received)
|
||||
}
|
||||
}
|
||||
|
||||
// buildQueryValues is a convenience function for knowing if a namespace should be in a query param or not
|
||||
func BuildQueryValues(query url.Values) url.Values {
|
||||
v := url.Values{}
|
||||
if query != nil {
|
||||
for key, values := range query {
|
||||
for _, value := range values {
|
||||
v.Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func ValidateLabels(a, b string) bool {
|
||||
sA, eA := labels.Parse(a)
|
||||
if eA != nil {
|
||||
return false
|
||||
}
|
||||
sB, eB := labels.Parse(b)
|
||||
if eB != nil {
|
||||
return false
|
||||
}
|
||||
return sA.String() == sB.String()
|
||||
}
|
||||
|
||||
func validateFields(a, b string) bool {
|
||||
sA, _ := fields.ParseSelector(a)
|
||||
sB, _ := fields.ParseSelector(b)
|
||||
return sA.String() == sB.String()
|
||||
}
|
||||
|
||||
func (c *Client) body(t *testing.T, obj runtime.Object, raw *string) *string {
|
||||
if obj != nil {
|
||||
fqKinds, _, err := api.Scheme.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected encoding error: %v", err)
|
||||
}
|
||||
groupName := fqKinds[0].GroupVersion().Group
|
||||
if c.ResourceGroup != "" {
|
||||
groupName = c.ResourceGroup
|
||||
}
|
||||
var bs []byte
|
||||
g, found := testapi.Groups[groupName]
|
||||
if !found {
|
||||
t.Errorf("Group %s is not registered in testapi", groupName)
|
||||
}
|
||||
bs, err = runtime.Encode(g.Codec(), obj)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected encoding error: %v", err)
|
||||
}
|
||||
body := string(bs)
|
||||
return &body
|
||||
}
|
||||
return raw
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue