From 59c8c1d02019ba8e17c93c7790339a6f07e79287 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 30 May 2017 08:13:47 +0100 Subject: [PATCH] vendor: k8s.io/client-go/tools/clientcmd/...@v3.0.0-beta.0 --- vendor/k8s.io/client-go/tools/auth/BUILD | 23 + .../k8s.io/client-go/tools/auth/clientauth.go | 125 ++++ .../tools/clientcmd/api/latest/latest.go | 66 ++ .../tools/clientcmd/api/v1/conversion.go | 227 +++++++ .../tools/clientcmd/api/v1/register.go | 46 ++ .../client-go/tools/clientcmd/api/v1/types.go | 170 +++++ .../client-go/tools/clientcmd/auth_loaders.go | 106 +++ .../tools/clientcmd/client_config.go | 537 +++++++++++++++ .../client-go/tools/clientcmd/config.go | 472 ++++++++++++++ .../k8s.io/client-go/tools/clientcmd/doc.go | 37 ++ .../client-go/tools/clientcmd/helpers.go | 35 + .../client-go/tools/clientcmd/loader.go | 609 ++++++++++++++++++ .../tools/clientcmd/merged_client_builder.go | 154 +++++ .../client-go/tools/clientcmd/overrides.go | 214 ++++++ .../client-go/tools/clientcmd/validation.go | 270 ++++++++ vendor/k8s.io/client-go/util/homedir/BUILD | 14 + .../k8s.io/client-go/util/homedir/homedir.go | 47 ++ vendor/vendor.json | 36 ++ 18 files changed, 3188 insertions(+) create mode 100644 vendor/k8s.io/client-go/tools/auth/BUILD create mode 100644 vendor/k8s.io/client-go/tools/auth/clientauth.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/api/latest/latest.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/api/v1/conversion.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/api/v1/register.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/api/v1/types.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/auth_loaders.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/client_config.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/config.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/doc.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/helpers.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/loader.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/merged_client_builder.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/overrides.go create mode 100644 vendor/k8s.io/client-go/tools/clientcmd/validation.go create mode 100644 vendor/k8s.io/client-go/util/homedir/BUILD create mode 100644 vendor/k8s.io/client-go/util/homedir/homedir.go diff --git a/vendor/k8s.io/client-go/tools/auth/BUILD b/vendor/k8s.io/client-go/tools/auth/BUILD new file mode 100644 index 000000000..af00eb646 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/auth/BUILD @@ -0,0 +1,23 @@ +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 = ["//vendor/k8s.io/client-go/rest:go_default_library"], +) + +go_test( + name = "go_default_xtest", + srcs = ["clientauth_test.go"], + tags = ["automanaged"], + deps = ["//vendor/k8s.io/client-go/tools/auth:go_default_library"], +) diff --git a/vendor/k8s.io/client-go/tools/auth/clientauth.go b/vendor/k8s.io/client-go/tools/auth/clientauth.go new file mode 100644 index 000000000..2213b9878 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/auth/clientauth.go @@ -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" + + restclient "k8s.io/client-go/rest" +) + +// 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 +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/api/latest/latest.go b/vendor/k8s.io/client-go/tools/clientcmd/api/latest/latest.go new file mode 100644 index 000000000..5fbbe3f13 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/api/latest/latest.go @@ -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/client-go/tools/clientcmd/api" + "k8s.io/client-go/tools/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, + ) +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/api/v1/conversion.go b/vendor/k8s.io/client-go/tools/clientcmd/api/v1/conversion.go new file mode 100644 index 000000000..b47bfbca2 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/api/v1/conversion.go @@ -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/client-go/tools/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 + }, + ) +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/api/v1/register.go b/vendor/k8s.io/client-go/tools/clientcmd/api/v1/register.go new file mode 100644 index 000000000..bc87706db --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/api/v1/register.go @@ -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) +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/api/v1/types.go b/vendor/k8s.io/client-go/tools/clientcmd/api/v1/types.go new file mode 100644 index 000000000..a0777ba53 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/api/v1/types.go @@ -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"` +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/auth_loaders.go b/vendor/k8s.io/client-go/tools/clientcmd/auth_loaders.go new file mode 100644 index 000000000..12331f6e6 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/auth_loaders.go @@ -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/client-go/tools/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{} +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/client_config.go b/vendor/k8s.io/client-go/tools/clientcmd/client_config.go new file mode 100644 index 000000000..0411e6235 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/client_config.go @@ -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/client-go/pkg/api" + restclient "k8s.io/client-go/rest" + clientauth "k8s.io/client-go/tools/auth" + clientcmdapi "k8s.io/client-go/tools/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() + + mergedContext := clientcmdapi.NewContext() + 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() + + mergedAuthInfo := clientcmdapi.NewAuthInfo() + 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() + + mergedClusterInfo := clientcmdapi.NewCluster() + 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() +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/config.go b/vendor/k8s.io/client-go/tools/clientcmd/config.go new file mode 100644 index 000000000..16ccdaf20 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/config.go @@ -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" + + restclient "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/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 +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/doc.go b/vendor/k8s.io/client-go/tools/clientcmd/doc.go new file mode 100644 index 000000000..424311ee1 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/doc.go @@ -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/client-go/tools/clientcmd" diff --git a/vendor/k8s.io/client-go/tools/clientcmd/helpers.go b/vendor/k8s.io/client-go/tools/clientcmd/helpers.go new file mode 100644 index 000000000..b609d1a76 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/helpers.go @@ -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)") +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/loader.go b/vendor/k8s.io/client-go/tools/clientcmd/loader.go new file mode 100644 index 000000000..1fcc51038 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/loader.go @@ -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" + restclient "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest" + "k8s.io/client-go/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 +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/merged_client_builder.go b/vendor/k8s.io/client-go/tools/clientcmd/merged_client_builder.go new file mode 100644 index 000000000..92c1a5a00 --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/merged_client_builder.go @@ -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" + + restclient "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/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 +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/overrides.go b/vendor/k8s.io/client-go/tools/clientcmd/overrides.go new file mode 100644 index 000000000..f18aa6a4a --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/overrides.go @@ -0,0 +1,214 @@ +/* +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/client-go/tools/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 +} + +// AddSecretAnnotation add secret flag to Annotation. +func (f FlagInfo) AddSecretAnnotation(flags *pflag.FlagSet) FlagInfo { + flags.SetAnnotation(f.LongName, "classified", []string{"true"}) + return f +} + +// BindStringFlag binds the flag based on the provided info. If LongName == "", nothing is registered +func (f FlagInfo) BindStringFlag(flags *pflag.FlagSet, target *string) FlagInfo { + // 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) + } + return f +} + +// BindBoolFlag binds the flag based on the provided info. If LongName == "", nothing is registered +func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo { + // 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) + } + return f +} + +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).AddSecretAnnotation(flags) + flagNames.ClientKey.BindStringFlag(flags, &authInfo.ClientKey).AddSecretAnnotation(flags) + flagNames.Token.BindStringFlag(flags, &authInfo.Token).AddSecretAnnotation(flags) + flagNames.Impersonate.BindStringFlag(flags, &authInfo.Impersonate).AddSecretAnnotation(flags) + flagNames.Username.BindStringFlag(flags, &authInfo.Username).AddSecretAnnotation(flags) + flagNames.Password.BindStringFlag(flags, &authInfo.Password).AddSecretAnnotation(flags) +} + +// 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) +} diff --git a/vendor/k8s.io/client-go/tools/clientcmd/validation.go b/vendor/k8s.io/client-go/tools/clientcmd/validation.go new file mode 100644 index 000000000..ceeeb042e --- /dev/null +++ b/vendor/k8s.io/client-go/tools/clientcmd/validation.go @@ -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/client-go/tools/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 +} diff --git a/vendor/k8s.io/client-go/util/homedir/BUILD b/vendor/k8s.io/client-go/util/homedir/BUILD new file mode 100644 index 000000000..aa49fefb5 --- /dev/null +++ b/vendor/k8s.io/client-go/util/homedir/BUILD @@ -0,0 +1,14 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["homedir.go"], + tags = ["automanaged"], +) diff --git a/vendor/k8s.io/client-go/util/homedir/homedir.go b/vendor/k8s.io/client-go/util/homedir/homedir.go new file mode 100644 index 000000000..816db57f5 --- /dev/null +++ b/vendor/k8s.io/client-go/util/homedir/homedir.go @@ -0,0 +1,47 @@ +/* +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 homedir + +import ( + "os" + "runtime" +) + +// HomeDir returns the home directory for the current user +func HomeDir() string { + if runtime.GOOS == "windows" { + + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); len(home) > 0 { + if _, err := os.Stat(home); err == nil { + return home + } + } + if homeDrive, homePath := os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"); len(homeDrive) > 0 && len(homePath) > 0 { + homeDir := homeDrive + homePath + if _, err := os.Stat(homeDir); err == nil { + return homeDir + } + } + if userProfile := os.Getenv("USERPROFILE"); len(userProfile) > 0 { + if _, err := os.Stat(userProfile); err == nil { + return userProfile + } + } + } + return os.Getenv("HOME") +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 37ded8621..b854f2870 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -4299,6 +4299,20 @@ "version": "v3.0.0-beta.0", "versionExact": "v3.0.0-beta.0" }, + { + "checksumSHA1": "1ckiORH3dJD8v3R8tLEZ/5wttAk=", + "path": "k8s.io/client-go/tools/auth", + "revision": "6b1566ccaa7c069f392461bef8a5fb17087f4729", + "revisionTime": "2017-05-22T17:54:13Z" + }, + { + "checksumSHA1": "y8RjCuBi8XrHvvVsguI7E97Fh6M=", + "path": "k8s.io/client-go/tools/clientcmd", + "revision": "3627aeb7d4f6ade38f995d2c923e459146493c7e", + "revisionTime": "2017-03-31T20:34:26Z", + "version": "v3.0.0-beta.0", + "versionExact": "v3.0.0-beta.0" + }, { "checksumSHA1": "1jUQmUDroNs5XYhsQna4kNTzMik=", "path": "k8s.io/client-go/tools/clientcmd/api", @@ -4307,6 +4321,22 @@ "version": "v3.0.0-beta.0", "versionExact": "v3.0.0-beta.0" }, + { + "checksumSHA1": "6cVFBhDYvRiAwBf0QYbmUoLi5lQ=", + "path": "k8s.io/client-go/tools/clientcmd/api/latest", + "revision": "3627aeb7d4f6ade38f995d2c923e459146493c7e", + "revisionTime": "2017-03-31T20:34:26Z", + "version": "v3.0.0-beta.0", + "versionExact": "v3.0.0-beta.0" + }, + { + "checksumSHA1": "DG6X6yy03Om6CFqIZm08+geRGkI=", + "path": "k8s.io/client-go/tools/clientcmd/api/v1", + "revision": "3627aeb7d4f6ade38f995d2c923e459146493c7e", + "revisionTime": "2017-03-31T20:34:26Z", + "version": "v3.0.0-beta.0", + "versionExact": "v3.0.0-beta.0" + }, { "checksumSHA1": "rRC9GCWXfyIXKHBCeKpw7exVybE=", "path": "k8s.io/client-go/tools/metrics", @@ -4347,6 +4377,12 @@ "version": "v3.0.0-beta.0", "versionExact": "v3.0.0-beta.0" }, + { + "checksumSHA1": "jP91eRmXcfaEo/NU7Lk1coovunQ=", + "path": "k8s.io/client-go/util/homedir", + "revision": "6b1566ccaa7c069f392461bef8a5fb17087f4729", + "revisionTime": "2017-05-22T17:54:13Z" + }, { "checksumSHA1": "W6bJiNAwgNwNJC+y+TPtacgUGlY=", "path": "k8s.io/client-go/util/integer",