Files
go-base/config/kube_config.go
2018-05-16 16:58:50 -07:00

278 lines
7.4 KiB
Go

/*
Copyright 2018 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 config
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"github.com/ghodss/yaml"
"k8s.io/client/kubernetes/client"
"k8s.io/client/kubernetes/config/api"
)
const (
defaultUserAgent = "Swagger-Codegen/0.1.0a1/go"
kubeConfigEnvName = "KUBECONFIG"
kubeConfigDefaultFilename = "~/.kube/config"
)
// KubeConfigLoader implements the util functions to load authentication and cluster
// info and hosts intermediate info values.
type KubeConfigLoader struct {
rawConfig api.Config
restConfig RestConfig
// Skip config persistence, default to false
skipConfigPersist bool
configFilename string
// Current cluster, user and context
cluster api.Cluster
user api.AuthInfo
currentContext api.Context
// Set this interface to pass in custom Google credential loader instead of
// using the default loader
gcLoader GoogleCredentialLoader
}
// RestConfig contains the information that a rest client needs to talk with a server
type RestConfig struct {
basePath string
host string
scheme string
// authentication token
token string
// TLS info
caCert []byte
clientCert []byte
clientKey []byte
// skip TLS verification, default to false
skipTLSVerify bool
}
// LoadKubeConfig loads authentication and cluster information from kube-config file
// and stores them in returned client.Configuration.
func LoadKubeConfig() (*client.Configuration, error) {
kubeConfigFilename := os.Getenv(kubeConfigEnvName)
// Fallback to default kubeconfig file location if no env variable set
if kubeConfigFilename == "" {
kubeConfigFilename = kubeConfigDefaultFilename
}
loader, err := NewKubeConfigLoaderFromYAMLFile(kubeConfigFilename, false)
if err != nil {
return nil, err
}
return loader.LoadAndSet()
}
// NewKubeConfigLoaderFromYAMLFile creates a new KubeConfigLoader with a parsed
// config yaml file.
func NewKubeConfigLoaderFromYAMLFile(filename string, skipConfigPersist bool) (*KubeConfigLoader, error) {
kubeConfig, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
// Init an empty api.Config as unmarshal layout template
c := api.Config{}
if err := yaml.Unmarshal(kubeConfig, &c); err != nil {
return nil, err
}
l := KubeConfigLoader{
rawConfig: c,
skipConfigPersist: skipConfigPersist,
configFilename: filename,
}
// Init loader with current cluster, user and context
if err := l.LoadActiveContext(); err != nil {
return nil, err
}
return &l, nil
}
// LoadAndSet loads authentication and cluster information from kube-config file and
// stores them in returned client.Configuration.
func (l *KubeConfigLoader) LoadAndSet() (*client.Configuration, error) {
l.loadAuthentication()
if err := l.loadClusterInfo(); err != nil {
return nil, err
}
return l.setConfig()
}
// loadClusterInfo uses the current cluster, user and context info stored in loader and
// gets necessary TLS information
func (l *KubeConfigLoader) loadClusterInfo() error {
// The swagger-codegen go client doesn't work well with base path having trailing slash.
// This is a short term fix.
l.restConfig.basePath = strings.TrimRight(l.cluster.Server, "/")
u, err := url.Parse(l.cluster.Server)
if err != nil {
return err
}
l.restConfig.host = u.Host
l.restConfig.scheme = u.Scheme
if l.cluster.InsecureSkipTLSVerify {
l.restConfig.skipTLSVerify = true
}
if l.restConfig.scheme == "https" {
if !l.restConfig.skipTLSVerify {
l.restConfig.caCert, err = DataOrFile(l.cluster.CertificateAuthorityData, l.cluster.CertificateAuthority)
if err != nil {
return err
}
}
l.restConfig.clientCert, err = DataOrFile(l.user.ClientCertificateData, l.user.ClientCertificate)
if err != nil {
return err
}
l.restConfig.clientKey, err = DataOrFile(l.user.ClientKeyData, l.user.ClientKey)
if err != nil {
return err
}
}
return nil
}
// setConfig converts authentication and TLS info into client Configuration
func (l *KubeConfigLoader) setConfig() (*client.Configuration, error) {
// Set TLS info
transport := http.Transport{}
if l.restConfig.scheme == "https" && !l.restConfig.skipTLSVerify {
cert, err := tls.X509KeyPair(l.restConfig.clientCert, l.restConfig.clientKey)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(l.restConfig.caCert)
transport.TLSClientConfig = &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
}
}
c := &http.Client{
Transport: &transport,
}
header := make(map[string]string)
// Add authentication info to default header
if l.restConfig.token != "" {
header["Authorization"] = l.restConfig.token
// Handle Golang dropping headers on redirect
c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
req.Header.Add("Authorization", l.restConfig.token)
return nil
}
}
return &client.Configuration{
BasePath: l.restConfig.basePath,
Host: l.restConfig.host,
Scheme: l.restConfig.scheme,
DefaultHeader: header,
UserAgent: defaultUserAgent,
HTTPClient: c,
}, nil
}
// RestConfig returns the value of RestConfig in a KubeConfigLoader
func (l *KubeConfigLoader) RestConfig() RestConfig {
return l.restConfig
}
// SetActiveContext sets the active context in rawConfig, performs necessary persistence,
// and reload active context. This function enables context switch
func (l *KubeConfigLoader) SetActiveContext(ctx string) error {
currentContext, err := getContextWithName(l.rawConfig.Contexts, ctx)
if err != nil {
return err
}
currentContext.DeepCopyInto(&l.currentContext)
l.rawConfig.CurrentContext = ctx
// Persist kube config file
if !l.skipConfigPersist {
if err := l.persistConfig(); err != nil {
return err
}
}
return l.LoadActiveContext()
}
// LoadActiveContext parses the loader's rawConfig using current context and set loader's
// current cluster and user.
func (l *KubeConfigLoader) LoadActiveContext() error {
currentContext, err := getContextWithName(l.rawConfig.Contexts, l.rawConfig.CurrentContext)
if err != nil {
return err
}
currentContext.DeepCopyInto(&l.currentContext)
cluster, err := getClusterWithName(l.rawConfig.Clusters, l.currentContext.Cluster)
if err != nil {
return err
}
cluster.DeepCopyInto(&l.cluster)
user, err := getUserWithName(l.rawConfig.AuthInfos, l.currentContext.AuthInfo)
if err != nil {
return err
}
// kube config may have no (current) user
if user != nil {
user.DeepCopyInto(&l.user)
}
return nil
}
// persisConfig saves the stored rawConfig to the config file. This function is not exposed and
// should be called only when skipConfigPersist is false.
// TODO(roycaihw): enable custom persistConfig function
func (l *KubeConfigLoader) persistConfig() error {
if l.skipConfigPersist {
return nil
}
data, err := yaml.Marshal(l.rawConfig)
if err != nil {
return err
}
return ioutil.WriteFile(l.configFilename, data, 0644)
}