diff --git a/go.mod b/go.mod index 2bdf3f0..8fc887a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ require ( github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/gobwas/glob v0.2.3 github.com/joho/godotenv v1.3.0 + github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 + github.com/skratchdot/open-golang v0.0.0-20190104022628-a2dfa6d0dab6 golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 gopkg.in/alecthomas/kingpin.v2 v2.2.6 ) diff --git a/go.sum b/go.sum index a3fc79d..89d4960 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/skratchdot/open-golang v0.0.0-20190104022628-a2dfa6d0dab6 h1:cGT4dcuEyBwwu/v6tosyqcDp2yoIo/LwjMGixUvg3nU= +github.com/skratchdot/open-golang v0.0.0-20190104022628-a2dfa6d0dab6/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= diff --git a/main.go b/main.go index e38c322..045beb3 100644 --- a/main.go +++ b/main.go @@ -11,8 +11,12 @@ import ( "os/signal" "os/user" "path/filepath" + "text/template" + "time" "george/forge" + "github.com/phayes/freeport" + "github.com/skratchdot/open-golang/open" kingpin "gopkg.in/alecthomas/kingpin.v2" ) @@ -55,6 +59,11 @@ var ( appLog = app.Command("log", "Print the latest Laravel application log.") appLogSite = appLog.Arg("site", "Site name.").Required().String() + + appSequelPro = app.Command("sequelpro", "Open site database in Sequel Pro.") + appSequelProSite = appSequelPro.Arg("site", "Site name."). + Required(). + String() ) func main() { @@ -230,6 +239,157 @@ func main() { if err := session.Wait(); err != nil { log.Fatalf("mysqldump: %v", err) } + case appSequelPro.FullCommand(): + server, site, err := george.SearchSite(*appSequelProSite) + if err != nil { + log.Fatal(err) + } + + // Parse .env file and extract MySQL connection settings. + env, err := client.Env(server.Id, site.Id).Get() + if err != nil { + log.Fatal(err) + } + dbConn, _ := env["DB_CONNECTION"] + if dbConn == "" { + log.Fatal("No database found for this site.") + } + if dbConn != "mysql" { + log.Fatalf("Unsupported database %s", dbConn) + } + dbPort := env.Get("DB_PORT") + dbName := env.Get("DB_DATABASE") + dbUser := env.Get("DB_USERNAME") + dbPwd := env.Get("DB_PASSWORD") + + // Open an SSH session and execute mysqldump. + err = george.SSHInstallKey(server.Id) + if err != nil { + log.Fatal(err) + } + + localPort, err := freeport.GetFreePort() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("tunneling for database %s:%s to 127.0.0.1:%d\n", + server.IPAddress, dbPort, localPort) + cmd := exec.Command("ssh", + "-L", + fmt.Sprintf("%d:%s:%s", localPort, server.IPAddress, dbPort), + "-N", + fmt.Sprintf("forge@%s", server.IPAddress)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, os.Kill) + go func() { + <-c + cmd.Process.Kill() + os.Exit(1) + }() + + go func() { + time.Sleep(1 * time.Second) + + fmt.Printf("Opening database in Sequel Pro...") + + const fileTemplate = ` + + + + + ContentFilters + + auto_connect + + data + + connection + + colorIndex + 0 + database + {{.DbName}} + host + 127.0.0.1 + name + {{.SiteName}} + password + {{.DbPwd}} + port + {{.LocalPort}} + rdbms_type + mysql + sslCACertFileLocation + + sslCACertFileLocationEnabled + 0 + sslCertificateFileLocation + + sslCertificateFileLocationEnabled + 0 + sslKeyFileLocation + + sslKeyFileLocationEnabled + 0 + type + SPTCPIPConnection + useSSL + 0 + user + {{.DbUser}} + + + encrypted + + format + connection + queryFavorites + + queryHistory + + rdbms_type + mysql + rdbms_version + 5.6.10 + version + 1 + + + ` + + t := template.Must(template.New("spf").Parse(fileTemplate)) + file, err := os.Create(os.TempDir() + site.Name + ".spf") + if err != nil { + log.Fatal(err) + } + + t.Execute(file, struct { + DbName string + DbPwd string + DbUser string + LocalPort int + SiteName string + }{ + dbName, + dbPwd, + dbUser, + localPort, + site.Name, + }) + + file.Close() + + open.Run(file.Name()) + }() + + err = cmd.Run() + if err != nil { + log.Fatal(err) + } default: app.FatalUsage("No command specified.") }