Skip to content

Commit ffe33d6

Browse files
committed
Settings via config file and environment variables
1 parent df97258 commit ffe33d6

File tree

6 files changed

+114
-72
lines changed

6 files changed

+114
-72
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ RUN go build -o /app/openserp .
1717
FROM zenika/alpine-chrome:with-chromedriver
1818

1919
COPY --from=builder /app/openserp /usr/local/bin/openserp
20+
ADD config.yaml /usr/src/app
2021

2122
ENTRYPOINT ["openserp"]
2223

cmd/root.go

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package cmd
22

33
import (
4+
"errors"
45
"fmt"
6+
"strings"
57

68
"github.com/karust/openserp/core"
79
"github.com/sirupsen/logrus"
@@ -11,60 +13,66 @@ import (
1113
)
1214

1315
const (
14-
version = "0.2.1"
15-
defaultConfigFilename = "config"
16-
envPrefix = "OPENSERP"
17-
replaceHyphenWithCamelCase = false
16+
version = "0.2.1"
17+
defaultConfigFilename = "config"
18+
envPrefix = "OPENSERP"
1819
)
1920

21+
type Config struct {
22+
App AppConfig `mapstructure:"app"`
23+
GoogleConfig core.SearchEngineOptions `mapstructure:"google"`
24+
YandexConfig core.SearchEngineOptions `mapstructure:"yandex"`
25+
BaiduConfig core.SearchEngineOptions `mapstructure:"baidu"`
26+
}
27+
2028
type AppConfig struct {
21-
Host string
22-
Port int
23-
Timeout int
24-
ConfigPath string
25-
IsBrowserHead bool `mapstructure:"head"`
26-
IsLeaveHead bool `mapstructure:"leave_head"`
27-
IsLeakless bool `mapstructure:"leakless"`
28-
IsDebug bool `mapstructure:"debug"`
29-
IsVerbose bool `mapstructure:"verbose"`
30-
IsRawRequests bool `mapstructure:"raw_requests"`
31-
GoogleConfig core.SearchEngineOptions `mapstructure:"google"`
32-
YandexConfig core.SearchEngineOptions `mapstructure:"yandex"`
33-
BaiduConfig core.SearchEngineOptions `mapstructure:"baidu"`
29+
Host string `mapstructure:"host"`
30+
Port int `mapstructure:"port"`
31+
Timeout int `mapstructure:"timeout"`
32+
ConfigPath string `mapstructure:"config_path"`
33+
IsBrowserHead bool `mapstructure:"head"`
34+
IsLeaveHead bool `mapstructure:"leave_head"`
35+
IsLeakless bool `mapstructure:"leakless"`
36+
IsDebug bool `mapstructure:"debug"`
37+
IsVerbose bool `mapstructure:"verbose"`
38+
IsRawRequests bool `mapstructure:"raw_requests"`
3439
}
3540

36-
var appConf = AppConfig{}
41+
var config = Config{}
3742

3843
var RootCmd = &cobra.Command{
39-
Use: "openserp",
40-
Short: "Open SERP",
41-
Long: `Search via Google, Yandex and Baidu`,
42-
Version: version,
44+
Use: "openserp",
45+
Short: "Open SERP",
46+
Long: `Get [Google, Yandex, Baidu] search engine results via API or CLI.`,
47+
Version: version,
48+
SilenceUsage: true,
4349
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
44-
core.InitLogger(appConf.IsVerbose, appConf.IsDebug)
50+
core.InitLogger(config.App.IsVerbose, config.App.IsDebug)
51+
4552
err := initializeConfig(cmd)
46-
logrus.Debugf("Config: %+v", appConf)
47-
return err
53+
if err != nil {
54+
return err
55+
}
56+
57+
logrus.Debugf("Final config: %+v", config)
58+
return nil
4859
},
60+
4961
// Run: func(cmd *cobra.Command, args []string) {
5062
// // Working with OutOrStdout/OutOrStderr allows us to unit test our command easier
5163
// //out := cmd.OutOrStdout()
52-
// logrus.Trace("Config:", appConf)
64+
// logrus.Trace("Config:", config)
5365
// },
5466
}
5567

5668
// Bind each cobra flag to its associated viper configuration (config file and environment variable)
5769
func bindFlags(cmd *cobra.Command, vpr *viper.Viper) {
5870
cmd.Flags().VisitAll(func(flg *pflag.Flag) {
5971
configName := "app." + flg.Name
60-
//if replaceHyphenWithCamelCase {
61-
// configName = strings.ReplaceAll(f.Name, "-", "")
62-
//}
6372

6473
// Apply viper config value to the flag if viper has a value
65-
if !flg.Changed && vpr.IsSet(configName) {
66-
val := vpr.Get(configName)
67-
cmd.Flags().Set(flg.Name, fmt.Sprintf("%v", val))
74+
if flg.Changed {
75+
vpr.Set(configName, flg.Value)
6876
}
6977
})
7078
}
@@ -77,34 +85,47 @@ func initializeConfig(cmd *cobra.Command) error {
7785
v.SetConfigName(defaultConfigFilename)
7886
v.AddConfigPath(".")
7987

80-
// Return an error if we cannot parse the config file.
81-
if err := v.ReadInConfig(); err != nil {
82-
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
83-
return err
84-
}
88+
// 1. Config. Return an error if we cannot parse the config file.
89+
err := v.ReadInConfig()
90+
if err != nil {
91+
err = errors.New(fmt.Sprintf("Cannot read config: %v", err))
92+
logrus.Warn(err)
8593
}
8694

87-
v.SetEnvPrefix(envPrefix)
88-
89-
// Bind environment variables to their equivalent keys with underscores
90-
//v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
91-
v.AutomaticEnv()
95+
// 2. Env. Bind environment variables to their equivalent keys with underscores
96+
for _, key := range v.AllKeys() {
97+
envKey := envPrefix + "_" + strings.ToUpper(strings.ReplaceAll(key, ".", "_"))
98+
err := v.BindEnv(key, envKey)
99+
if err != nil {
100+
logrus.Errorf("Unable to bind ENV valye: %v", err)
101+
}
102+
}
92103

93-
// Bind the current command's flags to viper
104+
// 3. Cmd flags. Bind the current command's flags to viper
94105
bindFlags(cmd, v)
95106

107+
// Dump Viper values to config struct
108+
err = v.Unmarshal(&config)
109+
if err != nil {
110+
return errors.New(fmt.Sprintf("Cannot unmarshall config: %v", err))
111+
}
112+
113+
if config.App.IsDebug {
114+
logrus.Debug("Viper config:")
115+
v.Debug()
116+
}
96117
return nil
97118
}
98119

99120
func init() {
100-
RootCmd.PersistentFlags().IntVarP(&appConf.Port, "port", "p", 7070, "Port number to run server")
101-
RootCmd.PersistentFlags().StringVarP(&appConf.Host, "host", "a", "127.0.0.1", "Host address to run server")
102-
RootCmd.PersistentFlags().IntVarP(&appConf.Timeout, "timeout", "t", 30, "Timeout to fail request")
103-
RootCmd.PersistentFlags().StringVarP(&appConf.ConfigPath, "config", "c", "./config.yaml", "Configuration file path")
104-
RootCmd.PersistentFlags().BoolVarP(&appConf.IsVerbose, "verbose", "v", false, "Use verbose output")
105-
RootCmd.PersistentFlags().BoolVarP(&appConf.IsDebug, "debug", "d", false, "Use debug output. Disable headless browser")
106-
RootCmd.PersistentFlags().BoolVarP(&appConf.IsBrowserHead, "head", "", false, "Enable browser UI")
107-
RootCmd.PersistentFlags().BoolVarP(&appConf.IsLeakless, "leakless", "l", false, "Use leakless mode to insure browser instances are closed after search")
108-
RootCmd.PersistentFlags().BoolVarP(&appConf.IsRawRequests, "raw", "r", false, "Disable browser usage, use HTTP requests")
109-
RootCmd.PersistentFlags().BoolVarP(&appConf.IsLeaveHead, "leave", "", false, "Leave browser and tabs opened after search is made")
121+
RootCmd.PersistentFlags().IntVarP(&config.App.Port, "port", "p", 7070, "Port number to run server")
122+
RootCmd.PersistentFlags().StringVarP(&config.App.Host, "host", "a", "127.0.0.1", "Host address to run server")
123+
RootCmd.PersistentFlags().IntVarP(&config.App.Timeout, "timeout", "t", 30, "Timeout to fail request")
124+
RootCmd.PersistentFlags().StringVarP(&config.App.ConfigPath, "config", "c", "", "Configuration file path")
125+
RootCmd.PersistentFlags().BoolVarP(&config.App.IsVerbose, "verbose", "v", false, "Use verbose output")
126+
RootCmd.PersistentFlags().BoolVarP(&config.App.IsDebug, "debug", "d", false, "Use debug output. Disable headless browser")
127+
RootCmd.PersistentFlags().BoolVarP(&config.App.IsBrowserHead, "head", "", false, "Enable browser UI")
128+
RootCmd.PersistentFlags().BoolVarP(&config.App.IsLeakless, "leakless", "l", false, "Use leakless mode to insure browser instances are closed after search")
129+
RootCmd.PersistentFlags().BoolVarP(&config.App.IsRawRequests, "raw", "r", false, "Disable browser usage, use HTTP requests")
130+
RootCmd.PersistentFlags().BoolVarP(&config.App.IsLeaveHead, "leave", "", false, "Leave browser and tabs opened after search is made")
110131
}

cmd/search.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func search(cmd *cobra.Command, args []string) {
3131
}
3232
results := []core.SearchResult{}
3333

34-
if appConf.IsRawRequests {
34+
if config.App.IsRawRequests {
3535
results, err = searchRaw(engineType, query)
3636
} else {
3737
results, err = searchBrowser(engineType, query)
@@ -54,13 +54,13 @@ func searchBrowser(engineType string, query core.Query) ([]core.SearchResult, er
5454
var engine core.SearchEngine
5555

5656
opts := core.BrowserOpts{
57-
IsHeadless: !appConf.IsBrowserHead, // Disable headless if browser head mode is set
58-
IsLeakless: appConf.IsLeakless,
59-
Timeout: time.Second * time.Duration(appConf.Timeout),
60-
LeavePageOpen: appConf.IsLeaveHead,
57+
IsHeadless: !config.App.IsBrowserHead, // Disable headless if browser head mode is set
58+
IsLeakless: config.App.IsLeakless,
59+
Timeout: time.Second * time.Duration(config.App.Timeout),
60+
LeavePageOpen: config.App.IsLeaveHead,
6161
}
6262

63-
if appConf.IsDebug {
63+
if config.App.IsDebug {
6464
opts.IsHeadless = false
6565
}
6666

@@ -71,11 +71,11 @@ func searchBrowser(engineType string, query core.Query) ([]core.SearchResult, er
7171

7272
switch strings.ToLower(engineType) {
7373
case "yandex":
74-
engine = yandex.New(*browser, appConf.YandexConfig)
74+
engine = yandex.New(*browser, config.YandexConfig)
7575
case "google":
76-
engine = google.New(*browser, appConf.GoogleConfig)
76+
engine = google.New(*browser, config.GoogleConfig)
7777
case "baidu":
78-
engine = baidu.New(*browser, appConf.BaiduConfig)
78+
engine = baidu.New(*browser, config.BaiduConfig)
7979
default:
8080
logrus.Infof("No `%s` search engine found", engineType)
8181
}

cmd/serve.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ var serveCMD = &cobra.Command{
2121

2222
func serve(cmd *cobra.Command, args []string) {
2323
opts := core.BrowserOpts{
24-
IsHeadless: !appConf.IsBrowserHead, // Disable headless if browser head mode is set
25-
IsLeakless: appConf.IsLeakless,
26-
Timeout: time.Second * time.Duration(appConf.Timeout),
27-
LeavePageOpen: appConf.IsLeaveHead,
24+
IsHeadless: !config.App.IsBrowserHead, // Disable headless if browser head mode is set
25+
IsLeakless: config.App.IsLeakless,
26+
Timeout: time.Second * time.Duration(config.App.Timeout),
27+
LeavePageOpen: config.App.IsLeaveHead,
2828
}
2929

30-
if appConf.IsDebug {
30+
if config.App.IsDebug {
3131
opts.IsHeadless = false
3232
}
3333

@@ -36,11 +36,11 @@ func serve(cmd *cobra.Command, args []string) {
3636
logrus.Error(err)
3737
}
3838

39-
yand := yandex.New(*browser, appConf.YandexConfig)
40-
gogl := google.New(*browser, appConf.GoogleConfig)
41-
baidu := baidu.New(*browser, appConf.BaiduConfig)
39+
yand := yandex.New(*browser, config.YandexConfig)
40+
gogl := google.New(*browser, config.GoogleConfig)
41+
baidu := baidu.New(*browser, config.BaiduConfig)
4242

43-
serv := core.NewServer(appConf.Host, appConf.Port, gogl, yand, baidu)
43+
serv := core.NewServer(config.App.Host, config.App.Port, gogl, yand, baidu)
4444
serv.Listen()
4545
}
4646

config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ app:
66
timeout: 15
77
head: false
88
leakless: false
9+
10+
google:
11+
rate_requests: 4 # Number of requests per Minute
12+
rate_burst: 2 # Number of non-ratelimited requests per Minute
13+
14+
yandex:
15+
rate_requests: 4
16+
rate_burst: 2
17+
18+
baidu:
19+
rate_requests: 4
20+
rate_burst: 2

docker-compose.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,12 @@ services:
77
context: .
88
ports:
99
- 7000:7000
10-
command: serve -a 0.0.0.0 -p 7000 -v -l
10+
command: serve -l
11+
#volumes:
12+
# - ./config.yaml:/usr/src/app/config.yaml
13+
environment:
14+
OPENSERP_APP_HOST: "0.0.0.0"
15+
OPENSERP_APP_PORT: 7000
16+
OPENSERP_BAIDU_RATE_REQUESTS: 6 # Number of requests per Minute
17+
OPENSERP_BAIDU_RATE_BURST: 2 # Number of non-ratelimited requests per Minute
18+

0 commit comments

Comments
 (0)