Repository: danvergara/dblab (go)

Issue:
[FEATURE] Add suport for config file like .db.yaml
**Is your feature request related to a problem? Please describe.**
Many tools like this, provide support for config files to customize or automate some tasks.

**Describe the solution you'd like**
Now, there are two ways to run the cli: 

1. By specifying the main individual flags:

```sh
$ dblab --host localhost --user myuser --db users --pass password --ssl disable --port 5432 --driver postgres
```

2. By defining the url flags:

```sh
$ db --url postgres://user:password@host:port/database
```

What if we'd just run the single binary with no flags, but a .dblab.yaml file has to be present with the necessary parameters to execute the application.

Example:

```yaml
config:
   host: localhost
   user: myuser
   db: users
   pass: password
   port: 5432
   driver: postgres
```

**Describe alternatives you've considered**
Other alternative is add a new flag called `config` that receives the path to the config file:

```sh
$ dblab --config ~/path/to/config
```

**Additional context**
This was originally proposed by @pa-m


Pull request description:
Config file
# config file

## Description

This is requested in two issues, so I decided to add the functionality there. I made a mistake at the beginning and a flag called `config` to make a reference to hypothetical config file, but I didn't added it. Finally I added that feature to dblab.

Fixes #34 and #31.

## Type of change

Please delete options that are not relevant.

- [X] New feature (non-breaking change which adds functionality)
- [X] This change requires a documentation update

## How Has This Been Tested?

A test file was added in the config pkg alongside the new function.

I dropped a config file in the home and the current directory to validate the new feature.

## Checklist:

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my own code
- [X] I have commented my code, particularly in hard-to-understand areas
- [X] I have made corresponding changes to the documentation
- [X] I have added tests that prove my fix is effective or that my feature works
- [X] New and existing unit tests pass locally with my changes
- [X] I have checked my code and corrected any misspellings


Patch:
diff --git a/.dblab.yaml b/.dblab.yaml
new file mode 100644
index 0000000..59d4646
--- /dev/null
+++ b/.dblab.yaml
@@ -0,0 +1,7 @@
+database:
+  host: "localhost"
+  port: 5432
+  db: "users"
+  password: "password"
+  user: "postgres"
+  driver: "postgres"
diff --git a/README.md b/README.md
index 54a80cc..da39362 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ Available Commands:
   version     The version of the project
 
 Flags:
-      --config string   config file (default is $HOME/.dblab.yaml)
+      --config          get the connection data from a config file (default is $HOME/.dblab.yaml or the current directory)
       --db string       Database name
       --driver string   Database driver
   -h, --help            help for dblab
