feat: use viper and more flags

License: MIT
Signed-off-by: Henrique Dias <[email protected]>
This commit is contained in:
Henrique Dias
2019-05-12 19:00:16 +01:00
parent 78ceb0e44c
commit 24fa8aa228
6 changed files with 448 additions and 254 deletions
+151
View File
@@ -0,0 +1,151 @@
package main
import (
"errors"
"log"
"os"
"regexp"
"strings"
"github.com/hacdias/webdav"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
wd "golang.org/x/net/webdav"
)
func parseRules(raw []interface{}) []*webdav.Rule {
rules := []*webdav.Rule{}
for _, v := range raw {
if r, ok := v.(map[interface{}]interface{}); ok {
rule := &webdav.Rule{
Regex: false,
Allow: false,
Path: "",
}
if regex, ok := r["regex"].(bool); ok {
rule.Regex = regex
}
if allow, ok := r["allow"].(bool); ok {
rule.Allow = allow
}
path, ok := r["path"].(string)
if !ok {
continue
}
if rule.Regex {
rule.Regexp = regexp.MustCompile(path)
} else {
rule.Path = path
}
rules = append(rules, rule)
}
}
return rules
}
func loadFromEnv(v string) (string, error) {
v = strings.TrimPrefix(v, "{env}")
if v == "" {
return "", errors.New("no environment variable specified")
}
v = os.Getenv(v)
if v == "" {
return "", errors.New("the environment variable is empty")
}
return v, nil
}
func parseUsers(raw []interface{}, c *webdav.Config) {
var err error
for _, v := range raw {
if u, ok := v.(map[interface{}]interface{}); ok {
username, ok := u["username"].(string)
if !ok {
log.Fatal("user needs an username")
}
if strings.HasPrefix(username, "{env}") {
username, err = loadFromEnv(username)
checkErr(err)
}
password, ok := u["password"].(string)
if !ok {
password = ""
}
if strings.HasPrefix(password, "{env}") {
password, err = loadFromEnv(password)
checkErr(err)
}
user := &webdav.User{
Username: username,
Password: password,
Scope: c.User.Scope,
Modify: c.User.Modify,
Rules: c.User.Rules,
}
if scope, ok := u["scope"].(string); ok {
user.Scope = scope
}
if modify, ok := u["modify"].(bool); ok {
user.Modify = modify
}
if rules, ok := u["rules"].([]interface{}); ok {
user.Rules = parseRules(rules)
}
user.Handler = &wd.Handler{
FileSystem: wd.Dir(user.Scope),
LockSystem: wd.NewMemLS(),
}
c.Users[username] = user
}
}
}
func readConfig(flags *pflag.FlagSet) *webdav.Config {
cfg := &webdav.Config{
User: &webdav.User{
Scope: getOpt(flags, "scope"),
Modify: getOptB(flags, "modify"),
Rules: []*webdav.Rule{},
Handler: &wd.Handler{
FileSystem: wd.Dir(getOpt(flags, "scope")),
LockSystem: wd.NewMemLS(),
},
},
Auth: getOptB(flags, "auth"),
Users: map[string]*webdav.User{},
}
rawRules := v.Get("rules")
if rules, ok := rawRules.([]interface{}); ok {
cfg.User.Rules = parseRules(rules)
}
rawUsers := v.Get("users")
if users, ok := rawUsers.([]interface{}); ok {
parseUsers(users, cfg)
}
if len(cfg.Users) != 0 && !cfg.Auth {
log.Print("Users will be ignored due to auth=false")
}
return cfg
}
+3 -252
View File
@@ -1,263 +1,14 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/hacdias/webdav"
wd "golang.org/x/net/webdav"
yaml "gopkg.in/yaml.v2"
"runtime"
)
var (
config string
defaultConfigs = []string{
"config.json",
"config.yaml",
"config.yml",
"/etc/webdav/config.json",
"/etc/webdav/config.yaml",
"/etc/webdav/config.yml",
}
)
func init() {
flag.StringVar(&config, "config", "", "Configuration file")
}
func parseRules(raw []map[string]interface{}) []*webdav.Rule {
rules := []*webdav.Rule{}
for _, r := range raw {
rule := &webdav.Rule{
Regex: false,
Allow: false,
Path: "",
}
if regex, ok := r["regex"].(bool); ok {
rule.Regex = regex
}
if allow, ok := r["allow"].(bool); ok {
rule.Allow = allow
}
path, ok := r["rule"].(string)
if !ok {
continue
}
if rule.Regex {
rule.Regexp = regexp.MustCompile(path)
} else {
rule.Path = path
}
rules = append(rules, rule)
}
return rules
}
func parseUsers(raw []map[string]interface{}, c *cfg) {
for _, r := range raw {
username, ok := r["username"].(string)
if !ok {
log.Fatal("user needs an username")
}
// load username from environment when prefix {env} is added
if strings.HasPrefix(username, "{env}") {
var envUsername = strings.TrimPrefix(username, "{env}")
if envUsername == "" {
log.Fatal("no environment variable specified for username")
}
username = os.Getenv(envUsername)
if username == "" {
log.Fatal("username must be set in environment")
}
}
password, ok := r["password"].(string)
if !ok {
password = ""
}
// load password from environment when prefix {env} is added
if strings.HasPrefix(password, "{env}") {
var envPassword = strings.TrimPrefix(password, "{env}")
if envPassword == "" {
log.Fatal("no environment variable specified for password")
}
password = os.Getenv(envPassword)
if password == "" {
log.Fatal("password must be set in environment")
}
}
user := &webdav.User{
Username: username,
Password: password,
Scope: c.webdav.User.Scope,
Modify: c.webdav.User.Modify,
Rules: c.webdav.User.Rules,
}
if scope, ok := r["scope"].(string); ok {
user.Scope = scope
}
if modify, ok := r["modify"].(bool); ok {
user.Modify = modify
}
if rules, ok := r["rules"].([]map[string]interface{}); ok {
user.Rules = parseRules(rules)
}
user.Handler = &wd.Handler{
FileSystem: wd.Dir(user.Scope),
LockSystem: wd.NewMemLS(),
}
c.webdav.Users[username] = user
}
}
func getConfig() []byte {
if config == "" {
for _, v := range defaultConfigs {
_, err := os.Stat(v)
if err == nil {
config = v
break
}
}
}
if config == "" {
log.Fatal("no config file specified; couldn't find any config.{yaml,json}")
}
file, err := ioutil.ReadFile(config)
if err != nil {
log.Fatal(err)
}
return file
}
type cfg struct {
webdav *webdav.Config
address string
port string
tls bool
cert string
key string
}
func parseConfig() *cfg {
file := getConfig()
data := struct {
Address string `json:"address" yaml:"address"`
Port string `json:"port" yaml:"port"`
TLS bool `json:"tls" yaml:"tls"`
Cert string `json:"cert" yaml:"cert"`
Auth bool `json:"auth" yaml:"auth"`
Key string `json:"key" yaml:"key"`
Scope string `json:"scope" yaml:"scope"`
Modify bool `json:"modify" yaml:"modify"`
Rules []map[string]interface{} `json:"rules" yaml:"rules"`
Users []map[string]interface{} `json:"users" yaml:"users"`
}{
Address: "0.0.0.0",
Port: "0",
TLS: false,
Cert: "cert.pem",
Key: "key.pem",
Scope: "./",
Auth: true,
Modify: true,
}
var err error
if filepath.Ext(config) == ".json" {
err = json.Unmarshal(file, &data)
} else {
err = yaml.Unmarshal(file, &data)
}
if err != nil {
log.Fatal(err)
}
config := &cfg{
address: data.Address,
port: data.Port,
tls: data.TLS,
cert: data.Cert,
key: data.Key,
webdav: &webdav.Config{
User: &webdav.User{
Scope: data.Scope,
Modify: data.Modify,
Rules: []*webdav.Rule{},
Handler: &wd.Handler{
FileSystem: wd.Dir(data.Scope),
LockSystem: wd.NewMemLS(),
},
},
Auth: data.Auth,
Users: map[string]*webdav.User{},
},
}
if len(data.Users) != 0 && !data.Auth {
log.Print("Users will be ignored due to auth=false")
}
if len(data.Rules) != 0 {
config.webdav.User.Rules = parseRules(data.Rules)
}
parseUsers(data.Users, config)
return config
}
func main() {
flag.Parse()
cfg := parseConfig()
runtime.GOMAXPROCS(runtime.NumCPU())
// Builds the address and a listener.
laddr := cfg.address + ":" + cfg.port
listener, err := net.Listen("tcp", laddr)
if err != nil {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
// Tell the user the port in which is listening.
fmt.Println("Listening on", listener.Addr().String())
// Starts the server.
if cfg.tls {
if err := http.ServeTLS(listener, cfg.webdav, cfg.cert, cfg.key); err != nil {
log.Fatal(err)
}
} else {
if err := http.Serve(listener, cfg.webdav); err != nil {
log.Fatal(err)
}
}
}
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"fmt"
"log"
"net"
"net/http"
"strings"
"github.com/spf13/cobra"
v "github.com/spf13/viper"
)
var (
cfgFile string
)
func init() {
cobra.OnInitialize(initConfig)
flags := rootCmd.Flags()
flags.StringVarP(&cfgFile, "config", "c", "", "config file path")
flags.Bool("auth", true, "whether to use authentication or not")
flags.BoolP("tls", "t", false, "enable tls")
flags.String("cert", "cert.pem", "TLS certificate")
flags.String("key", "key.pem", "TLS key")
flags.StringP("address", "a", "0.0.0.0", "address to listen to")
flags.StringP("port", "p", "0", "port to listen to")
}
var rootCmd = &cobra.Command{
Use: "webdav",
Short: "A simple to use Web Dav server",
Run: func(cmd *cobra.Command, args []string) {
flags := cmd.Flags()
cfg := readConfig(flags)
// Builds the address and a listener.
laddr := getOpt(flags, "address") + ":" + getOpt(flags, "port")
listener, err := net.Listen("tcp", laddr)
if err != nil {
log.Fatal(err)
}
// Tell the user the port in which is listening.
fmt.Println("Listening on", listener.Addr().String())
// Starts the server.
if getOptB(flags, "tls") {
if err := http.ServeTLS(listener, cfg, getOpt(flags, "cert"), getOpt(flags, "key")); err != nil {
log.Fatal(err)
}
} else {
if err := http.Serve(listener, cfg); err != nil {
log.Fatal(err)
}
}
},
}
func initConfig() {
if cfgFile == "" {
v.AddConfigPath(".")
v.AddConfigPath("/etc/webdav/")
v.SetConfigName("config")
} else {
v.SetConfigFile(cfgFile)
}
v.SetEnvPrefix("WD")
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
err := v.ReadInConfig()
checkErr(err)
}
+55
View File
@@ -0,0 +1,55 @@
package main
import (
"log"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
)
// getOption returns a parameter as a string.
//
// NOTE: we could simply bind the flags to viper and use IsSet.
// Although there is a bug on Viper that always returns true on IsSet
// if a flag is binded. Our alternative way is to manually check
// the flag and then the value from env/config/gotten by viper.
// https://github.com/spf13/viper/pull/331
func getOpt(flags *pflag.FlagSet, key string) string {
value, _ := flags.GetString(key)
// If set on Flags, use it.
if flags.Changed(key) {
return value
}
// If set through viper (env, config), return it.
if v.IsSet(key) {
return v.GetString(key)
}
// Otherwise use default value on flags.
return value
}
func getOptB(flags *pflag.FlagSet, key string) bool {
value, _ := flags.GetBool(key)
// If set on Flags, use it.
if flags.Changed(key) {
return value
}
// If set through viper (env, config), return it.
if v.IsSet(key) {
return v.GetBool(key)
}
// Otherwise use default value on flags.
return value
}
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}