This commit is contained in:
Dan Sosedoff
2019-11-02 13:00:23 -05:00
parent 7475f398b1
commit 8428d268b1
10 changed files with 51 additions and 46 deletions

View File

@@ -138,7 +138,7 @@ func Connect(c *gin.Context) {
return return
} }
opts := command.Options{Url: url} opts := command.Options{URL: url}
url, err := connection.FormatURL(opts) url, err := connection.FormatURL(opts)
if err != nil { if err != nil {

View File

@@ -13,24 +13,27 @@ import (
"github.com/sosedoff/pgweb/pkg/shared" "github.com/sosedoff/pgweb/pkg/shared"
) )
// Bookmark contains information about bookmarked database connection
type Bookmark struct { type Bookmark struct {
Url string `json:"url"` // Postgres connection URL URL string `json:"url"` // Postgres connection URL
Host string `json:"host"` // Server hostname Host string `json:"host"` // Server hostname
Port int `json:"port"` // Server port Port int `json:"port"` // Server port
User string `json:"user"` // Database user User string `json:"user"` // Database user
Password string `json:"password"` // User password Password string `json:"password"` // User password
Database string `json:"database"` // Database name Database string `json:"database"` // Database name
Ssl string `json:"ssl"` // Connection SSL mode Ssl string `json:"ssl"` // Connection SSL mode
Ssh *shared.SSHInfo `json:"ssh"` // SSH tunnel config SSH *shared.SSHInfo `json:"ssh"` // SSH tunnel config
} }
// SSHInfoIsEmpty returns true if ssh configration is not provided
func (b Bookmark) SSHInfoIsEmpty() bool { func (b Bookmark) SSHInfoIsEmpty() bool {
return b.Ssh == nil || b.Ssh.User == "" && b.Ssh.Host == "" && b.Ssh.Port == "" return b.SSH == nil || b.SSH.User == "" && b.SSH.Host == "" && b.SSH.Port == ""
} }
// ConvertToOptions returns an options struct from connection details
func (b Bookmark) ConvertToOptions() command.Options { func (b Bookmark) ConvertToOptions() command.Options {
return command.Options{ return command.Options{
Url: b.Url, URL: b.URL,
Host: b.Host, Host: b.Host,
Port: b.Port, Port: b.Port,
User: b.User, User: b.User,
@@ -72,8 +75,8 @@ func readServerConfig(path string) (Bookmark, error) {
} }
// Set default SSH port if it's not provided by user // Set default SSH port if it's not provided by user
if bookmark.Ssh != nil && bookmark.Ssh.Port == "" { if bookmark.SSH != nil && bookmark.SSH.Port == "" {
bookmark.Ssh.Port = "22" bookmark.SSH.Port = "22"
} }
return bookmark, err return bookmark, err
@@ -84,15 +87,16 @@ func fileBasename(path string) string {
return strings.Replace(filename, filepath.Ext(path), "", 1) return strings.Replace(filename, filepath.Ext(path), "", 1)
} }
// Path returns bookmarks storage path
func Path(overrideDir string) string { func Path(overrideDir string) string {
if overrideDir == "" { if overrideDir == "" {
path, _ := homedir.Dir() path, _ := homedir.Dir()
return fmt.Sprintf("%s/.pgweb/bookmarks", path) return fmt.Sprintf("%s/.pgweb/bookmarks", path)
} }
return overrideDir return overrideDir
} }
// ReadAll returns all available bookmarks
func ReadAll(path string) (map[string]Bookmark, error) { func ReadAll(path string) (map[string]Bookmark, error) {
results := map[string]Bookmark{} results := map[string]Bookmark{}
@@ -106,7 +110,7 @@ func ReadAll(path string) (map[string]Bookmark, error) {
continue continue
} }
fullPath := path + "/" + file.Name() fullPath := filepath.Join(path, file.Name())
key := fileBasename(file.Name()) key := fileBasename(file.Name())
config, err := readServerConfig(fullPath) config, err := readServerConfig(fullPath)
@@ -121,6 +125,7 @@ func ReadAll(path string) (map[string]Bookmark, error) {
return results, nil return results, nil
} }
// GetBookmark reads an existing bookmark
func GetBookmark(bookmarkPath string, bookmarkName string) (Bookmark, error) { func GetBookmark(bookmarkPath string, bookmarkName string) (Bookmark, error) {
bookmarks, err := ReadAll(bookmarkPath) bookmarks, err := ReadAll(bookmarkPath)
if err != nil { if err != nil {

View File

@@ -26,7 +26,7 @@ func Test_Bookmark(t *testing.T) {
assert.Equal(t, "mydatabase", bookmark.Database) assert.Equal(t, "mydatabase", bookmark.Database)
assert.Equal(t, "disable", bookmark.Ssl) assert.Equal(t, "disable", bookmark.Ssl)
assert.Equal(t, "", bookmark.Password) assert.Equal(t, "", bookmark.Password)
assert.Equal(t, "", bookmark.Url) assert.Equal(t, "", bookmark.URL)
bookmark, err = readServerConfig("../../data/bookmark_invalid_ssl.toml") bookmark, err = readServerConfig("../../data/bookmark_invalid_ssl.toml")
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
@@ -37,7 +37,7 @@ func Test_Bookmark_URL(t *testing.T) {
bookmark, err := readServerConfig("../../data/bookmark_url.toml") bookmark, err := readServerConfig("../../data/bookmark_url.toml")
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "postgres://username:password@host:port/database?sslmode=disable", bookmark.Url) assert.Equal(t, "postgres://username:password@host:port/database?sslmode=disable", bookmark.URL)
assert.Equal(t, "", bookmark.Host) assert.Equal(t, "", bookmark.Host)
assert.Equal(t, 5432, bookmark.Port) assert.Equal(t, 5432, bookmark.Port)
assert.Equal(t, "", bookmark.User) assert.Equal(t, "", bookmark.User)
@@ -106,19 +106,19 @@ func Test_Bookmark_SSHInfoIsEmpty(t *testing.T) {
User: "postgres", User: "postgres",
} }
b := Bookmark{Ssh: nil} b := Bookmark{SSH: nil}
assert.True(t, b.SSHInfoIsEmpty()) assert.True(t, b.SSHInfoIsEmpty())
b = Bookmark{Ssh: emptySSH} b = Bookmark{SSH: emptySSH}
assert.True(t, b.SSHInfoIsEmpty()) assert.True(t, b.SSHInfoIsEmpty())
b.Ssh = populatedSSH b.SSH = populatedSSH
assert.False(t, b.SSHInfoIsEmpty()) assert.False(t, b.SSHInfoIsEmpty())
} }
func Test_ConvertToOptions(t *testing.T) { func Test_ConvertToOptions(t *testing.T) {
b := Bookmark{ b := Bookmark{
Url: "postgres://username:password@host:port/database?sslmode=disable", URL: "postgres://username:password@host:port/database?sslmode=disable",
Host: "localhost", Host: "localhost",
Port: 5432, Port: 5432,
User: "postgres", User: "postgres",
@@ -128,7 +128,7 @@ func Test_ConvertToOptions(t *testing.T) {
} }
expOpt := command.Options{ expOpt := command.Options{
Url: "postgres://username:password@host:port/database?sslmode=disable", URL: "postgres://username:password@host:port/database?sslmode=disable",
Host: "localhost", Host: "localhost",
Port: 5432, Port: 5432,
User: "postgres", User: "postgres",

View File

@@ -44,8 +44,8 @@ func initClientUsingBookmark(bookmarkPath, bookmarkName string) (*client.Client,
opt := bookmark.ConvertToOptions() opt := bookmark.ConvertToOptions()
var connStr string var connStr string
if opt.Url != "" { // if the bookmark has url set, use it if opt.URL != "" { // if the bookmark has url set, use it
connStr = opt.Url connStr = opt.URL
} else { } else {
connStr, err = connection.BuildStringFromOptions(opt) connStr, err = connection.BuildStringFromOptions(opt)
if err != nil { if err != nil {
@@ -55,7 +55,7 @@ func initClientUsingBookmark(bookmarkPath, bookmarkName string) (*client.Client,
var ssh *shared.SSHInfo var ssh *shared.SSHInfo
if !bookmark.SSHInfoIsEmpty() { if !bookmark.SSHInfoIsEmpty() {
ssh = bookmark.Ssh ssh = bookmark.SSH
} }
return client.NewFromUrl(connStr, ssh) return client.NewFromUrl(connStr, ssh)
@@ -87,7 +87,7 @@ func initClient() {
msg := err.Error() msg := err.Error()
// Check if we're trying to connect to the default database. // Check if we're trying to connect to the default database.
if command.Opts.DbName == "" && command.Opts.Url == "" { if command.Opts.DbName == "" && command.Opts.URL == "" {
// If database does not exist, allow user to connect from the UI. // If database does not exist, allow user to connect from the UI.
if strings.Contains(msg, "database") && strings.Contains(msg, "does not exist") { if strings.Contains(msg, "database") && strings.Contains(msg, "does not exist") {
fmt.Println("Error:", msg) fmt.Println("Error:", msg)
@@ -164,7 +164,7 @@ func startServer() {
fmt.Println("Starting server...") fmt.Println("Starting server...")
go func() { go func() {
err := router.Run(fmt.Sprintf("%v:%v", options.HttpHost, options.HttpPort)) err := router.Run(fmt.Sprintf("%v:%v", options.HTTPHost, options.HTTPPort))
if err != nil { if err != nil {
fmt.Println("Cant start server:", err) fmt.Println("Cant start server:", err)
if strings.Contains(err.Error(), "address already in use") { if strings.Contains(err.Error(), "address already in use") {
@@ -182,7 +182,7 @@ func handleSignals() {
} }
func openPage() { func openPage() {
url := fmt.Sprintf("http://%v:%v/%s", options.HttpHost, options.HttpPort, options.Prefix) url := fmt.Sprintf("http://%v:%v/%s", options.HTTPHost, options.HTTPPort, options.Prefix)
fmt.Println("To view database open", url, "in browser") fmt.Println("To view database open", url, "in browser")
if options.SkipOpen { if options.SkipOpen {

View File

@@ -78,8 +78,8 @@ func onWindows() bool {
} }
func setup() { func setup() {
// No pretty JSON for testsm // No pretty JSON for tests
command.Opts.DisablePrettyJson = true command.Opts.DisablePrettyJSON = true
out, err := exec.Command( out, err := exec.Command(
testCommands["createdb"], testCommands["createdb"],

View File

@@ -121,7 +121,7 @@ func (res *Result) CSV() []byte {
func (res *Result) JSON() []byte { func (res *Result) JSON() []byte {
var data []byte var data []byte
if command.Opts.DisablePrettyJson { if command.Opts.DisablePrettyJSON {
data, _ = json.Marshal(res.Format()) data, _ = json.Marshal(res.Format())
} else { } else {
data, _ = json.MarshalIndent(res.Format(), "", " ") data, _ = json.MarshalIndent(res.Format(), "", " ")

View File

@@ -12,15 +12,15 @@ import (
type Options struct { type Options struct {
Version bool `short:"v" long:"version" description:"Print version"` Version bool `short:"v" long:"version" description:"Print version"`
Debug bool `short:"d" long:"debug" description:"Enable debugging mode"` Debug bool `short:"d" long:"debug" description:"Enable debugging mode"`
Url string `long:"url" description:"Database connection string"` URL string `long:"url" description:"Database connection string"`
Host string `long:"host" description:"Server hostname or IP" default:"localhost"` Host string `long:"host" description:"Server hostname or IP" default:"localhost"`
Port int `long:"port" description:"Server port" default:"5432"` Port int `long:"port" description:"Server port" default:"5432"`
User string `long:"user" description:"Database user"` User string `long:"user" description:"Database user"`
Pass string `long:"pass" description:"Password for user"` Pass string `long:"pass" description:"Password for user"`
DbName string `long:"db" description:"Database name"` DbName string `long:"db" description:"Database name"`
Ssl string `long:"ssl" description:"SSL option"` Ssl string `long:"ssl" description:"SSL option"`
HttpHost string `long:"bind" description:"HTTP server host" default:"localhost"` HTTPHost string `long:"bind" description:"HTTP server host" default:"localhost"`
HttpPort uint `long:"listen" description:"HTTP server listen port" default:"8081"` HTTPPort uint `long:"listen" description:"HTTP server listen port" default:"8081"`
AuthUser string `long:"auth-user" description:"HTTP basic auth user"` AuthUser string `long:"auth-user" description:"HTTP basic auth user"`
AuthPass string `long:"auth-pass" description:"HTTP basic auth password"` AuthPass string `long:"auth-pass" description:"HTTP basic auth password"`
SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"` SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"`
@@ -30,7 +30,7 @@ type Options struct {
LockSession bool `long:"lock-session" description:"Lock session to a single database connection"` LockSession bool `long:"lock-session" description:"Lock session to a single database connection"`
Bookmark string `short:"b" long:"bookmark" description:"Bookmark to use for connection. Bookmark files are stored under $HOME/.pgweb/bookmarks/*.toml" default:""` Bookmark string `short:"b" long:"bookmark" description:"Bookmark to use for connection. Bookmark files are stored under $HOME/.pgweb/bookmarks/*.toml" default:""`
BookmarksDir string `long:"bookmarks-dir" description:"Overrides default directory for bookmark files to search" default:""` BookmarksDir string `long:"bookmarks-dir" description:"Overrides default directory for bookmark files to search" default:""`
DisablePrettyJson bool `long:"no-pretty-json" description:"Disable JSON formatting feature for result export"` DisablePrettyJSON bool `long:"no-pretty-json" description:"Disable JSON formatting feature for result export"`
DisableSSH bool `long:"no-ssh" description:"Disable database connections via SSH"` DisableSSH bool `long:"no-ssh" description:"Disable database connections via SSH"`
ConnectBackend string `long:"connect-backend" description:"Enable database authentication through a third party backend"` ConnectBackend string `long:"connect-backend" description:"Enable database authentication through a third party backend"`
ConnectToken string `long:"connect-token" description:"Authentication token for the third-party connect backend"` ConnectToken string `long:"connect-token" description:"Authentication token for the third-party connect backend"`
@@ -51,8 +51,8 @@ func ParseOptions(args []string) (Options, error) {
return opts, err return opts, err
} }
if opts.Url == "" { if opts.URL == "" {
opts.Url = os.Getenv("DATABASE_URL") opts.URL = os.Getenv("DATABASE_URL")
} }
if opts.Prefix == "" { if opts.Prefix == "" {
@@ -80,7 +80,7 @@ func ParseOptions(args []string) (Options, error) {
if opts.Sessions || opts.ConnectBackend != "" { if opts.Sessions || opts.ConnectBackend != "" {
opts.Bookmark = "" opts.Bookmark = ""
opts.Url = "" opts.URL = ""
opts.Host = "" opts.Host = ""
opts.User = "" opts.User = ""
opts.Pass = "" opts.Pass = ""

View File

@@ -15,7 +15,7 @@ func TestParseOptions(t *testing.T) {
assert.Equal(t, "", opts.ConnectToken) assert.Equal(t, "", opts.ConnectToken)
assert.Equal(t, "", opts.ConnectHeaders) assert.Equal(t, "", opts.ConnectHeaders)
assert.Equal(t, false, opts.DisableSSH) assert.Equal(t, false, opts.DisableSSH)
assert.Equal(t, false, opts.DisablePrettyJson) assert.Equal(t, false, opts.DisablePrettyJSON)
assert.Equal(t, false, opts.DisableConnectionIdleTimeout) assert.Equal(t, false, opts.DisableConnectionIdleTimeout)
assert.Equal(t, 180, opts.ConnectionIdleTimeout) assert.Equal(t, 180, opts.ConnectionIdleTimeout)
assert.Equal(t, false, opts.Cors) assert.Equal(t, false, opts.Cors)

View File

@@ -48,7 +48,7 @@ func valsFromQuery(vals neturl.Values) map[string]string {
// FormatURL reformats the existing connection string // FormatURL reformats the existing connection string
func FormatURL(opts command.Options) (string, error) { func FormatURL(opts command.Options) (string, error) {
url := opts.Url url := opts.URL
// Validate connection string prefix // Validate connection string prefix
if !hasValidPrefix(url) { if !hasValidPrefix(url) {
@@ -88,13 +88,13 @@ func FormatURL(opts command.Options) (string, error) {
// IsBlank returns true if command options do not contain connection details // IsBlank returns true if command options do not contain connection details
func IsBlank(opts command.Options) bool { func IsBlank(opts command.Options) bool {
return opts.Host == "" && opts.User == "" && opts.DbName == "" && opts.Url == "" return opts.Host == "" && opts.User == "" && opts.DbName == "" && opts.URL == ""
} }
// BuildStringFromOptions returns a new connection string built from options // BuildStringFromOptions returns a new connection string built from options
func BuildStringFromOptions(opts command.Options) (string, error) { func BuildStringFromOptions(opts command.Options) (string, error) {
// If connection string is provided we just use that // If connection string is provided we just use that
if opts.Url != "" { if opts.URL != "" {
return FormatURL(opts) return FormatURL(opts)
} }

View File

@@ -19,7 +19,7 @@ func Test_Invalid_Url(t *testing.T) {
} }
for _, val := range examples { for _, val := range examples {
opts.Url = val opts.URL = val
str, err := BuildStringFromOptions(opts) str, err := BuildStringFromOptions(opts)
assert.Equal(t, "", str) assert.Equal(t, "", str)
@@ -30,7 +30,7 @@ func Test_Invalid_Url(t *testing.T) {
func Test_Valid_Url(t *testing.T) { func Test_Valid_Url(t *testing.T) {
url := "postgres://myhost/database" url := "postgres://myhost/database"
str, err := BuildStringFromOptions(command.Options{Url: url}) str, err := BuildStringFromOptions(command.Options{URL: url})
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, url, str) assert.Equal(t, url, str)
@@ -38,7 +38,7 @@ func Test_Valid_Url(t *testing.T) {
func Test_Url_And_Ssl_Flag(t *testing.T) { func Test_Url_And_Ssl_Flag(t *testing.T) {
str, err := BuildStringFromOptions(command.Options{ str, err := BuildStringFromOptions(command.Options{
Url: "postgres://myhost/database", URL: "postgres://myhost/database",
Ssl: "disable", Ssl: "disable",
}) })
@@ -48,13 +48,13 @@ func Test_Url_And_Ssl_Flag(t *testing.T) {
func Test_Localhost_Url_And_No_Ssl_Flag(t *testing.T) { func Test_Localhost_Url_And_No_Ssl_Flag(t *testing.T) {
str, err := BuildStringFromOptions(command.Options{ str, err := BuildStringFromOptions(command.Options{
Url: "postgres://localhost/database", URL: "postgres://localhost/database",
}) })
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "postgres://localhost/database?sslmode=disable", str) assert.Equal(t, "postgres://localhost/database?sslmode=disable", str)
str, err = BuildStringFromOptions(command.Options{ str, err = BuildStringFromOptions(command.Options{
Url: "postgres://127.0.0.1/database", URL: "postgres://127.0.0.1/database",
}) })
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "postgres://127.0.0.1/database?sslmode=disable", str) assert.Equal(t, "postgres://127.0.0.1/database?sslmode=disable", str)
@@ -62,14 +62,14 @@ func Test_Localhost_Url_And_No_Ssl_Flag(t *testing.T) {
func Test_Localhost_Url_And_Ssl_Flag(t *testing.T) { func Test_Localhost_Url_And_Ssl_Flag(t *testing.T) {
str, err := BuildStringFromOptions(command.Options{ str, err := BuildStringFromOptions(command.Options{
Url: "postgres://localhost/database", URL: "postgres://localhost/database",
Ssl: "require", Ssl: "require",
}) })
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "postgres://localhost/database?sslmode=require", str) assert.Equal(t, "postgres://localhost/database?sslmode=require", str)
str, err = BuildStringFromOptions(command.Options{ str, err = BuildStringFromOptions(command.Options{
Url: "postgres://127.0.0.1/database", URL: "postgres://127.0.0.1/database",
Ssl: "require", Ssl: "require",
}) })
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
@@ -78,13 +78,13 @@ func Test_Localhost_Url_And_Ssl_Flag(t *testing.T) {
func Test_Localhost_Url_And_Ssl_Arg(t *testing.T) { func Test_Localhost_Url_And_Ssl_Arg(t *testing.T) {
str, err := BuildStringFromOptions(command.Options{ str, err := BuildStringFromOptions(command.Options{
Url: "postgres://localhost/database?sslmode=require", URL: "postgres://localhost/database?sslmode=require",
}) })
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "postgres://localhost/database?sslmode=require", str) assert.Equal(t, "postgres://localhost/database?sslmode=require", str)
str, err = BuildStringFromOptions(command.Options{ str, err = BuildStringFromOptions(command.Options{
Url: "postgres://127.0.0.1/database?sslmode=require", URL: "postgres://127.0.0.1/database?sslmode=require",
}) })
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "postgres://127.0.0.1/database?sslmode=require", str) assert.Equal(t, "postgres://127.0.0.1/database?sslmode=require", str)
@@ -159,5 +159,5 @@ func Test_Blank(t *testing.T) {
assert.Equal(t, true, IsBlank(command.Options{})) assert.Equal(t, true, IsBlank(command.Options{}))
assert.Equal(t, false, IsBlank(command.Options{Host: "host", User: "user"})) assert.Equal(t, false, IsBlank(command.Options{Host: "host", User: "user"}))
assert.Equal(t, false, IsBlank(command.Options{Host: "host", User: "user", DbName: "db"})) assert.Equal(t, false, IsBlank(command.Options{Host: "host", User: "user", DbName: "db"}))
assert.Equal(t, false, IsBlank(command.Options{Url: "url"})) assert.Equal(t, false, IsBlank(command.Options{URL: "url"}))
} }