Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend kubeconfig writer to support global path with merging #80

Merged
merged 7 commits into from
Jul 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ RUN mkdir -p "$(dirname ${EKSCTL})"
COPY . $EKSCTL

WORKDIR $EKSCTL
RUN make \
RUN make test && make \
&& cp ./eksctl /out/usr/local/bin/eksctl

RUN go build ./vendor/github.com/heptio/authenticator/cmd/heptio-authenticator-aws \
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ EKSCTL_IMAGE ?= weaveworks/eksctl:latest
build:
go build -ldflags "-X main.gitCommit=$(git_commit) -X main.builtAt=$(built_at)" ./cmd/eksctl

.PHONY: test
test:
go test -v ./pkg/... ./cmd/...

.PHONY: update-bindata
update-bindata:
go generate ./pkg/eks
Expand Down
25 changes: 14 additions & 11 deletions cmd/eksctl/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/weaveworks/eksctl/pkg/eks"
"github.com/weaveworks/eksctl/pkg/utils"
"github.com/weaveworks/eksctl/pkg/utils/kubeconfig"
)

func createCmd() *cobra.Command {
Expand All @@ -35,14 +36,13 @@ const (
DEFAULT_NODE_COUNT = 2
DEFAULT_NODE_TYPE = "m5.large"
DEFAULT_SSH_PUBLIC_KEY = "~/.ssh/id_rsa.pub"

DEFAULT_KUBECONFIG_PATH = "kubeconfig"
)

var (
writeKubeconfig bool
kubeconfigPath string
autoKubeconfigPath bool
setContext bool
)

func createClusterCmd() *cobra.Command {
Expand Down Expand Up @@ -78,8 +78,9 @@ func createClusterCmd() *cobra.Command {
fs.StringVar(&cfg.SSHPublicKeyPath, "ssh-public-key", DEFAULT_SSH_PUBLIC_KEY, "SSH public key to use for nodes (import from local path, or use existing EC2 key pair)")

fs.BoolVar(&writeKubeconfig, "write-kubeconfig", true, "toggle writing of kubeconfig")
fs.BoolVar(&autoKubeconfigPath, "auto-kubeconfig", false, fmt.Sprintf("save kubconfig file by cluster name, e.g. %q", utils.ConfigPath(exampleClusterName)))
fs.StringVar(&kubeconfigPath, "kubeconfig", DEFAULT_KUBECONFIG_PATH, "path to write kubeconfig (incompatible with --auto-kubeconfig)")
fs.BoolVar(&autoKubeconfigPath, "auto-kubeconfig", false, fmt.Sprintf("save kubconfig file by cluster name, e.g. %q", kubeconfig.AutoPath(exampleClusterName)))
fs.StringVar(&kubeconfigPath, "kubeconfig", kubeconfig.DefaultPath, "path to write kubeconfig (incompatible with --auto-kubeconfig)")
fs.BoolVar(&setContext, "set-kubeconfig-context", true, "if true then current-context will be set in kubeconfig; if a context is already set then it will be overwritten")

fs.DurationVar(&cfg.AWSOperationTimeout, "aws-api-timeout", 20*time.Minute, "number of seconds after which to timeout AWS API operations")

Expand All @@ -101,10 +102,10 @@ func doCreateCluster(cfg *eks.ClusterConfig, name string) error {
cfg.ClusterName = utils.ClusterName(cfg.ClusterName, name)

if autoKubeconfigPath {
if kubeconfigPath != DEFAULT_KUBECONFIG_PATH {
if kubeconfigPath != kubeconfig.DefaultPath {
return fmt.Errorf("--kubeconfig and --auto-kubeconfig cannot be used at the same time")
}
kubeconfigPath = utils.ConfigPath(cfg.ClusterName)
kubeconfigPath = kubeconfig.AutoPath(cfg.ClusterName)
}

if cfg.SSHPublicKeyPath == "" {
Expand Down Expand Up @@ -143,12 +144,14 @@ func doCreateCluster(cfg *eks.ClusterConfig, name string) error {
return err
}

// TODO: https://github.com/weaveworks/eksctl/issues/29
if writeKubeconfig {
if err := clientConfigBase.WithExecHeptioAuthenticator().WriteToFile(kubeconfigPath); err != nil {
config := clientConfigBase.WithExecHeptioAuthenticator()
kubeconfigPath, err = kubeconfig.Write(kubeconfigPath, config.Client, setContext)
if err != nil {
return errors.Wrap(err, "writing kubeconfig")
}
logger.Info("wrote %q", kubeconfigPath)

logger.Success("saved kubeconfig as %q", kubeconfigPath)
} else {
kubeconfigPath = ""
}
Expand All @@ -172,9 +175,9 @@ func doCreateCluster(cfg *eks.ClusterConfig, name string) error {
// check kubectl version, and offer install instructions if missing or old
// also check heptio-authenticator
// TODO: https://github.com/weaveworks/eksctl/issues/30
if err := utils.CheckAllCommands(kubeconfigPath); err != nil {
if err := utils.CheckAllCommands(kubeconfigPath, setContext, clientConfigBase.ContextName); err != nil {
logger.Critical(err.Error())
logger.Info("cluster should be functional despite missing client binaries that need to be installed in the PATH")
logger.Info("cluster should be functional despite missing (or misconfigured) client binaries")
}
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/eksctl/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/kubicorn/kubicorn/pkg/logger"

"github.com/weaveworks/eksctl/pkg/eks"
"github.com/weaveworks/eksctl/pkg/utils"
"github.com/weaveworks/eksctl/pkg/utils/kubeconfig"
)

func deleteCmd() *cobra.Command {
Expand Down Expand Up @@ -89,7 +89,7 @@ func doDeleteCluster(cfg *eks.ClusterConfig, name string) error {

ctl.MaybeDeletePublicSSHKey()

utils.MaybeDeleteConfig(cfg.ClusterName)
kubeconfig.MaybeDeleteConfig(cfg.ClusterName)

logger.Success("all EKS cluster %q resource will be deleted (if in doubt, check CloudFormation console)", cfg.ClusterName)

Expand Down
14 changes: 9 additions & 5 deletions cmd/eksctl/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import (

"github.com/kubicorn/kubicorn/pkg/logger"
"github.com/weaveworks/eksctl/pkg/eks"
"github.com/weaveworks/eksctl/pkg/utils"
"github.com/weaveworks/eksctl/pkg/utils/kubeconfig"
)

var (
utilsKubeconfigInputPath string
utilsKubeconfigOutputPath string
utilsSetContext bool
)

func utilsCmd() *cobra.Command {
Expand Down Expand Up @@ -100,7 +101,8 @@ func writeKubeconfigCmd() *cobra.Command {
fs.StringVarP(&cfg.Region, "region", "r", DEFAULT_EKS_REGION, "AWS region")
fs.StringVarP(&cfg.Profile, "profile", "p", "", "AWS profile to use. If provided, this overrides the AWS_PROFILE environment variable")

fs.StringVar(&utilsKubeconfigOutputPath, "kubeconfig", "", "path to write kubeconfig")
fs.StringVar(&utilsKubeconfigOutputPath, "kubeconfig", kubeconfig.DefaultPath, "path to write kubeconfig")
fs.BoolVar(&utilsSetContext, "set-kubeconfig-context", true, "if true then current-context will be set in kubeconfig; if a context is already set then it will be overwritten")

return cmd
}
Expand All @@ -125,7 +127,7 @@ func doWriteKubeconfigCmd(cfg *eks.ClusterConfig, name string) error {
}

if utilsKubeconfigOutputPath == "" {
utilsKubeconfigOutputPath = utils.ConfigPath(cfg.ClusterName)
utilsKubeconfigOutputPath = kubeconfig.AutoPath(cfg.ClusterName)
}

cluster, err := ctl.DescribeControlPlane()
Expand All @@ -144,11 +146,13 @@ func doWriteKubeconfigCmd(cfg *eks.ClusterConfig, name string) error {
return err
}

if err := clientConfigBase.WithExecHeptioAuthenticator().WriteToFile(utilsKubeconfigOutputPath); err != nil {
config := clientConfigBase.WithExecHeptioAuthenticator()
filename, err := kubeconfig.Write(utilsKubeconfigOutputPath, config.Client, utilsSetContext)
if err != nil {
return errors.Wrap(err, "writing kubeconfig")
}

logger.Info("wrote kubeconfig file %q", utilsKubeconfigOutputPath)
logger.Success("saved kubeconfig as %q", filename)

return nil
}
33 changes: 16 additions & 17 deletions pkg/eks/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,19 @@ func (c *ClusterProvider) MaybeDeletePublicSSHKey() {

func (c *ClusterProvider) getUsername() string {
usernameParts := strings.Split(c.svc.arn, "/")
username := usernameParts[len(usernameParts)-1]
return username
if len(usernameParts) > 1 {
return usernameParts[len(usernameParts)-1]
}
return "iam-root-account"
}

type ClientConfig struct {
Client *clientcmdapi.Config
Cluster *ClusterConfig
roleARN string
sts stsiface.STSAPI
Client *clientcmdapi.Config
Cluster *ClusterConfig
ClusterName string
ContextName string
roleARN string
sts stsiface.STSAPI
}

// based on "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
Expand Down Expand Up @@ -109,8 +113,10 @@ func (c *ClusterProvider) NewClientConfig() (*ClientConfig, error) {
},
CurrentContext: contextName,
},
roleARN: c.svc.arn,
sts: c.svc.sts,
ClusterName: clusterName,
ContextName: contextName,
roleARN: c.svc.arn,
sts: c.svc.sts,
}

return clientConfig, nil
Expand All @@ -119,7 +125,7 @@ func (c *ClusterProvider) NewClientConfig() (*ClientConfig, error) {
func (c *ClientConfig) WithExecHeptioAuthenticator() *ClientConfig {
clientConfigCopy := *c

x := clientConfigCopy.Client.AuthInfos[c.Client.CurrentContext]
x := clientConfigCopy.Client.AuthInfos[c.ContextName]
x.Exec = &clientcmdapi.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1alpha1",
Command: "heptio-authenticator-aws",
Expand Down Expand Up @@ -150,19 +156,12 @@ func (c *ClientConfig) WithEmbeddedToken() (*ClientConfig, error) {
return nil, errors.Wrap(err, "could not get token")
}

x := c.Client.AuthInfos[c.Client.CurrentContext]
x := c.Client.AuthInfos[c.ContextName]
x.Token = tok

return &clientConfigCopy, nil
}

func (c *ClientConfig) WriteToFile(filename string) error {
if err := clientcmd.WriteToFile(*c.Client, filename); err != nil {
return errors.Wrapf(err, "couldn't write client config file %q", filename)
}
return nil
}

func (c *ClientConfig) NewClientSet() (*clientset.Clientset, error) {
clientConfig, err := clientcmd.NewDefaultClientConfig(*c.Client, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
Expand Down
126 changes: 126 additions & 0 deletions pkg/utils/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package kubeconfig

import (
"fmt"
"os"
"path"
"strings"

"github.com/weaveworks/eksctl/pkg/utils"

"github.com/pkg/errors"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"

"github.com/kubicorn/kubicorn/pkg/logger"
)

var DefaultPath = clientcmd.RecommendedHomeFile

// Write will write Kubernetes client configuration to a file.
// If path isn't specified then the path will be determined by client-go.
// If file pointed to by path doesn't exist it will be created.
// If the file already exists then the configuration will be merged with the existing file.
func Write(path string, newConfig *api.Config, setContext bool) (string, error) {
configAccess := getConfigAccess(path)

config, err := configAccess.GetStartingConfig()

logger.Debug("merging kubeconfig files")
merged, err := merge(config, newConfig)
if err != nil {
return "", errors.Wrapf(err, "unable to merge configuration with existing kubeconfig file %q", path)
}

if setContext && newConfig.CurrentContext != "" {
logger.Debug("setting current-context to %s", newConfig.CurrentContext)
merged.CurrentContext = newConfig.CurrentContext
}

if err := clientcmd.ModifyConfig(configAccess, *merged, true); err != nil {
return "", nil
}

return configAccess.GetDefaultFilename(), nil
}

func getConfigAccess(explicitPath string) clientcmd.ConfigAccess {
pathOptions := clientcmd.NewDefaultPathOptions()
if explicitPath != "" && explicitPath != DefaultPath {
pathOptions.LoadingRules.ExplicitPath = explicitPath
}

return interface{}(pathOptions).(clientcmd.ConfigAccess)
}

func merge(existing *api.Config, tomerge *api.Config) (*api.Config, error) {
for k, v := range tomerge.Clusters {
existing.Clusters[k] = v
}
for k, v := range tomerge.AuthInfos {
existing.AuthInfos[k] = v
}
for k, v := range tomerge.Contexts {
existing.Contexts[k] = v
}

return existing, nil
}

func AutoPath(name string) string {
return path.Join(clientcmd.RecommendedConfigDir, "eksctl", "clusters", name)
}

func isValidConfig(p, name string) error {
clientConfig, err := clientcmd.LoadFromFile(p)
if err != nil {
return errors.Wrapf(err, "unable to load config %q", p)
}

if err := clientcmd.ConfirmUsable(*clientConfig, ""); err != nil {
return errors.Wrapf(err, "unable to parse config %q", p)
}

// we want to make sure we only delete config files that haven't be modified by the user
// checking context name is a good start, we might want ot do deeper checks later, e.g. checksum,
// as we don't want to delete any files by accident that didn't belong to us
ctxFmtErr := fmt.Errorf("unable to verify ownership of config %q, unexpected contex name %q", p, clientConfig.CurrentContext)

ctx := strings.Split(clientConfig.CurrentContext, "@")
if len(ctx) != 2 {
return ctxFmtErr
}
if strings.HasPrefix(ctx[1], name+".") && strings.HasSuffix(ctx[1], ".eksctl.io") {
return nil
}
return ctxFmtErr
}

func tryDeleteConfig(p, name string) {
if err := isValidConfig(p, name); err != nil {
logger.Debug("ignoring error while checking config file %q: %s", p, err.Error())
return
}
if err := os.Remove(p); err != nil {
logger.Debug("ignoring error while removing config file %q: %s", p, err.Error())
}
}

func MaybeDeleteConfig(name string) {
p := AutoPath(name)

autoConfExists, err := utils.FileExists(p)
if err != nil {
logger.Debug("error checking if auto-generated kubeconfig file exists %q: %s", p, err.Error())
return
}
if autoConfExists {
if err := os.Remove(p); err != nil {
logger.Debug("ignoring error while removing auto-generated config file %q: %s", p, err.Error())
}
return
}

// Print message to manually remove from config file
logger.Warning("as you are not using the auto-generated kubeconfig file you will need to remove the details of cluster %s manually", name)
}
Loading