From 9a0e12258a311e9811ec9f3ecafedf78624395db Mon Sep 17 00:00:00 2001 From: decentral1se Date: Sun, 24 Oct 2021 23:15:38 +0200 Subject: [PATCH] feat: provision docker installation --- cli/server/add.go | 113 ++++++++++++++++++++++++++++++++++++++++++---- go.mod | 4 ++ go.sum | 10 ++++ pkg/ssh/ssh.go | 45 ++++++++++++++++++ 4 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 pkg/ssh/ssh.go diff --git a/cli/server/add.go b/cli/server/add.go index 60576577..60ede7b9 100644 --- a/cli/server/add.go +++ b/cli/server/add.go @@ -14,10 +14,12 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/client" "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" ) @@ -40,6 +42,24 @@ var provisionFlag = &cli.BoolFlag{ Destination: &provision, } +var sshAuth string +var sshAuthFlag = &cli.StringFlag{ + Name: "ssh-auth", + Aliases: []string{"sh"}, + Value: "identity-file", + Usage: "Select SSH authentication method (identity-file, password)", + Destination: &sshAuth, +} + +var askSudoPass bool +var askSudoPassFlag = &cli.BoolFlag{ + Name: "ask-sudo-pass", + Aliases: []string{"as"}, + Value: false, + Usage: "Ask for sudo password", + Destination: &askSudoPass, +} + var traefik bool var traefikFlag = &cli.BoolFlag{ Name: "traefik", @@ -165,9 +185,67 @@ source for this script can be seen here: logrus.Fatal("exiting as requested") } - // TODO: implement this remote installer run - // https://stackoverflow.com/questions/37679939/how-do-i-execute-a-command-on-a-remote-machine-in-a-golang-cli - logrus.Warn("NOT IMPLEMENTED - COMING SOON") + hasIdentityFile := true + if sshAuth == "password" { + hasIdentityFile = false + } + + hostConfig, err := ssh.GetHostConfig(domainName, hasIdentityFile) + if err != nil { + return err + } + + var client *simplessh.Client + if sshAuth == "identity-file" { + var err error + client, err = simplessh.ConnectWithAgent(hostConfig.Host, hostConfig.User) + if err != nil { + return err + } + } else { + password := "" + prompt := &survey.Password{ + Message: "SSH password?", + } + if err := survey.AskOne(prompt, &password); err != nil { + return err + } + + var err error + client, err = simplessh.ConnectWithPassword(hostConfig.Host, hostConfig.User, password) + if err != nil { + return err + } + } + + defer client.Close() + logrus.Debugf("successfully created SSH client for %s", domainName) + + cmd := "curl -s https://get.docker.com | bash" + + var sudoPass string + if askSudoPass { + prompt := &survey.Password{ + Message: "sudo password?", + } + if err := survey.AskOne(prompt, &sudoPass); err != nil { + return err + } + logrus.Debugf("running '%s' on %s now with sudo password", cmd, domainName) + _, err := client.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) + if err != nil { + return err + } + } + + logrus.Infof("docker has successfully been installed on %s", domainName) + return nil } } @@ -245,12 +323,24 @@ var serverAddCommand = &cli.Command{ Usage: "Add a server to your configuration", Description: ` This command adds a new server to your configuration so that it can be managed -by Abra. +by Abra. This can be useful when you already have a server provisioned and want +to start running Abra commands against it. -This can be useful when you already have a server provisioned and want to start -running Abra commands against it. This command can also provision your server -("--provision/-p") so that it is capable of hosting Co-op Cloud apps. See below -for more on that. +This command can also provision your server ("--provision/-p") so that it is +capable of hosting Co-op Cloud apps. Abra will default to expecting that you +have a working SSH config for the host in your ~/.ssh/config file. E.g. for +"example.com", you'll want to have something like: + +Host example.com + Hostname 192.168.178.31 # domain name also works + User myuserontheserver + Port 12345 + IdentityFile ~/.ssh/mysecretkey.local + +If you have no SSH key configured for this host and are instead using password +authentication, you may pass "--ssh-auth password" to have Abra ask you for the +password. "--ask-sudo-pass" may be passed if you run your provisioning commands +via sudo privilege escalation. If "--local" is passed, then Abra assumes that the current local server is intended as the target server. This is useful when you want to have your entire @@ -285,6 +375,8 @@ You may omit flags to avoid performing this provisioning logic. Flags: []cli.Flag{ localFlag, provisionFlag, + sshAuthFlag, + askSudoPassFlag, traefikFlag, }, ArgsUsage: " [] []", @@ -299,6 +391,11 @@ You may omit flags to avoid performing this provisioning logic. internal.ShowSubcommandHelpAndError(c, err) } + if sshAuth != "password" || sshAuth != "identity-file" { + err := errors.New("--ssh-auth only accepts 'identity-file' or 'password'") + internal.ShowSubcommandHelpAndError(c, err) + } + if local { if err := newLocalServer(c, "default"); err != nil { logrus.Fatal(err) diff --git a/go.mod b/go.mod index af9a01aa..68b60731 100644 --- a/go.mod +++ b/go.mod @@ -28,16 +28,20 @@ require ( coopcloud.tech/libcapsul v0.0.0-20211022074848-c35e78fe3f3e github.com/Microsoft/hcsshim v0.8.21 // indirect github.com/containerd/containerd v1.5.5 // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/fvbommel/sortorder v1.0.2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 github.com/libdns/gandi v1.0.2 github.com/libdns/libdns v0.2.1 github.com/moby/sys/mount v0.2.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/runc v1.0.2 // indirect + github.com/pkg/sftp v1.13.4 // indirect + github.com/sfreiberg/simplessh v0.0.0-20180301191542-495cbb862a9c github.com/theupdateframework/notary v0.7.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index 2bcce79b..67ac91c0 100644 --- a/go.sum +++ b/go.sum @@ -253,6 +253,8 @@ github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7h github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -499,6 +501,8 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -639,6 +643,8 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg= +github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= @@ -693,6 +699,8 @@ github.com/schultz-is/passgen v1.0.1/go.mod h1:NnqzT2aSfvyheNQvBtlLUa0YlPFLDj60J github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sfreiberg/simplessh v0.0.0-20180301191542-495cbb862a9c h1:7Q+2oF0uBoLEV+j13E3/xUkPkI7f+sFNPZOPo2jmrWk= +github.com/sfreiberg/simplessh v0.0.0-20180301191542-495cbb862a9c/go.mod h1:sB7d6wQapoRM+qx5MgQYB6JVHtel4YHRr0NXXCkXiwQ= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -819,6 +827,7 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -965,6 +974,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go new file mode 100644 index 00000000..1611cc92 --- /dev/null +++ b/pkg/ssh/ssh.go @@ -0,0 +1,45 @@ +package ssh + +import ( + "fmt" + + "github.com/kevinburke/ssh_config" +) + +// HostConfig is a SSH host config. +type HostConfig struct { + Host string + IdentityFile string + Port string + User string +} + +// GetHostConfig retrieves a ~/.ssh/config config for a host. +func GetHostConfig(hostname string, hasIdentityFile bool) (HostConfig, error) { + var hostConfig HostConfig + + var host, user, port, idf string + + if host = ssh_config.Get(hostname, "Hostname"); host == "" { + return hostConfig, fmt.Errorf("SSH hostname missing for %s from SSH config", hostname) + } + + if user = ssh_config.Get(hostname, "User"); user == "" { + return hostConfig, fmt.Errorf("SSH user missing for %s from SSH config", hostname) + } + + if port = ssh_config.Get(hostname, "Port"); port == "" { + return hostConfig, fmt.Errorf("SSH port missing for %s from SSH config", hostname) + } + + if idf = ssh_config.Get(hostname, "IdentityFile"); idf == "" && hasIdentityFile { + return hostConfig, fmt.Errorf("SSH identity file missing for %s from SSH config", hostname) + } + + hostConfig.Host = host + hostConfig.IdentityFile = idf + hostConfig.Port = port + hostConfig.User = user + + return hostConfig, nil +}