373 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			373 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
/*
 | 
						|
Copyright 2016 The Kubernetes Authors All rights reserved.
 | 
						|
 | 
						|
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 util
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"net"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strconv"
 | 
						|
	"sync/atomic"
 | 
						|
 | 
						|
	"github.com/golang/glog"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime"
 | 
						|
	"k8s.io/client-go/tools/clientcmd/api"
 | 
						|
	"k8s.io/client-go/tools/clientcmd/api/latest"
 | 
						|
)
 | 
						|
 | 
						|
// KubeConfigSetup is the kubeconfig setup
 | 
						|
type KubeConfigSetup struct {
 | 
						|
	// The name of the cluster for this context
 | 
						|
	ClusterName string
 | 
						|
 | 
						|
	// ClusterServerAddress is the address of the kubernetes cluster
 | 
						|
	ClusterServerAddress string
 | 
						|
 | 
						|
	// ClientCertificate is the path to a client cert file for TLS.
 | 
						|
	ClientCertificate string
 | 
						|
 | 
						|
	// CertificateAuthority is the path to a cert file for the certificate authority.
 | 
						|
	CertificateAuthority string
 | 
						|
 | 
						|
	// ClientKey is the path to a client key file for TLS.
 | 
						|
	ClientKey string
 | 
						|
 | 
						|
	// Should the current context be kept when setting up this one
 | 
						|
	KeepContext bool
 | 
						|
 | 
						|
	// Should the certificate files be embedded instead of referenced by path
 | 
						|
	EmbedCerts bool
 | 
						|
 | 
						|
	// kubeConfigFile is the path where the kube config is stored
 | 
						|
	// Only access this with atomic ops
 | 
						|
	kubeConfigFile atomic.Value
 | 
						|
}
 | 
						|
 | 
						|
// SetKubeConfigFile sets the kubeconfig file
 | 
						|
func (k *KubeConfigSetup) SetKubeConfigFile(kubeConfigFile string) {
 | 
						|
	k.kubeConfigFile.Store(kubeConfigFile)
 | 
						|
}
 | 
						|
 | 
						|
// GetKubeConfigFile gets the kubeconfig file
 | 
						|
func (k *KubeConfigSetup) GetKubeConfigFile() string {
 | 
						|
	return k.kubeConfigFile.Load().(string)
 | 
						|
}
 | 
						|
 | 
						|
// PopulateKubeConfig populates an api.Config object.
 | 
						|
func PopulateKubeConfig(cfg *KubeConfigSetup, kubecfg *api.Config) error {
 | 
						|
	var err error
 | 
						|
	clusterName := cfg.ClusterName
 | 
						|
	cluster := api.NewCluster()
 | 
						|
	cluster.Server = cfg.ClusterServerAddress
 | 
						|
	if cfg.EmbedCerts {
 | 
						|
		cluster.CertificateAuthorityData, err = ioutil.ReadFile(cfg.CertificateAuthority)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		cluster.CertificateAuthority = cfg.CertificateAuthority
 | 
						|
	}
 | 
						|
	kubecfg.Clusters[clusterName] = cluster
 | 
						|
 | 
						|
	// user
 | 
						|
	userName := cfg.ClusterName
 | 
						|
	user := api.NewAuthInfo()
 | 
						|
	if cfg.EmbedCerts {
 | 
						|
		user.ClientCertificateData, err = ioutil.ReadFile(cfg.ClientCertificate)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		user.ClientKeyData, err = ioutil.ReadFile(cfg.ClientKey)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		user.ClientCertificate = cfg.ClientCertificate
 | 
						|
		user.ClientKey = cfg.ClientKey
 | 
						|
	}
 | 
						|
	kubecfg.AuthInfos[userName] = user
 | 
						|
 | 
						|
	// context
 | 
						|
	contextName := cfg.ClusterName
 | 
						|
	context := api.NewContext()
 | 
						|
	context.Cluster = cfg.ClusterName
 | 
						|
	context.AuthInfo = userName
 | 
						|
	kubecfg.Contexts[contextName] = context
 | 
						|
 | 
						|
	// Only set current context to minikube if the user has not used the keepContext flag
 | 
						|
	if !cfg.KeepContext {
 | 
						|
		kubecfg.CurrentContext = cfg.ClusterName
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// SetupKubeConfig reads config from disk, adds the minikube settings, and writes it back.
 | 
						|
// activeContext is true when minikube is the CurrentContext
 | 
						|
// If no CurrentContext is set, the given name will be used.
 | 
						|
func SetupKubeConfig(cfg *KubeConfigSetup) error {
 | 
						|
	glog.Infoln("Using kubeconfig: ", cfg.GetKubeConfigFile())
 | 
						|
 | 
						|
	// read existing config or create new if does not exist
 | 
						|
	config, err := ReadConfigOrNew(cfg.GetKubeConfigFile())
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = PopulateKubeConfig(cfg, config)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// write back to disk
 | 
						|
	if err := WriteConfig(config, cfg.GetKubeConfigFile()); err != nil {
 | 
						|
		return errors.Wrap(err, "writing kubeconfig")
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// ReadConfigOrNew retrieves Kubernetes client configuration from a file.
 | 
						|
// If no files exists, an empty configuration is returned.
 | 
						|
func ReadConfigOrNew(filename string) (*api.Config, error) {
 | 
						|
	data, err := ioutil.ReadFile(filename)
 | 
						|
	if os.IsNotExist(err) {
 | 
						|
		return api.NewConfig(), nil
 | 
						|
	} else if err != nil {
 | 
						|
		return nil, errors.Wrapf(err, "Error reading file %q", filename)
 | 
						|
	}
 | 
						|
 | 
						|
	// decode config, empty if no bytes
 | 
						|
	config, err := decode(data)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Errorf("could not read config: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// initialize nil maps
 | 
						|
	if config.AuthInfos == nil {
 | 
						|
		config.AuthInfos = map[string]*api.AuthInfo{}
 | 
						|
	}
 | 
						|
	if config.Clusters == nil {
 | 
						|
		config.Clusters = map[string]*api.Cluster{}
 | 
						|
	}
 | 
						|
	if config.Contexts == nil {
 | 
						|
		config.Contexts = map[string]*api.Context{}
 | 
						|
	}
 | 
						|
 | 
						|
	return config, nil
 | 
						|
}
 | 
						|
 | 
						|
// WriteConfig encodes the configuration and writes it to the given file.
 | 
						|
// If the file exists, it's contents will be overwritten.
 | 
						|
func WriteConfig(config *api.Config, filename string) error {
 | 
						|
	if config == nil {
 | 
						|
		glog.Errorf("could not write to '%s': config can't be nil", filename)
 | 
						|
	}
 | 
						|
 | 
						|
	// encode config to YAML
 | 
						|
	data, err := runtime.Encode(latest.Codec, config)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Errorf("could not write to '%s': failed to encode config: %v", filename, err)
 | 
						|
	}
 | 
						|
 | 
						|
	// create parent dir if doesn't exist
 | 
						|
	dir := filepath.Dir(filename)
 | 
						|
	if _, err := os.Stat(dir); os.IsNotExist(err) {
 | 
						|
		if err = os.MkdirAll(dir, 0755); err != nil {
 | 
						|
			return errors.Wrapf(err, "Error creating directory: %s", dir)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// write with restricted permissions
 | 
						|
	if err := ioutil.WriteFile(filename, data, 0600); err != nil {
 | 
						|
		return errors.Wrapf(err, "Error writing file %s", filename)
 | 
						|
	}
 | 
						|
	if err := MaybeChownDirRecursiveToMinikubeUser(dir); err != nil {
 | 
						|
		return errors.Wrapf(err, "Error recursively changing ownership for dir: %s", dir)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// decode reads a Config object from bytes.
 | 
						|
// Returns empty config if no bytes.
 | 
						|
func decode(data []byte) (*api.Config, error) {
 | 
						|
	// if no data, return empty config
 | 
						|
	if len(data) == 0 {
 | 
						|
		return api.NewConfig(), nil
 | 
						|
	}
 | 
						|
 | 
						|
	config, _, err := latest.Codec.Decode(data, nil, nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Wrapf(err, "Error decoding config from data: %s", string(data))
 | 
						|
	}
 | 
						|
 | 
						|
	return config.(*api.Config), nil
 | 
						|
}
 | 
						|
 | 
						|
// GetKubeConfigStatus verifies the ip stored in kubeconfig.
 | 
						|
func GetKubeConfigStatus(ip net.IP, filename string, machineName string) (bool, error) {
 | 
						|
	if ip == nil {
 | 
						|
		return false, fmt.Errorf("Error, empty ip passed")
 | 
						|
	}
 | 
						|
	kip, err := getIPFromKubeConfig(filename, machineName)
 | 
						|
	if err != nil {
 | 
						|
		return false, err
 | 
						|
	}
 | 
						|
	if kip.Equal(ip) {
 | 
						|
		return true, nil
 | 
						|
	}
 | 
						|
	// Kubeconfig IP misconfigured
 | 
						|
	return false, nil
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// UpdateKubeconfigIP overwrites the IP stored in kubeconfig with the provided IP.
 | 
						|
func UpdateKubeconfigIP(ip net.IP, filename string, machineName string) (bool, error) {
 | 
						|
	if ip == nil {
 | 
						|
		return false, fmt.Errorf("Error, empty ip passed")
 | 
						|
	}
 | 
						|
	kip, err := getIPFromKubeConfig(filename, machineName)
 | 
						|
	if err != nil {
 | 
						|
		return false, err
 | 
						|
	}
 | 
						|
	if kip.Equal(ip) {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
	kport, err := GetPortFromKubeConfig(filename, machineName)
 | 
						|
	if err != nil {
 | 
						|
		return false, err
 | 
						|
	}
 | 
						|
	con, err := ReadConfigOrNew(filename)
 | 
						|
	if err != nil {
 | 
						|
		return false, errors.Wrap(err, "Error getting kubeconfig status")
 | 
						|
	}
 | 
						|
	// Safe to lookup server because if field non-existent getIPFromKubeconfig would have given an error
 | 
						|
	con.Clusters[machineName].Server = "https://" + ip.String() + ":" + strconv.Itoa(kport)
 | 
						|
	err = WriteConfig(con, filename)
 | 
						|
	if err != nil {
 | 
						|
		return false, err
 | 
						|
	}
 | 
						|
	// Kubeconfig IP reconfigured
 | 
						|
	return true, nil
 | 
						|
}
 | 
						|
 | 
						|
// getIPFromKubeConfig returns the IP address stored for minikube in the kubeconfig specified
 | 
						|
func getIPFromKubeConfig(filename, machineName string) (net.IP, error) {
 | 
						|
	con, err := ReadConfigOrNew(filename)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Wrap(err, "Error getting kubeconfig status")
 | 
						|
	}
 | 
						|
	cluster, ok := con.Clusters[machineName]
 | 
						|
	if !ok {
 | 
						|
		return nil, errors.Errorf("Kubeconfig does not have a record of the machine cluster")
 | 
						|
	}
 | 
						|
	kurl, err := url.Parse(cluster.Server)
 | 
						|
	if err != nil {
 | 
						|
		return net.ParseIP(cluster.Server), nil
 | 
						|
	}
 | 
						|
	kip, _, err := net.SplitHostPort(kurl.Host)
 | 
						|
	if err != nil {
 | 
						|
		return net.ParseIP(kurl.Host), nil
 | 
						|
	}
 | 
						|
	ip := net.ParseIP(kip)
 | 
						|
	return ip, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetPortFromKubeConfig returns the Port number stored for minikube in the kubeconfig specified
 | 
						|
func GetPortFromKubeConfig(filename, machineName string) (int, error) {
 | 
						|
	con, err := ReadConfigOrNew(filename)
 | 
						|
	if err != nil {
 | 
						|
		return 0, errors.Wrap(err, "Error getting kubeconfig status")
 | 
						|
	}
 | 
						|
	cluster, ok := con.Clusters[machineName]
 | 
						|
	if !ok {
 | 
						|
		return 0, errors.Errorf("Kubeconfig does not have a record of the machine cluster")
 | 
						|
	}
 | 
						|
	kurl, err := url.Parse(cluster.Server)
 | 
						|
	if err != nil {
 | 
						|
		return APIServerPort, nil
 | 
						|
	}
 | 
						|
	_, kport, err := net.SplitHostPort(kurl.Host)
 | 
						|
	if err != nil {
 | 
						|
		return APIServerPort, nil
 | 
						|
	}
 | 
						|
	port, err := strconv.Atoi(kport)
 | 
						|
	return port, err
 | 
						|
}
 | 
						|
 | 
						|
//UnsetCurrentContext unsets the current-context from minikube to "" on minikube stop
 | 
						|
func UnsetCurrentContext(filename, machineName string) error {
 | 
						|
	confg, err := ReadConfigOrNew(filename)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "Error getting kubeconfig status")
 | 
						|
	}
 | 
						|
 | 
						|
	// Unset current-context only if profile is the current-context
 | 
						|
	if confg.CurrentContext == machineName {
 | 
						|
		confg.CurrentContext = ""
 | 
						|
		if err := WriteConfig(confg, filename); err != nil {
 | 
						|
			return errors.Wrap(err, "writing kubeconfig")
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
//SetCurrentContext sets the kubectl's current-context
 | 
						|
func SetCurrentContext(kubeCfgPath, name string) error {
 | 
						|
	kcfg, err := ReadConfigOrNew(kubeCfgPath)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "Error getting kubeconfig status")
 | 
						|
	}
 | 
						|
	kcfg.CurrentContext = name
 | 
						|
	if err := WriteConfig(kcfg, kubeCfgPath); err != nil {
 | 
						|
		return errors.Wrap(err, "writing kubeconfig")
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// DeleteKubeConfigContext deletes the specified machine's kubeconfig context
 | 
						|
func DeleteKubeConfigContext(kubeCfgPath, machineName string) error {
 | 
						|
	kcfg, err := ReadConfigOrNew(kubeCfgPath)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "Error getting kubeconfig status")
 | 
						|
	}
 | 
						|
 | 
						|
	if kcfg == nil || api.IsConfigEmpty(kcfg) {
 | 
						|
		glog.V(2).Info("kubeconfig is empty")
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	delete(kcfg.Clusters, machineName)
 | 
						|
	delete(kcfg.AuthInfos, machineName)
 | 
						|
	delete(kcfg.Contexts, machineName)
 | 
						|
 | 
						|
	if kcfg.CurrentContext == machineName {
 | 
						|
		kcfg.CurrentContext = ""
 | 
						|
	}
 | 
						|
 | 
						|
	if err := WriteConfig(kcfg, kubeCfgPath); err != nil {
 | 
						|
		return errors.Wrap(err, "writing kubeconfig")
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |