diff --git a/cobra/README.md b/cobra/README.md new file mode 100644 index 0000000..f6de5fd --- /dev/null +++ b/cobra/README.md @@ -0,0 +1,242 @@ +# Cobra pakcage +Help build integration of spf13/Cobra lib. +This package will simplify call and use of flags / command for a CLI Tools. + +## Exmaple of implement +Make some folder / file like this: +``` +/api +|- root.go +|- one_command.go +/pkg +|- common.go +/main.go +``` + +### Main init : + +This `commong.go` file will make the init of version, logger, cobra, viper and config packages. + +```go +import ( + "strings" + + libcbr "github.com/nabbar/golib/cobra" + libcfg "github.com/nabbar/golib/config" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) +``` + +Some variables : +```go +var( + // Golib Version Package + vrs libver.Version + + // Main config / context Golib package + cfg libcfg.Config + + // Root logger before config + log liblog.Logger + + // Main Cobra / Viper resource + cbr libcbr.Cobra + vpr libvpr.Viper +) +``` + +This function will init the libs if not set and return the lib resource : +```go +func GetViper() libvpr.Viper { + if vpr == nil { + vpr = libvpr.New() + vpr.SetHomeBaseName(strings.ToLower(GetVersion().GetPackage())) + vpr.SetEnvVarsPrefix(GetVersion().GetPrefix()) + vpr.SetDefaultConfig(GetConfig().DefaultConfig) + } + + return vpr +} + +func GetCobra() libcbr.Cobra { + if cbr == nil { + cbr = libcbr.New() + cbr.SetVersion(GetVersion()) + cbr.SetLogger(GetLogger) + cbr.SetViper(GetViper) + } + + return cbr +} + +func GetConfig() libcfg.Config { + if cfg == nil { + cfg = libcfg.New() + cfg.RegisterFuncViper(GetViper) + } + + return cfg +} + +func GetVersion() libver.Version { + if vrs == nil { + //vrs = libver.NewVersion(...) + } + + return vrs +} + +func GetLogger() liblog.Logger { + if log == nil { + log = liblog.New(cfg.Context()) + _ = log.SetOptions(&liblog.Options{ + DisableStandard: false, + DisableStack: false, + DisableTimestamp: false, + EnableTrace: true, + TraceFilter: GetVersion().GetRootPackagePath(), + DisableColor: false, + LogFile: nil, // TODO + LogSyslog: nil, // TODO + }) + log.SetLevel(liblog.InfoLevel) + log.SetSPF13Level(liblog.InfoLevel, nil) + } + + return log +} +``` + +### The root file in command folder +This file will be the global file for cobra. It will config and init the cobra for yours command. +This file will import your `pkg/common.go` file : +```go +import( + "fmt" + "path" + + libcns "github.com/nabbar/golib/console" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + + compkg "../pkg" + apipkg "../api" +) +``` + +And will define some flag vars : +```go +var ( + // var for config file path to be load by viper + cfgFile string + + // var for log verbose + flgVerbose int + + // flag for init cobra has error + iniErr liberr.Error +) +``` + +The `InitCommand` function will initialize the cobra / viper package and will be call by the main init function into you `main/init()` function: +```go +func InitCommand() { + //the initFunc is a function call on init cobra before calling command but after parsing flag / command ... + compkg.GetCobra().SetFuncInit(initConfig) + + // must init after the SetFuncinit function + compkg.GetCobra().Init() + + // add the Config file flag + compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "Add Flag Config File", compkg.GetCobra().SetFlagConfig(true, &cfgFile)) + + // add the verbose flas + compkg.GetCobra().SetFlagVerbose(true, &flgVerbose) + + // Add some generic command + compkg.GetCobra().AddCommandCompletion() + compkg.GetCobra().AddCommandConfigure("", compkg.GetConfig().DefaultConfig) + compkg.GetCobra().AddCommandPrintErrorCode(cmdPrintError) + + // Add one custom command. The best is to isolate each command into a specific file + // Each command file, will having a main function to create the cobra command. + // Here we will only call this main function to add each command into the main cobra command like this + compkg.GetCobra().AddCommand(initCustomCommand1()) + + // Carefully with the `init` function, because you will not manage the order of running each init function. + // To prevent it, the best is to not having init function, but custom init call in the awaiting order into the `main/init` function +} + +// this function will be use in the PrintError command to print first ErrorCode => Package +func cmdPrintError(item, value string) { + println(fmt.Sprintf("%s : %s", libcns.PadLeft(item, 15, " "), path.Dir(value))) +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + // Redefine the root logger level with the verbose flag + switch flgVerbose { + case 0: + compkg.GetLogger().SetLevel(liblog.ErrorLevel) + case 1: + compkg.GetLogger().SetLevel(liblog.WarnLevel) + case 2: + compkg.GetLogger().SetLevel(liblog.InfoLevel) + default: + compkg.GetLogger().SetLevel(liblog.DebugLevel) + } + + // Define the config file into viper to load it if is set + iniErr = compkg.GetViper().SetConfigFile(cfgFile) + compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "define config file", iniErr) + + // try to init viper with config (local file or remote) + if iniErr = compkg.GetViper().Config(liblog.NilLevel, liblog.NilLevel); iniErr == nil { + // register the reload config function with the watch FS function + compkg.GetViper().SetRemoteReloadFunc(func() { + _ = compkg.GetLogger().CheckError(liblog.ErrorLevel, liblog.DebugLevel, "config reload", compkg.GetConfig().Reload()) + }) + compkg.GetViper().WatchFS(liblog.InfoLevel) + } + + // Make the config for this app (cf config package) + apipkg.SetConfig(compkg.GetLogger().GetLevel(), apirtr.GetHandlers()) +} + +// This is called by main.main() to parse and run the app. +func Execute() { + compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "RootCmd Executed", compkg.GetCobra().Execute()) +} +``` + +### The main file of your app +The main file of your app, wil implement the `main/init()` function and the `main/main()` function. + +```go +import( + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + + compkg "./pkg" + cmdpkg "./cmd" +) + +func init() { + // Configure the golib error package + liberr.SetModeReturnError(liberr.ErrorReturnStringErrorFull) + + // Check the go runtime use to build + compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "Checking go version", compkg.GetVersion().CheckGo("1.16", ">=")) + + // Call the `cmd/InitCommand` function + cmdpkg.InitCommand() +} + +func main() { + // run the command Execute function + cmdpkg.Execute() +} + +``` \ No newline at end of file diff --git a/cobra/completion.go b/cobra/completion.go new file mode 100644 index 0000000..5654122 --- /dev/null +++ b/cobra/completion.go @@ -0,0 +1,96 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package cobra + +import ( + "os" + "path/filepath" + "strings" + + liblog "github.com/nabbar/golib/logger" + spfcbr "github.com/spf13/cobra" +) + +func (c *cobra) AddCommandCompletion() { + pkg := c.getPackageName() + + desc := "This command will create a completion shel script for simplify the use of this app.\n" + + "To do this," + + "\n\t 1- generate a completion script for your shell, like this : " + + "\n\t\t" + pkg + " completion bash /etc/bash_completion.d/" + pkg + + "\n\n 2- enable completion into your shell" + + "\n\t\t example to bash, you need to install the package `bash-completion`" + + "\n\n 3- enable completion into your shell profile" + + "\n\t\t example to bash, you need to uncomment the completion section into your /home//.bashrc" + + "\n\n" + + cmd := &spfcbr.Command{ + Use: "completion ", + Example: "completion bash /etc/bash_completion.d/" + pkg, + Short: "Generate a completion scripts for bash, zsh, fish or powershell", + Long: desc, + Run: func(cmd *spfcbr.Command, args []string) { + var file string + + if len(args) < 1 { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "missing args", cmd.Usage()) + os.Exit(1) + } else if len(args) >= 2 { + file = filepath.Clean(args[1]) + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "create file path", os.MkdirAll(filepath.Dir(file), 0755)) + } + + switch strings.ToLower(args[0]) { + case "bash": + if file == "" { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenBashCompletionV2(os.Stdout, true)) + } else { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenBashCompletionFileV2(file, true)) + } + case "fish": + if file == "" { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenFishCompletion(os.Stdout, true)) + } else { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenFishCompletionFile(file, true)) + } + case "powershell": + if file == "" { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenPowerShellCompletionWithDesc(os.Stdout)) + } else { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenPowerShellCompletionFileWithDesc(file)) + } + case "zsh": + if file == "" { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenZshCompletion(os.Stdout)) + } else { + c.getLog().CheckError(liblog.ErrorLevel, liblog.NilLevel, "generating bash completion", c.c.GenZshCompletionFile(file)) + } + } + }, + } + c.c.AddCommand(cmd) +} diff --git a/cobra/configure.go b/cobra/configure.go new file mode 100644 index 0000000..7de28b1 --- /dev/null +++ b/cobra/configure.go @@ -0,0 +1,140 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package cobra + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + + "github.com/mitchellh/go-homedir" + "github.com/pelletier/go-toml" + "gopkg.in/yaml.v2" + + liblog "github.com/nabbar/golib/logger" + spfcbr "github.com/spf13/cobra" +) + +func (c *cobra) getDefaultPath(baseName string) (string, error) { + path := "" + + // Find home directory. + home, err := homedir.Dir() + c.getLog().CheckError(liblog.WarnLevel, liblog.InfoLevel, "Loading home dir", err) + + // set configname based on package name + if baseName == "" { + return "", fmt.Errorf("arguments missing: requires the destination file path") + } + + path = filepath.Clean(home + string(filepath.Separator) + baseName + ".json") + + if path == "." || path == ".json" { + return "", fmt.Errorf("arguments missing: requires the destination file path") + } + + return path, nil +} + +func (c *cobra) AddCommandConfigure(basename string, defaultConfig func() io.Reader) { + pkg := c.getPackageName() + + if basename == "" && pkg != "" { + basename = "." + strings.ToLower(pkg) + } + + var cfgFile string + + c.c.AddCommand(&spfcbr.Command{ + Use: "configure ", + Example: "configure ~/." + strings.ToLower(pkg) + ".yml", + Short: "Generate config file", + Long: `Generates a configuration file based on giving existing config flag +override by passed flag in command line and completed with default for non existing values.`, + + Run: func(cmd *spfcbr.Command, args []string) { + var fs *os.File + + defer func() { + if fs != nil { + _ = fs.Close() + } + }() + + buf, err := ioutil.ReadAll(defaultConfig()) + c.getLog().CheckError(liblog.FatalLevel, liblog.DebugLevel, "reading default config", err) + + if len(path.Ext(cfgFile)) > 0 && strings.ToLower(path.Ext(cfgFile)) != ".json" { + var mod = make(map[string]interface{}, 0) + + err = json.Unmarshal(buf, &mod) + c.getLog().CheckError(liblog.FatalLevel, liblog.DebugLevel, "transform json default config", err) + + switch strings.ToLower(path.Ext(cfgFile)) { + case ".toml": + buf, err = toml.Marshal(mod) + case ".yml", ".yaml": + buf, err = yaml.Marshal(mod) + default: + c.getLog().CheckError(liblog.FatalLevel, liblog.DebugLevel, "get encode for extension file", fmt.Errorf("extension file '%s' not compatible", path.Ext(cfgFile))) + } + } + + fs, err = os.OpenFile(cfgFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + c.getLog().CheckError(liblog.FatalLevel, liblog.DebugLevel, "opening destination config file for exclusive write with truncate", err) + + _, err = fs.Write(buf) + c.getLog().CheckError(liblog.FatalLevel, liblog.DebugLevel, fmt.Sprintf("writing config to file '%s'", cfgFile), err) + + err = os.Chmod(cfgFile, 0600) + if !c.getLog().CheckError(liblog.ErrorLevel, liblog.InfoLevel, fmt.Sprintf("setting permission for config file '%s'", cfgFile), err) { + println(fmt.Sprintf("\n\t>> Config File '%s' has been created and file permission have been set.", cfgFile)) + println("\t>> To explicitly specify this config file when you call this tool, use the '-c' flag like this: ") + println(fmt.Sprintf("\t\t\t %s -c %s ...\n", pkg, cfgFile)) + } + }, + + Args: func(cmd *spfcbr.Command, args []string) error { + if len(args) < 1 { + var err error + cfgFile, err = c.getDefaultPath(basename) + return err + } else if len(args) > 1 { + return fmt.Errorf("arguments error: too many file path specify") + } else { + cfgFile = args[0] + } + + return nil + }, + }) +} diff --git a/cobra/interface.go b/cobra/interface.go new file mode 100644 index 0000000..c8d40a0 --- /dev/null +++ b/cobra/interface.go @@ -0,0 +1,74 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package cobra + +import ( + "io" + + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfcbr "github.com/spf13/cobra" +) + +type FuncInit func() +type FuncLogger func() liblog.Logger +type FuncViper func() libvpr.Viper +type FuncPrintErrorCode func(item, value string) + +type Cobra interface { + SetVersion(v libver.Version) + + SetFuncInit(fct FuncInit) + SetViper(fct FuncViper) + SetLogger(fct FuncLogger) + SetForceNoInfo(flag bool) + + Init() + + SetFlagConfig(persistent bool, flagVar *string) error + SetFlagVerbose(persistent bool, flagVar *int) + + NewCommand(cmd, short, long, useWithoutCmd, exampleWithoutCmd string) *spfcbr.Command + AddCommand(subCmd ...*spfcbr.Command) + + AddCommandCompletion() + AddCommandConfigure(basename string, defaultConfig func() io.Reader) + AddCommandPrintErrorCode(fct FuncPrintErrorCode) + + Execute() error +} + +func New() Cobra { + return &cobra{ + c: nil, + s: nil, + v: nil, + i: nil, + l: nil, + } +} diff --git a/cobra/model.go b/cobra/model.go new file mode 100644 index 0000000..7d784d2 --- /dev/null +++ b/cobra/model.go @@ -0,0 +1,172 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package cobra + +import ( + "fmt" + "os" + "path" + "strings" + + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + spfcbr "github.com/spf13/cobra" +) + +type cobra struct { + c *spfcbr.Command + s libver.Version + b bool + d string + + v FuncViper + i FuncInit + l FuncLogger +} + +func (c *cobra) Init() { + + c.c = &spfcbr.Command{ + TraverseChildren: true, + Use: c.getPackageName(), + Version: c.getPackageVersion(), + Short: c.getPackageDescShort(), + Long: c.getPackageDescLong(), + } + + // launch cobra flag parsing + spfcbr.OnInitialize(c.printHeader, c.i) +} + +func (c *cobra) printHeader() { + if !c.b { + println(c.s.GetHeader()) + } +} + +func (c *cobra) SetForceNoInfo(flag bool) { + c.b = flag +} + +func (c *cobra) Execute() error { + return c.c.Execute() +} + +func (c *cobra) SetVersion(vers libver.Version) { + c.s = vers +} + +func (c *cobra) SetFuncInit(fct FuncInit) { + c.i = fct +} + +func (c *cobra) SetViper(fct FuncViper) { + c.v = fct +} + +func (c *cobra) SetLogger(fct FuncLogger) { + c.l = fct +} + +func (c *cobra) SetFlagConfig(persistent bool, flagVar *string) error { + if persistent { + c.c.PersistentFlags().StringVarP(flagVar, "config", "c", "", "specify the config file to load (default is $HOME/."+strings.ToLower(c.getPackageName())+".[yaml|json|toml])") + return c.c.MarkPersistentFlagFilename("config", "json", "toml", "yaml", "yml") + } else { + c.c.Flags().StringVarP(flagVar, "config", "c", "", "specify the config file to load (default is $HOME/."+strings.ToLower(c.getPackageName())+".[yaml|json|toml])") + return c.c.MarkFlagFilename("config", "json", "toml", "yaml", "yml") + } +} + +func (c *cobra) SetFlagVerbose(persistent bool, flagVar *int) { + if persistent { + c.c.PersistentFlags().CountVarP(flagVar, "verbose", "v", "enable verbose mode (multi allowed v, vv, vvv)") + } else { + c.c.Flags().CountVarP(flagVar, "verbose", "v", "enable verbose mode (multi allowed v, vv, vvv)") + } +} + +func (c *cobra) NewCommand(cmd, short, long, useWithoutCmd, exampleWithoutCmd string) *spfcbr.Command { + return &spfcbr.Command{ + Use: fmt.Sprintf("%s %s", cmd, useWithoutCmd), + Short: short, + Long: long, + Example: fmt.Sprintf("%s %s", cmd, exampleWithoutCmd), + } +} + +func (c *cobra) AddCommand(subCmd ...*spfcbr.Command) { + c.c.AddCommand(subCmd...) +} + +func (c *cobra) getLog() liblog.Logger { + var l liblog.Logger + + if c.l != nil { + l = c.l() + } + + if l != nil { + return l + } + + return liblog.GetDefault() +} + +func (c *cobra) getPackageName() string { + pkg := path.Base(os.Args[0]) + + if pkg == "" { + if f, e := os.Executable(); e == nil { + pkg = path.Base(f) + } else { + pkg = c.s.GetPackage() + } + } + + return pkg +} + +func (c *cobra) getPackageVersion() string { + if c.s == nil { + return "missing version" + } + + return fmt.Sprintf("Version details: \n\tHash: %s\n\tVersion: %s\n\tRuntime: %s\n\tAuthor: %s\n\tDate: %s\n\tLicence: %s\n", c.s.GetBuild(), c.s.GetRelease(), c.s.GetAppId(), c.s.GetAuthor(), c.s.GetDate(), c.s.GetLicenseName()) +} + +func (c *cobra) getPackageGRootPath() string { + return c.s.GetRootPackagePath() +} + +func (c *cobra) getPackageDescShort() string { + return c.d +} + +func (c *cobra) getPackageDescLong() string { + return c.s.GetDescription() +} diff --git a/cobra/printError.go b/cobra/printError.go new file mode 100644 index 0000000..50f6311 --- /dev/null +++ b/cobra/printError.go @@ -0,0 +1,60 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package cobra + +import ( + "fmt" + "sort" + + liberr "github.com/nabbar/golib/errors" + spfcbr "github.com/spf13/cobra" +) + +func (c *cobra) AddCommandPrintErrorCode(fct FuncPrintErrorCode) { + c.c.AddCommand(&spfcbr.Command{ + Use: "error", + Example: "error", + Short: "Print error code with package path related", + Long: "", + Run: func(cmd *spfcbr.Command, args []string) { + var ( + lst = liberr.GetCodePackages(c.getPackageGRootPath()) + key = make([]int, 0) + ) + + for c := range lst { + key = append(key, int(c.GetUint16())) + } + + sort.Ints(key) + + for _, c := range key { + fct(fmt.Sprintf("%d", c), lst[liberr.CodeError(uint16(c))]) + } + }, + }) +} diff --git a/config/README.md b/config/README.md new file mode 100644 index 0000000..9ce73f5 --- /dev/null +++ b/config/README.md @@ -0,0 +1,119 @@ +# Config pakcage +This package will make a global config management package for an app. +This package work wil component who's will make the real intelligence of the config. +For example, the main config file is not existing into this config, because is generated by the sum of all component config part. + +## Exmaple of implement +You can follow the initialization define into the [`cobra` package](../cobra/README.md) +Here we will define the intialization of the config package. + +### Main init of config : + +Make a package's file to be called by yours application command or routers +Into this file we will use some import +```go +import ( + "fmt" + "net/http" + + liberr "github.com/nabbar/golib/errors" + libsts "github.com/nabbar/golib/status" + liblog "github.com/nabbar/golib/logger" + librtr "github.com/nabbar/golib/router" + + cmphea "github.com/nabbar/golib/config/components/head" + cmphtp "github.com/nabbar/golib/config/components/http" + cmplog "github.com/nabbar/golib/config/components/log" + cmptls "github.com/nabbar/golib/config/components/tls" + + compkg "../pkg" +) +``` + +Some constant to prevents error on copy/paste : +```go +const ( + ConfigKeyHead = "headers" + ConfigKeyHttp = "servers" + ConfigKeyLog = "log" + ConfigKeyTls = "tls" +) +``` + +This function will init the libs and return the lib resource. +This is an example, but you can create your own component to add it into you config. +The config can have multi instance of same component type but with different config key. +As That, you can for example define a TLS config for HTTP server, a TLS config for a client, ... + +```go +func SetConfig(lvl liblog.Level, handler map[string]http.Handler) { + compkg.GetConfig().ComponentSet(ConfigKeyHead, cmphea.New()) + compkg.GetConfig().ComponentSet(ConfigKeyLog, cmplog.New(lvl, compkg.GetLogger)) + compkg.GetConfig().ComponentSet(ConfigKeyTls, cmptls.New()) + compkg.GetConfig().ComponentSet(ConfigKeyHttp, cmphtp.New(_ConfigKeyTls, _ConfigKeyLog, handler)) +} + +// This function will return the router header based of the component header stored into the config +func GetHeader() librtr.Headers { + if cmp := cmphea.Load(compkg.GetConfig().ComponentGet, _ConfigKeyHead); cmp != nil { + return cmp.GetHeaders() + } else { + GetLog().Fatal("component '%s' is not initialized", _ConfigKeyHead) + return nil + } +} + +// This function will return the logger for the application. +// If the component is not started, this function will return the root logger +func GetLog() liblog.Logger { + if !compkg.GetConfig().ComponentIsStarted() { + return nil + } + + if cmp := cmplog.Load(compkg.GetConfig().ComponentGet, _ConfigKeyLog); cmp != nil { + return cmp.Log() + } else { + compkg.GetLogger().Error("component '%s' is not initialized", _ConfigKeyLog) + return compkg.GetLogger() + } +} +``` + +This function will connect the `status` package of golib with the `httpserver` package stored into the config : + +```go +// This function can be call by the implementation of Status Package to expose the status of the pool web server +// This function will update the status after each reload the pool web server. +func SetRouteStatus(sts libsts.RouteStatus) { + compkg.GetConfig().RegisterFuncReloadAfter(func() liberr.Error { + var cmp cmphtp.ComponentHttp + + // If component has not been started, skill the function + if !compkg.GetConfig().ComponentIsStarted() { + return nil + } + + // if cannot access to the Http component, generate an error + if cmp = cmphtp.Load(compkg.GetConfig().ComponentGet, ConfigKeyHttp); cmp == nil { + return ErrorComponentNotInitialized.ErrorParent(fmt.Errorf("component '%s'", ConfigKeyHttp)) + } + + // Get the Pool Server + pool := cmp.GetPool() + pool.StatusRoute(_ConfigKeyHttp, GetRouteStatusMessage, sts) + + // Don't forget to update the component pool + cmp.SetPool(pool) + + return nil + }) +} + +``` + +To generate the config example, just call this function : +```go + compkg.GetConfig().DefaultConfig() +``` +This function will associate the config Key with the component default config into a main buffer to return it. +As that, config can be extended easily with many component and the config is automatically managed. \ No newline at end of file diff --git a/config/component.go b/config/component.go new file mode 100644 index 0000000..2109b0e --- /dev/null +++ b/config/component.go @@ -0,0 +1,89 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package config + +import ( + liberr "github.com/nabbar/golib/errors" +) + +type Component interface { + // Type return the component type. + Type() string + + // RegisterContext is called by Config to register a function to get the main context. + // This function can be used into start / reload function to use context interface. + RegisterContext(fct FuncContext) + + // RegisterGet is called by Config to register a function to get a component by his key. + // This function can be used for dependencies into start / reload function. + RegisterGet(fct FuncComponentGet) + + // RegisterFuncStartBefore is called to register a function to be called before the start function. + RegisterFuncStartBefore(fct func() liberr.Error) + + // RegisterFuncStartAfter is called to register a function to be called after the start function. + RegisterFuncStartAfter(fct func() liberr.Error) + + // RegisterFuncReloadBefore is called to register a function to be called before the reload function. + RegisterFuncReloadBefore(fct func() liberr.Error) + + // RegisterFuncReloadAfter is called to register a function to be called after the reload function. + RegisterFuncReloadAfter(fct func() liberr.Error) + + // Start is called by the Config interface when the global configuration as been started + // This function can be usefull to start server in go routine with a configuration stored + // itself. + Start(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error + + // IsStarted is trigger by the Config interface with function ComponentIsStarted. + // This function can be usefull to know if the start server function is still call. + IsStarted() bool + + // Reload is called by the Config interface when the global configuration as been updated + // It receives a func as param to grab a config model by sending a model structure. + // It must configure itself, and stop / start his server if possible or return an error. + Reload(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error + + // Stop is called by the Config interface when global context is done. + // The context done can arrive by stopping the application or by received a signal KILL/TERM. + // This function must stop cleanly the component. + Stop() + + // IsRunning is trigger by the Config interface with function ComponentIsRunning. + // This function can be usefully to know if the component server function is still call. + // The atLeast param is used to know if the function must return true on first server is running + // or if all server must be running to return true. + IsRunning(atLeast bool) bool + + // DefaultConfig is called by Config.GetDefault. + // It must return a slice of byte containing the default json config for this component. + DefaultConfig() []byte + + // Dependencies is called by Config to define if this component need other component. + // Each other component can be call by calling Config.Get + Dependencies() []string +} diff --git a/config/components/head/default.go b/config/components/head/default.go new file mode 100644 index 0000000..4b4c5fa --- /dev/null +++ b/config/components/head/default.go @@ -0,0 +1,45 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package head + +var _defaultConfig = []byte(`{ + "Content-Security-Policy":"default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: image/svg+xml*; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'self'; child-src 'none'; frame-src 'none'; worker-src 'none'; frame-ancestors 'none'; form-action 'none'; upgrade-insecure-requests 1; block-all-mixed-content; disown-opener; require-sri-for script style; sandbox allow-same-origin allow-scripts; reflected-xss block; referrer no-referrer", + "Feature-Policy":"geolocation 'self'; midi 'self'; notifications 'self'; push 'self'; sync-xhr 'self'; microphone 'self'; camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; vibrate 'self'; fullscreen 'self'; payment 'self';", + "Strict-Transport-Security":"max-age=1; preload; includeSubDomains", + "X-Frame-Options":"DENY", + "X-Xss-Protection":"1; mode=block", + "X-Content-Type-Options":"nosniff", + "Referrer-Policy":"no-referrer" +}`) + +func (c *componentHead) DefaultConfig() []byte { + return _defaultConfig +} + +func SetDefaultConfig(cfg []byte) { + _defaultConfig = cfg +} diff --git a/config/components/head/errors.go b/config/components/head/errors.go new file mode 100644 index 0000000..97428b1 --- /dev/null +++ b/config/components/head/errors.go @@ -0,0 +1,71 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package head + +import ( + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamsEmpty liberr.CodeError = iota + libcfg.MinErrorComponentHttp + ErrorParamsInvalid + ErrorComponentNotInitialized + ErrorConfigInvalid + ErrorReloadPoolServer + ErrorReloadTLSDefault +) + +func init() { + isCodeError = liberr.ExistInMapMessage(ErrorParamsEmpty) + liberr.RegisterIdFctMessage(ErrorParamsEmpty, getMessage) +} + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamsEmpty: + return "at least one given parameters is empty" + case ErrorParamsInvalid: + return "at least one given parameters is invalid" + case ErrorComponentNotInitialized: + return "this component seems to not be correctly initialized" + case ErrorConfigInvalid: + return "server invalid config" + case ErrorReloadPoolServer: + return "cannot update pool servers with new config" + case ErrorReloadTLSDefault: + return "cannot update default TLS with new config" + } + + return "" +} diff --git a/config/components/head/interface.go b/config/components/head/interface.go new file mode 100644 index 0000000..2f6a41d --- /dev/null +++ b/config/components/head/interface.go @@ -0,0 +1,67 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package head + +import ( + libcfg "github.com/nabbar/golib/config" + librtr "github.com/nabbar/golib/router" +) + +const ( + ComponentType = "head" +) + +type ComponentHead interface { + libcfg.Component + + GetHeaders() librtr.Headers + SetHeaders(head librtr.Headers) +} + +func New() ComponentHead { + return &componentHead{ + head: nil, + } +} + +func Register(cfg libcfg.Config, key string, cpt ComponentHead) { + cfg.ComponentSet(key, cpt) +} + +func RegisterNew(cfg libcfg.Config, key string) { + cfg.ComponentSet(key, New()) +} + +func Load(getCpt libcfg.FuncComponentGet, key string) ComponentHead { + if c := getCpt(key); c == nil { + return nil + } else if h, ok := c.(ComponentHead); !ok { + return nil + } else { + return h + } +} diff --git a/config/components/head/model.go b/config/components/head/model.go new file mode 100644 index 0000000..a84ac75 --- /dev/null +++ b/config/components/head/model.go @@ -0,0 +1,171 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package head + +import ( + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" + libhts "github.com/nabbar/golib/httpserver" + librtr "github.com/nabbar/golib/router" +) + +type DefaultModel struct { + Head librtr.HeadersConfig + Http libhts.PoolServerConfig +} + +type componentHead struct { + ctx libcfg.FuncContext + get libcfg.FuncComponentGet + fsa func() liberr.Error + fsb func() liberr.Error + fra func() liberr.Error + frb func() liberr.Error + + head librtr.Headers +} + +func (c *componentHead) Type() string { + return ComponentType +} + +func (c *componentHead) RegisterContext(fct libcfg.FuncContext) { + c.ctx = fct +} + +func (c *componentHead) RegisterGet(fct libcfg.FuncComponentGet) { + c.get = fct +} + +func (c *componentHead) RegisterFuncStartBefore(fct func() liberr.Error) { + c.fsb = fct +} + +func (c *componentHead) RegisterFuncStartAfter(fct func() liberr.Error) { + c.fsa = fct +} + +func (c *componentHead) RegisterFuncReloadBefore(fct func() liberr.Error) { + c.frb = fct +} + +func (c *componentHead) RegisterFuncReloadAfter(fct func() liberr.Error) { + c.fra = fct +} + +func (c *componentHead) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { + if c == nil { + return ErrorComponentNotInitialized.Error(nil) + } + + cnf := DefaultModel{} + if err := getCfg(&cnf); err != nil { + return ErrorParamsInvalid.Error(err) + } + + c.head = cnf.Head.New() + + return nil +} + +func (c *componentHead) Start(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var err liberr.Error + + if c.fsb != nil { + if err = c.fsb(); err != nil { + return err + } + } + + if err = c._run(getCfg); err != nil { + return err + } + + if c.fsa != nil { + if err = c.fsa(); err != nil { + return err + } + } + + return nil +} + +func (c *componentHead) Reload(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var ( + err liberr.Error + ) + + if c.frb != nil { + if err = c.frb(); err != nil { + return err + } + } + + if err = c._run(getCfg); err != nil { + return err + } + + if c.fra != nil { + if err = c.fra(); err != nil { + return err + } + } + + return nil +} + +func (c *componentHead) Stop() { + +} + +func (c *componentHead) IsStarted() bool { + return c.head == nil +} + +func (c *componentHead) IsRunning(atLeast bool) bool { + return c.head == nil +} + +func (c *componentHead) Dependencies() []string { + return []string{} +} + +func (c *componentHead) GetHeaders() librtr.Headers { + if c == nil || c.head == nil { + return nil + } + + return c.head +} + +func (c *componentHead) SetHeaders(head librtr.Headers) { + if c == nil { + return + } + + c.head = head +} diff --git a/config/components/http/default.go b/config/components/http/default.go new file mode 100644 index 0000000..e2c9ad5 --- /dev/null +++ b/config/components/http/default.go @@ -0,0 +1,113 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package http + +var _defaultConfig = []byte(`[ + { + "disabled":false, + "mandatory":true, + "timeout_cache_info":"30s", + "timeout_cache_health":"30s", + "read_timeout":"0s", + "read_header_timeout":"0s", + "write_timeout":"0s", + "idle_timeout":"0s", + "max_header_bytes":0, + "max_handlers":0, + "max_concurrent_streams":0, + "max_read_frame_size":0, + "permit_prohibited_cipher_suites":false, + "max_upload_buffer_per_connection":0, + "max_upload_buffer_per_stream":0, + "name":"status_http", + "handler_keys":"status", + "tls_mandatory":false, + "listen":"0.0.0.0:6080", + "expose":"http://0.0.0.0", + "tls":{ + + } + }, + { + "disabled":false, + "mandatory":true, + "timeout_cache_info":"30s", + "timeout_cache_health":"30s", + "read_timeout":"0s", + "read_header_timeout":"0s", + "write_timeout":"0s", + "idle_timeout":"0s", + "max_header_bytes":0, + "max_handlers":0, + "max_concurrent_streams":0, + "max_read_frame_size":0, + "permit_prohibited_cipher_suites":false, + "max_upload_buffer_per_connection":0, + "max_upload_buffer_per_stream":0, + "handler_keys":"api", + "tls_mandatory":false, + "name":"api_http", + "listen":"0.0.0.0:7080", + "expose":"http://0.0.0.0", + "tls":{ + + } + }, + { + "disabled":false, + "mandatory":true, + "timeout_cache_info":"30s", + "timeout_cache_health":"30s", + "read_timeout":"0s", + "read_header_timeout":"0s", + "write_timeout":"0s", + "idle_timeout":"0s", + "max_header_bytes":0, + "max_handlers":0, + "max_concurrent_streams":0, + "max_read_frame_size":0, + "permit_prohibited_cipher_suites":false, + "max_upload_buffer_per_connection":0, + "max_upload_buffer_per_stream":0, + "handler_keys":"metrics", + "tls_mandatory":false, + "name":"metrics_http", + "listen":"0.0.0.0:8080", + "expose":"http://0.0.0.0", + "tls":{ + + } + } +]`) + +func (c *componentHttp) DefaultConfig() []byte { + return _defaultConfig +} + +func SetDefaultConfig(cfg []byte) { + _defaultConfig = cfg +} diff --git a/config/components/http/errors.go b/config/components/http/errors.go new file mode 100644 index 0000000..9f7da41 --- /dev/null +++ b/config/components/http/errors.go @@ -0,0 +1,77 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package http + +import ( + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamsEmpty liberr.CodeError = iota + libcfg.MinErrorComponentHttp + ErrorParamsInvalid + ErrorComponentNotInitialized + ErrorConfigInvalid + ErrorStartPoolServer + ErrorReloadPoolServer + ErrorDependencyTLSDefault + ErrorDependencyLogDefault +) + +func init() { + isCodeError = liberr.ExistInMapMessage(ErrorParamsEmpty) + liberr.RegisterIdFctMessage(ErrorParamsEmpty, getMessage) +} + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamsEmpty: + return "at least one given parameters is empty" + case ErrorParamsInvalid: + return "at least one given parameters is invalid" + case ErrorComponentNotInitialized: + return "this component seems to not be correctly initialized" + case ErrorConfigInvalid: + return "server invalid config" + case ErrorStartPoolServer: + return "cannot start new pool servers with config" + case ErrorReloadPoolServer: + return "cannot update pool servers with new config" + case ErrorDependencyTLSDefault: + return "cannot retrieve default TLS" + case ErrorDependencyLogDefault: + return "cannot retrieve default Logger" + } + + return "" +} diff --git a/config/components/http/interface.go b/config/components/http/interface.go new file mode 100644 index 0000000..f48f556 --- /dev/null +++ b/config/components/http/interface.go @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package http + +import ( + "net/http" + + libcfg "github.com/nabbar/golib/config" + libhts "github.com/nabbar/golib/httpserver" +) + +const ( + DefaultTlsKey = "tls" + DefaultLogKey = "log" + ComponentType = "http" +) + +type ComponentHttp interface { + libcfg.Component + + SetTLSKey(tlsKey string) + SetLOGKey(logKey string) + SetHandler(handler map[string]http.Handler) + + GetPool() libhts.PoolServer + SetPool(pool libhts.PoolServer) +} + +func New(tlsKey, logKey string, handler map[string]http.Handler) ComponentHttp { + if tlsKey == "" { + tlsKey = DefaultTlsKey + } + + if logKey == "" { + logKey = DefaultLogKey + } + + return &componentHttp{ + tls: tlsKey, + log: logKey, + run: false, + hand: handler, + pool: nil, + } +} + +func Register(cfg libcfg.Config, key string, cpt ComponentHttp) { + cfg.ComponentSet(key, cpt) +} + +func RegisterNew(cfg libcfg.Config, key string, tlsKey, logKey string, handler map[string]http.Handler) { + cfg.ComponentSet(key, New(tlsKey, logKey, handler)) +} + +func Load(getCpt libcfg.FuncComponentGet, key string) ComponentHttp { + if c := getCpt(key); c == nil { + return nil + } else if h, ok := c.(ComponentHttp); !ok { + return nil + } else { + return h + } +} diff --git a/config/components/http/model.go b/config/components/http/model.go new file mode 100644 index 0000000..af322db --- /dev/null +++ b/config/components/http/model.go @@ -0,0 +1,306 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package http + +import ( + "fmt" + "net/http" + + libtls "github.com/nabbar/golib/certificates" + libcfg "github.com/nabbar/golib/config" + cptlog "github.com/nabbar/golib/config/components/log" + cpttls "github.com/nabbar/golib/config/components/tls" + liberr "github.com/nabbar/golib/errors" + libhts "github.com/nabbar/golib/httpserver" + liblog "github.com/nabbar/golib/logger" +) + +type componentHttp struct { + ctx libcfg.FuncContext + get libcfg.FuncComponentGet + fsa func() liberr.Error + fsb func() liberr.Error + fra func() liberr.Error + frb func() liberr.Error + + tls string + log string + + run bool + hand map[string]http.Handler + + pool libhts.PoolServer +} + +func (c *componentHttp) _CheckDep() bool { + return c != nil && len(c.hand) > 0 && c.tls != "" && c.log != "" +} + +func (c *componentHttp) _CheckInit() bool { + return c != nil && c._CheckDep() && c.pool != nil +} + +func (c *componentHttp) Type() string { + return ComponentType +} + +func (c *componentHttp) RegisterContext(fct libcfg.FuncContext) { + c.ctx = fct +} + +func (c *componentHttp) RegisterGet(fct libcfg.FuncComponentGet) { + c.get = fct +} + +func (c *componentHttp) RegisterFuncStartBefore(fct func() liberr.Error) { + c.fsb = fct +} + +func (c *componentHttp) RegisterFuncStartAfter(fct func() liberr.Error) { + c.fsa = fct +} + +func (c *componentHttp) RegisterFuncReloadBefore(fct func() liberr.Error) { + c.frb = fct +} + +func (c *componentHttp) RegisterFuncReloadAfter(fct func() liberr.Error) { + c.fra = fct +} + +func (c *componentHttp) getTLS(getCpt libcfg.FuncComponentGet) (libtls.TLSConfig, liberr.Error) { + if !c._CheckDep() { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if i := cpttls.Load(getCpt, c.tls); i == nil { + return nil, ErrorDependencyTLSDefault.Error(nil) + } else if tls := i.GetTLS(); tls == nil { + return nil, ErrorDependencyTLSDefault.Error(nil) + } else { + return tls, nil + } +} + +func (c *componentHttp) getLogger(getCpt libcfg.FuncComponentGet) (liblog.Logger, liberr.Error) { + if !c._CheckDep() { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if i := cptlog.Load(getCpt, c.log); i == nil { + return nil, ErrorDependencyLogDefault.Error(nil) + } else if log := i.Log(); log == nil { + return nil, ErrorDependencyLogDefault.Error(nil) + } else { + return log, nil + } +} + +func (c *componentHttp) _getPoolServerConfig(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) (libhts.PoolServerConfig, liberr.Error) { + cnf := make(libhts.PoolServerConfig, 0) + + if !c._CheckDep() { + return cnf, ErrorComponentNotInitialized.Error(nil) + } + + if err := getCfg(&cnf); err != nil { + return cnf, ErrorParamsInvalid.Error(err) + } + + if tls, err := c.getTLS(getCpt); err != nil { + return cnf, err + } else { + cnf.MapUpdate(func(sCFG libhts.ServerConfig) libhts.ServerConfig { + sCFG.SetDefaultTLS(func() libtls.TLSConfig { + return tls + }) + + sCFG.SetParentContext(c.ctx) + return sCFG + }) + } + + if err := cnf.Validate(); err != nil { + return cnf, ErrorConfigInvalid.Error(err) + } else if len(c.hand) < 1 { + return cnf, ErrorComponentNotInitialized.ErrorParent(fmt.Errorf("missing handler")) + } + + return cnf, nil +} + +func (c *componentHttp) Start(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var ( + err liberr.Error + cnf libhts.PoolServerConfig + ) + + if c.fsb != nil { + if err = c.fsb(); err != nil { + return err + } + } + + if cnf, err = c._getPoolServerConfig(getCpt, getCfg); err != nil { + return err + } + + if p, e := cnf.PoolServer(); e != nil { + return ErrorStartPoolServer.Error(e) + } else { + c.pool = p + } + + c.pool.SetLogger(func() liblog.Logger { + if log, err := c.getLogger(getCpt); err != nil { + return liblog.GetDefault() + } else { + return log + } + }) + + if err = c.pool.ListenMultiHandler(c.hand); err != nil { + return ErrorStartPoolServer.Error(err) + } + + if c.fsa != nil { + if err = c.fsa(); err != nil { + return err + } + } + + return nil +} + +func (c *componentHttp) Reload(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var ( + err liberr.Error + cnf libhts.PoolServerConfig + ) + + if c.frb != nil { + if err = c.frb(); err != nil { + return err + } + } + + if cnf, err = c._getPoolServerConfig(getCpt, getCfg); err != nil { + return err + } + + if c.pool != nil { + if p, e := cnf.UpdatePoolServer(c.pool); e != nil { + return ErrorReloadPoolServer.Error(e) + } else { + c.pool = p + } + } else if p, e := cnf.PoolServer(); e != nil { + return ErrorReloadPoolServer.Error(e) + } else { + c.pool = p + } + + c.pool.SetLogger(func() liblog.Logger { + if log, err := c.getLogger(getCpt); err != nil { + return liblog.GetDefault() + } else { + return log + } + }) + + c.pool.Restart() + + if c.fra != nil { + if err = c.fra(); err != nil { + return err + } + } + + return nil +} + +func (c *componentHttp) Stop() { + c.run = false + + if !c._CheckInit() { + return + } + + c.pool.Shutdown() +} + +func (c *componentHttp) IsStarted() bool { + return c._CheckInit() && c.pool.IsRunning(true) +} + +func (c *componentHttp) IsRunning(atLeast bool) bool { + return c._CheckInit() && c.pool.IsRunning(atLeast) +} + +func (c *componentHttp) Dependencies() []string { + return []string{cpttls.ComponentType, cptlog.ComponentType} +} + +func (c *componentHttp) SetTLSKey(tlsKey string) { + if c == nil { + return + } + + c.tls = tlsKey +} + +func (c *componentHttp) SetLOGKey(logKey string) { + if c == nil { + return + } + + c.log = logKey +} + +func (c *componentHttp) SetHandler(handler map[string]http.Handler) { + if c == nil { + return + } + + c.hand = handler +} + +func (c *componentHttp) GetPool() libhts.PoolServer { + if c == nil || c.pool == nil { + return nil + } + + return c.pool +} + +func (c *componentHttp) SetPool(pool libhts.PoolServer) { + if c == nil { + return + } + + c.pool = pool +} diff --git a/config/components/log/default.go b/config/components/log/default.go new file mode 100644 index 0000000..5f96f43 --- /dev/null +++ b/config/components/log/default.go @@ -0,0 +1,85 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package log + +var _defaultConfig = []byte(` +{ + "disableStandard":false, + "disableStack":false, + "disableTimestamp":false, + "enableTrace":true, + "traceFilter":"", + "disableColor":false, + "logFile":[ + { + "logLevel":[ + "Debug", + "Info", + "Warning", + "Error", + "Fatal", + "Critical" + ], + "filepath":"", + "create":false, + "createPath":false, + "fileMode":"0644", + "pathMode":"0755", + "disableStack":false, + "disableTimestamp":false, + "enableTrace":true + } + ], + "logSyslog":[ + { + "logLevel":[ + "Debug", + "Info", + "Warning", + "Error", + "Fatal", + "Critical" + ], + "network":"tcp", + "host":"", + "severity":"Error", + "facility":"local0", + "tag":"", + "disableStack":false, + "disableTimestamp":false, + "enableTrace":true + } + ] +}`) + +func (c *componentLog) DefaultConfig() []byte { + return _defaultConfig +} + +func SetDefaultConfig(cfg []byte) { + _defaultConfig = cfg +} diff --git a/config/components/log/errors.go b/config/components/log/errors.go new file mode 100644 index 0000000..ae48165 --- /dev/null +++ b/config/components/log/errors.go @@ -0,0 +1,71 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package log + +import ( + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamsEmpty liberr.CodeError = iota + libcfg.MinErrorComponentLog + ErrorParamsInvalid + ErrorConfigInvalid + ErrorComponentNotInitialized + ErrorStartLog + ErrorReloadLog +) + +func init() { + isCodeError = liberr.ExistInMapMessage(ErrorParamsEmpty) + liberr.RegisterIdFctMessage(ErrorParamsEmpty, getMessage) +} + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamsEmpty: + return "at least one given parameters is empty" + case ErrorParamsInvalid: + return "at least one given parameters is invalid" + case ErrorConfigInvalid: + return "server invalid config" + case ErrorComponentNotInitialized: + return "this component seems to not be correctly initialized" + case ErrorStartLog: + return "cannot start Logger" + case ErrorReloadLog: + return "cannot update Logger with new config" + } + + return "" +} diff --git a/config/components/log/interface.go b/config/components/log/interface.go new file mode 100644 index 0000000..6021e34 --- /dev/null +++ b/config/components/log/interface.go @@ -0,0 +1,74 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package log + +import ( + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" +) + +const ( + DefaultLevel = liblog.InfoLevel + ComponentType = "log" +) + +type ComponentLog interface { + libcfg.Component + + Log() liblog.Logger + + SetLevel(lvl liblog.Level) + SetField(fields liblog.Fields) + SetOptions(opt *liblog.Options) liberr.Error +} + +func New(lvl liblog.Level, defLogger func() liblog.Logger) ComponentLog { + return &componentLog{ + d: defLogger, + l: nil, + v: lvl, + } +} + +func Register(cfg libcfg.Config, key string, cpt ComponentLog) { + cfg.ComponentSet(key, cpt) +} + +func RegisterNew(cfg libcfg.Config, key string, lvl liblog.Level, defLogger func() liblog.Logger) { + cfg.ComponentSet(key, New(lvl, defLogger)) +} + +func Load(getCpt libcfg.FuncComponentGet, key string) ComponentLog { + if c := getCpt(key); c == nil { + return nil + } else if h, ok := c.(ComponentLog); !ok { + return nil + } else { + return h + } +} diff --git a/config/components/log/model.go b/config/components/log/model.go new file mode 100644 index 0000000..2d4b78e --- /dev/null +++ b/config/components/log/model.go @@ -0,0 +1,196 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package log + +import ( + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" +) + +type DefaultModel struct { + liblog.Options +} + +type componentLog struct { + ctx libcfg.FuncContext + get libcfg.FuncComponentGet + fsa func() liberr.Error + fsb func() liberr.Error + fra func() liberr.Error + frb func() liberr.Error + + d func() liblog.Logger + l liblog.Logger + v liblog.Level +} + +func (c *componentLog) Type() string { + return ComponentType +} + +func (c *componentLog) RegisterContext(fct libcfg.FuncContext) { + c.ctx = fct +} + +func (c *componentLog) RegisterGet(fct libcfg.FuncComponentGet) { + c.get = fct +} + +func (c *componentLog) RegisterFuncStartBefore(fct func() liberr.Error) { + c.fsb = fct +} + +func (c *componentLog) RegisterFuncStartAfter(fct func() liberr.Error) { + c.fsa = fct +} + +func (c *componentLog) RegisterFuncReloadBefore(fct func() liberr.Error) { + c.frb = fct +} + +func (c *componentLog) RegisterFuncReloadAfter(fct func() liberr.Error) { + c.fra = fct +} + +func (c *componentLog) _run(errCode liberr.CodeError, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + if c.ctx == nil { + return ErrorComponentNotInitialized.Error(nil) + } + + if c.l == nil { + c.l = liblog.New(c.ctx()) + } + + cnf := DefaultModel{} + if err := getCfg(&cnf); err != nil { + return ErrorParamsInvalid.Error(err) + } else if err = cnf.Validate(); err != nil { + return ErrorConfigInvalid.Error(err) + } else if e := c.l.SetOptions(&cnf.Options); e != nil { + return errCode.ErrorParent(e) + } + + return nil +} + +func (c *componentLog) Start(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var err liberr.Error + + if c.fsb != nil { + if err = c.fsb(); err != nil { + return err + } + } + + if err = c._run(ErrorStartLog, getCfg); err != nil { + return err + } + + if c.fsa != nil { + if err = c.fsa(); err != nil { + return err + } + } + + return nil +} + +func (c *componentLog) Reload(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var err liberr.Error + + if c.frb != nil { + if err = c.frb(); err != nil { + return err + } + } + + if err = c._run(ErrorStartLog, getCfg); err != nil { + return err + } + + if c.fra != nil { + if err = c.fra(); err != nil { + return err + } + } + + return nil +} + +func (c *componentLog) Stop() { + +} + +func (c *componentLog) IsStarted() bool { + return c.l != nil +} + +func (c *componentLog) IsRunning(atLeast bool) bool { + return c.l != nil +} + +func (c *componentLog) Dependencies() []string { + return make([]string, 0) +} + +func (c *componentLog) Log() liblog.Logger { + if c.l != nil { + _l, _ := c.l.Clone() + return _l + } + + return c.d() +} + +func (c *componentLog) SetLevel(lvl liblog.Level) { + if c.l != nil { + return + } + + c.l.SetLevel(lvl) +} + +func (c *componentLog) SetField(fields liblog.Fields) { + if c.l != nil { + return + } + + c.l.SetFields(fields) +} + +func (c *componentLog) SetOptions(opt *liblog.Options) liberr.Error { + if c.l != nil { + return ErrorComponentNotInitialized.Error(nil) + } + + if e := c.l.SetOptions(opt); e != nil { + return ErrorConfigInvalid.ErrorParent(e) + } + + return nil +} diff --git a/config/components/tls/default.go b/config/components/tls/default.go new file mode 100644 index 0000000..3743a55 --- /dev/null +++ b/config/components/tls/default.go @@ -0,0 +1,83 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package tls + +var _defaultConfig = []byte(`{ + "versionMin":"1.2", + "versionMax":"1.2", + "dynamicSizingDisable":false, + "sessionTicketDisable":false, + "authClient":"none", + "curveList":[ + "X25519", + "P256", + "P384", + "P521" + ], + "cipherList":[ + "RSA-AES128-GCM", + "RSA-AES128-CBC", + "RSA-AES256-GCM", + "RSA-CHACHA", + "ECDSA-AES128-GCM", + "ECDSA-AES128-CBC", + "ECDSA-AES256-GCM", + "ECDSA-CHACHA" + ], + "rootCA":[ + "" + ], + "rootCAFiles":[ + "" + ], + "clientCA":[ + "" + ], + "clientCAFiles":[ + "" + ], + "certPair":[ + { + "key":"", + "pem":"" + } + ], + "certPairFiles":[ + { + "key":"", + "pem":"" + } + ] +}`) + +func (c *componentTls) DefaultConfig() []byte { + return _defaultConfig +} + +func SetDefaultConfig(cfg []byte) { + _defaultConfig = cfg +} diff --git a/config/components/tls/errors.go b/config/components/tls/errors.go new file mode 100644 index 0000000..d1a956b --- /dev/null +++ b/config/components/tls/errors.go @@ -0,0 +1,71 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package tls + +import ( + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamsEmpty liberr.CodeError = iota + libcfg.MinErrorComponentTls + ErrorParamsInvalid + ErrorConfigInvalid + ErrorComponentNotInitialized + ErrorStartTLS + ErrorReloadTLS +) + +func init() { + isCodeError = liberr.ExistInMapMessage(ErrorParamsEmpty) + liberr.RegisterIdFctMessage(ErrorParamsEmpty, getMessage) +} + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamsEmpty: + return "at least one given parameters is empty" + case ErrorParamsInvalid: + return "at least one given parameters is invalid" + case ErrorConfigInvalid: + return "server invalid config" + case ErrorComponentNotInitialized: + return "this component seems to not be correctly initialized" + case ErrorStartTLS: + return "cannot create new TLS Config" + case ErrorReloadTLS: + return "cannot update TLS Config" + } + + return "" +} diff --git a/config/components/tls/interface.go b/config/components/tls/interface.go new file mode 100644 index 0000000..a388019 --- /dev/null +++ b/config/components/tls/interface.go @@ -0,0 +1,66 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package tls + +import ( + libtls "github.com/nabbar/golib/certificates" + libcfg "github.com/nabbar/golib/config" +) + +const ( + ComponentType = "tls" +) + +type ComponentTlS interface { + libcfg.Component + GetTLS() libtls.TLSConfig + SetTLS(tls libtls.TLSConfig) +} + +func New() ComponentTlS { + return &componentTls{ + tls: nil, + } +} + +func Register(cfg libcfg.Config, key string, cpt ComponentTlS) { + cfg.ComponentSet(key, cpt) +} + +func RegisterNew(cfg libcfg.Config, key string) { + cfg.ComponentSet(key, New()) +} + +func Load(getCpt libcfg.FuncComponentGet, key string) ComponentTlS { + if c := getCpt(key); c == nil { + return nil + } else if h, ok := c.(ComponentTlS); !ok { + return nil + } else { + return h + } +} diff --git a/config/components/tls/model.go b/config/components/tls/model.go new file mode 100644 index 0000000..20402b3 --- /dev/null +++ b/config/components/tls/model.go @@ -0,0 +1,182 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package tls + +import ( + libtls "github.com/nabbar/golib/certificates" + libcfg "github.com/nabbar/golib/config" + liberr "github.com/nabbar/golib/errors" +) + +type DefaultModel struct { + libtls.Config +} + +type componentTls struct { + ctx libcfg.FuncContext + get libcfg.FuncComponentGet + fsa func() liberr.Error + fsb func() liberr.Error + fra func() liberr.Error + frb func() liberr.Error + + tls libtls.TLSConfig +} + +func (c *componentTls) Type() string { + return ComponentType +} + +func (c *componentTls) RegisterContext(fct libcfg.FuncContext) { + c.ctx = fct +} + +func (c *componentTls) RegisterGet(fct libcfg.FuncComponentGet) { + c.get = fct +} + +func (c *componentTls) RegisterFuncStartBefore(fct func() liberr.Error) { + c.fsb = fct +} + +func (c *componentTls) RegisterFuncStartAfter(fct func() liberr.Error) { + c.fsa = fct +} + +func (c *componentTls) RegisterFuncReloadBefore(fct func() liberr.Error) { + c.frb = fct +} + +func (c *componentTls) RegisterFuncReloadAfter(fct func() liberr.Error) { + c.fra = fct +} + +func (c *componentTls) _run(errCode liberr.CodeError, getCfg libcfg.FuncComponentConfigGet) (libtls.TLSConfig, liberr.Error) { + cnf := DefaultModel{} + + if c == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if err := getCfg(&cnf); err != nil { + return nil, ErrorParamsInvalid.Error(err) + } + + if err := cnf.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + if tls, err := cnf.New(); err != nil { + return nil, errCode.Error(err) + } else { + return tls, nil + } +} + +func (c *componentTls) Start(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var ( + err liberr.Error + tls libtls.TLSConfig + ) + + if c.fsb != nil { + if err = c.fsb(); err != nil { + return err + } + } + + if tls, err = c._run(ErrorStartTLS, getCfg); err != nil { + return err + } else { + c.tls = tls + } + + if c.fsa != nil { + if err = c.fsa(); err != nil { + return err + } + } + + return nil +} + +func (c *componentTls) Reload(getCpt libcfg.FuncComponentGet, getCfg libcfg.FuncComponentConfigGet) liberr.Error { + var ( + err liberr.Error + tls libtls.TLSConfig + ) + + if c.frb != nil { + if err = c.frb(); err != nil { + return err + } + } + + if tls, err = c._run(ErrorReloadTLS, getCfg); err != nil { + return err + } else { + c.tls = tls + } + + if c.fra != nil { + if err = c.fra(); err != nil { + return err + } + } + + return nil +} + +func (c *componentTls) Stop() {} + +func (c *componentTls) Dependencies() []string { + return make([]string, 0) +} + +func (c *componentTls) IsStarted() bool { + return c.tls != nil +} + +func (c *componentTls) IsRunning(atLeast bool) bool { + return c.tls != nil +} + +func (c *componentTls) GetTLS() libtls.TLSConfig { + if c == nil || c.tls == nil { + return nil + } + + return c.tls +} + +func (c *componentTls) SetTLS(tls libtls.TLSConfig) { + if c == nil || tls == nil { + return + } + + c.tls = tls +} diff --git a/config/cptList.go b/config/cptList.go new file mode 100644 index 0000000..fea0411 --- /dev/null +++ b/config/cptList.go @@ -0,0 +1,355 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package config + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "sync" + "sync/atomic" + + liberr "github.com/nabbar/golib/errors" +) + +type ComponentList interface { + // ComponentHas return true if the key is a registered Component + ComponentHas(key string) bool + + // ComponentType return the Component Type of the registered key. + ComponentType(key string) string + + // ComponentGet return the given component associated with the config Key. + // The component can be transTyped to other interface to be exploited + ComponentGet(key string) Component + + // ComponentDel remove the given Component key from the config. + ComponentDel(key string) + + // ComponentSet stores the given Component with a key. + ComponentSet(key string, cpt Component) + + // ComponentList returns a map of stored couple keyType and Component + ComponentList() map[string]Component + + // ComponentKeys returns a slice of stored Component keys + ComponentKeys() []string + + // ComponentStart trigger the Start function of each Component. + // This function will keep the dependencies of each Component. + // This function will stop the Start sequence on any error triggered. + ComponentStart(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error + + // ComponentIsStarted will trigger the IsStarted function of all registered component. + // If any component return false, this func return false. + ComponentIsStarted() bool + + // ComponentReload trigger the Reload function of each Component. + // This function will keep the dependencies of each Component. + // This function will stop the Reload sequence on any error triggered. + ComponentReload(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error + + // ComponentStop trigger the Stop function of each Component. + // This function will not keep the dependencies of each Component. + ComponentStop() + + // ComponentIsRunning will trigger the IsRunning function of all registered component. + // If any component return false, this func return false. + ComponentIsRunning(atLeast bool) bool + + // DefaultConfig aggregates all registered components' default config + // Returns a filled buffer with a complete config json model + DefaultConfig() io.Reader +} + +func newComponentList() ComponentList { + return &componentList{ + m: sync.Mutex{}, + l: make(map[string]*atomic.Value, 0), + } +} + +type componentList struct { + m sync.Mutex + l map[string]*atomic.Value +} + +func (c *componentList) ComponentHas(key string) bool { + c.m.Lock() + defer c.m.Unlock() + + _, ok := c.l[key] + return ok +} + +func (c *componentList) ComponentType(key string) string { + if !c.ComponentHas(key) { + return "" + } else if o := c.ComponentGet(key); o == nil { + return "" + } else { + return o.Type() + } +} + +func (c *componentList) ComponentGet(key string) Component { + if !c.ComponentHas(key) { + return nil + } + + c.m.Lock() + defer c.m.Unlock() + + if len(c.l) < 1 { + c.l = make(map[string]*atomic.Value, 0) + } + + if v := c.l[key]; v == nil { + return nil + } else if i := v.Load(); i == nil { + return nil + } else if o, ok := i.(Component); !ok { + return nil + } else { + return o + } +} + +func (c *componentList) ComponentDel(key string) { + if !c.ComponentHas(key) { + return + } + + c.m.Lock() + defer c.m.Unlock() + + if len(c.l) < 1 { + c.l = make(map[string]*atomic.Value, 0) + } + + if v := c.l[key]; v == nil { + return + } else { + c.l[key] = new(atomic.Value) + } +} + +func (c *componentList) ComponentSet(key string, cpt Component) { + c.m.Lock() + defer c.m.Unlock() + + if len(c.l) < 1 { + c.l = make(map[string]*atomic.Value, 0) + } + + if v, ok := c.l[key]; !ok || v == nil { + c.l[key] = new(atomic.Value) + } + + c.l[key].Store(cpt) +} + +func (c *componentList) ComponentList() map[string]Component { + var res = make(map[string]Component, 0) + + for _, k := range c.ComponentKeys() { + res[k] = c.ComponentGet(k) + } + + return res +} + +func (c *componentList) ComponentKeys() []string { + c.m.Lock() + defer c.m.Unlock() + + var res = make([]string, 0) + + for k := range c.l { + res = append(res, k) + } + + return res +} + +func (c *componentList) startOne(key string, getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error { + var cpt Component + + if !c.ComponentHas(key) { + return ErrorComponentNotFound.ErrorParent(fmt.Errorf("component: %s", key)) + } else if cpt = c.ComponentGet(key); cpt == nil { + return ErrorComponentNotFound.ErrorParent(fmt.Errorf("component: %s", key)) + } else if cpt.IsStarted() { + return nil + } + + if dep := cpt.Dependencies(); len(dep) > 0 { + for _, k := range dep { + if err := c.startOne(k, getCpt, getCfg); err != nil { + return err + } + } + } + + if err := cpt.Start(getCpt, getCfg); err != nil { + return err + } else { + c.ComponentSet(key, cpt) + } + + return nil +} + +func (c *componentList) ComponentStart(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error { + for _, key := range c.ComponentKeys() { + if err := c.startOne(key, getCpt, getCfg); err != nil { + return err + } + } + + return nil +} + +func (c *componentList) ComponentIsStarted() bool { + for _, k := range c.ComponentKeys() { + if cpt := c.ComponentGet(k); cpt == nil { + continue + } else if ok := cpt.IsStarted(); !ok { + return false + } + } + + return true +} + +func (c *componentList) reloadOne(isReload []string, key string, getCpt FuncComponentGet, getCfg FuncComponentConfigGet) ([]string, liberr.Error) { + var ( + err liberr.Error + cpt Component + ) + + if !c.ComponentHas(key) { + return isReload, ErrorComponentNotFound.ErrorParent(fmt.Errorf("component: %s", key)) + } else if cpt = c.ComponentGet(key); cpt == nil { + return isReload, ErrorComponentNotFound.ErrorParent(fmt.Errorf("component: %s", key)) + } else if stringIsInSlice(isReload, key) { + return isReload, nil + } + + if dep := cpt.Dependencies(); len(dep) > 0 { + for _, k := range dep { + if isReload, err = c.reloadOne(isReload, k, getCpt, getCfg); err != nil { + return isReload, err + } + } + } + + if err = cpt.Reload(getCpt, getCfg); err != nil { + return isReload, err + } else { + c.ComponentSet(key, cpt) + isReload = append(isReload, key) + } + + return isReload, nil +} + +func (c *componentList) ComponentReload(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error { + var ( + err liberr.Error + key string + + isReload = make([]string, 0) + ) + + for _, key = range c.ComponentKeys() { + if isReload, err = c.reloadOne(isReload, key, getCpt, getCfg); err != nil { + return err + } + } + + return nil +} + +func (c *componentList) ComponentStop() { + for _, key := range c.ComponentKeys() { + if !c.ComponentHas(key) { + continue + } + + cpt := c.ComponentGet(key) + if cpt == nil { + continue + } + + cpt.Stop() + } +} + +func (c *componentList) ComponentIsRunning(atLeast bool) bool { + for _, k := range c.ComponentKeys() { + if cpt := c.ComponentGet(k); cpt == nil { + continue + } else if ok := cpt.IsRunning(atLeast); !ok { + return false + } + } + + return true +} + +func (c *componentList) DefaultConfig() io.Reader { + var buffer = bytes.NewBuffer(make([]byte, 0)) + + buffer.WriteString("{") + buffer.WriteString("\n") + + n := buffer.Len() + + for _, k := range c.ComponentKeys() { + if cpt := c.ComponentGet(k); cpt == nil { + continue + } else if p := cpt.DefaultConfig(); len(p) > 0 { + if buffer.Len() > n { + buffer.WriteString(",") + } + buffer.WriteString(fmt.Sprintf(" \"%s\": ", k)) + buffer.Write(p) + } + } + + buffer.WriteString("\n") + buffer.WriteString("}") + + var res = bytes.NewBuffer(make([]byte, 0)) + if err := json.Indent(res, buffer.Bytes(), "", " "); err != nil { + return buffer + } + + return res +} diff --git a/config/errors.go b/config/errors.go new file mode 100644 index 0000000..18bbe43 --- /dev/null +++ b/config/errors.go @@ -0,0 +1,73 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package config + +import "github.com/nabbar/golib/errors" + +const ( + ErrorParamsEmpty errors.CodeError = iota + errors.MinPkgConfig + ErrorConfigMissingViper + ErrorComponentNotFound + ErrorComponentConfigNotFound + ErrorComponentConfigError +) + +const ( + MinErrorComponentHttp = ErrorParamsEmpty + 10 + MinErrorComponentLog = MinErrorComponentHttp + 10 + MinErrorComponentTls = MinErrorComponentLog + 10 +) + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func init() { + isCodeError = errors.ExistInMapMessage(ErrorParamsEmpty) + errors.RegisterIdFctMessage(ErrorParamsEmpty, getMessage) +} + +func getMessage(code errors.CodeError) (message string) { + switch code { + case errors.UNK_ERROR: + return "" + case ErrorParamsEmpty: + return "given parameters is empty" + case ErrorConfigMissingViper: + return "missing valid viper function" + case ErrorComponentNotFound: + return "component is not found" + case ErrorComponentConfigNotFound: + return "config keys for component is not found" + case ErrorComponentConfigError: + return "config for component is trigger an error" + } + + return "" +} diff --git a/config/interface.go b/config/interface.go new file mode 100644 index 0000000..c89e49d --- /dev/null +++ b/config/interface.go @@ -0,0 +1,158 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package config + +import ( + "context" + "os" + "os/signal" + "sync" + "syscall" + + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + libvpr "github.com/nabbar/golib/viper" +) + +type FuncContext func() context.Context +type FuncComponentGet func(key string) Component +type FuncComponentConfigGet func(model interface{}) liberr.Error + +type Config interface { + /* + // Section Context : github.com/nabbar/golib/context + */ + + // Context return the current context pointer + Context() context.Context + + // ContextMerge trigger the golib/context/config interface + // and will merge the stored context value into current context + ContextMerge(ctx libctx.Config) bool + + // ContextStore trigger the golib/context/config interface + // and will store a context value into current context + ContextStore(key string, cfg interface{}) + + // ContextLoad trigger the golib/context/config interface + // and will restore a context value or nil + ContextLoad(key string) interface{} + + // ContextSetCancel allow to register a custom function called on cancel context. + // On context cancel event or signal kill, term... this function will be called + // before config stop and main context cancel function + ContextSetCancel(fct func()) + + /* + // Section Event : github.com/nabbar/golib/config + */ + + RegisterFuncViper(fct func() libvpr.Viper) + + // Start will trigger the start function of all registered component. + // If any component return an error, this func will stop the start + // process and return the error. + Start() liberr.Error + + // RegisterFuncStartBefore allow to register a func to be call when the config Start + // is trigger. This func is call before the start sequence. + RegisterFuncStartBefore(fct func() liberr.Error) + + // RegisterFuncStartAfter allow to register a func to be call when the config Start + // is trigger. This func is call after the start sequence. + RegisterFuncStartAfter(fct func() liberr.Error) + + // Reload triggers the Reload function of each registered Component. + Reload() liberr.Error + + // RegisterFuncReloadBefore allow to register a func to be call when the config Reload + // is trigger. This func is call before the reload sequence. + RegisterFuncReloadBefore(fct func() liberr.Error) + + // RegisterFuncReloadAfter allow to register a func to be call when the config Reload + // is trigger. This func is call after the reload sequence. + RegisterFuncReloadAfter(fct func() liberr.Error) + + // Stop will trigger the stop function of all registered component. + // All component must stop cleanly. + Stop() + + // RegisterFuncStopBefore allow to register a func to be call when the config Stop + // is trigger. This func is call before the stop sequence. + RegisterFuncStopBefore(fct func()) + + // RegisterFuncStopAfter allow to register a func to be call when the config Stop + // is trigger. This func is call after the stop sequence. + RegisterFuncStopAfter(fct func()) + + /* + // Section Component : github.com/nabbar/golib/config + */ + ComponentList +} + +var ( + ctx context.Context + cnl context.CancelFunc +) + +func init() { + ctx, cnl = context.WithCancel(context.Background()) + + go func() { + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 5 seconds. + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT) + signal.Notify(quit, syscall.SIGTERM) + signal.Notify(quit, syscall.SIGQUIT) + + select { + case <-quit: + cnl() + case <-ctx.Done(): + cnl() + } + }() +} + +func New() Config { + c := &configModel{ + m: sync.Mutex{}, + ctx: libctx.NewConfig(ctx), + cpt: newComponentList(), + } + + go func() { + select { + case <-c.ctx.Done(): + c.cancel() + } + }() + + return c +} diff --git a/config/model.go b/config/model.go new file mode 100644 index 0000000..c12f13f --- /dev/null +++ b/config/model.go @@ -0,0 +1,318 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package config + +import ( + "context" + "fmt" + + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + libsts "github.com/nabbar/golib/status" + libvpr "github.com/nabbar/golib/viper" + spfvpr "github.com/spf13/viper" + + "io" + "sync" +) + +type configModel struct { + m sync.Mutex + + ctx libctx.Config + fcnl func() + + cpt ComponentList + + fctGolibViper func() libvpr.Viper + fctRouteStatus func() libsts.RouteStatus + fctStartBefore func() liberr.Error + fctStartAfter func() liberr.Error + fctReloadBefore func() liberr.Error + fctReloadAfter func() liberr.Error + fctStopBefore func() + fctStopAfter func() +} + +func (c *configModel) _ComponentGetConfig(key string, model interface{}) liberr.Error { + var ( + err error + vpr libvpr.Viper + vip *spfvpr.Viper + ) + + if c.cpt.ComponentHas(key) { + if c.fctGolibViper == nil { + return ErrorConfigMissingViper.Error(nil) + } else if vpr = c.fctGolibViper(); vpr == nil { + return ErrorConfigMissingViper.Error(nil) + } else if vip = vpr.Viper(); vip == nil { + return ErrorConfigMissingViper.Error(nil) + } + + err = vip.UnmarshalKey(key, model) + } else { + return ErrorComponentNotFound.ErrorParent(fmt.Errorf("component '%s'", key)) + } + + return ErrorComponentConfigError.Iferror(err) +} + +func (c *configModel) _FuncComponentGetConfig(key string) FuncComponentConfigGet { + return func(model interface{}) liberr.Error { + return c._ComponentGetConfig(key, model) + } +} + +func (c *configModel) Context() context.Context { + return c.ctx +} + +func (c *configModel) ContextMerge(ctx libctx.Config) bool { + return c.ctx.Merge(ctx) +} + +func (c *configModel) ContextStore(key string, cfg interface{}) { + c.ctx.Store(key, cfg) +} + +func (c *configModel) ContextLoad(key string) interface{} { + return c.ctx.Load(key) +} + +func (c *configModel) ContextSetCancel(fct func()) { + c.m.Lock() + defer c.m.Unlock() + + c.fcnl = fct +} + +func (c *configModel) cancel() { + c.cancelCustom() + c.Stop() +} + +func (c *configModel) cancelCustom() { + c.m.Lock() + defer c.m.Unlock() + + if c.fcnl != nil { + c.fcnl() + } +} + +func (c *configModel) RegisterFuncViper(fct func() libvpr.Viper) { + c.fctGolibViper = fct +} + +func (c *configModel) Start() liberr.Error { + c.m.Lock() + defer c.m.Unlock() + + var err liberr.Error + + if c.fctStartBefore != nil { + if err = c.fctStartBefore(); err != nil { + return err + } + } + + getCpt := func(key string) Component { + return c.ComponentGet(key) + } + + for _, k := range c.ComponentKeys() { + cpt := c.ComponentGet(k) + + if cpt == nil { + continue + } + + if err = cpt.Start(getCpt, c._FuncComponentGetConfig(k)); err != nil { + return err + } + } + + if c.fctStartAfter != nil { + if err = c.fctStartAfter(); err != nil { + return err + } + } + + return nil +} + +func (c *configModel) RegisterFuncStartBefore(fct func() liberr.Error) { + c.m.Lock() + defer c.m.Unlock() + c.fctStartBefore = fct +} + +func (c *configModel) RegisterFuncStartAfter(fct func() liberr.Error) { + c.m.Lock() + defer c.m.Unlock() + c.fctStartAfter = fct +} + +func (c *configModel) Reload() liberr.Error { + c.m.Lock() + defer c.m.Unlock() + + var err liberr.Error + + if c.fctReloadBefore != nil { + if err = c.fctReloadBefore(); err != nil { + return err + } + } + + getCpt := func(key string) Component { + return c.ComponentGet(key) + } + + for _, k := range c.ComponentKeys() { + cpt := c.ComponentGet(k) + + if cpt == nil { + continue + } + + if err = cpt.Reload(getCpt, c._FuncComponentGetConfig(k)); err != nil { + return err + } + } + + if c.fctReloadAfter != nil { + if err = c.fctReloadAfter(); err != nil { + return err + } + } + + return nil +} + +func (c *configModel) RegisterFuncReloadBefore(fct func() liberr.Error) { + c.m.Lock() + defer c.m.Unlock() + c.fctReloadBefore = fct +} + +func (c *configModel) RegisterFuncReloadAfter(fct func() liberr.Error) { + c.m.Lock() + defer c.m.Unlock() + c.fctReloadAfter = fct +} + +func (c *configModel) Stop() { + c.m.Lock() + defer c.m.Unlock() + + if c.fctStopBefore != nil { + c.fctStopBefore() + } + + for _, k := range c.ComponentKeys() { + cpt := c.ComponentGet(k) + + if cpt == nil { + continue + } + + cpt.Stop() + } + + if c.fctStopAfter != nil { + c.fctStopAfter() + } +} + +func (c *configModel) RegisterFuncStopBefore(fct func()) { + c.m.Lock() + defer c.m.Unlock() + c.fctStopBefore = fct +} + +func (c *configModel) RegisterFuncStopAfter(fct func()) { + c.m.Lock() + defer c.m.Unlock() + c.fctStopAfter = fct +} + +func (c *configModel) ComponentHas(key string) bool { + return c.cpt.ComponentHas(key) +} + +func (c *configModel) ComponentType(key string) string { + return c.cpt.ComponentType(key) +} + +func (c *configModel) ComponentGet(key string) Component { + return c.cpt.ComponentGet(key) +} + +func (c *configModel) ComponentDel(key string) { + c.cpt.ComponentDel(key) +} + +func (c *configModel) ComponentSet(key string, cpt Component) { + cpt.RegisterContext(c.Context) + cpt.RegisterGet(c.ComponentGet) + + c.cpt.ComponentSet(key, cpt) +} + +func (c *configModel) ComponentList() map[string]Component { + return c.cpt.ComponentList() +} + +func (c *configModel) ComponentKeys() []string { + return c.cpt.ComponentKeys() +} + +func (c *configModel) ComponentStart(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error { + return c.cpt.ComponentStart(getCpt, getCfg) +} + +func (c *configModel) ComponentIsStarted() bool { + return c.cpt.ComponentIsStarted() +} + +func (c *configModel) ComponentReload(getCpt FuncComponentGet, getCfg FuncComponentConfigGet) liberr.Error { + return c.cpt.ComponentReload(getCpt, getCfg) +} + +func (c *configModel) ComponentStop() { + c.cpt.ComponentStop() +} + +func (c *configModel) ComponentIsRunning(atLeast bool) bool { + return c.cpt.ComponentIsRunning(atLeast) +} + +func (c *configModel) DefaultConfig() io.Reader { + return c.cpt.DefaultConfig() +} diff --git a/config/tools.go b/config/tools.go new file mode 100644 index 0000000..60f6a6d --- /dev/null +++ b/config/tools.go @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package config + +func stringIsInSlice(list []string, key string) bool { + if len(list) < 1 { + return false + } + + for _, k := range list { + if k == key { + return true + } + } + + return false +} diff --git a/context/config.go b/context/config.go index 053a651..8656cef 100644 --- a/context/config.go +++ b/context/config.go @@ -26,6 +26,7 @@ package context import ( "context" + "sync" "sync/atomic" ) @@ -41,53 +42,49 @@ type Config interface { func NewConfig(ctx context.Context) Config { return &configContext{ Context: ctx, - cfg: new(atomic.Value), + cfg: make(map[string]*atomic.Value, 0), } } type configContext struct { context.Context - cfg *atomic.Value + m sync.Mutex + cfg map[string]*atomic.Value } -func (c configContext) getMap() map[string]*atomic.Value { +func (c configContext) Load(key string) interface{} { + c.m.Lock() + defer c.m.Unlock() + var ( - v interface{} - s map[string]*atomic.Value + i interface{} ok bool ) if c.cfg == nil { - c.cfg = new(atomic.Value) - } else if v = c.cfg.Load(); v == nil { - s = make(map[string]*atomic.Value) - - } else if s, ok = v.(map[string]*atomic.Value); !ok { - s = make(map[string]*atomic.Value) + c.cfg = make(map[string]*atomic.Value, 0) + } else if i, ok = c.cfg[key]; ok && i != nil { + return i } - return s + return nil } -func (c *configContext) Store(key string, cfg interface{}) { - s := c.getMap() +func (c configContext) Store(key string, cfg interface{}) { + c.m.Lock() + defer c.m.Unlock() - if _, ok := s[key]; !ok { - s[key] = &atomic.Value{} + var ok bool + + if c.cfg == nil { + c.cfg = make(map[string]*atomic.Value, 0) } - s[key].Store(cfg) - c.cfg.Store(s) -} - -func (c *configContext) Load(key string) interface{} { - s := c.getMap() - - if _, ok := s[key]; !ok { - return nil - } else { - return s[key].Load() + if _, ok = c.cfg[key]; !ok { + c.cfg[key] = new(atomic.Value) } + + c.cfg[key].Store(cfg) } func (c *configContext) Merge(cfg Config) bool { @@ -100,9 +97,10 @@ func (c *configContext) Merge(cfg Config) bool { return false } - s := c.getMap() + x.m.Lock() + defer x.m.Unlock() - for k, v := range x.getMap() { + for k, v := range x.cfg { if k == "" || v == nil { continue } @@ -110,22 +108,9 @@ func (c *configContext) Merge(cfg Config) bool { if i := v.Load(); i == nil { continue } else { - s[k] = &atomic.Value{} - s[k].Store(i) + c.Store(k, i) } } - c.cfg.Store(s) - return true } - -//Deprecated: use Store. -func (c *configContext) ObjectStore(key string, obj interface{}) { - c.Store(key, obj) -} - -//Deprecated: use Load. -func (c *configContext) ObjectLoad(key string) interface{} { - return c.Load(key) -} diff --git a/errors/modules.go b/errors/modules.go index ec941f3..1349334 100644 --- a/errors/modules.go +++ b/errors/modules.go @@ -31,26 +31,28 @@ const ( MinPkgArtifact = 200 MinPkgCertificate = 300 MinPkgCluster = 400 - MinPkgConsole = 500 - MinPkgCrypt = 600 - MinPkgHttpCli = 700 - MinPkgHttpServer = 800 - MinPkgIOUtils = 900 - MinPkgLDAP = 1000 - MinPkgLogger = 1100 - MinPkgMail = 1200 - MinPkgMailer = 1300 - MinPkgMailPooler = 1400 - MinPkgNetwork = 1500 - MinPkgNats = 1600 - MinPkgNutsDB = 1700 - MinPkgOAuth = 1800 - MinPkgAws = 1900 - MinPkgRouter = 2000 - MinPkgSemaphore = 2100 - MinPkgSMTP = 2200 - MinPkgStatic = 2300 - MinPkgVersion = 2400 + MinPkgConfig = 500 + MinPkgConsole = 600 + MinPkgCrypt = 700 + MinPkgHttpCli = 800 + MinPkgHttpServer = 900 + MinPkgIOUtils = 1000 + MinPkgLDAP = 1100 + MinPkgLogger = 1200 + MinPkgMail = 1300 + MinPkgMailer = 1400 + MinPkgMailPooler = 1500 + MinPkgNetwork = 1600 + MinPkgNats = 1700 + MinPkgNutsDB = 1800 + MinPkgOAuth = 1900 + MinPkgAws = 2000 + MinPkgRouter = 2100 + MinPkgSemaphore = 2200 + MinPkgSMTP = 2300 + MinPkgStatic = 2400 + MinPkgVersion = 2500 + MinPkgViper = 2600 MinAvailable = 4000 diff --git a/go.mod b/go.mod index 3b2b17e..96f7926 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.24.1 github.com/c-bata/go-prompt v0.2.6 github.com/fatih/color v1.13.0 + github.com/fsnotify/fsnotify v1.5.1 github.com/fxamacker/cbor/v2 v2.4.0 github.com/gin-gonic/gin v1.7.7 - github.com/go-ldap/ldap/v3 v3.4.1 + github.com/go-ldap/ldap/v3 v3.4.2 github.com/go-playground/validator/v10 v10.10.0 github.com/google/go-github/v33 v33.0.0 github.com/hashicorp/go-hclog v1.1.0 @@ -22,16 +23,20 @@ require ( github.com/lni/dragonboat/v3 v3.3.5 github.com/matcornic/hermes/v2 v2.1.0 github.com/mattn/go-colorable v0.1.12 + github.com/mitchellh/go-homedir v1.1.0 github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 github.com/nats-io/nats-server/v2 v2.7.2 github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d - github.com/onsi/ginkgo/v2 v2.1.1 + github.com/onsi/ginkgo/v2 v2.1.3 github.com/onsi/gomega v1.18.1 + github.com/pelletier/go-toml v1.9.4 github.com/shirou/gopsutil v3.21.11+incompatible github.com/sirupsen/logrus v1.8.1 + github.com/spf13/cobra v1.3.0 github.com/spf13/jwalterweatherman v1.1.0 + github.com/spf13/viper v1.10.1 github.com/vbauerster/mpb/v5 v5.4.0 - github.com/xanzy/go-gitlab v0.55.0 + github.com/xanzy/go-gitlab v0.55.1 github.com/xhit/go-simple-mail v2.2.2+incompatible github.com/xujiajun/nutsdb v0.6.0 github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b @@ -40,6 +45,7 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220209214540-3681064d5158 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 + gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -53,7 +59,7 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/aokoli/goutils v1.1.1 // indirect - github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect + github.com/armon/go-metrics v0.3.10 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.2.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4 // indirect @@ -76,7 +82,7 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/gogo/protobuf v1.3.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/google/btree v1.0.0 // indirect @@ -85,28 +91,32 @@ require ( github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-immutable-radix v1.0.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.3 // indirect - github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/hashicorp/go-sockaddr v1.0.0 // indirect - github.com/hashicorp/golang-lru v0.5.1 // indirect - github.com/hashicorp/memberlist v0.2.2 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/memberlist v0.3.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/ratelimit v1.0.2-0.20191002062651-f60b32039441 // indirect - github.com/klauspost/compress v1.14.2 // indirect + github.com/klauspost/compress v1.14.3 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lni/goutils v1.3.0 // indirect + github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-tty v0.0.4 // indirect - github.com/miekg/dns v1.1.26 // indirect + github.com/miekg/dns v1.1.41 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -119,8 +129,11 @@ require ( github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/spf13/afero v1.8.1 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect + github.com/subosito/gotenv v1.2.0 // indirect github.com/ugorji/go/codec v1.2.6 // indirect github.com/valyala/fastrand v1.0.0 // indirect github.com/valyala/histogram v1.0.1 // indirect @@ -129,11 +142,11 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xujiajun/mmap-go v1.0.1 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/crypto v0.0.0-20220209195652-db638375bc3a // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/exp v0.0.0-20200513190911-00229845015e // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect ) diff --git a/httpserver/config.go b/httpserver/config.go index 1632109..8a9d8b3 100644 --- a/httpserver/config.go +++ b/httpserver/config.go @@ -283,7 +283,7 @@ func (c *ServerConfig) SetParentContext(f func() context.Context) { c.getParentContext = f } -func (c ServerConfig) GetTLS() (libtls.TLSConfig, liberr.Error) { +func (c *ServerConfig) GetTLS() (libtls.TLSConfig, liberr.Error) { var def libtls.TLSConfig if c.TLS.InheritDefault && c.getTLSDefault != nil { @@ -293,7 +293,7 @@ func (c ServerConfig) GetTLS() (libtls.TLSConfig, liberr.Error) { return c.TLS.NewFrom(def) } -func (c ServerConfig) IsTLS() bool { +func (c *ServerConfig) IsTLS() bool { if ssl, err := c.GetTLS(); err == nil && ssl != nil && ssl.LenCertificatePair() > 0 { return true } @@ -301,7 +301,7 @@ func (c ServerConfig) IsTLS() bool { return false } -func (c ServerConfig) getContext() context.Context { +func (c *ServerConfig) getContext() context.Context { var ctx context.Context if c.getParentContext != nil { @@ -315,7 +315,7 @@ func (c ServerConfig) getContext() context.Context { return ctx } -func (c ServerConfig) GetListen() *url.URL { +func (c *ServerConfig) GetListen() *url.URL { var ( err error add *url.URL @@ -342,7 +342,7 @@ func (c ServerConfig) GetListen() *url.URL { return add } -func (c ServerConfig) GetExpose() *url.URL { +func (c *ServerConfig) GetExpose() *url.URL { var ( err error add *url.URL @@ -367,14 +367,18 @@ func (c ServerConfig) GetExpose() *url.URL { return add } -func (c ServerConfig) GetHandlerKey() string { +func (c *ServerConfig) GetHandlerKey() string { return c.HandlerKeys } -func (c ServerConfig) Validate() liberr.Error { +func (c *ServerConfig) Validate() liberr.Error { val := validator.New() err := val.Struct(c) + if err == nil { + return nil + } + if e, ok := err.(*validator.InvalidValidationError); ok { return ErrorServerValidate.ErrorParent(e) } @@ -394,6 +398,6 @@ func (c ServerConfig) Validate() liberr.Error { } -func (c ServerConfig) Server() Server { - return NewServer(&c) +func (c *ServerConfig) Server() Server { + return NewServer(c) } diff --git a/httpserver/run.go b/httpserver/run.go index 1ae7bf5..55a789a 100644 --- a/httpserver/run.go +++ b/httpserver/run.go @@ -36,6 +36,7 @@ import ( "net/http" "os" "os/signal" + "sync" "sync/atomic" "syscall" "time" @@ -48,6 +49,8 @@ import ( const _TimeoutWaitingPortFreeing = 500 * time.Microsecond type srvRun struct { + m sync.Mutex + log liblog.FuncLog // return golib logger interface err *atomic.Value // last err occured run *atomic.Value // is running @@ -70,6 +73,7 @@ type run interface { func newRun(log liblog.FuncLog) run { return &srvRun{ + m: sync.Mutex{}, log: log, err: new(atomic.Value), run: new(atomic.Value), @@ -270,7 +274,10 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error { } s.ctx, s.cnl = context.WithCancel(cfg.getContext()) + + s.m.Lock() s.srv = srv + s.m.Unlock() go func(ctx context.Context, cnl context.CancelFunc, name, host string, tlsMandatory bool) { var _log = s.getLogger() @@ -284,9 +291,14 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error { s.setRunning(false) }() + s.m.Lock() + if s.srv == nil { + return + } s.srv.BaseContext = func(listener net.Listener) context.Context { return s.ctx } + s.m.Unlock() var er error _log.Entry(liblog.InfoLevel, "Server is starting").Log() @@ -337,7 +349,11 @@ func (s *srvRun) srvShutdown() { cancel() if s.srv != nil { err := s.srv.Close() + + s.m.Lock() s.srv = nil + s.m.Unlock() + _log.Entry(liblog.ErrorLevel, "closing server").ErrorAdd(true, err).Check(liblog.InfoLevel) } }() diff --git a/version/license.go b/version/license.go index cc6e898..b911291 100644 --- a/version/license.go +++ b/version/license.go @@ -101,6 +101,35 @@ func (lic license) GetLicense() string { return "" } +func (lic license) GetLicenseName() string { + switch lic { + case License_Apache_v2: + return "Apache License - Version 2.0, January 2004" + case License_GNU_Affero_GPL_v3: + return "GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007" + case License_GNU_GPL_v3: + return "GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007" + case License_GNU_Lesser_GPL_v3: + return "GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007" + case License_MIT: + return "MIT License" + case License_Mozilla_PL_v2: + return "Mozilla Public License Version 2.0" + case License_Unlicense: + return "Free and unencumbered software" + case License_Creative_Common_Zero_v1: + return "Creative Commons - CC0 1.0 Universal" + case License_Creative_Common_Attribution_v4_int: + return "Creative Commons - Attribution 4.0 International" + case License_Creative_Common_Attribution_Share_Alike_v4_int: + return "Creative Commons - Attribution-ShareAlike 4.0 International" + case License_SIL_Open_Font_1_1: + return "SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007" + } + + return "" +} + func boiler_MIT(Year, Author string) string { return fmt.Sprintf(` MIT License diff --git a/version/version.go b/version/version.go index e6d41f6..51df535 100644 --- a/version/version.go +++ b/version/version.go @@ -55,6 +55,7 @@ type Version interface { GetAppId() string GetAuthor() string GetBuild() string + GetDate() string GetDescription() string GetHeader() string GetInfo() string @@ -63,6 +64,7 @@ type Version interface { GetPrefix() string GetRelease() string + GetLicenseName() string GetLicenseLegal(addMoreLicence ...license) string GetLicenseFull(addMoreLicence ...license) string GetLicenseBoiler(addMoreLicence ...license) string @@ -156,6 +158,19 @@ func (vers versionModel) GetHeader() string { return fmt.Sprintf("%s (%s)", vers.versionPackage, vers.GetInfo()) } +func (vers versionModel) GetDate() string { + var ( + err error + ts time.Time + ) + + if ts, err = time.Parse(time.RFC3339, vers.versionDate); err != nil { + ts = time.Time{} + } + + return ts.Format(time.RFC1123) +} + func (vers versionModel) GetBuild() string { return vers.versionBuild } @@ -176,6 +191,10 @@ func (vers versionModel) GetRelease() string { return vers.versionRelease } +func (vers versionModel) GetLicenseName() string { + return vers.licenceType.GetLicenseName() +} + func (vers versionModel) GetLicenseLegal(addMoreLicence ...license) string { if len(addMoreLicence) == 0 { return vers.licenceType.GetLicense() diff --git a/viper/cleaner.go b/viper/cleaner.go new file mode 100644 index 0000000..3d524ef --- /dev/null +++ b/viper/cleaner.go @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package viper + +import ( + "bytes" + "encoding/json" + "strings" + + spfvpr "github.com/spf13/viper" +) + +func (v *viper) Unset(key ...string) error { + if len(key) < 1 { + return nil + } + + configMap := v.v.AllSettings() + + for _, k := range key { + configMap = unsetSub(configMap, k) + } + + _v := &viper{ + v: spfvpr.New(), + base: v.base, + prfx: v.prfx, + deft: v.deft, + remote: v.remote, + } + + var ( + encodedConfig []byte + err error + ) + + if encodedConfig, err = json.MarshalIndent(configMap, "", " "); err != nil { + return err + } + + _v.v.SetConfigType("json") + + if err = _v.v.ReadConfig(bytes.NewReader(encodedConfig)); err != nil { + return err + } + + v.v = _v.v + + return nil +} + +func unsetSub(configMap map[string]interface{}, key string) map[string]interface{} { + kkey := strings.Split(key, ".") + + if len(kkey) == 1 { + delete(configMap, key) + return configMap + } + + for k, v := range configMap { + if k != kkey[0] { + continue + } + + configMap[k] = unsetSub(v.(map[string]interface{}), strings.Join(kkey[1:], ".")) + break + } + + return configMap +} diff --git a/viper/config.go b/viper/config.go new file mode 100644 index 0000000..667bfda --- /dev/null +++ b/viper/config.go @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package viper + +import ( + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" +) + +func (v *viper) Config(logLevelRemoteKO, logLevelRemoteOK liblog.Level) liberr.Error { + if err := v.initAddRemote(); err == nil { + v.initWatchRemote(logLevelRemoteKO, logLevelRemoteOK) + return nil + } else if !err.IsCodeError(ErrorParamMissing) { + return err + } + + err := v.v.ReadInConfig() + + if err == nil { + return nil + } + + if v.deft != nil { + v.v.SetConfigType("json") + if err = v.v.ReadConfig(v.deft()); err != nil { + return ErrorConfigReadDefault.ErrorParent(err) + } + + return ErrorConfigIsDefault.Error(nil) + } + + return ErrorConfigRead.ErrorParent(err) +} diff --git a/viper/errors.go b/viper/errors.go new file mode 100644 index 0000000..8d785da --- /dev/null +++ b/viper/errors.go @@ -0,0 +1,85 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package viper + +import ( + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgViper + ErrorParamMissing + ErrorHomePathNotFound + ErrorBasePathNotFound + ErrorRemoteProvider + ErrorRemoteProviderSecure + ErrorRemoteProviderRead + ErrorRemoteProviderMarshall + ErrorConfigRead + ErrorConfigReadDefault + ErrorConfigIsDefault +) + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func init() { + isCodeError = liberr.ExistInMapMessage(ErrorParamEmpty) + liberr.RegisterIdFctMessage(ErrorParamEmpty, getMessage) +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case liberr.UNK_ERROR: + return "" + case ErrorParamMissing: + return "at least one parameter is missing" + case ErrorHomePathNotFound: + return "cannot retrieve user home path" + case ErrorBasePathNotFound: + return "cannot retrieve base config path" + case ErrorRemoteProvider: + return "cannot define remote provider" + case ErrorRemoteProviderSecure: + return "cannot define secure remote provider" + case ErrorRemoteProviderRead: + return "cannot read config from remote provider" + case ErrorRemoteProviderMarshall: + return "cannot marshall config from remote provider" + case ErrorConfigRead: + return "cannot read config from file" + case ErrorConfigReadDefault: + return "cannot read default config" + case ErrorConfigIsDefault: + return "cannot read config, use default config" + } + + return "" +} diff --git a/viper/interface.go b/viper/interface.go new file mode 100644 index 0000000..7c758d3 --- /dev/null +++ b/viper/interface.go @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package viper + +import ( + "io" + + spfvpr "github.com/spf13/viper" + + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" +) + +type Viper interface { + SetRemoteProvider(provider string) + SetRemoteEndpoint(endpoint string) + SetRemotePath(path string) + SetRemoteSecureKey(key string) + SetRemoteModel(model interface{}) + SetRemoteReloadFunc(fct func()) + + SetHomeBaseName(base string) + SetEnvVarsPrefix(prefix string) + SetDefaultConfig(fct func() io.Reader) + SetConfigFile(fileConfig string) liberr.Error + + Config(logLevelRemoteKO, logLevelRemoteOK liblog.Level) liberr.Error + Viper() *spfvpr.Viper + WatchFS(logLevelFSInfo liblog.Level) + Unset(key ...string) error +} + +func New() Viper { + return &viper{ + v: spfvpr.New(), + } +} diff --git a/viper/model.go b/viper/model.go new file mode 100644 index 0000000..7092b54 --- /dev/null +++ b/viper/model.go @@ -0,0 +1,127 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package viper + +import ( + "fmt" + "io" + + libhom "github.com/mitchellh/go-homedir" + liberr "github.com/nabbar/golib/errors" + spfvpr "github.com/spf13/viper" +) + +const ( + RemoteETCD = "etcd" +) + +type viperRemote struct { + model interface{} + provider string + endpoint string + path string + secure string + fct func() +} + +type viper struct { + v *spfvpr.Viper + + base string + prfx string + deft func() io.Reader + + remote viperRemote +} + +func (v *viper) SetRemoteProvider(provider string) { + v.remote.provider = provider +} + +func (v *viper) SetRemoteEndpoint(endpoint string) { + v.remote.endpoint = endpoint +} + +func (v *viper) SetRemotePath(path string) { + v.remote.path = path +} + +func (v *viper) SetRemoteSecureKey(key string) { + v.remote.secure = key +} + +func (v *viper) SetRemoteModel(model interface{}) { + v.remote.model = model +} + +func (v *viper) SetRemoteReloadFunc(fct func()) { + v.remote.fct = fct +} + +func (v *viper) SetHomeBaseName(base string) { + v.base = base +} + +func (v *viper) SetEnvVarsPrefix(prefix string) { + v.prfx = prefix +} + +func (v *viper) SetDefaultConfig(fct func() io.Reader) { + v.deft = fct +} + +func (v *viper) SetConfigFile(fileConfig string) liberr.Error { + if fileConfig != "" { + v.v.SetConfigFile(fileConfig) + } else { + // Find home directory. + home, err := libhom.Dir() + if err != nil { + return ErrorHomePathNotFound.ErrorParent(err) + } + + // Search config in home directory with name defined in SetConfigName (without extension). + v.v.AddConfigPath(home) + + if v.base == "" { + return ErrorBasePathNotFound.ErrorParent(fmt.Errorf("base name of config file is empty")) + } + + v.v.SetConfigName("." + v.base) + + if v.prfx != "" { + v.v.SetEnvPrefix(v.prfx) + v.v.AutomaticEnv() + } + } + + return nil +} + +func (v *viper) Viper() *spfvpr.Viper { + return v.v +} diff --git a/viper/remote.go b/viper/remote.go new file mode 100644 index 0000000..95bae52 --- /dev/null +++ b/viper/remote.go @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package viper + +import ( + liberr "github.com/nabbar/golib/errors" +) + +func (v *viper) initAddRemote() liberr.Error { + if v.remote.provider == "" || v.remote.endpoint == "" || v.remote.path == "" { + return ErrorParamMissing.Error(nil) + } + + if v.remote.secure != "" { + if err := v.v.AddSecureRemoteProvider(v.remote.provider, v.remote.endpoint, v.remote.path, v.remote.secure); err != nil { + return ErrorRemoteProviderSecure.ErrorParent(err) + } + } else if err := v.v.AddRemoteProvider(v.remote.provider, v.remote.endpoint, v.remote.path); err != nil { + return ErrorRemoteProvider.ErrorParent(err) + } + + return v.initSetRemote() +} + +func (v *viper) initSetRemote() liberr.Error { + v.v.SetConfigType("json") + + if err := v.v.ReadRemoteConfig(); err != nil { + return ErrorRemoteProviderRead.ErrorParent(err) + } + + if err := v.v.Unmarshal(v.remote.model); err != nil { + return ErrorRemoteProviderMarshall.ErrorParent(err) + } + + return nil +} diff --git a/viper/watch.go b/viper/watch.go new file mode 100644 index 0000000..6c00ef4 --- /dev/null +++ b/viper/watch.go @@ -0,0 +1,74 @@ +/* + * MIT License + * + * Copyright (c) 2022 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package viper + +import ( + "time" + + libnot "github.com/fsnotify/fsnotify" + liblog "github.com/nabbar/golib/logger" +) + +func (v *viper) initWatchRemote(logLevelRemoteKO, logLevelRemoteOK liblog.Level) { + // open a goroutine to watch remote changes forever + go func() { + // unstopped for loop + for { + // delay after each request + time.Sleep(time.Second * 5) + + if v.remote.provider == RemoteETCD { + if logLevelRemoteKO.LogErrorCtxf(logLevelRemoteOK, "Remote config watching", v.v.WatchRemoteConfig()) { + // skip error and try next time + continue + } + } else { + // reading remote config + if logLevelRemoteKO.LogErrorCtxf(logLevelRemoteOK, "Remote config loading", v.v.ReadRemoteConfig()) { + // skip error and try next time + continue + } + } + + // add config model + if logLevelRemoteKO.LogErrorCtxf(logLevelRemoteOK, "Remote config parsing", v.v.Unmarshal(v.remote.model)) { + // skip error and try next time + continue + } + } + }() +} + +func (v *viper) WatchFS(logLevelFSInfo liblog.Level) { + v.v.WatchConfig() + v.v.OnConfigChange(func(e libnot.Event) { + if v.remote.fct != nil { + logLevelFSInfo.Logf("reloaded config file '%s'...", e.Name) + v.remote.fct() + } + }) +}