mirror of
https://github.com/sosedoff/pgweb.git
synced 2024-12-13 15:35:28 +03:00
Establish connections using bookmark ID only (#619)
* Establish connections using bookmark ID only * Refactor specs * Extra tests * Fix homedir assertion for bookmarks path * Fix newline in the warning message * Check for bookmark file existence before reading * Connect code restructure
This commit is contained in:
parent
0b9e7cdb4e
commit
69233cd769
@ -142,27 +142,16 @@ func Connect(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
url := c.Request.FormValue("url")
|
||||
if url == "" {
|
||||
badRequest(c, errURLRequired)
|
||||
return
|
||||
}
|
||||
var (
|
||||
cl *client.Client
|
||||
err error
|
||||
)
|
||||
|
||||
url, err := connection.FormatURL(command.Options{
|
||||
URL: url,
|
||||
Passfile: command.Opts.Passfile,
|
||||
})
|
||||
if err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
if bookmarkID := c.Request.FormValue("bookmark_id"); bookmarkID != "" {
|
||||
cl, err = ConnectWithBookmark(bookmarkID)
|
||||
} else {
|
||||
cl, err = ConnectWithURL(c)
|
||||
}
|
||||
|
||||
var sshInfo *shared.SSHInfo
|
||||
if c.Request.FormValue("ssh") != "" {
|
||||
sshInfo = parseSshInfo(c)
|
||||
}
|
||||
|
||||
cl, err := client.NewFromUrl(url, sshInfo)
|
||||
if err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
@ -187,6 +176,39 @@ func Connect(c *gin.Context) {
|
||||
successResponse(c, info.Format()[0])
|
||||
}
|
||||
|
||||
func ConnectWithURL(c *gin.Context) (*client.Client, error) {
|
||||
url := c.Request.FormValue("url")
|
||||
if url == "" {
|
||||
return nil, errURLRequired
|
||||
}
|
||||
|
||||
url, err := connection.FormatURL(command.Options{
|
||||
URL: url,
|
||||
Passfile: command.Opts.Passfile,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sshInfo *shared.SSHInfo
|
||||
if c.Request.FormValue("ssh") != "" {
|
||||
sshInfo = parseSshInfo(c)
|
||||
}
|
||||
|
||||
return client.NewFromUrl(url, sshInfo)
|
||||
}
|
||||
|
||||
func ConnectWithBookmark(id string) (*client.Client, error) {
|
||||
manager := bookmarks.NewManager(command.Opts.BookmarksDir)
|
||||
|
||||
bookmark, err := manager.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.NewFromBookmark(bookmark)
|
||||
}
|
||||
|
||||
// SwitchDb perform database switch for the client connection
|
||||
func SwitchDb(c *gin.Context) {
|
||||
if command.Opts.LockSession {
|
||||
@ -500,8 +522,9 @@ func HandleQuery(query string, c *gin.Context) {
|
||||
|
||||
// GetBookmarks renders the list of available bookmarks
|
||||
func GetBookmarks(c *gin.Context) {
|
||||
bookmarks, err := bookmarks.ReadAll(bookmarks.Path(command.Opts.BookmarksDir))
|
||||
serveResult(c, bookmarks, err)
|
||||
manager := bookmarks.NewManager(command.Opts.BookmarksDir)
|
||||
ids, err := manager.ListIDs()
|
||||
serveResult(c, ids, err)
|
||||
}
|
||||
|
||||
// GetInfo renders the pgweb system information
|
||||
|
@ -1,28 +1,21 @@
|
||||
package bookmarks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/sosedoff/pgweb/pkg/command"
|
||||
"github.com/sosedoff/pgweb/pkg/shared"
|
||||
)
|
||||
|
||||
// Bookmark contains information about bookmarked database connection
|
||||
type Bookmark struct {
|
||||
URL string `json:"url"` // Postgres connection URL
|
||||
Host string `json:"host"` // Server hostname
|
||||
Port int `json:"port"` // Server port
|
||||
User string `json:"user"` // Database user
|
||||
Password string `json:"password"` // User password
|
||||
Database string `json:"database"` // Database name
|
||||
SSLMode string `json:"ssl"` // Connection SSL mode
|
||||
SSH *shared.SSHInfo `json:"ssh"` // SSH tunnel config
|
||||
ID string // ID generated from the filename
|
||||
URL string // Postgres connection URL
|
||||
Host string // Server hostname
|
||||
Port int // Server port
|
||||
User string // Database user
|
||||
Password string // User password
|
||||
Database string // Database name
|
||||
SSLMode string // Connection SSL mode
|
||||
SSH *shared.SSHInfo // SSH tunnel config
|
||||
}
|
||||
|
||||
// SSHInfoIsEmpty returns true if ssh configuration is not provided
|
||||
@ -42,100 +35,3 @@ func (b Bookmark) ConvertToOptions() command.Options {
|
||||
SSLMode: b.SSLMode,
|
||||
}
|
||||
}
|
||||
|
||||
func readServerConfig(path string) (Bookmark, error) {
|
||||
bookmark := Bookmark{}
|
||||
|
||||
buff, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
_, err = toml.Decode(string(buff), &bookmark)
|
||||
|
||||
if bookmark.Port == 0 {
|
||||
bookmark.Port = 5432
|
||||
}
|
||||
|
||||
// List of all supported postgres modes
|
||||
modes := []string{"disable", "allow", "prefer", "require", "verify-ca", "verify-full"}
|
||||
valid := false
|
||||
|
||||
for _, mode := range modes {
|
||||
if bookmark.SSLMode == mode {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to a default mode if mode is not set or invalid
|
||||
// Typical typo: ssl mode set to "disabled"
|
||||
if bookmark.SSLMode == "" || !valid {
|
||||
bookmark.SSLMode = "disable"
|
||||
}
|
||||
|
||||
// Set default SSH port if it's not provided by user
|
||||
if bookmark.SSH != nil && bookmark.SSH.Port == "" {
|
||||
bookmark.SSH.Port = "22"
|
||||
}
|
||||
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
func fileBasename(path string) string {
|
||||
filename := filepath.Base(path)
|
||||
return strings.Replace(filename, filepath.Ext(path), "", 1)
|
||||
}
|
||||
|
||||
// Path returns bookmarks storage path
|
||||
func Path(overrideDir string) string {
|
||||
if overrideDir == "" {
|
||||
path, _ := homedir.Dir()
|
||||
return fmt.Sprintf("%s/.pgweb/bookmarks", path)
|
||||
}
|
||||
return overrideDir
|
||||
}
|
||||
|
||||
// ReadAll returns all available bookmarks
|
||||
func ReadAll(path string) (map[string]Bookmark, error) {
|
||||
results := map[string]Bookmark{}
|
||||
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if filepath.Ext(file.Name()) != ".toml" {
|
||||
continue
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(path, file.Name())
|
||||
key := fileBasename(file.Name())
|
||||
config, err := readServerConfig(fullPath)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("%s parse error: %s\n", fullPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
results[key] = config
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetBookmark reads an existing bookmark
|
||||
func GetBookmark(bookmarkPath string, bookmarkName string) (Bookmark, error) {
|
||||
bookmarks, err := ReadAll(bookmarkPath)
|
||||
if err != nil {
|
||||
return Bookmark{}, err
|
||||
}
|
||||
|
||||
bookmark, ok := bookmarks[bookmarkName]
|
||||
if !ok {
|
||||
return Bookmark{}, fmt.Errorf("couldn't find a bookmark with name %s", bookmarkName)
|
||||
}
|
||||
|
||||
return bookmark, nil
|
||||
}
|
||||
|
@ -8,115 +8,34 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Invalid_Bookmark_Files(t *testing.T) {
|
||||
_, err := readServerConfig("foobar")
|
||||
assert.Error(t, err)
|
||||
func TestBookmarkSSHInfoIsEmpty(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
info := &shared.SSHInfo{
|
||||
Host: "",
|
||||
Port: "",
|
||||
User: "",
|
||||
}
|
||||
|
||||
_, err = readServerConfig("../../data/invalid.toml")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "toml: line 1: expected '.' or '=', but got 'e' instead", err.Error())
|
||||
b := Bookmark{SSH: nil}
|
||||
assert.True(t, b.SSHInfoIsEmpty())
|
||||
|
||||
b = Bookmark{SSH: info}
|
||||
assert.True(t, b.SSHInfoIsEmpty())
|
||||
})
|
||||
|
||||
t.Run("populated", func(t *testing.T) {
|
||||
info := &shared.SSHInfo{
|
||||
Host: "localhost",
|
||||
Port: "8080",
|
||||
User: "postgres",
|
||||
}
|
||||
|
||||
b := Bookmark{SSH: info}
|
||||
assert.False(t, b.SSHInfoIsEmpty())
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Bookmark(t *testing.T) {
|
||||
bookmark, err := readServerConfig("../../data/bookmark.toml")
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "localhost", bookmark.Host)
|
||||
assert.Equal(t, 5432, bookmark.Port)
|
||||
assert.Equal(t, "postgres", bookmark.User)
|
||||
assert.Equal(t, "mydatabase", bookmark.Database)
|
||||
assert.Equal(t, "disable", bookmark.SSLMode)
|
||||
assert.Equal(t, "", bookmark.Password)
|
||||
assert.Equal(t, "", bookmark.URL)
|
||||
|
||||
bookmark, err = readServerConfig("../../data/bookmark_invalid_ssl.toml")
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "disable", bookmark.SSLMode)
|
||||
}
|
||||
|
||||
func Test_Bookmark_URL(t *testing.T) {
|
||||
bookmark, err := readServerConfig("../../data/bookmark_url.toml")
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "postgres://username:password@host:port/database?sslmode=disable", bookmark.URL)
|
||||
assert.Equal(t, "", bookmark.Host)
|
||||
assert.Equal(t, 5432, bookmark.Port)
|
||||
assert.Equal(t, "", bookmark.User)
|
||||
assert.Equal(t, "", bookmark.Database)
|
||||
assert.Equal(t, "disable", bookmark.SSLMode)
|
||||
assert.Equal(t, "", bookmark.Password)
|
||||
}
|
||||
|
||||
func Test_Bookmarks_Path(t *testing.T) {
|
||||
assert.NotEqual(t, "/.pgweb/bookmarks", Path(""))
|
||||
}
|
||||
|
||||
func Test_Basename(t *testing.T) {
|
||||
assert.Equal(t, "filename", fileBasename("filename.toml"))
|
||||
assert.Equal(t, "filename", fileBasename("path/filename.toml"))
|
||||
assert.Equal(t, "filename", fileBasename("~/long/path/filename.toml"))
|
||||
assert.Equal(t, "filename", fileBasename("filename"))
|
||||
}
|
||||
|
||||
func Test_ReadBookmarks_Invalid(t *testing.T) {
|
||||
bookmarks, err := ReadAll("foobar")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 0, len(bookmarks))
|
||||
}
|
||||
|
||||
func Test_ReadBookmarks(t *testing.T) {
|
||||
bookmarks, err := ReadAll("../../data")
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 3, len(bookmarks))
|
||||
}
|
||||
|
||||
func Test_GetBookmark(t *testing.T) {
|
||||
expBookmark := Bookmark{
|
||||
|
||||
Host: "localhost",
|
||||
Port: 5432,
|
||||
User: "postgres",
|
||||
Password: "",
|
||||
Database: "mydatabase",
|
||||
SSLMode: "disable",
|
||||
}
|
||||
b, err := GetBookmark("../../data", "bookmark")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, expBookmark, b)
|
||||
}
|
||||
|
||||
_, err = GetBookmark("../../data", "bar")
|
||||
expErrStr := "couldn't find a bookmark with name bar"
|
||||
assert.Equal(t, expErrStr, err.Error())
|
||||
|
||||
_, err = GetBookmark("foo", "bookmark")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func Test_Bookmark_SSHInfoIsEmpty(t *testing.T) {
|
||||
emptySSH := &shared.SSHInfo{
|
||||
Host: "",
|
||||
Port: "",
|
||||
User: "",
|
||||
}
|
||||
populatedSSH := &shared.SSHInfo{
|
||||
Host: "localhost",
|
||||
Port: "8080",
|
||||
User: "postgres",
|
||||
}
|
||||
|
||||
b := Bookmark{SSH: nil}
|
||||
assert.True(t, b.SSHInfoIsEmpty())
|
||||
|
||||
b = Bookmark{SSH: emptySSH}
|
||||
assert.True(t, b.SSHInfoIsEmpty())
|
||||
|
||||
b.SSH = populatedSSH
|
||||
assert.False(t, b.SSHInfoIsEmpty())
|
||||
}
|
||||
|
||||
func Test_ConvertToOptions(t *testing.T) {
|
||||
func TestBookmarkConvertToOptions(t *testing.T) {
|
||||
b := Bookmark{
|
||||
URL: "postgres://username:password@host:port/database?sslmode=disable",
|
||||
Host: "localhost",
|
||||
@ -136,6 +55,7 @@ func Test_ConvertToOptions(t *testing.T) {
|
||||
DbName: "mydatabase",
|
||||
SSLMode: "disable",
|
||||
}
|
||||
|
||||
opt := b.ConvertToOptions()
|
||||
assert.Equal(t, expOpt, opt)
|
||||
}
|
||||
|
152
pkg/bookmarks/manager.go
Normal file
152
pkg/bookmarks/manager.go
Normal file
@ -0,0 +1,152 @@
|
||||
package bookmarks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
func NewManager(dir string) Manager {
|
||||
return Manager{
|
||||
dir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Manager) Get(id string) (*Bookmark, error) {
|
||||
bookmarks, err := m.list()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, b := range bookmarks {
|
||||
if b.ID == id {
|
||||
return &b, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("bookmark %v not found", id)
|
||||
}
|
||||
|
||||
func (m Manager) List() ([]Bookmark, error) {
|
||||
return m.list()
|
||||
}
|
||||
|
||||
func (m Manager) ListIDs() ([]string, error) {
|
||||
bookmarks, err := m.list()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids := make([]string, len(bookmarks))
|
||||
for i, bookmark := range bookmarks {
|
||||
ids[i] = bookmark.ID
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (m Manager) list() ([]Bookmark, error) {
|
||||
result := []Bookmark{}
|
||||
|
||||
if m.dir == "" {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
info, err := os.Stat(m.dir)
|
||||
if err != nil {
|
||||
// Do not fail if base dir does not exists: it's not created by default
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] bookmarks dir %s does not exist\n", m.dir)
|
||||
return result, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return nil, fmt.Errorf("path %s is not a directory", m.dir)
|
||||
}
|
||||
|
||||
dirEntries, err := os.ReadDir(m.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
name := entry.Name()
|
||||
if filepath.Ext(name) != ".toml" {
|
||||
continue
|
||||
}
|
||||
|
||||
bookmark, err := readBookmark(filepath.Join(m.dir, name))
|
||||
if err != nil {
|
||||
// Do not fail if one of the bookmarks is invalid
|
||||
fmt.Fprintf(os.Stderr, "[WARN] bookmark file %s is invalid: %s\n", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, bookmark)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func readBookmark(path string) (Bookmark, error) {
|
||||
bookmark := Bookmark{
|
||||
ID: fileBasename(path),
|
||||
}
|
||||
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = fmt.Errorf("bookmark file %s does not exist", path)
|
||||
}
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
buff, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
_, err = toml.Decode(string(buff), &bookmark)
|
||||
|
||||
if bookmark.Port == 0 {
|
||||
bookmark.Port = 5432
|
||||
}
|
||||
|
||||
// List of all supported postgres modes
|
||||
modes := []string{"disable", "allow", "prefer", "require", "verify-ca", "verify-full"}
|
||||
valid := false
|
||||
|
||||
for _, mode := range modes {
|
||||
if bookmark.SSLMode == mode {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to a default mode if mode is not set or invalid
|
||||
// Typical typo: ssl mode set to "disabled"
|
||||
if bookmark.SSLMode == "" || !valid {
|
||||
bookmark.SSLMode = "disable"
|
||||
}
|
||||
|
||||
// Set default SSH port if it's not provided by user
|
||||
if bookmark.SSH != nil && bookmark.SSH.Port == "" {
|
||||
bookmark.SSH.Port = "22"
|
||||
}
|
||||
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
func fileBasename(path string) string {
|
||||
filename := filepath.Base(path)
|
||||
return strings.Replace(filename, filepath.Ext(path), "", 1)
|
||||
}
|
98
pkg/bookmarks/manager_test.go
Normal file
98
pkg/bookmarks/manager_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
package bookmarks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestManagerList(t *testing.T) {
|
||||
examples := []struct {
|
||||
dir string
|
||||
num int
|
||||
err string
|
||||
}{
|
||||
{"../../data", 3, ""},
|
||||
{"../../data/bookmark.toml", 0, "is not a directory"},
|
||||
{"../../data2", 0, ""},
|
||||
{"", 0, ""},
|
||||
}
|
||||
|
||||
for _, ex := range examples {
|
||||
t.Run(ex.dir, func(t *testing.T) {
|
||||
bookmarks, err := NewManager(ex.dir).List()
|
||||
if ex.err != "" {
|
||||
assert.Contains(t, err.Error(), ex.err)
|
||||
}
|
||||
assert.Len(t, bookmarks, ex.num)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerListIDs(t *testing.T) {
|
||||
ids, err := NewManager("../../data").ListIDs()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"bookmark", "bookmark_invalid_ssl", "bookmark_url"}, ids)
|
||||
}
|
||||
|
||||
func TestManagerGet(t *testing.T) {
|
||||
manager := NewManager("../../data")
|
||||
|
||||
b, err := manager.Get("bookmark")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "bookmark", b.ID)
|
||||
|
||||
b, err = manager.Get("foo")
|
||||
assert.Equal(t, "bookmark foo not found", err.Error())
|
||||
assert.Nil(t, b)
|
||||
}
|
||||
|
||||
func Test_fileBasename(t *testing.T) {
|
||||
assert.Equal(t, "filename", fileBasename("filename.toml"))
|
||||
assert.Equal(t, "filename", fileBasename("path/filename.toml"))
|
||||
assert.Equal(t, "filename", fileBasename("~/long/path/filename.toml"))
|
||||
assert.Equal(t, "filename", fileBasename("filename"))
|
||||
}
|
||||
|
||||
func Test_readBookmark(t *testing.T) {
|
||||
t.Run("good", func(t *testing.T) {
|
||||
b, err := readBookmark("../../data/bookmark.toml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "bookmark", b.ID)
|
||||
assert.Equal(t, "localhost", b.Host)
|
||||
assert.Equal(t, 5432, b.Port)
|
||||
assert.Equal(t, "postgres", b.User)
|
||||
assert.Equal(t, "mydatabase", b.Database)
|
||||
assert.Equal(t, "disable", b.SSLMode)
|
||||
assert.Equal(t, "", b.Password)
|
||||
assert.Equal(t, "", b.URL)
|
||||
})
|
||||
|
||||
t.Run("with url", func(t *testing.T) {
|
||||
b, err := readBookmark("../../data/bookmark_url.toml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "postgres://username:password@host:port/database?sslmode=disable", b.URL)
|
||||
assert.Equal(t, "", b.Host)
|
||||
assert.Equal(t, 5432, b.Port)
|
||||
assert.Equal(t, "", b.User)
|
||||
assert.Equal(t, "", b.Database)
|
||||
assert.Equal(t, "disable", b.SSLMode)
|
||||
assert.Equal(t, "", b.Password)
|
||||
})
|
||||
|
||||
t.Run("invalid ssl", func(t *testing.T) {
|
||||
b, err := readBookmark("../../data/bookmark_invalid_ssl.toml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "disable", b.SSLMode)
|
||||
})
|
||||
|
||||
t.Run("invalid file", func(t *testing.T) {
|
||||
_, err := readBookmark("foobar")
|
||||
assert.Equal(t, "bookmark file foobar does not exist", err.Error())
|
||||
})
|
||||
|
||||
t.Run("invalid syntax", func(t *testing.T) {
|
||||
_, err := readBookmark("../../data/invalid.toml")
|
||||
assert.Equal(t, "toml: line 1: expected '.' or '=', but got 'e' instead", err.Error())
|
||||
})
|
||||
}
|
@ -19,7 +19,6 @@ import (
|
||||
"github.com/sosedoff/pgweb/pkg/client"
|
||||
"github.com/sosedoff/pgweb/pkg/command"
|
||||
"github.com/sosedoff/pgweb/pkg/connection"
|
||||
"github.com/sosedoff/pgweb/pkg/shared"
|
||||
"github.com/sosedoff/pgweb/pkg/util"
|
||||
)
|
||||
|
||||
@ -47,30 +46,14 @@ func exitWithMessage(message string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func initClientUsingBookmark(bookmarkPath, bookmarkName string) (*client.Client, error) {
|
||||
bookmark, err := bookmarks.GetBookmark(bookmarkPath, bookmarkName)
|
||||
func initClientUsingBookmark(baseDir, bookmarkName string) (*client.Client, error) {
|
||||
manager := bookmarks.NewManager(baseDir)
|
||||
bookmark, err := manager.Get(bookmarkName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt := bookmark.ConvertToOptions()
|
||||
var connStr string
|
||||
|
||||
if opt.URL != "" { // if the bookmark has url set, use it
|
||||
connStr = opt.URL
|
||||
} else {
|
||||
connStr, err = connection.BuildStringFromOptions(opt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building connection string: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var ssh *shared.SSHInfo
|
||||
if !bookmark.SSHInfoIsEmpty() {
|
||||
ssh = bookmark.SSH
|
||||
}
|
||||
|
||||
return client.NewFromUrl(connStr, ssh)
|
||||
return client.NewFromBookmark(bookmark)
|
||||
}
|
||||
|
||||
func initClient() {
|
||||
@ -82,7 +65,7 @@ func initClient() {
|
||||
var err error
|
||||
|
||||
if options.Bookmark != "" {
|
||||
cl, err = initClientUsingBookmark(bookmarks.Path(options.BookmarksDir), options.Bookmark)
|
||||
cl, err = initClientUsingBookmark(options.BookmarksDir, options.Bookmark)
|
||||
} else {
|
||||
cl, err = client.New()
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/sosedoff/pgweb/pkg/bookmarks"
|
||||
"github.com/sosedoff/pgweb/pkg/command"
|
||||
"github.com/sosedoff/pgweb/pkg/connection"
|
||||
"github.com/sosedoff/pgweb/pkg/history"
|
||||
@ -137,6 +138,30 @@ func NewFromUrl(url string, sshInfo *shared.SSHInfo) (*Client, error) {
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
func NewFromBookmark(bookmark *bookmarks.Bookmark) (*Client, error) {
|
||||
var (
|
||||
connStr string
|
||||
err error
|
||||
)
|
||||
|
||||
options := bookmark.ConvertToOptions()
|
||||
if options.URL != "" {
|
||||
connStr = options.URL
|
||||
} else {
|
||||
connStr, err = connection.BuildStringFromOptions(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var sshInfo *shared.SSHInfo
|
||||
if !bookmark.SSHInfoIsEmpty() {
|
||||
sshInfo = bookmark.SSH
|
||||
}
|
||||
|
||||
return NewFromUrl(connStr, sshInfo)
|
||||
}
|
||||
|
||||
func (client *Client) init() {
|
||||
if command.Opts.QueryTimeout > 0 {
|
||||
client.queryTimeout = time.Second * time.Duration(command.Opts.QueryTimeout)
|
||||
|
@ -59,14 +59,13 @@ func parsePrivateKey(keyPath string, keyPass string) (ssh.Signer, error) {
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(buff)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "cannot decode encrypted private keys") {
|
||||
if keyPass == "" {
|
||||
return nil, errors.New("SSH key password is not provided")
|
||||
}
|
||||
return sshkeys.ParseEncryptedPrivateKey(buff, []byte(keyPass))
|
||||
if _, ok := err.(*ssh.PassphraseMissingError); ok {
|
||||
if keyPass == "" {
|
||||
return nil, errors.New("SSH key password is not provided")
|
||||
}
|
||||
return sshkeys.ParseEncryptedPrivateKey(buff, []byte(keyPass))
|
||||
}
|
||||
|
||||
return signer, err
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgpassfile"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -154,6 +155,13 @@ func ParseOptions(args []string) (Options, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if opts.BookmarksDir == "" {
|
||||
path, err := homedir.Dir()
|
||||
if err == nil {
|
||||
opts.BookmarksDir = filepath.Join(path, ".pgweb/bookmarks")
|
||||
}
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,19 @@ package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseOptions(t *testing.T) {
|
||||
var hdir string
|
||||
if d, err := homedir.Dir(); err == nil {
|
||||
hdir = d
|
||||
}
|
||||
|
||||
t.Run("defaults", func(t *testing.T) {
|
||||
opts, err := ParseOptions([]string{})
|
||||
assert.NoError(t, err)
|
||||
@ -22,6 +29,7 @@ func TestParseOptions(t *testing.T) {
|
||||
assert.Equal(t, false, opts.Cors)
|
||||
assert.Equal(t, "*", opts.CorsOrigin)
|
||||
assert.Equal(t, "", opts.Passfile)
|
||||
assert.Equal(t, filepath.Join(hdir, ".pgweb/bookmarks"), opts.BookmarksDir)
|
||||
})
|
||||
|
||||
t.Run("sessions", func(t *testing.T) {
|
||||
|
@ -1000,18 +1000,18 @@ function getLatestReleaseInfo(current) {
|
||||
function showConnectionSettings() {
|
||||
// Show the current postgres version
|
||||
$(".connection-settings .version").text("v" + appInfo.version).show();
|
||||
$("#connection_window").show();
|
||||
|
||||
// Check github release page for updates
|
||||
getLatestReleaseInfo(appInfo);
|
||||
|
||||
getBookmarks(function(data) {
|
||||
// Do not add any bookmarks if we've got an error
|
||||
if (data.error) {
|
||||
console.log("Cant get bookmarks:", data.error);
|
||||
console.log("Error while fetching bookmarks:", data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
if (data.length > 0) {
|
||||
// Set bookmarks in global var
|
||||
bookmarks = data;
|
||||
|
||||
@ -1019,10 +1019,10 @@ function showConnectionSettings() {
|
||||
$("#connection_bookmarks").html("");
|
||||
|
||||
// Add blank option
|
||||
$("<option value=''></option>").appendTo("#connection_bookmarks");
|
||||
$("<option value=''>Select a bookmarked database to connect to</option>").appendTo("#connection_bookmarks");
|
||||
|
||||
// Add all available bookmarks
|
||||
for (key in data) {
|
||||
for (key of data) {
|
||||
$("<option value='" + key + "''>" + key + "</option>").appendTo("#connection_bookmarks");
|
||||
}
|
||||
|
||||
@ -1032,8 +1032,6 @@ function showConnectionSettings() {
|
||||
$(".bookmarks").hide();
|
||||
}
|
||||
});
|
||||
|
||||
$("#connection_window").show();
|
||||
}
|
||||
|
||||
function getConnectionString() {
|
||||
@ -1624,67 +1622,43 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
$("#connection_bookmarks").on("change", function(e) {
|
||||
var name = $.trim($(this).val());
|
||||
if (name == "") return;
|
||||
var selection = $(this).val();
|
||||
|
||||
var item = bookmarks[name];
|
||||
var inputs = [
|
||||
$("#connection_form input[type='text']"),
|
||||
$("#connection_form input[type='password']"),
|
||||
$("#connection_ssl")
|
||||
];
|
||||
|
||||
// Check if bookmark only has url set
|
||||
if (item.url && item.url != "") {
|
||||
$("#connection_url").val(item.url);
|
||||
$("#connection_scheme").click();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill in bookmarked connection settings
|
||||
$("#pg_host").val(item.host);
|
||||
$("#pg_port").val(item.port);
|
||||
$("#pg_user").val(item.user);
|
||||
$("#pg_password").val(item.password);
|
||||
$("#pg_db").val(item.database);
|
||||
$("#connection_ssl").val(item.ssl);
|
||||
|
||||
if (item.ssh && Object.keys(item.ssh).length > 0) {
|
||||
$("#ssh_host").val(item.ssh.host);
|
||||
$("#ssh_port").val(item.ssh.port);
|
||||
$("#ssh_user").val(item.ssh.user);
|
||||
$("#ssh_password").val(item.ssh.password);
|
||||
$("#ssh_key").val(item.ssh.key);
|
||||
$("#ssh_key_password").val(item.ssh.keypassword);
|
||||
$("#connection_ssh").click();
|
||||
}
|
||||
else {
|
||||
$("#ssh_host").val("");
|
||||
$("#ssh_port").val("");
|
||||
$("#ssh_user").val("");
|
||||
$("#ssh_password").val("");
|
||||
$("#ssh_key").val("");
|
||||
$("#ssh_key_password").val("");
|
||||
$(".connection-ssh-group").hide();
|
||||
$("#connection_standard").click();
|
||||
}
|
||||
inputs.forEach(function(selector) {
|
||||
selector.val("").prop("disabled", selection == "" ? "" : "disabled");
|
||||
});
|
||||
});
|
||||
|
||||
$("#connection_form").on("submit", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var button = $(this).find("button.open-connection");
|
||||
var params = {
|
||||
url: getConnectionString()
|
||||
};
|
||||
var params = {};
|
||||
|
||||
if (params.url.length == 0) {
|
||||
return;
|
||||
if ($("#connection_bookmarks").val() != "") {
|
||||
params["bookmark_id"] = $("#connection_bookmarks").val();
|
||||
}
|
||||
else {
|
||||
params.url = getConnectionString();
|
||||
if (params.url.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(".connection-group-switch button.active").attr("data") == "ssh") {
|
||||
params["ssh"] = 1
|
||||
params["ssh_host"] = $("#ssh_host").val();
|
||||
params["ssh_port"] = $("#ssh_port").val();
|
||||
params["ssh_user"] = $("#ssh_user").val();
|
||||
params["ssh_password"] = $("#ssh_password").val();
|
||||
params["ssh_key"] = $("#ssh_key").val();
|
||||
params["ssh_key_password"] = $("#ssh_key_password").val()
|
||||
if ($(".connection-group-switch button.active").attr("data") == "ssh") {
|
||||
params["ssh"] = 1
|
||||
params["ssh_host"] = $("#ssh_host").val();
|
||||
params["ssh_port"] = $("#ssh_port").val();
|
||||
params["ssh_user"] = $("#ssh_user").val();
|
||||
params["ssh_password"] = $("#ssh_password").val();
|
||||
params["ssh_key"] = $("#ssh_key").val();
|
||||
params["ssh_key_password"] = $("#ssh_key_password").val()
|
||||
}
|
||||
}
|
||||
|
||||
$("#connection_error").hide();
|
||||
|
Loading…
Reference in New Issue
Block a user