2015-01-07 20:59:22 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2015-01-07 20:59:22 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES 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 (
2015-02-19 04:17:42 +00:00
"fmt"
2015-04-10 12:54:22 +00:00
"io"
2015-01-07 20:59:22 +00:00
"io/ioutil"
"os"
2015-04-10 12:54:22 +00:00
"path"
2015-01-30 18:51:47 +00:00
"path/filepath"
2015-11-20 21:29:23 +00:00
goruntime "runtime"
2015-06-29 20:27:31 +00:00
"strings"
2015-01-07 20:59:22 +00:00
2015-11-13 21:16:59 +00:00
"github.com/golang/glog"
2015-01-07 20:59:22 +00:00
"github.com/imdario/mergo"
2014-12-17 13:03:03 +00:00
2015-12-21 05:31:34 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2015-08-12 18:18:22 +00:00
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
clientcmdlatest "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest"
2015-12-21 05:31:34 +00:00
"k8s.io/kubernetes/pkg/runtime"
2015-10-14 05:18:37 +00:00
utilerrors "k8s.io/kubernetes/pkg/util/errors"
2015-11-20 21:29:23 +00:00
"k8s.io/kubernetes/pkg/util/homedir"
2015-01-07 20:59:22 +00:00
)
const (
RecommendedConfigPathFlag = "kubeconfig"
RecommendedConfigPathEnvVar = "KUBECONFIG"
2015-09-16 23:31:45 +00:00
RecommendedHomeDir = ".kube"
RecommendedFileName = "config"
RecommendedSchemaName = "schema"
2015-01-07 20:59:22 +00:00
)
2015-11-20 21:29:23 +00:00
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
}
2015-04-10 12:54:22 +00:00
2016-05-24 03:20:27 +00:00
type ClientConfigLoader interface {
ConfigAccess
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 nil , nil
}
func ( g * ClientConfigGetter ) GetDefaultFilename ( ) string {
return ""
}
func ( g * ClientConfigGetter ) IsExplicitFile ( ) bool {
return false
}
func ( g * ClientConfigGetter ) GetExplicitFile ( ) string {
return ""
}
2015-03-17 12:21:10 +00:00
// 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:
2015-04-10 12:54:22 +00:00
// EnvVarPathFiles if set (a list of files if set) OR the HomeDirectoryPath
2015-03-17 12:21:10 +00:00
// ExplicitPath is special, because if a user specifically requests a certain file be used and error is reported if thie file is not present
2015-01-07 20:59:22 +00:00
type ClientConfigLoadingRules struct {
2015-03-17 12:21:10 +00:00
ExplicitPath string
Precedence [ ] string
2015-04-08 14:32:32 +00:00
2015-04-10 12:54:22 +00:00
// 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
2015-04-08 14:32:32 +00:00
// 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
2015-01-07 20:59:22 +00:00
}
2016-05-24 03:20:27 +00:00
// ClientConfigLoadingRules implements the ClientConfigLoader interface.
var _ ClientConfigLoader = & ClientConfigLoadingRules { }
2015-03-17 12:21:10 +00:00
// NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to
2015-01-07 20:59:22 +00:00
// use this constructor
2015-03-17 12:21:10 +00:00
func NewDefaultClientConfigLoadingRules ( ) * ClientConfigLoadingRules {
2015-04-10 12:54:22 +00:00
chain := [ ] string { }
envVarFiles := os . Getenv ( RecommendedConfigPathEnvVar )
if len ( envVarFiles ) != 0 {
chain = append ( chain , filepath . SplitList ( envVarFiles ) ... )
} else {
chain = append ( chain , RecommendedHomeFile )
}
2015-01-07 20:59:22 +00:00
return & ClientConfigLoadingRules {
2015-04-10 12:54:22 +00:00
Precedence : chain ,
2015-11-20 21:29:23 +00:00
MigrationRules : currentMigrationRules ( ) ,
2015-01-07 20:59:22 +00:00
}
}
2015-04-10 12:54:22 +00:00
// 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
2015-03-17 12:21:10 +00:00
// A missing ExplicitPath file produces an error. Empty filenames or other missing files are ignored.
2015-02-19 04:17:42 +00:00
// Read errors or files with non-deserializable content produce errors.
2015-02-13 13:21:55 +00:00
// 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
2015-01-07 20:59:22 +00:00
// non-conflicting entries from the second file's "red-user" are discarded.
2015-01-30 18:51:47 +00:00
// Relative paths inside of the .kubeconfig files are resolved against the .kubeconfig file's parent folder
// and only absolute file paths are returned.
2014-12-17 13:03:03 +00:00
func ( rules * ClientConfigLoadingRules ) Load ( ) ( * clientcmdapi . Config , error ) {
2015-04-10 12:54:22 +00:00
if err := rules . Migrate ( ) ; err != nil {
return nil , err
}
2015-02-19 04:17:42 +00:00
errlist := [ ] error { }
2015-04-10 12:54:22 +00:00
kubeConfigFiles := [ ] string { }
2015-02-19 04:17:42 +00:00
// Make sure a file we were explicitly told to use exists
2015-03-17 12:21:10 +00:00
if len ( rules . ExplicitPath ) > 0 {
if _ , err := os . Stat ( rules . ExplicitPath ) ; os . IsNotExist ( err ) {
2015-04-10 12:54:22 +00:00
return nil , err
2015-02-19 04:17:42 +00:00
}
2015-04-10 12:54:22 +00:00
kubeConfigFiles = append ( kubeConfigFiles , rules . ExplicitPath )
2015-02-19 04:17:42 +00:00
2015-04-10 12:54:22 +00:00
} else {
kubeConfigFiles = append ( kubeConfigFiles , rules . Precedence ... )
2016-02-15 15:42:38 +00:00
}
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
}
2015-04-10 12:54:22 +00:00
2016-02-15 15:42:38 +00:00
kubeconfigs = append ( kubeconfigs , config )
2015-04-10 12:54:22 +00:00
}
2015-01-30 18:51:47 +00:00
2015-02-13 13:21:55 +00:00
// first merge all of our maps
mapConfig := clientcmdapi . NewConfig ( )
2016-02-15 15:42:38 +00:00
for _ , kubeconfig := range kubeconfigs {
mergo . Merge ( mapConfig , kubeconfig )
2015-02-13 13:21:55 +00:00
}
2015-01-30 18:51:47 +00:00
2015-02-13 13:21:55 +00:00
// merge all of the struct values in the reverse order so that priority is given correctly
2015-02-19 04:17:42 +00:00
// errors are not added to the list the second time
2015-02-13 13:21:55 +00:00
nonMapConfig := clientcmdapi . NewConfig ( )
2016-02-15 15:42:38 +00:00
for i := len ( kubeconfigs ) - 1 ; i >= 0 ; i -- {
kubeconfig := kubeconfigs [ i ]
mergo . Merge ( nonMapConfig , kubeconfig )
2015-02-13 13:21:55 +00:00
}
2015-01-30 18:51:47 +00:00
2015-02-13 13:21:55 +00:00
// 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 )
2015-01-07 20:59:22 +00:00
2015-06-29 20:27:31 +00:00
if rules . ResolvePaths ( ) {
if err := ResolveLocalPaths ( config ) ; err != nil {
errlist = append ( errlist , err )
}
}
2015-10-14 05:18:37 +00:00
return config , utilerrors . NewAggregate ( errlist )
2015-01-07 20:59:22 +00:00
}
2015-04-10 12:54:22 +00:00
// 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 . 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 ) {
// if the source file doesn't exist, 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
}
2016-04-14 18:55:34 +00:00
// 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
}
}
2016-05-11 07:14:23 +00:00
// If none exists, use the first from precedence.
2016-04-14 18:55:34 +00:00
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
}
2015-01-07 20:59:22 +00:00
// LoadFromFile takes a filename and deserializes the contents into Config object
2014-12-17 13:03:03 +00:00
func LoadFromFile ( filename string ) ( * clientcmdapi . Config , error ) {
2015-01-07 20:59:22 +00:00
kubeconfigBytes , err := ioutil . ReadFile ( filename )
if err != nil {
return nil , err
}
2015-04-08 14:32:32 +00:00
config , err := Load ( kubeconfigBytes )
if err != nil {
return nil , err
}
2016-01-25 00:08:20 +00:00
glog . V ( 6 ) . Infoln ( "Config loaded from file" , filename )
2015-04-08 14:32:32 +00:00
// 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
}
2015-11-11 19:55:58 +00:00
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 { }
}
2015-04-08 14:32:32 +00:00
return config , nil
2015-02-18 02:37:43 +00:00
}
2015-01-07 20:59:22 +00:00
2015-02-18 02:37:43 +00:00
// 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 ) {
2015-02-20 13:27:08 +00:00
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
}
2015-12-21 05:31:34 +00:00
decoded , _ , err := clientcmdlatest . Codec . Decode ( data , & unversioned . GroupVersionKind { Version : clientcmdlatest . Version , Kind : "Config" } , config )
if err != nil {
2015-01-07 20:59:22 +00:00
return nil , err
}
2015-12-21 05:31:34 +00:00
return decoded . ( * clientcmdapi . Config ) , nil
2015-01-07 20:59:22 +00:00
}
2015-02-18 02:37:43 +00:00
// 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
2015-01-07 20:59:22 +00:00
// it stomps the contents
2014-12-17 13:03:03 +00:00
func WriteToFile ( config clientcmdapi . Config , filename string ) error {
2015-02-18 02:37:43 +00:00
content , err := Write ( config )
2015-01-07 20:59:22 +00:00
if err != nil {
return err
}
2015-04-16 17:17:54 +00:00
dir := filepath . Dir ( filename )
if _ , err := os . Stat ( dir ) ; os . IsNotExist ( err ) {
if err = os . MkdirAll ( dir , 0755 ) ; err != nil {
return err
}
}
2015-02-18 02:37:43 +00:00
if err := ioutil . WriteFile ( filename , content , 0600 ) ; err != nil {
2015-01-07 20:59:22 +00:00
return err
}
2015-02-18 02:37:43 +00:00
return nil
}
2015-01-07 20:59:22 +00:00
2015-02-18 02:37:43 +00:00
// Write serializes the config to yaml.
// Encapsulates serialization without assuming the destination is a file.
func Write ( config clientcmdapi . Config ) ( [ ] byte , error ) {
2015-12-21 05:31:34 +00:00
return runtime . Encode ( clientcmdlatest . Codec , & config )
2015-01-07 20:59:22 +00:00
}
2015-04-08 14:32:32 +00:00
func ( rules ClientConfigLoadingRules ) ResolvePaths ( ) bool {
return ! rules . DoNotResolvePaths
}
2015-06-29 20:27:31 +00:00
// 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 }
}
// 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
}