minikube/pkg/util/kubeconfig.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
}