From ce08b1b235bd16ca2e61bbc7a231f218824f7374 Mon Sep 17 00:00:00 2001 From: "Alex Ellis (OpenFaaS Ltd)" Date: Mon, 25 Nov 2019 13:23:48 +0000 Subject: [PATCH] Add local install mode for k3sup install Closes: #2 This feature allows the k3sup binary to be used to install k3s within cloud-init, through bash, or for general local usage without needing to pass through ssh. Tested against a Civo Ubuntu node both remotely and with an IP given. The new flag is --local. Signed-off-by: Alex Ellis (OpenFaaS Ltd) --- pkg/cmd/install.go | 107 ++++++++++++++++++++++++----------- pkg/cmd/join.go | 6 +- pkg/operator/operator.go | 31 ++++++++++ pkg/{ssh => operator}/ssh.go | 20 +++---- 4 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 pkg/operator/operator.go rename pkg/{ssh => operator}/ssh.go (76%) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 53c6f7e5..b8e8df27 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -11,7 +11,7 @@ import ( "strings" config "github.com/alexellis/k3sup/pkg/config" - kssh "github.com/alexellis/k3sup/pkg/ssh" + operator "github.com/alexellis/k3sup/pkg/operator" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" @@ -32,7 +32,7 @@ func MakeInstall() *cobra.Command { SilenceUsage: true, } - command.Flags().IP("ip", nil, "Public IP of node") + command.Flags().IP("ip", net.ParseIP("127.0.0.1"), "Public IP of node") command.Flags().String("user", "root", "Username for SSH login") command.Flags().String("ssh-key", "~/.ssh/id_rsa", "The ssh key to use for remote login") @@ -45,6 +45,8 @@ func MakeInstall() *cobra.Command { command.Flags().Bool("merge", false, "Merge the config with existing kubeconfig if it already exists.\nProvide the --local-path flag with --merge if a kubeconfig already exists in some other directory") command.Flags().String("k3s-version", config.K3sVersion, "Optional version to install, pinned at a default") + command.Flags().Bool("local", false, "Perform a local install without using ssh") + command.RunE = func(command *cobra.Command, args []string) error { localKubeconfig, _ := command.Flags().GetString("local-path") @@ -57,18 +59,49 @@ func MakeInstall() *cobra.Command { sudoPrefix = "sudo " } - port, _ := command.Flags().GetInt("ssh-port") + k3sVersion, _ := command.Flags().GetString("k3s-version") + k3sExtraArgs, _ := command.Flags().GetString("k3s-extra-args") + + local, _ := command.Flags().GetBool("local") ip, _ := command.Flags().GetIP("ip") - fmt.Println("Public IP: " + ip.String()) - user, _ := command.Flags().GetString("user") - sshKey, _ := command.Flags().GetString("ssh-key") + installK3scommand := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --tls-san %s %s' INSTALL_K3S_VERSION='%s' sh -\n", ip, strings.TrimSpace(k3sExtraArgs), k3sVersion) + getConfigcommand := fmt.Sprintf(sudoPrefix + "cat /etc/rancher/k3s/k3s.yaml\n") merge, _ := command.Flags().GetBool("merge") - k3sExtraArgs, _ := command.Flags().GetString("k3s-extra-args") context, _ := command.Flags().GetString("context") - k3sVersion, _ := command.Flags().GetString("k3s-version") + if local { + operator := operator.ExecOperator{} + + fmt.Printf("Executing: %s\n", installK3scommand) + + res, err := operator.Execute(installK3scommand) + if err != nil { + return err + } + + if len(res.StdErr) > 0 { + fmt.Printf("stderr: %q", res.StdErr) + } + if len(res.StdOut) > 0 { + fmt.Printf("stdout: %q", res.StdOut) + } + + err = obtainKubeconfig(operator, getConfigcommand, ip.String(), context, localKubeconfig, merge) + if err != nil { + return err + } + + return nil + } + + port, _ := command.Flags().GetInt("ssh-port") + + fmt.Println("Public IP: " + ip.String()) + + user, _ := command.Flags().GetString("user") + sshKey, _ := command.Flags().GetString("ssh-key") sshKeyPath := expandPath(sshKey) fmt.Printf("ssh -i %s %s@%s\n", sshKeyPath, user, ip.String()) @@ -89,7 +122,7 @@ func MakeInstall() *cobra.Command { } address := fmt.Sprintf("%s:%d", ip.String(), port) - operator, err := kssh.NewSSHOperator(address, config) + operator, err := operator.NewSSHOperator(address, config) if err != nil { return errors.Wrapf(err, "unable to connect to %s over ssh", address) @@ -98,7 +131,6 @@ func MakeInstall() *cobra.Command { defer operator.Close() if !skipInstall { - installK3scommand := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --tls-san %s %s' INSTALL_K3S_VERSION='%s' sh -\n", ip, strings.TrimSpace(k3sExtraArgs), k3sVersion) fmt.Printf("ssh: %s\n", installK3scommand) res, err := operator.Execute(installK3scommand) @@ -110,32 +142,11 @@ func MakeInstall() *cobra.Command { fmt.Printf("Result: %s %s\n", string(res.StdOut), string(res.StdErr)) } - getConfigcommand := fmt.Sprintf(sudoPrefix + "cat /etc/rancher/k3s/k3s.yaml\n") fmt.Printf("ssh: %s\n", getConfigcommand) - res, err := operator.Execute(getConfigcommand) - + err = obtainKubeconfig(operator, getConfigcommand, ip.String(), context, localKubeconfig, merge) if err != nil { - return fmt.Errorf("Error received processing command: %s", err) - } - - fmt.Printf("Result: %s %s\n", string(res.StdOut), string(res.StdErr)) - - absPath, _ := filepath.Abs(localKubeconfig) - - kubeconfig := rewriteKubeconfig(string(res.StdOut), ip.String(), context) - - if merge { - // Create a merged kubeconfig - kubeconfig, err = mergeConfigs(absPath, []byte(kubeconfig)) - if err != nil { - return err - } - } - - // Create a new kubeconfig - if writeErr := writeConfig(absPath, []byte(kubeconfig), false); writeErr != nil { - return writeErr + return err } return nil @@ -156,11 +167,41 @@ func MakeInstall() *cobra.Command { return command } +func obtainKubeconfig(operator operator.CommandOperator, getConfigcommand, ip, context, localKubeconfig string, merge bool) error { + + res, err := operator.Execute(getConfigcommand) + + if err != nil { + return fmt.Errorf("Error received processing command: %s", err) + } + + fmt.Printf("Result: %s %s\n", string(res.StdOut), string(res.StdErr)) + + absPath, _ := filepath.Abs(localKubeconfig) + + kubeconfig := rewriteKubeconfig(string(res.StdOut), ip, context) + + if merge { + // Create a merged kubeconfig + kubeconfig, err = mergeConfigs(absPath, []byte(kubeconfig)) + if err != nil { + return err + } + } + + // Create a new kubeconfig + if writeErr := writeConfig(absPath, []byte(kubeconfig), false); writeErr != nil { + return writeErr + } + return nil +} + // Generates config files give the path to file: string and the data: []byte func writeConfig(path string, data []byte, suppressMessage bool) error { absPath, _ := filepath.Abs(path) if !suppressMessage { fmt.Printf("Saving file to: %s\n", absPath) + fmt.Printf("\n# Test your cluster with:\nexport KUBECONFIG=%s\nkubectl get node -o wide\n", absPath) } writeErr := ioutil.WriteFile(absPath, []byte(data), 0600) if writeErr != nil { diff --git a/pkg/cmd/join.go b/pkg/cmd/join.go index de1f3af2..db3a2502 100644 --- a/pkg/cmd/join.go +++ b/pkg/cmd/join.go @@ -6,7 +6,7 @@ import ( "strings" config "github.com/alexellis/k3sup/pkg/config" - kssh "github.com/alexellis/k3sup/pkg/ssh" + operator "github.com/alexellis/k3sup/pkg/operator" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" @@ -86,7 +86,7 @@ func MakeJoin() *cobra.Command { } address := fmt.Sprintf("%s:%d", serverIP.String(), serverPort) - operator, err := kssh.NewSSHOperator(address, config) + operator, err := operator.NewSSHOperator(address, config) if err != nil { return errors.Wrapf(err, "unable to connect to %s over ssh", address) @@ -153,7 +153,7 @@ func setupAgent(serverIP, ip net.IP, port int, user, sshKeyPath, joinToken, k3sE } address := fmt.Sprintf("%s:%d", ip.String(), port) - operator, err := kssh.NewSSHOperator(address, config) + operator, err := operator.NewSSHOperator(address, config) if err != nil { return errors.Wrapf(err, "unable to connect to %s over ssh", address) diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go new file mode 100644 index 00000000..22eface9 --- /dev/null +++ b/pkg/operator/operator.go @@ -0,0 +1,31 @@ +package ssh + +import ( + goexecute "github.com/alexellis/go-execute/pkg/v1" +) + +type CommandOperator interface { + Execute(command string) (CommandRes, error) +} + +type ExecOperator struct { +} + +func (ex ExecOperator) Execute(command string) (CommandRes, error) { + + task := goexecute.ExecTask{ + Command: command, + Shell: true, + } + + res, err := task.Execute() + if err != nil { + return CommandRes{}, err + } + + return CommandRes{ + StdErr: []byte(res.Stderr), + StdOut: []byte(res.Stdout), + }, nil + +} diff --git a/pkg/ssh/ssh.go b/pkg/operator/ssh.go similarity index 76% rename from pkg/ssh/ssh.go rename to pkg/operator/ssh.go index cc3fb14c..78612494 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/operator/ssh.go @@ -13,7 +13,7 @@ type SSHOperator struct { conn *ssh.Client } -func (s *SSHOperator) Close() error { +func (s SSHOperator) Close() error { return s.conn.Close() } @@ -31,18 +31,18 @@ func NewSSHOperator(address string, config *ssh.ClientConfig) (*SSHOperator, err return &operator, nil } -func (s *SSHOperator) Execute(command string) (commandRes, error) { +func (s SSHOperator) Execute(command string) (CommandRes, error) { sess, err := s.conn.NewSession() if err != nil { - return commandRes{}, err + return CommandRes{}, err } defer sess.Close() sessStdOut, err := sess.StdoutPipe() if err != nil { - return commandRes{}, err + return CommandRes{}, err } output := bytes.Buffer{} @@ -57,7 +57,7 @@ func (s *SSHOperator) Execute(command string) (commandRes, error) { }() sessStderr, err := sess.StderrPipe() if err != nil { - return commandRes{}, err + return CommandRes{}, err } errorOutput := bytes.Buffer{} @@ -73,21 +73,21 @@ func (s *SSHOperator) Execute(command string) (commandRes, error) { wg.Wait() if err != nil { - return commandRes{}, err + return CommandRes{}, err } - return commandRes{ + return CommandRes{ StdErr: errorOutput.Bytes(), StdOut: output.Bytes(), }, nil } -type commandRes struct { +type CommandRes struct { StdOut []byte StdErr []byte } -func executeCommand(cmd string) (commandRes, error) { +func executeCommand(cmd string) (CommandRes, error) { - return commandRes{}, nil + return CommandRes{}, nil }