From 8fc352ec4f99807cae5bd8ba8d1c6cab4a1de216 Mon Sep 17 00:00:00 2001 From: Artem Chernyshev Date: Fri, 10 Jul 2020 16:08:50 +0300 Subject: [PATCH] feat: merge mode in talosctl kubeconfig New flag `-m` will enable merge mechanism in `talosctl kubeconfig` Command examples: ``` talosctl kubeconfig -m talosctl kubeconfig -m ~/.kube/config ``` Signed-off-by: Artem Chernyshev --- cmd/talosctl/cmd/talos/kubeconfig.go | 70 +++++++++++++++++- cmd/talosctl/pkg/talos/helpers/archive.go | 39 ++++++++++ .../pkg/talos/helpers/helpers_test.go | 38 ++++++++-- .../pkg/talos/helpers/testdata/archive.tar.gz | Bin 0 -> 6738 bytes docs/talosctl/talosctl_kubeconfig.md | 2 + go.mod | 10 ++- go.sum | 52 ++++++++++++- internal/integration/cli/kubeconfig.go | 27 ++++++- 8 files changed, 221 insertions(+), 17 deletions(-) create mode 100644 cmd/talosctl/pkg/talos/helpers/testdata/archive.tar.gz diff --git a/cmd/talosctl/cmd/talos/kubeconfig.go b/cmd/talosctl/cmd/talos/kubeconfig.go index 456c76dab..d861d4708 100644 --- a/cmd/talosctl/cmd/talos/kubeconfig.go +++ b/cmd/talosctl/cmd/talos/kubeconfig.go @@ -7,24 +7,33 @@ package talos import ( "context" "fmt" + "io" "os" + "os/user" "path/filepath" "sync" + "github.com/particledecay/kconf/pkg/kubeconfig" + "k8s.io/client-go/tools/clientcmd" + "github.com/spf13/cobra" "github.com/talos-systems/talos/cmd/talosctl/pkg/talos/helpers" "github.com/talos-systems/talos/pkg/client" ) -var force bool +var ( + force bool + merge bool +) // kubeconfigCmd represents the kubeconfig command. var kubeconfigCmd = &cobra.Command{ Use: "kubeconfig [local-path]", Short: "Download the admin kubeconfig from the node", Long: `Download the admin kubeconfig from the node. -Kubeconfig will be written to PWD/kubeconfig or [local-path]/kubeconfig if specified.`, +Kubeconfig will be written to PWD/kubeconfig or [local-path]/kubeconfig if specified. +If merge flag is defined, config will be merged with ~/.kube/config or [local-path] if specified.`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return WithClient(func(ctx context.Context, c *client.Client) error { @@ -49,11 +58,14 @@ Kubeconfig will be written to PWD/kubeconfig or [local-path]/kubeconfig if speci defer wg.Wait() + if merge { + return extractAndMerge(args, r) + } + localPath, err := os.Getwd() if err != nil { return fmt.Errorf("error getting current working directory: %s", err) } - if len(args) == 1 { localPath = args[0] } @@ -72,7 +84,59 @@ Kubeconfig will be written to PWD/kubeconfig or [local-path]/kubeconfig if speci }, } +func extractAndMerge(args []string, r io.ReadCloser) error { + data, err := helpers.ExtractFileFromTarGz("kubeconfig", r) + if err != nil { + return err + } + + var localPath string + + if len(args) == 1 { + localPath = filepath.Clean(args[0]) + } else { + var usr *user.User + usr, err = user.Current() + + if err != nil { + return err + } + + localPath = filepath.Join(usr.HomeDir, ".kube/config") + } + + config, err := clientcmd.Load(data) + if err != nil { + return err + } + + // base file does not exist, dump config as is + if _, err = os.Stat(localPath); os.IsNotExist(err) { + err = os.MkdirAll(filepath.Dir(localPath), 0755) + if err != nil { + return err + } + + return clientcmd.WriteToFile(*config, localPath) + } + + baseConfig, err := clientcmd.LoadFromFile(localPath) + if err != nil { + return err + } + + kconf := kubeconfig.KConf{Config: *baseConfig} + err = kconf.Merge(config, Cmdcontext) + + if err != nil { + return err + } + + return clientcmd.WriteToFile(kconf.Config, localPath) +} + func init() { kubeconfigCmd.Flags().BoolVarP(&force, "force", "f", false, "Force overwrite of kubeconfig if already present") + kubeconfigCmd.Flags().BoolVarP(&merge, "merge", "m", false, "Merge with existing kubeconfig") addCommand(kubeconfigCmd) } diff --git a/cmd/talosctl/pkg/talos/helpers/archive.go b/cmd/talosctl/pkg/talos/helpers/archive.go index 1c51ceca2..b49a6437f 100644 --- a/cmd/talosctl/pkg/talos/helpers/archive.go +++ b/cmd/talosctl/pkg/talos/helpers/archive.go @@ -9,12 +9,51 @@ import ( "compress/gzip" "fmt" "io" + "io/ioutil" "os" "path/filepath" "github.com/talos-systems/talos/pkg/safepath" ) +// ExtractFileFromTarGz reads a single file data from an archive. +func ExtractFileFromTarGz(filename string, r io.ReadCloser) ([]byte, error) { + defer r.Close() //nolint:errcheck + + zr, err := gzip.NewReader(r) + if err != nil { + return nil, fmt.Errorf("error initializing gzip: %w", err) + } + + tr := tar.NewReader(zr) + + for { + hdr, err := tr.Next() + if err != nil { + if err == io.EOF { + break + } + + return nil, err + } + + hdrPath := safepath.CleanPath(hdr.Name) + if hdrPath == "" { + return nil, fmt.Errorf("empty tar header path") + } + + if hdrPath == filename { + if hdr.Typeflag == tar.TypeDir || hdr.Typeflag == tar.TypeSymlink { + return nil, fmt.Errorf("%s is not a file", filename) + } + + return ioutil.ReadAll(tr) + } + } + + return nil, fmt.Errorf("couldn't find file %s in the archive", filename) +} + // ExtractTarGz extracts .tar.gz archive from r into filesystem under localPath. // //nolint: gocyclo diff --git a/cmd/talosctl/pkg/talos/helpers/helpers_test.go b/cmd/talosctl/pkg/talos/helpers/helpers_test.go index 5ea27158f..3e027fda7 100644 --- a/cmd/talosctl/pkg/talos/helpers/helpers_test.go +++ b/cmd/talosctl/pkg/talos/helpers/helpers_test.go @@ -4,11 +4,37 @@ package helpers_test -import "testing" +import ( + "os" + "testing" -func TestEmpty(t *testing.T) { - // added for accurate coverage estimation - // - // please remove it once any unit-test is added - // for this package + yaml "gopkg.in/yaml.v2" + + "github.com/stretchr/testify/assert" + + "github.com/talos-systems/talos/cmd/talosctl/pkg/talos/helpers" +) + +type cfg struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` +} + +func TestExtractFileFromTarGz(t *testing.T) { + file, err := os.Open("./testdata/archive.tar.gz") + assert.NoError(t, err) + + data, err := helpers.ExtractFileFromTarGz("kubeconfig", file) + assert.NoError(t, err) + + // just some primitive sanity check that yaml file inside was not corrupted somehow + var c cfg + err = yaml.Unmarshal(data, &c) + assert.NoError(t, err) + + assert.Equal(t, c.APIVersion, "v1") + assert.Equal(t, c.Kind, "Config") + + _, err = helpers.ExtractFileFromTarGz("void", file) + assert.Error(t, err) } diff --git a/cmd/talosctl/pkg/talos/helpers/testdata/archive.tar.gz b/cmd/talosctl/pkg/talos/helpers/testdata/archive.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..6883b382eb951975d650966df2bddd91d9c77a93 GIT binary patch literal 6738 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