Skip to content

Commit 47cd5f3

Browse files
committed
feat: add configuration via config file
Uses a configuration file to change settings in CertGuard Issue: #79
1 parent 7676a1b commit 47cd5f3

File tree

9 files changed

+302
-21
lines changed

9 files changed

+302
-21
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ With CertGuard it is currently possible to:
1818
![demo](docs/demo.gif)
1919

2020
## File locations
21-
CertGuard uses two file locations:
21+
CertGuard uses following default file locations:
2222
- `~/.cache/certguard` location of the database/storage file
2323
- `~/.cache/certguard/import` import directory for importing CRLs from file
2424
- `~/.local/share/certguard` for the `debug.log` file
25+
- `~/.config/certguard` for the `config.yaml` file
26+
27+
All these locations can be changed in the [config](#configuration) file.
2528

2629
## Themes
2730
CertGuard has predefined themes that can be switched using the `--theme` argument. Currently supported themes are:
@@ -55,6 +58,17 @@ All information on CRL's and revoked certificates are stored on a local SQLite d
5558
The Database schema used for Certguard only stores public information:
5659
![database schema](docs/db_schema.svg)
5760

61+
## Configuration
62+
CertGuard can be configured using one of three ways:
63+
1. command line flags
64+
2. environment variables
65+
3. config file
66+
67+
The precedence is `command line flags` > `environment variables` > `config file` > `defaults`
68+
69+
A sample config file is included in the repo: `config.yaml`
70+
The default locations CertGuard looks for the config file are the current directory (`.`) and `$HOME/.config/certguard`
71+
5872
## Development
5973
A MAKE file has been included for convenience:
6074
- `make run` builds and run the `certguard` application in `debug` mode

cmd/root.go

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path/filepath"
1010

1111
tea "github.com/charmbracelet/bubbletea"
12+
"github.com/pimg/certguard/config"
1213
"github.com/pimg/certguard/internal/adapter/db"
1314
"github.com/pimg/certguard/internal/ports/models"
1415
cmds "github.com/pimg/certguard/internal/ports/models/commands"
@@ -17,31 +18,40 @@ import (
1718
"github.com/spf13/cobra"
1819
)
1920

21+
var v *config.ViperConfig
22+
2023
func init() {
21-
rootCmd.PersistentFlags().Bool("debug", false, "enables debug logging to a file located in ~/.local/certguard/debug.log")
22-
rootCmd.PersistentFlags().String("theme", "dracula", "set the theme of the application. Allowed values: 'dracula', 'gruvbox'")
24+
v = config.NewViperConfig()
25+
rootCmd.PersistentFlags().BoolVarP(&v.Config().Log.Debug, "debug", "d", false, "enables debug logging to a file located in ~/.local/certguard/debug.log")
26+
rootCmd.PersistentFlags().StringVarP(&v.Config().Theme.Name, "theme", "t", "dracula", "set the theme of the application. Allowed values: 'dracula', 'gruvbox'")
27+
28+
// bind Cobra flags to viper config
29+
_ = v.BindPFlag("config.theme.name", rootCmd.PersistentFlags().Lookup("theme"))
30+
_ = v.BindPFlag("config.log.debug", rootCmd.PersistentFlags().Lookup("debug"))
31+
2332
}
2433

2534
var rootCmd = &cobra.Command{
2635
Version: "v0.0.1",
2736
Use: "certguard",
2837
Long: "Certguard can download, store and inspect x.509 Certificate Revocation Lists",
2938
Example: "certguard",
30-
RunE: runInteractiveCertGuard,
39+
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
40+
return v.InitializeConfig()
41+
},
42+
RunE: runInteractiveCertGuard,
3143
}
3244