@@ -69,8 +69,30 @@ $ dblab --url postgres://user:password@host:port/database?sslmode=[mode]
 $ dblab --url mysql://user:password@tcp(host:port)/db
 ```
 
+Now, you can use a configuration file to make a connection to the database.
+
+```sh
+$ dbladb --config
+```
+
+`dblab` is going to look for a file called `.dblab.yaml`. For now, the only two places where you can drop a config file are $HOME ($HOME/.dblab.yaml) and the current directory where you run the command line tool.
+
+`.dblab.yaml` example:
+
+```yaml
+database:
+  host: "localhost"
+  port: 5432
+  db: "users"
+  password: "password"
+  user: "postgres"
+  driver: "postgres"
+```
+
+Only the `host` and `ssl` fields are optionals. `127.0.0.1` and `disable`, respectively.
+
 If the query panel is active, type the desired query and press <kbd>Ctrl+Space</kbd> to see the results on the rows panel below.
-Otherwise, you might me located at the tables panel, then you can navigate by using the arrows <kbd>Up</kbd> and <kbd>Down</kbd>. If you want to see the rows of a table, press <kbd>Enter</kbd>.
+Otherwise, you might me located at the tables panel, then you can navigate by using the arrows <kbd>Up</kbd> and <kbd>Down</kbd> (or the keys <kbd>k</kbd> and <kbd>j</kbd> respectively). If you want to see the rows of a table, press <kbd>Enter</kbd>. To see the the schema of a table, locate yourself on the `rows` panel and press <kbd>Ctrl+S</kbd> to switch to the `structure` panel, then switch <kbd>Ctrl+S</kbd> to switch back.
 
 <img src="screenshots/dblab-screen-shot.png" />
 
diff --git a/cmd/root.go b/cmd/root.go
index 699ad6a..e261b98 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,49 +1,28 @@
 package cmd
 
-/*
-Copyright © 2021 NAME HERE <EMAIL ADDRESS>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
 import (
-	"fmt"
-	"os"
-
 	"github.com/danvergara/dblab/pkg/app"
 	"github.com/danvergara/dblab/pkg/client"
 	"github.com/danvergara/dblab/pkg/command"
+	"github.com/danvergara/dblab/pkg/config"
 	"github.com/danvergara/dblab/pkg/connection"
 	"github.com/danvergara/dblab/pkg/form"
 	"github.com/danvergara/dblab/pkg/gui"
 	"github.com/jroimartin/gocui"
 	"github.com/spf13/cobra"
-
-	homedir "github.com/mitchellh/go-homedir"
-	"github.com/spf13/viper"
 )
 
 // Define all the global flags.
 var (
-	cfgFile string
-	driver  string
-	url     string
-	host    string
-	port    string
-	user    string
-	pass    string
-	db      string
-	ssl     string
+	cfg    bool
+	driver string
+	url    string
+	host   string
+	port   string
+	user   string
+	pass   string
+	db     string
+	ssl    string
 )
 
 // NewRootCmd returns the root command.
@@ -56,22 +35,29 @@ func NewRootCmd() *cobra.Command {
 			var opts command.Options
 			var err error
 
-			opts = command.Options{
-				Driver: driver,
-				URL:    url,
-				Host:   host,
-				Port:   port,
-				User:   user,
-				Pass:   pass,
-				DBName: db,
-				SSL:    ssl,
-			}
-
-			if form.IsEmpty(opts) {
-				opts, err = form.Run()
+			if cfg {
+				opts, err = config.Init()
 				if err != nil {
 					return err
 				}
+			} else {
+				opts = command.Options{
+					Driver: driver,
+					URL:    url,
+					Host:   host,
+					Port:   port,
+					User:   user,
+					Pass:   pass,
+					DBName: db,
+					SSL:    ssl,
+				}
+
+				if form.IsEmpty(opts) {
+					opts, err = form.Run()
+					if err != nil {
+						return err
+					}
+				}
 			}
 
 			if err := connection.ValidateOpts(opts); err != nil {
@@ -111,13 +97,11 @@ func Execute() {
 }
 
 func init() {
-	cobra.OnInitialize(initConfig)
-
 	// Here you will define your flags and configuration settings.
 	// Cobra supports persistent flags, which, if defined here,
 	// will be global for your application.
 
-	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dblab.yaml)")
+	rootCmd.PersistentFlags().BoolVarP(&cfg, "config", "", false, "get the connection data from a config file (default is $HOME/.dblab.yaml or the current directory)")
 
 	// global flags used to open a database connection.
 	rootCmd.Flags().StringVarP(&driver, "driver", "", "", "Database driver")
@@ -129,26 +113,3 @@ func init() {
 	rootCmd.Flags().StringVarP(&db, "db", "", "", "Database name")
 	rootCmd.Flags().StringVarP(&ssl, "ssl", "", "", "SSL mode")
 }
-
-// initConfig reads in config file and ENV variables if set.
-func initConfig() {
-	if cfgFile != "" {
-		// Use config file from the flag.
-		viper.SetConfigFile(cfgFile)
-	} else {
-		// Find home directory.
-		home, err := homedir.Dir()
-		cobra.CheckErr(err)
-
-		// Search config in home directory with name ".dblab" (without extension).
-		viper.AddConfigPath(home)
-		viper.SetConfigName(".dblab")
-	}
-
-	viper.AutomaticEnv() // read in environment variables that match
-
-	// If a config file is found, read it in.
-	if err := viper.ReadInConfig(); err == nil {
-		fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
-	}
-}
diff --git a/docker-compose.yml b/docker-compose.yml
index 2af6e5a..029af31 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -28,6 +28,8 @@ services:
     build:
       context: .
       target: builder
+    volumes:
+      - ./:/src/app:z
     depends_on:
       - postgres
     environment:
@@ -45,6 +47,8 @@ services:
     build:
       context: .
       target: builder
+    volumes:
+      - ./:/src/app:z
     depends_on:
       - mysql
     environment:
diff --git a/go.mod b/go.mod
index e2a375a..e5bcf92 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
 	github.com/jmoiron/sqlx v1.3.3
 	github.com/jroimartin/gocui v0.4.0
+	github.com/kkyr/fig v0.2.0 // indirect
 	github.com/lib/pq v1.10.0
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/muesli/termenv v0.8.1
diff --git a/go.sum b/go.sum
index f40bb1f..281cefd 100644
--- a/go.sum
+++ b/go.sum
@@ -288,6 +288,8 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kkyr/fig v0.2.0 h1:t/5yENaBw8ATXbQSWpPqwXLCn6wdhEi6jWXRfUgytZI=
+github.com/kkyr/fig v0.2.0/go.mod h1:iqSnedEGFSofGzaB8p34xOhX1ppE1kMulSmJLZ2tNnw=
 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=
@@ -370,6 +372,8 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
+github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/pkg/config/.dblab.yaml b/pkg/config/.dblab.yaml
new file mode 100644
index 0000000..59d4646
--- /dev/null
+++ b/pkg/config/.dblab.yaml
@@ -0,0 +1,7 @@
+database:
+  host: "localhost"
+  port: 5432
+  db: "users"
+  password: "password"
+  user: "postgres"
+  driver: "postgres"
diff --git a/pkg/config/config.go b/pkg/config/config.go
index b3027dd..98d3b81 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -4,10 +4,22 @@ import (
 	"flag"
 	"fmt"
 	"os"
+
+	"github.com/danvergara/dblab/pkg/command"
+	"github.com/kkyr/fig"
 )
 
 // Config struct is used to store the db connection data.
 type Config struct {
+	Database struct {
+		Host     string `default:"127.0.0.1"`
+		Port     string `validate:"required"`
+		DB       string `validate:"required"`
+		User     string `validate:"required"`
+		Password string `validate:"required"`
+		Driver   string `validate:"required"`
+		SSL      string `default:"disable"`
+	}
 	dbUser     string
 	dbPswd     string
 	dbHost     string
@@ -40,6 +52,33 @@ func Get() *Config {
 	return conf
 }
 
+// Init reads in config file and returns a commands/Options instance.
+func Init() (command.Options, error) {
+	var opts command.Options
+	var cfg Config
+
+	home, err := os.UserHomeDir()
+	if err != nil {
+		return opts, err
+	}
+
+	if err := fig.Load(&cfg, fig.File(".dblab.yaml"), fig.Dirs(".", home)); err != nil {
+		return opts, err
+	}
+
+	opts = command.Options{
+		Driver: cfg.Database.Driver,
+		Host:   cfg.Database.Host,
+		Port:   cfg.Database.Port,
+		User:   cfg.Database.User,
+		Pass:   cfg.Database.Password,
+		DBName: cfg.Database.DB,
+		SSL:    cfg.Database.SSL,
+	}
+
+	return opts, nil
+}
+
 // GetDBConnStr returns the connection string.
 func (c *Config) GetDBConnStr() string {
 	return c.getDBConnStr(c.dbHost, c.dbName)


Test patch:
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
new file mode 100644
index 0000000..e7cfdb0
--- /dev/null
+++ b/pkg/config/config_test.go
@@ -0,0 +1,20 @@
+package config_test
+
+import (
+	"testing"
+
+	"github.com/danvergara/dblab/pkg/config"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestInit(t *testing.T) {
+	opts, err := config.Init()
+
+	assert.NoError(t, err)
+	assert.Equal(t, "localhost", opts.Host)
+	assert.Equal(t, "5432", opts.Port)
+	assert.Equal(t, "users", opts.DBName)
+	assert.Equal(t, "postgres", opts.User)
+	assert.Equal(t, "password", opts.Pass)
+	assert.Equal(t, "postgres", opts.Driver)
+}