diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 2d12281..7669423 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/sosedoff/pgweb", - "GoVersion": "go1.9", + "GoVersion": "go1.10", "GodepVersion": "v79", "Packages": [ "./..." @@ -112,6 +112,16 @@ { "ImportPath": "gopkg.in/yaml.v2", "Rev": "a5b47d31c556af34a302ce5d659e6fea44d90de0" + }, + { + "ImportPath": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew", + "Comment": "v1.1.3", + "Rev": "f390dcf405f7b83c997eac1b06768bb9f44dec18" + }, + { + "ImportPath": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib", + "Comment": "v1.1.3", + "Rev": "f390dcf405f7b83c997eac1b06768bb9f44dec18" } ] } diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c7c19fa..2c7f82b 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -94,7 +94,7 @@ func initClient() { } func initOptions() { - err := command.ParseOptions() + opts, err := command.ParseOptions(os.Args) if err != nil { switch err.(type) { case *flags.Error: @@ -104,8 +104,8 @@ func initOptions() { } os.Exit(1) } - - options = command.Opts + command.Opts = opts + options = opts if options.Version { printVersion() diff --git a/pkg/client/client.go b/pkg/client/client.go index 2841c14..64b387d 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -367,7 +367,13 @@ func (client *Client) Close() error { } func (client *Client) IsIdle() bool { - return time.Since(client.lastQueryTime).Minutes() > command.Opts.ConnectionIdleTimeout + mins := int(time.Since(client.lastQueryTime).Minutes()) + + if command.Opts.ConnectionIdleTimeout > 0 { + return mins >= command.Opts.ConnectionIdleTimeout + } + + return false } // Fetch all rows as strings for a single column diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 79c985d..22f3416 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -2,12 +2,16 @@ package client import ( "fmt" + "log" "os" "os/exec" "runtime" "testing" + "time" "github.com/stretchr/testify/assert" + + "github.com/sosedoff/pgweb/pkg/command" ) var ( @@ -43,6 +47,11 @@ func getVar(name, def string) string { } func initVars() { + // We need to load default options to make sure all stuff works + if err := command.SetDefaultOptions(); err != nil { + log.Fatal(err) + } + serverHost = getVar("PGHOST", "localhost") serverPort = getVar("PGPORT", "5432") serverUser = getVar("PGUSER", "postgres") @@ -148,6 +157,21 @@ func test_NewClientFromUrl2(t *testing.T) { assert.Equal(t, url, client.ConnectionString) } +func test_ClientIdleTime(t *testing.T) { + examples := map[time.Time]bool{ + time.Now(): false, // Current time + time.Now().Add(time.Minute * -30): false, // 30 minutes ago + time.Now().Add(time.Minute * -240): true, // 240 minutes ago + time.Now().Add(time.Minute * 30): false, // 30 minutes in future + time.Now().Add(time.Minute * 128): false, // 128 minutes in future + } + + for ts, expected := range examples { + testClient.lastQueryTime = ts + assert.Equal(t, expected, testClient.IsIdle()) + } +} + func test_Test(t *testing.T) { assert.Equal(t, nil, testClient.Test()) } @@ -367,6 +391,7 @@ func TestAll(t *testing.T) { setupClient() test_NewClientFromUrl(t) + test_ClientIdleTime(t) test_Test(t) test_Info(t) test_Activity(t) diff --git a/pkg/command/options.go b/pkg/command/options.go index f6ba211..3a86164 100644 --- a/pkg/command/options.go +++ b/pkg/command/options.go @@ -9,86 +9,97 @@ import ( ) type Options struct { - Version bool `short:"v" long:"version" description:"Print version"` - Debug bool `short:"d" long:"debug" description:"Enable debugging mode" default:"false"` - Url string `long:"url" description:"Database connection string"` - Host string `long:"host" description:"Server hostname or IP"` - Port int `long:"port" description:"Server port" default:"5432"` - User string `long:"user" description:"Database user"` - Pass string `long:"pass" description:"Password for user"` - DbName string `long:"db" description:"Database name"` - Ssl string `long:"ssl" description:"SSL option"` - HttpHost string `long:"bind" description:"HTTP server host" default:"localhost"` - HttpPort uint `long:"listen" description:"HTTP server listen port" default:"8081"` - AuthUser string `long:"auth-user" description:"HTTP basic auth user"` - AuthPass string `long:"auth-pass" description:"HTTP basic auth password"` - SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"` - Sessions bool `long:"sessions" description:"Enable multiple database sessions" default:"false"` - Prefix string `long:"prefix" description:"Add a url prefix"` - ReadOnly bool `long:"readonly" description:"Run database connection in readonly mode"` - LockSession bool `long:"lock-session" description:"Lock session to a single database connection" default:"false"` - 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:""` - DisablePrettyJson bool `long:"no-pretty-json" description:"Disable JSON formatting feature for result export" default:"false"` - DisableSSH bool `long:"no-ssh" description:"Disable database connections via SSH" default:"false"` - 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"` - ConnectHeaders string `long:"connect-headers" description:"List of headers to pass to the connect backend"` - DisableConnectionIdleTimeout bool `long:"no-idle-timeout" description:"Disable connection idle timeout" default:"false"` - ConnectionIdleTimeout float64 `long:"idle-timeout" description:"Set connection idle timeout in minutes" default:"180"` - Cors bool `long:"cors" description:"Enable Cross-Origin Resource Sharing (CORS)" default:"false"` - CorsOrigin string `long:"cors-origin" description:"Allowed CORS origins" default:"*"` + Version bool `short:"v" long:"version" description:"Print version"` + Debug bool `short:"d" long:"debug" description:"Enable debugging mode" default:"false"` + Url string `long:"url" description:"Database connection string"` + Host string `long:"host" description:"Server hostname or IP"` + Port int `long:"port" description:"Server port" default:"5432"` + User string `long:"user" description:"Database user"` + Pass string `long:"pass" description:"Password for user"` + DbName string `long:"db" description:"Database name"` + Ssl string `long:"ssl" description:"SSL option"` + HttpHost string `long:"bind" description:"HTTP server host" default:"localhost"` + HttpPort uint `long:"listen" description:"HTTP server listen port" default:"8081"` + AuthUser string `long:"auth-user" description:"HTTP basic auth user"` + AuthPass string `long:"auth-pass" description:"HTTP basic auth password"` + SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"` + Sessions bool `long:"sessions" description:"Enable multiple database sessions" default:"false"` + Prefix string `long:"prefix" description:"Add a url prefix"` + ReadOnly bool `long:"readonly" description:"Run database connection in readonly mode"` + LockSession bool `long:"lock-session" description:"Lock session to a single database connection" default:"false"` + 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:""` + DisablePrettyJson bool `long:"no-pretty-json" description:"Disable JSON formatting feature for result export" default:"false"` + DisableSSH bool `long:"no-ssh" description:"Disable database connections via SSH" default:"false"` + 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"` + ConnectHeaders string `long:"connect-headers" description:"List of headers to pass to the connect backend"` + DisableConnectionIdleTimeout bool `long:"no-idle-timeout" description:"Disable connection idle timeout" default:"false"` + ConnectionIdleTimeout int `long:"idle-timeout" description:"Set connection idle timeout in minutes" default:"180"` + Cors bool `long:"cors" description:"Enable Cross-Origin Resource Sharing (CORS)" default:"false"` + CorsOrigin string `long:"cors-origin" description:"Allowed CORS origins" default:"*"` } var Opts Options -func ParseOptions() error { - _, err := flags.ParseArgs(&Opts, os.Args) +func ParseOptions(args []string) (Options, error) { + var opts = Options{} + + _, err := flags.ParseArgs(&opts, args) if err != nil { - return err + return opts, err } - if Opts.Url == "" { - Opts.Url = os.Getenv("DATABASE_URL") + if opts.Url == "" { + opts.Url = os.Getenv("DATABASE_URL") } if os.Getenv("SESSIONS") != "" { - Opts.Sessions = true + opts.Sessions = true } if os.Getenv("LOCK_SESSION") != "" { - Opts.LockSession = true - Opts.Sessions = false + opts.LockSession = true + opts.Sessions = false } - if Opts.Prefix != "" && !strings.Contains(Opts.Prefix, "/") { - Opts.Prefix = Opts.Prefix + "/" + if opts.Prefix != "" && !strings.Contains(opts.Prefix, "/") { + opts.Prefix = opts.Prefix + "/" } - if Opts.AuthUser == "" && os.Getenv("AUTH_USER") != "" { - Opts.AuthUser = os.Getenv("AUTH_USER") + if opts.AuthUser == "" && os.Getenv("AUTH_USER") != "" { + opts.AuthUser = os.Getenv("AUTH_USER") } - if Opts.AuthPass == "" && os.Getenv("AUTH_PASS") != "" { - Opts.AuthPass = os.Getenv("AUTH_PASS") + if opts.AuthPass == "" && os.Getenv("AUTH_PASS") != "" { + opts.AuthPass = os.Getenv("AUTH_PASS") } - if Opts.Bookmark != "" && Opts.Sessions { - return errors.New("--bookmark is not allowed in multi-session mode") + if opts.Bookmark != "" && opts.Sessions { + return opts, errors.New("--bookmark is not allowed in multi-session mode") } - if Opts.ConnectBackend != "" { - if !Opts.Sessions { - return errors.New("--sessions flag must be set") + if opts.ConnectBackend != "" { + if !opts.Sessions { + return opts, errors.New("--sessions flag must be set") } - if Opts.ConnectToken == "" { - return errors.New("--connect-token flag must be set") + if opts.ConnectToken == "" { + return opts, errors.New("--connect-token flag must be set") } } else { - if Opts.ConnectToken != "" || Opts.ConnectHeaders != "" { - return errors.New("--connect-backend flag must be set") + if opts.ConnectToken != "" || opts.ConnectHeaders != "" { + return opts, errors.New("--connect-backend flag must be set") } } + return opts, nil +} + +func SetDefaultOptions() error { + opts, err := ParseOptions([]string{}) + if err != nil { + return err + } + Opts = opts return nil } diff --git a/pkg/command/options_test.go b/pkg/command/options_test.go index 3780c04..f937742 100644 --- a/pkg/command/options_test.go +++ b/pkg/command/options_test.go @@ -1,38 +1,50 @@ package command import ( - "os" "testing" "github.com/stretchr/testify/assert" ) -func Test_Options(t *testing.T) { - err := ParseOptions() - +func TestParseOptions(t *testing.T) { + // Test default behavior + opts, err := ParseOptions([]string{}) + assert.NoError(t, err) + assert.Equal(t, false, opts.Sessions) + assert.Equal(t, "", opts.Prefix) + assert.Equal(t, "", opts.ConnectToken) + assert.Equal(t, "", opts.ConnectHeaders) + assert.Equal(t, false, opts.DisableSSH) + assert.Equal(t, false, opts.DisablePrettyJson) + assert.Equal(t, false, opts.DisableConnectionIdleTimeout) + assert.Equal(t, 180, opts.ConnectionIdleTimeout) + assert.Equal(t, false, opts.Cors) + assert.Equal(t, "*", opts.CorsOrigin) + + // Test sessions + opts, err = ParseOptions([]string{"--sessions", "1"}) + assert.NoError(t, err) + assert.Equal(t, true, opts.Sessions) + + opts, err = ParseOptions([]string{"--sessions", "1", "--bookmark", "test"}) + assert.EqualError(t, err, "--bookmark is not allowed in multi-session mode") + + // Test url prefix + opts, err = ParseOptions([]string{"--prefix", "pgweb"}) + assert.NoError(t, err) + assert.Equal(t, "pgweb/", opts.Prefix) + + opts, err = ParseOptions([]string{"--prefix", "pgweb/"}) + assert.NoError(t, err) + assert.Equal(t, "pgweb/", opts.Prefix) + + // Test connect backend options + opts, err = ParseOptions([]string{"--connect-backend", "test"}) + assert.EqualError(t, err, "--sessions flag must be set") + + opts, err = ParseOptions([]string{"--connect-backend", "test", "--sessions"}) + assert.EqualError(t, err, "--connect-token flag must be set") + + opts, err = ParseOptions([]string{"--connect-backend", "test", "--sessions", "--connect-token", "token"}) assert.NoError(t, err) - assert.Equal(t, false, Opts.Sessions) - assert.Equal(t, "", Opts.Prefix) -} - -func Test_SessionsOption(t *testing.T) { - oldargs := os.Args - defer func() { os.Args = oldargs }() - - os.Args = []string{"--sessions", "1"} - assert.NoError(t, ParseOptions()) - assert.Equal(t, true, Opts.Sessions) -} - -func Test_PrefixOption(t *testing.T) { - oldargs := os.Args - defer func() { os.Args = oldargs }() - - os.Args = []string{"--prefix", "pgweb"} - assert.NoError(t, ParseOptions()) - assert.Equal(t, "pgweb/", Opts.Prefix) - - os.Args = []string{"--prefix", "pgweb/"} - assert.NoError(t, ParseOptions()) - assert.Equal(t, "pgweb/", Opts.Prefix) } diff --git a/pkg/data/bindata.go b/pkg/data/bindata.go index cbda16e..8fcf300 100644 --- a/pkg/data/bindata.go +++ b/pkg/data/bindata.go @@ -101,7 +101,7 @@ func staticCssAppCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/css/app.css", size: 11385, mode: os.FileMode(420), modTime: time.Unix(1509762282, 0)} + info := bindataFileInfo{name: "static/css/app.css", size: 11385, mode: os.FileMode(420), modTime: time.Unix(1517374041, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -301,7 +301,7 @@ func staticIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/index.html", size: 12058, mode: os.FileMode(420), modTime: time.Unix(1513313307, 0)} + info := bindataFileInfo{name: "static/index.html", size: 12058, mode: os.FileMode(420), modTime: time.Unix(1517374041, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -361,7 +361,7 @@ func staticJsAppJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/js/app.js", size: 34563, mode: os.FileMode(420), modTime: time.Unix(1512708944, 0)} + info := bindataFileInfo{name: "static/js/app.js", size: 34563, mode: os.FileMode(420), modTime: time.Unix(1517374041, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/script/test_all.sh b/script/test_all.sh index 478978a..0389d76 100755 --- a/script/test_all.sh +++ b/script/test_all.sh @@ -15,15 +15,17 @@ export PGPASSWORD="" export PGDATABASE="booktown" export PGPORT="15432" -for i in {1..6} -do - export PGVERSION="9.$i" +versions="9.1 9.2 9.3 9.4 9.5 9.6 10 10.1 10.2" - echo "---------------- BEGIN TEST ----------------" +for i in $versions +do + export PGVERSION="$i" + + echo "------------------------------- BEGIN TEST -------------------------------" echo "Running tests against PostgreSQL v$PGVERSION" docker rm -f postgres || true docker run -p $PGPORT:5432 --name postgres -e POSTGRES_PASSWORD=$PGPASSWORD -d postgres:$PGVERSION sleep 5 make test - echo "---------------- END TEST ------------------" + echo "-------------------------------- END TEST --------------------------------" done \ No newline at end of file