refactor: better SSH connection details handling

This commit is contained in:
decentral1se 2021-10-25 10:42:39 +02:00
parent f9e2d24550
commit 9e0d77d5c6
No known key found for this signature in database
GPG Key ID: 5E2EF5A63E3718CC
3 changed files with 106 additions and 73 deletions

View File

@ -24,18 +24,16 @@ var recipeNewCommand = &cli.Command{
directory := path.Join(config.APPS_DIR, recipe.Name) directory := path.Join(config.APPS_DIR, recipe.Name)
if _, err := os.Stat(directory); !os.IsNotExist(err) { if _, err := os.Stat(directory); !os.IsNotExist(err) {
logrus.Fatalf("'%s' recipe directory already exists?", directory) logrus.Fatalf("'%s' recipe directory already exists?", directory)
return nil
} }
url := fmt.Sprintf("%s/example.git", config.REPOS_BASE_URL) url := fmt.Sprintf("%s/example.git", config.REPOS_BASE_URL)
if err := git.Clone(directory, url); err != nil { if err := git.Clone(directory, url); err != nil {
return err logrus.Fatal(err)
} }
gitRepo := path.Join(config.APPS_DIR, recipe.Name, ".git") gitRepo := path.Join(config.APPS_DIR, recipe.Name, ".git")
if err := os.RemoveAll(gitRepo); err != nil { if err := os.RemoveAll(gitRepo); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
return nil
} }
logrus.Debugf("removed git repo in '%s'", gitRepo) 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) file, err := os.OpenFile(path, os.O_RDWR, 0755)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
return nil
} }
tpl, err := template.ParseFiles(path) tpl, err := template.ParseFiles(path)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
return nil
} }
// TODO: ask for description and probably other things so that the // TODO: ask for description and probably other things so that the
@ -65,7 +61,6 @@ var recipeNewCommand = &cli.Command{
Description string Description string
}{recipe.Name, "TODO"}); err != nil { }{recipe.Name, "TODO"}); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
return nil
} }
} }

View File

@ -8,21 +8,43 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
"path/filepath"
"strings" "strings"
"time" "time"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/server" "coopcloud.tech/abra/pkg/server"
"coopcloud.tech/abra/pkg/ssh" "coopcloud.tech/abra/pkg/ssh"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/sfreiberg/simplessh"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "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 local bool
var localFlag = &cli.BoolFlag{ var localFlag = &cli.BoolFlag{
Name: "local", Name: "local",
@ -68,6 +90,40 @@ var traefikFlag = &cli.BoolFlag{
Destination: &traefik, 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 { func newLocalServer(c *cli.Context, domainName string) error {
if err := createServerDir(domainName); err != nil { if err := createServerDir(domainName); err != nil {
return err return err
@ -80,7 +136,7 @@ func newLocalServer(c *cli.Context, domainName string) error {
if _, err := exec.LookPath("docker"); err != nil { if _, err := exec.LookPath("docker"); err != nil {
if provision { if provision {
if err := installDocker(c, cl, domainName); err != nil { if err := installDockerLocal(c); err != nil {
return err return err
} }
if err := initSwarm(c, cl, domainName); err != nil { if err := initSwarm(c, cl, domainName); err != nil {
@ -104,21 +160,7 @@ func newLocalServer(c *cli.Context, domainName string) error {
return nil return nil
} }
func newContext(c *cli.Context, domainName string) error { func newContext(c *cli.Context, domainName, username, port 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"
}
store := client.NewDefaultDockerContextStore() store := client.NewDefaultDockerContextStore()
contexts, err := store.Store.List() contexts, err := store.Store.List()
if err != nil { 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) { func newClient(c *cli.Context, domainName string) (*dockerClient.Client, error) {
cl, err := client.New(domainName) cl, err := client.New(domainName)
if err != nil { 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 &dockerClient.Client{}, err
} }
return cl, nil return cl, nil
} }
func installDocker(c *cli.Context, cl *dockerClient.Client, domainName string) error { func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *simplessh.Client, domainName string) error {
logrus.Debugf("attempting to construct SSH client for %s", domainName) result, err := sshCl.Exec("which docker")
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")
if err != nil && string(result) != "" { if err != nil && string(result) != "" {
return err return err
} }
if string(result) == "" { if string(result) == "" {
fmt.Println(fmt.Sprintf(` fmt.Println(fmt.Sprintf(dockerInstallMsg, domainName))
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))
response := false response := false
prompt := &survey.Confirm{ prompt := &survey.Confirm{
@ -209,13 +222,13 @@ source for this script can be seen here:
return err return err
} }
logrus.Debugf("running '%s' on %s now with sudo password", cmd, domainName) 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 { if err != nil {
return err return err
} }
} else { } else {
logrus.Debugf("running '%s' on %s now without sudo password", cmd, domainName) logrus.Debugf("running '%s' on %s now without sudo password", cmd, domainName)
_, err := client.Exec(cmd) _, err := sshCl.Exec(cmd)
if err != nil { if err != nil {
return err return err
} }
@ -378,11 +391,25 @@ You may omit flags to avoid performing this provisioning logic.
logrus.Fatal(err) 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 { if err := createServerDir(domainName); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := newContext(c, domainName); err != nil { if err := newContext(c, domainName, username, port); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -392,7 +419,15 @@ You may omit flags to avoid performing this provisioning logic.
} }
if provision { 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) logrus.Fatal(err)
} }
if err := initSwarm(c, cl, domainName); err != nil { if err := initSwarm(c, cl, domainName); err != nil {

View File

@ -19,29 +19,32 @@ type HostConfig struct {
} }
// GetHostConfig retrieves a ~/.ssh/config config for a host. // 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 hostConfig HostConfig
var host, sshUser, port, idf string var host, idf string
if host = ssh_config.Get(hostname, "Hostname"); host == "" { if host = ssh_config.Get(hostname, "Hostname"); host == "" {
logrus.Debugf("no hostname found in SSH config, assuming %s", hostname) logrus.Debugf("no hostname found in SSH config, assuming %s", hostname)
host = hostname host = hostname
} }
if sshUser = ssh_config.Get(hostname, "User"); sshUser == "" { if username == "" {
systemUser, err := user.Current() if username = ssh_config.Get(hostname, "User"); username == "" {
if err != nil { systemUser, err := user.Current()
return hostConfig, err 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 == "" { if port == "" {
logrus.Debugf("no port found in SSH config, assuming 22") if port = ssh_config.Get(hostname, "Port"); port == "" {
port = "22" logrus.Debugf("no port found in SSH config or passed on command-line, assuming 22")
port = "22"
}
} }
dummyVal := "~/.ssh/identity" dummyVal := "~/.ssh/identity"
@ -52,7 +55,7 @@ func GetHostConfig(hostname string, hasIdentityFile bool) (HostConfig, error) {
hostConfig.Host = host hostConfig.Host = host
hostConfig.IdentityFile = idf hostConfig.IdentityFile = idf
hostConfig.Port = port hostConfig.Port = port
hostConfig.User = sshUser hostConfig.User = username
logrus.Debugf("constructed SSH config %s for %s", hostConfig, hostname) 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. // 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 var client *simplessh.Client
hasIdentityFile := true hasIdentityFile := true
@ -68,7 +71,7 @@ func New(domainName, sshAuth string) (*simplessh.Client, error) {
hasIdentityFile = false hasIdentityFile = false
} }
hostConfig, err := GetHostConfig(domainName, hasIdentityFile) hostConfig, err := GetHostConfig(domainName, username, port, hasIdentityFile)
if err != nil { if err != nil {
return client, err return client, err
} }