From 9e0d77d5c69432a59e5d78a8159d3a4e0ac0b11b Mon Sep 17 00:00:00 2001 From: decentral1se Date: Mon, 25 Oct 2021 10:42:39 +0200 Subject: [PATCH] refactor: better SSH connection details handling --- cli/recipe/new.go | 7 +-- cli/server/add.go | 139 +++++++++++++++++++++++++++++----------------- pkg/ssh/ssh.go | 33 ++++++----- 3 files changed, 106 insertions(+), 73 deletions(-) diff --git a/cli/recipe/new.go b/cli/recipe/new.go index f487e5740..ed5134872 100644 --- a/cli/recipe/new.go +++ b/cli/recipe/new.go @@ -24,18 +24,16 @@ var recipeNewCommand = &cli.Command{ directory := path.Join(config.APPS_DIR, recipe.Name) if _, err := os.Stat(directory); !os.IsNotExist(err) { logrus.Fatalf("'%s' recipe directory already exists?", directory) - return nil } url := fmt.Sprintf("%s/example.git", config.REPOS_BASE_URL) if err := git.Clone(directory, url); err != nil { - return err + logrus.Fatal(err) } gitRepo := path.Join(config.APPS_DIR, recipe.Name, ".git") if err := os.RemoveAll(gitRepo); err != nil { logrus.Fatal(err) - return nil } logrus.Debugf("removed git repo in '%s'", gitRepo) @@ -48,13 +46,11 @@ var recipeNewCommand = &cli.Command{ file, err := os.OpenFile(path, os.O_RDWR, 0755) if err != nil { logrus.Fatal(err) - return nil } tpl, err := template.ParseFiles(path) if err != nil { logrus.Fatal(err) - return nil } // TODO: ask for description and probably other things so that the @@ -65,7 +61,6 @@ var recipeNewCommand = &cli.Command{ Description string }{recipe.Name, "TODO"}); err != nil { logrus.Fatal(err) - return nil } } diff --git a/cli/server/add.go b/cli/server/add.go index a1840f767..d8cdad5a0 100644 --- a/cli/server/add.go +++ b/cli/server/add.go @@ -8,21 +8,43 @@ import ( "os" "os/exec" "os/user" + "path/filepath" "strings" "time" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/client" + "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/server" "coopcloud.tech/abra/pkg/ssh" "github.com/AlecAivazis/survey/v2" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" dockerClient "github.com/docker/docker/client" + "github.com/sfreiberg/simplessh" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) +var ( + dockerInstallMsg = ` +A docker installation cannot be found on %s. This is a required system dependency +for running Co-op Cloud on your server. If you would like, Abra can attempt to install +Docker for you using the upstream non-interactive installation script. + +See the following documentation for more: + + https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script + +N.B Docker doesn't recommend it for production environments but many use it for +such purposes. Docker stable is now installed by default by this script. The +source for this script can be seen here: + + https://github.com/docker/docker-install + +` +) + var local bool var localFlag = &cli.BoolFlag{ Name: "local", @@ -68,6 +90,40 @@ var traefikFlag = &cli.BoolFlag{ Destination: &traefik, } +func cleanUp(domainName string) { + logrus.Warnf("cleaning up context for %s", domainName) + if err := client.DeleteContext(domainName); err != nil { + logrus.Fatal(err) + } + + logrus.Warnf("cleaning up server directory for %s", domainName) + if err := os.RemoveAll(filepath.Join(config.ABRA_SERVER_FOLDER, domainName)); err != nil { + logrus.Fatal(err) + } +} + +func installDockerLocal(c *cli.Context) error { + fmt.Println(fmt.Sprintf(dockerInstallMsg, "this local server")) + + response := false + prompt := &survey.Confirm{ + Message: fmt.Sprintf("attempt install docker on local server?"), + } + if err := survey.AskOne(prompt, &response); err != nil { + return err + } + if !response { + logrus.Fatal("exiting as requested") + } + + cmd := exec.Command("bash", "-c", "curl -s https://get.docker.com | bash") + if err := internal.RunCmd(cmd); err != nil { + return err + } + + return nil +} + func newLocalServer(c *cli.Context, domainName string) error { if err := createServerDir(domainName); err != nil { return err @@ -80,7 +136,7 @@ func newLocalServer(c *cli.Context, domainName string) error { if _, err := exec.LookPath("docker"); err != nil { if provision { - if err := installDocker(c, cl, domainName); err != nil { + if err := installDockerLocal(c); err != nil { return err } if err := initSwarm(c, cl, domainName); err != nil { @@ -104,21 +160,7 @@ func newLocalServer(c *cli.Context, domainName string) error { return nil } -func newContext(c *cli.Context, domainName string) error { - username := c.Args().Get(1) - if username == "" { - systemUser, err := user.Current() - if err != nil { - return err - } - username = systemUser.Username - } - - port := c.Args().Get(2) - if port == "" { - port = "22" - } - +func newContext(c *cli.Context, domainName, username, port string) error { store := client.NewDefaultDockerContextStore() contexts, err := store.Store.List() if err != nil { @@ -144,48 +186,19 @@ func newContext(c *cli.Context, domainName string) error { func newClient(c *cli.Context, domainName string) (*dockerClient.Client, error) { cl, err := client.New(domainName) if err != nil { - logrus.Warn("cleaning up context due to connection failure") - if err := client.DeleteContext(domainName); err != nil { - return &dockerClient.Client{}, err - } return &dockerClient.Client{}, err } return cl, nil } -func installDocker(c *cli.Context, cl *dockerClient.Client, domainName string) error { - logrus.Debugf("attempting to construct SSH client for %s", domainName) - - client, err := ssh.New(domainName, sshAuth) - if err != nil { - return err - } - defer client.Close() - - logrus.Debugf("successfully created SSH client for %s", domainName) - - result, err := client.Exec("which docker") +func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *simplessh.Client, domainName string) error { + result, err := sshCl.Exec("which docker") if err != nil && string(result) != "" { return err } if string(result) == "" { - fmt.Println(fmt.Sprintf(` -A docker installation cannot be found on %s. This is a required system dependency -for running Co-op Cloud on your server. If you would like, Abra can attempt to install -Docker for you using the upstream non-interactive installation script. - -See the following documentation for more: - - https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script - -N.B Docker doesn't recommend it for production environments but many use it for -such purposes. Docker stable is now installed by default by this script. The -source for this script can be seen here: - - https://github.com/docker/docker-install - -`, domainName)) + fmt.Println(fmt.Sprintf(dockerInstallMsg, domainName)) response := false prompt := &survey.Confirm{ @@ -209,13 +222,13 @@ source for this script can be seen here: return err } logrus.Debugf("running '%s' on %s now with sudo password", cmd, domainName) - _, err := client.ExecSudo(cmd, sudoPass) + _, err := sshCl.ExecSudo(cmd, sudoPass) if err != nil { return err } } else { logrus.Debugf("running '%s' on %s now without sudo password", cmd, domainName) - _, err := client.Exec(cmd) + _, err := sshCl.Exec(cmd) if err != nil { return err } @@ -378,11 +391,25 @@ You may omit flags to avoid performing this provisioning logic. logrus.Fatal(err) } + username := c.Args().Get(1) + if username == "" { + systemUser, err := user.Current() + if err != nil { + return err + } + username = systemUser.Username + } + + port := c.Args().Get(2) + if port == "" { + port = "22" + } + if err := createServerDir(domainName); err != nil { logrus.Fatal(err) } - if err := newContext(c, domainName); err != nil { + if err := newContext(c, domainName, username, port); err != nil { logrus.Fatal(err) } @@ -392,7 +419,15 @@ You may omit flags to avoid performing this provisioning logic. } if provision { - if err := installDocker(c, cl, domainName); err != nil { + logrus.Debugf("attempting to construct SSH client for %s", domainName) + sshCl, err := ssh.New(domainName, sshAuth, username, port) + if err != nil { + logrus.Fatal(err) + } + defer sshCl.Close() + logrus.Debugf("successfully created SSH client for %s", domainName) + + if err := installDocker(c, cl, sshCl, domainName); err != nil { logrus.Fatal(err) } if err := initSwarm(c, cl, domainName); err != nil { diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index dce734f30..0832fd5b3 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -19,29 +19,32 @@ type HostConfig struct { } // GetHostConfig retrieves a ~/.ssh/config config for a host. -func GetHostConfig(hostname string, hasIdentityFile bool) (HostConfig, error) { +func GetHostConfig(hostname, username, port string, hasIdentityFile bool) (HostConfig, error) { var hostConfig HostConfig - var host, sshUser, port, idf string + var host, idf string if host = ssh_config.Get(hostname, "Hostname"); host == "" { logrus.Debugf("no hostname found in SSH config, assuming %s", hostname) host = hostname } - if sshUser = ssh_config.Get(hostname, "User"); sshUser == "" { - systemUser, err := user.Current() - if err != nil { - return hostConfig, err + if username == "" { + if username = ssh_config.Get(hostname, "User"); username == "" { + systemUser, err := user.Current() + if err != nil { + return hostConfig, err + } + logrus.Debugf("no username found in SSH config or passed on command-line, assuming %s", username) + username = systemUser.Username } - username := systemUser.Username - logrus.Debugf("no username found in SSH config, assuming %s", username) - sshUser = username } - if port = ssh_config.Get(hostname, "Port"); port == "" { - logrus.Debugf("no port found in SSH config, assuming 22") - port = "22" + if port == "" { + if port = ssh_config.Get(hostname, "Port"); port == "" { + logrus.Debugf("no port found in SSH config or passed on command-line, assuming 22") + port = "22" + } } dummyVal := "~/.ssh/identity" @@ -52,7 +55,7 @@ func GetHostConfig(hostname string, hasIdentityFile bool) (HostConfig, error) { hostConfig.Host = host hostConfig.IdentityFile = idf hostConfig.Port = port - hostConfig.User = sshUser + hostConfig.User = username logrus.Debugf("constructed SSH config %s for %s", hostConfig, hostname) @@ -60,7 +63,7 @@ func GetHostConfig(hostname string, hasIdentityFile bool) (HostConfig, error) { } // New creates a new SSH client connection. -func New(domainName, sshAuth string) (*simplessh.Client, error) { +func New(domainName, sshAuth, username, port string) (*simplessh.Client, error) { var client *simplessh.Client hasIdentityFile := true @@ -68,7 +71,7 @@ func New(domainName, sshAuth string) (*simplessh.Client, error) { hasIdentityFile = false } - hostConfig, err := GetHostConfig(domainName, hasIdentityFile) + hostConfig, err := GetHostConfig(domainName, username, port, hasIdentityFile) if err != nil { return client, err }