Replace use of kubectl with calls to library

In particular, when setting configuration, just set it using the
clientcmd functions.

Lets us do this in one change.

Follow up from #1663
This commit is contained in:
Justin Santa Barbara 2017-02-11 12:44:12 -05:00
parent 8b847578b4
commit 851ffc4f5a
3 changed files with 111 additions and 262 deletions

View File

@ -21,6 +21,7 @@ import (
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kops/cmd/kops/util"
api "k8s.io/kops/pkg/apis/kops"
@ -182,9 +183,11 @@ func RunDeleteCluster(f *util.Factory, out io.Writer, options *DeleteClusterOpti
}
}
err = kutil.DeleteConfig(clusterName)
b := kutil.NewKubeconfigBuilder()
b.Context = clusterName
err = b.DeleteKubeConfig()
if err != nil {
return fmt.Errorf("error deleting kube config: %v", err)
glog.Warningf("error removing kube config: %v", err)
}
fmt.Fprintf(out, "\nCluster deleted\n")

View File

@ -18,24 +18,16 @@ package kutil
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
// KubeconfigBuilder builds a kubecfg file
// This logic previously lives in the bash scripts (create-kubeconfig in cluster/common.sh)
type KubeconfigBuilder struct {
KubectlPath string
KubeconfigPath string
KubeMasterIP string
Context string
@ -48,63 +40,42 @@ type KubeconfigBuilder struct {
CACert []byte
ClientCert []byte
ClientKey []byte
}
const KUBE_CFG_ENV = clientcmd.RecommendedConfigPathEnvVar + "=%s"
configAccess clientcmd.ConfigAccess
}
// Create new KubeconfigBuilder
func NewKubeconfigBuilder() *KubeconfigBuilder {
c := &KubeconfigBuilder{}
c.KubectlPath = "kubectl" // default to in-path
kubeConfig := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
c.KubeconfigPath = c.getKubectlPath(kubeConfig)
c.configAccess = clientcmd.NewDefaultPathOptions()
return c
}
func DeleteConfig(name string) error {
filename := clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
config, err := clientcmd.LoadFromFile(filename)
func (b *KubeconfigBuilder) DeleteKubeConfig() error {
config, err := b.configAccess.GetStartingConfig()
if err != nil {
return fmt.Errorf("error loading kube config: %v", err)
return fmt.Errorf("error loading kubeconfig: %v", err)
}
if api.IsConfigEmpty(config) {
return fmt.Errorf("kube config is empty")
if config == nil || clientcmdapi.IsConfigEmpty(config) {
glog.V(2).Infof("kubeconfig is empty")
return nil
}
_, ok := config.Clusters[name]
if !ok {
return fmt.Errorf("cluster %s does not exist", name)
}
delete(config.Clusters, name)
delete(config.Clusters, b.Context)
delete(config.AuthInfos, b.Context)
delete(config.AuthInfos, fmt.Sprintf("%s-basic-auth", b.Context))
delete(config.Contexts, b.Context)
_, ok = config.AuthInfos[name]
if !ok {
return fmt.Errorf("user %s does not exist", name)
}
delete(config.AuthInfos, name)
_, ok = config.AuthInfos[fmt.Sprintf("%s-basic-auth", name)]
if !ok {
return fmt.Errorf("user %s-basic-auth does not exist", name)
}
delete(config.AuthInfos, fmt.Sprintf("%s-basic-auth", name))
_, ok = config.Contexts[name]
if !ok {
return fmt.Errorf("context %s does not exist", name)
}
delete(config.Contexts, name)
if config.CurrentContext == name {
if config.CurrentContext == b.Context {
config.CurrentContext = ""
}
err = clientcmd.WriteToFile(*config, filename)
if err != nil {
return err
if err := clientcmd.ModifyConfig(b.configAccess, *config, false); err != nil {
return fmt.Errorf("error writing kubeconfig: %v", err)
}
fmt.Printf("Deleted kubectl config for %s\n", b.Context)
return nil
}
@ -129,161 +100,109 @@ func (c *KubeconfigBuilder) BuildRestConfig() (*restclient.Config, error) {
}
// Write out a new kubeconfig
func (c *KubeconfigBuilder) WriteKubecfg() error {
tmpdir, err := ioutil.TempDir("", "k8s")
func (b *KubeconfigBuilder) WriteKubecfg() error {
config, err := b.configAccess.GetStartingConfig()
if err != nil {
return fmt.Errorf("error creating temporary directory: %v", err)
return fmt.Errorf("error reading kubeconfig: %v", err)
}
defer func() {
err := os.RemoveAll(tmpdir)
if err != nil {
glog.Warningf("error deleting tempdir %q: %v", tmpdir, err)
}
}()
if _, err := os.Stat(c.KubeconfigPath); os.IsNotExist(err) {
err := os.MkdirAll(path.Dir(c.KubeconfigPath), 0700)
if err != nil {
return fmt.Errorf("error creating directories for %q: %v", c.KubeconfigPath, err)
}
f, err := os.OpenFile(c.KubeconfigPath, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return fmt.Errorf("error creating config file %q: %v", c.KubeconfigPath, err)
}
f.Close()
}
var clusterArgs []string
clusterArgs = append(clusterArgs, "--server=https://"+c.KubeMasterIP)
if c.CACert == nil {
clusterArgs = append(clusterArgs, "--insecure-skip-tls-verify=true")
} else {
caCert := path.Join(tmpdir, "ca.crt")
if err := ioutil.WriteFile(caCert, c.CACert, 0600); err != nil {
return err
}
clusterArgs = append(clusterArgs, "--certificate-authority="+caCert)
clusterArgs = append(clusterArgs, "--embed-certs=true")
}
var userArgs []string
if c.KubeBearerToken != "" {
userArgs = append(userArgs, "--token="+c.KubeBearerToken)
} else if c.KubeUser != "" && c.KubePassword != "" {
userArgs = append(userArgs, "--username="+c.KubeUser)
userArgs = append(userArgs, "--password="+c.KubePassword)
}
if c.ClientCert != nil && c.ClientKey != nil {
clientCert := path.Join(tmpdir, "client.crt")
if err := ioutil.WriteFile(clientCert, c.ClientCert, 0600); err != nil {
return err
}
clientKey := path.Join(tmpdir, "client.key")
if err := ioutil.WriteFile(clientKey, c.ClientKey, 0600); err != nil {
return err
}
userArgs = append(userArgs, "--client-certificate="+clientCert)
userArgs = append(userArgs, "--client-key="+clientKey)
userArgs = append(userArgs, "--embed-certs=true")
}
setClusterArgs := []string{"config", "set-cluster", c.Context}
setClusterArgs = append(setClusterArgs, clusterArgs...)
err = c.execKubectl(setClusterArgs...)
if err != nil {
return err
}
if len(userArgs) != 0 {
setCredentialsArgs := []string{"config", "set-credentials", c.Context}
setCredentialsArgs = append(setCredentialsArgs, userArgs...)
err := c.execKubectl(setCredentialsArgs...)
if err != nil {
return err
}
if config == nil {
config = &clientcmdapi.Config{}
}
{
args := []string{"config", "set-context", c.Context, "--cluster=" + c.Context, "--user=" + c.Context}
if c.Namespace != "" {
args = append(args, "--namespace", c.Namespace)
cluster := config.Clusters[b.Context]
if cluster == nil {
cluster = clientcmdapi.NewCluster()
}
err = c.execKubectl(args...)
if err != nil {
return err
cluster.Server = "https://" + b.KubeMasterIP
if b.CACert == nil {
cluster.InsecureSkipTLSVerify = true
cluster.CertificateAuthority = ""
cluster.CertificateAuthorityData = nil
} else {
cluster.InsecureSkipTLSVerify = false
cluster.CertificateAuthority = ""
cluster.CertificateAuthorityData = b.CACert
}
if config.Clusters == nil {
config.Clusters = make(map[string]*clientcmdapi.Cluster)
}
config.Clusters[b.Context] = cluster
}
err = c.execKubectl("config", "use-context", c.Context, "--cluster="+c.Context, "--user="+c.Context)
if err != nil {
return err
{
authInfo := config.AuthInfos[b.Context]
if authInfo == nil {
authInfo = clientcmdapi.NewAuthInfo()
}
if b.KubeBearerToken != "" {
authInfo.Token = b.KubeBearerToken
} else if b.KubeUser != "" && b.KubePassword != "" {
authInfo.Username = b.KubeUser
authInfo.Password = b.KubePassword
}
if b.ClientCert != nil && b.ClientKey != nil {
authInfo.ClientCertificate = ""
authInfo.ClientCertificateData = b.ClientCert
authInfo.ClientKey = ""
authInfo.ClientKeyData = b.ClientKey
}
if config.AuthInfos == nil {
config.AuthInfos = make(map[string]*clientcmdapi.AuthInfo)
}
config.AuthInfos[b.Context] = authInfo
}
// If we have a bearer token, also create a credential entry with basic auth
// so that it is easy to discover the basic auth password for your cluster
// to use in a web browser.
if c.KubeUser != "" && c.KubePassword != "" {
err := c.execKubectl("config", "set-credentials", c.Context+"-basic-auth", "--username="+c.KubeUser, "--password="+c.KubePassword)
if err != nil {
return err
if b.KubeUser != "" && b.KubePassword != "" {
name := b.Context + "-basic-auth"
authInfo := config.AuthInfos[name]
if authInfo == nil {
authInfo = clientcmdapi.NewAuthInfo()
}
authInfo.Username = b.KubeUser
authInfo.Password = b.KubePassword
if config.AuthInfos == nil {
config.AuthInfos = make(map[string]*clientcmdapi.AuthInfo)
}
config.AuthInfos[name] = authInfo
}
fmt.Printf("Wrote config for %s to %q\n", c.Context, c.KubeconfigPath)
fmt.Printf("Kops has set your kubectl context to %s\n", c.Context)
return nil
}
func (c *KubeconfigBuilder) DeleteKubeConfig() {
c.execKubectl("config", "unset", fmt.Sprintf("clusters.%s", c.Context))
c.execKubectl("config", "unset", fmt.Sprintf("users.%s", c.Context))
c.execKubectl("config", "unset", fmt.Sprintf("users.%s-basic-auth", c.Context))
c.execKubectl("config", "unset", fmt.Sprintf("contexts.%s", c.Context))
config, err := clientcmd.LoadFromFile(c.KubeconfigPath)
if err != nil {
fmt.Printf("kubectl unset current-context failed: %v", err)
}
if config.CurrentContext == c.Context {
c.execKubectl("config", "unset", "current-context")
}
fmt.Printf("Deleted kubectl config for %s\n", c.Context)
}
// get the correct path. Handle empty and multiple values.
func (c *KubeconfigBuilder) getKubectlPath(kubeConfig string) string {
if kubeConfig == "" {
return clientcmd.RecommendedHomeFile
}
split := strings.Split(kubeConfig, ":")
if len(split) > 1 {
return split[0]
}
return kubeConfig
}
func (c *KubeconfigBuilder) execKubectl(args ...string) error {
cmd := exec.Command(c.KubectlPath, args...)
env := os.Environ()
env = append(env, fmt.Sprintf(KUBE_CFG_ENV, c.KubeconfigPath))
cmd.Env = env
glog.V(2).Infof("Running command: %s", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
if len(output) != 0 {
glog.Info("error running kubectl. Output follows:")
glog.Info(string(output))
}
return fmt.Errorf("error running kubectl: %v", err)
}
glog.V(2).Info(string(output))
{
context := config.Contexts[b.Context]
if context == nil {
context = clientcmdapi.NewContext()
}
context.Cluster = b.Context
context.AuthInfo = b.Context
if b.Namespace != "" {
context.Namespace = b.Namespace
}
if config.Contexts == nil {
config.Contexts = make(map[string]*clientcmdapi.Context)
}
config.Contexts[b.Context] = context
}
config.CurrentContext = b.Context
if err := clientcmd.ModifyConfig(b.configAccess, *config, true); err != nil {
return err
}
fmt.Printf("Kops has set your kubectl context to %s\n", b.Context)
return nil
}

View File

@ -1,73 +0,0 @@
/*
Copyright 2016 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 kutil
import (
"path"
"testing"
"k8s.io/kubernetes/pkg/util/homedir"
)
const (
RecommendedHomeDir = ".kube"
RecommendedFileName = "config"
)
func TestGetKubectlMultiplePath(t *testing.T) {
c := testCreateKubectlBuilder()
path := c.getKubectlPath(c.KubeconfigPath)
if path != "/tmp/config" {
t.Fatalf("Wrong path got: %s, but expected /tmp/config", path)
}
}
func TestGetKubectlSinglePath(t *testing.T) {
c := testCreateKubectlBuilder()
c.KubeconfigPath = "/bar/config"
path := c.getKubectlPath(c.KubeconfigPath)
if path != "/bar/config" {
t.Fatalf("Wrong path got: %s, but expected /bar/config", path)
}
}
func TestGetKubectlDefault(t *testing.T) {
c := testCreateKubectlBuilder()
c.KubeconfigPath = "/bar/config"
recommendedHomeFile := path.Join(homedir.HomeDir(), RecommendedHomeDir, RecommendedFileName)
path := c.getKubectlPath("")
if path != recommendedHomeFile {
t.Fatalf("Wrong path got: %s, but expected /bar/config", path)
}
}
func testCreateKubectlBuilder() *KubeconfigBuilder {
return &KubeconfigBuilder{
KubectlPath: "/usr/local/bin/kubectl",
KubeconfigPath: "/tmp/config:/config:path2:path3",
KubeMasterIP: "127.0.0.1",
Context: "my-context",
Namespace: "default",
KubeBearerToken: "token",
KubeUser: "user",
KubePassword: "password",
}
}