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
|
|
}
|