3345
func runInteractiveCertGuard(cmd *cobra.Command, args []string) error {
34-
debug, _ := cmd.Flags().GetBool("debug")
35-
theme, _ := cmd.Flags().GetString("theme")
46+
debug := v.Config().Log.Debug
47+
theme := v.Config().Theme.Name
3648

3749
if debug {
38-
homeDir, err := os.UserHomeDir()
50+
logDir, err := logDir()
3951
if err != nil {
40-
fmt.Println("fatal:", err)
41-
os.Exit(1)
52+
return err
4253
}
4354

44-
logDir := filepath.Join(homeDir, ".local", "share", "certguard")
4555
err = os.MkdirAll(logDir, 0o777)
4656
if err != nil {
4757
return err
@@ -55,7 +65,12 @@ func runInteractiveCertGuard(cmd *cobra.Command, args []string) error {
5565
defer f.Close()
5666
}
5767

58-
cacheDir, err := determineCacheDir()
68+
cacheDir, err := cacheDir()
69+
if err != nil {
70+
return err
71+
}
72+
73+
importDir, err := importDir()
5974
if err != nil {
6075
return err
6176
}
@@ -86,7 +101,7 @@ func runInteractiveCertGuard(cmd *cobra.Command, args []string) error {
86101
return err
87102
}
88103

89-
storage, err := crl.NewStorage(libsqlStorage, cacheDir)
104+
storage, err := crl.NewStorage(libsqlStorage, cacheDir, importDir)
90105
if err != nil {
91106
return err
92107
}
@@ -107,7 +122,41 @@ func Execute() error {
107122
return rootCmd.Execute()
108123
}
109124

110-
func determineCacheDir() (string, error) {
125+
func cacheDir() (string, error) {
126+
if v.Config().CacheDirectory != "" {
127+
return v.Config().CacheDirectory, nil
128+
}
129+
130+
return defaultDir()
131+
}
132+
133+
func importDir() (string, error) {
134+
if v.Config().ImportDirectory != "" {
135+
return v.Config().ImportDirectory, nil
136+
}
137+
138+
dir, err := defaultDir()
139+
if err != nil {
140+
return "", err
141+
}
142+
143+
return filepath.Join(dir, "import"), nil
144+
}
145+
146+
func logDir() (string, error) {
147+
if v.Config().Log.Directory != "" {
148+
return v.Config().Log.Directory, nil
149+
}
150+
151+
homeDir, err := os.UserHomeDir()
152+
if err != nil {
153+
return "", errors.New("could not create file path to User home dir, logging will not be enabled")
154+
}
155+
156+
return filepath.Join(homeDir, ".local", "share", "certguard"), nil
157+
}
158+
159+
func defaultDir() (string, error) {
111160
homeDir, err := os.UserHomeDir()
112161
if err != nil {
113162
return "", errors.New("could not create file path to User home dir, Cache will not be enabled")

config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
config:
2+
theme:
3+
name: gruvbox
4+
log:
5+
debug: true

config/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package config
2+
3+
type Config struct {
4+
CacheDirectory string
5+
ImportDirectory string
6+
Log Log
7+
Theme Theme
8+
}
9+
10+
type Log struct {
11+
Debug bool
12+
Directory string
13+
}
14+
15+
type Theme struct {
16+
Name string
17+
}
18+
19+
func New() *Config {
20+
return &Config{}
21+
}

config/file.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package config
2+
3+
import (
4+
"strings"
5+
6+
"github.com/spf13/viper"
7+
)
8+
9+
const (
10+
configFileName = "config"
11+
envPrefix = "CG"
12+
)
13+
14+
type ViperConfig struct {
15+
*viper.Viper
16+
cfg *Config
17+
}
18+
19+
func NewViperConfig() *ViperConfig {
20+
return &ViperConfig{viper.New(), New()}
21+
}
22+
23+
func (v *ViperConfig) Config() *Config {
24+
return v.cfg
25+
}
26+
27+
func (v *ViperConfig) InitializeConfig() error {
28+
//func Initialize(cmd *cobra.Command, cfg *Config) error {
29+
// Set the base name of the config file, without the file extension.
30+
v.SetConfigName(configFileName)
31+
v.SetConfigType("yaml")
32+
33+
// Set as many paths as you like where viper should look for the
34+
// config file. We are only looking in the current working directory.
35+
v.AddConfigPath(".")
36+
v.AddConfigPath("$HOME/.config/certguard")
37+
38+
// Attempt to read the config file, gracefully ignoring errors
39+
// caused by a config file not being found. Return an error
40+
// if we cannot parse the config file.
41+
if err := v.ReadInConfig(); err != nil {
42+
// It's okay if there isn't a config file
43+
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
44+
return err
45+
}
46+
}
47+
48+
// When we bind flags to environment variables expect that the
49+
// environment variables are prefixed, e.g. a flag like --number
50+
// binds to an environment variable STING_NUMBER. This helps
51+
// avoid conflicts.
52+
v.SetEnvPrefix(envPrefix)
53+
54+
// Environment variables can't have dashes in them, so bind them to their equivalent
55+
// keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR
56+
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
57+
58+
// Bind to environment variables
59+
// Works great for simple config names, but needs help for names
60+
// like --favorite-color which we fix in the setFlags function
61+
v.AutomaticEnv()
62+
63+
// set flag values based on the value of the viper config. Since the flags are bound
64+
// Since the flags are bound, precedence is flag -> file -> default
65+
//cfg.setFlags(v)
66+
//cfg := v.setFlags()
67+
68+
v.cfg.Theme.Name = v.GetString("config.theme.name")
69+
v.cfg.Log.Debug = v.GetBool("config.log.debug")
70+
v.cfg.Log.Directory = v.GetString("config.log.file")
71+
72+
return nil
73+
}
74+
75+
//func Initialize() (*viper.Viper, *Config, error) { // TODO arguments are not needed when Initialize returns viper and config
76+
// //func Initialize(cmd *cobra.Command, cfg *Config) error {
77+
// // Set the base name of the config file, without the file extension.
78+
// v.SetConfigName(configFileName)
79+
// v.SetConfigType("yaml")
80+
//
81+
// // Set as many paths as you like where viper should look for the
82+
// // config file. We are only looking in the current working directory.
83+
// v.AddConfigPath(".")
84+
//
85+
// // Attempt to read the config file, gracefully ignoring errors
86+
// // caused by a config file not being found. Return an error
87+
// // if we cannot parse the config file.
88+
// if err := v.ReadInConfig(); err != nil {
89+
// // It's okay if there isn't a config file
90+
// if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
91+
// return nil, nil, err
92+
// }
93+
// }
94+
//
95+
// // When we bind flags to environment variables expect that the
96+
// // environment variables are prefixed, e.g. a flag like --number
97+
// // binds to an environment variable STING_NUMBER. This helps
98+
// // avoid conflicts.
99+
// v.SetEnvPrefix(envPrefix)
100+
//
101+
// // Environment variables can't have dashes in them, so bind them to their equivalent
102+
// // keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR
103+
// v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
104+
//
105+
// // Bind to environment variables
106+
// // Works great for simple config names, but needs help for names
107+
// // like --favorite-color which we fix in the setFlags function
108+
// v.AutomaticEnv()
109+
//
110+
// // set flag values based on the value of the viper config. Since the flags are bound
111+
// // Since the flags are bound, precedence is flag -> file -> default
112+
// cfg.setFlags(v)
113+
//
114+
// return v, cfg, nil
115+
//}
116+
117+
func (v *ViperConfig) setFlags() *Config {
118+
cfg := New()
119+
120+
cfg.Theme.Name = v.GetString("config.theme.name")
121+
cfg.Log.Debug = v.GetBool("config.log.debug")
122+
cfg.Log.Directory = v.GetString("config.log.file")
123+
124+
return cfg
125+
}
126+
127+
func (cfg *Config) setFlags(v *viper.Viper) {
128+
cfg.Theme.Name = v.GetString("config.theme.name")
129+
cfg.Log.Debug = v.GetBool("config.log.debug")
130+
cfg.Log.Directory = v.GetString("config.log.file")
131+
}

go.mod

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/charmbracelet/lipgloss v1.0.0
99
github.com/rubenv/sql-migrate v1.7.1
1010
github.com/spf13/cobra v1.8.1
11+
github.com/spf13/viper v1.19.0
1112
github.com/stretchr/testify v1.10.0
1213
github.com/tursodatabase/go-libsql v0.0.0-20241113154718-293fe7f21b08
1314
golang.org/x/crypto v0.32.0
@@ -19,26 +20,40 @@ require (
1920
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2021
github.com/charmbracelet/x/ansi v0.4.5 // indirect
2122
github.com/charmbracelet/x/term v0.2.1 // indirect
22-
github.com/davecgh/go-spew v1.1.1 // indirect
23+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2324
github.com/dustin/go-humanize v1.0.1 // indirect
2425
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
26+
github.com/fsnotify/fsnotify v1.7.0 // indirect
2527
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
28+
github.com/hashicorp/hcl v1.0.0 // indirect
2629
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2730
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240721121621-c0bdc870f11c // indirect
2831
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
32+
github.com/magiconair/properties v1.8.7 // indirect
2933
github.com/mattn/go-isatty v0.0.20 // indirect
3034
github.com/mattn/go-localereader v0.0.1 // indirect
3135
github.com/mattn/go-runewidth v0.0.16 // indirect
36+
github.com/mitchellh/mapstructure v1.5.0 // indirect
3237
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
3338
github.com/muesli/cancelreader v0.2.2 // indirect
3439
github.com/muesli/termenv v0.15.2 // indirect
35-
github.com/pmezard/go-difflib v1.0.0 // indirect
40+
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
41+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3642
github.com/rivo/uniseg v0.4.7 // indirect
43+
github.com/sagikazarmark/locafero v0.4.0 // indirect
44+
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
3745
github.com/sahilm/fuzzy v0.1.1 // indirect
46+
github.com/sourcegraph/conc v0.3.0 // indirect
47+
github.com/spf13/afero v1.11.0 // indirect
48+
github.com/spf13/cast v1.6.0 // indirect
3849
github.com/spf13/pflag v1.0.5 // indirect
50+
github.com/subosito/gotenv v1.6.0 // indirect
51+
go.uber.org/atomic v1.9.0 // indirect
52+
go.uber.org/multierr v1.9.0 // indirect
3953
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
4054
golang.org/x/sync v0.10.0 // indirect
4155
golang.org/x/sys v0.29.0 // indirect
4256
golang.org/x/text v0.21.0 // indirect
57+
gopkg.in/ini.v1 v1.67.0 // indirect
4358
gopkg.in/yaml.v3 v3.0.1 // indirect
4459
)

0 commit comments

Comments
 (0)