From 49db5e2afb244ddf8387e2bbdd431ec7125c590f Mon Sep 17 00:00:00 2001 From: Nicolas JUHEL Date: Sat, 12 Feb 2022 14:41:57 +0100 Subject: [PATCH] Add Package Config : - major dependancies are : - golib/context config - golib/viper - interface config extend : - golib/context config interface - component list - interface component list : this interface implement all function to manage a collection of component. All component are registred with they config key. A component must implement basic function like start, stop, reload, defaultConfig... The main config json is set by calling all component config with the config key attached Each component have some status status like isStarted, isRunning, ... Each component must also declare his dependencies with other component. As that when start or reload is called, the component is sure that dependencies are still started or reloaded. - They are 4 component for now : log, tls, headers and http server - The config package will start a new context / cancelfunc on init to be sure to stop cleanly all component and process Add Package Viper : - this package is an helper to the config package with the spf13 viper lib - this package can watch any change of a config file or can be connected to a remote config cluster like ETCD Add Package Cobra : - this package is an helper to make a CLI with flag / command - this package is based on spf13 cobra has all method to be connected to viper golib --- cobra/README.md | 242 +++++++++++++++++++ cobra/completion.go | 96 ++++++++ cobra/configure.go | 140 +++++++++++ cobra/interface.go | 74 ++++++ cobra/model.go | 172 ++++++++++++++ cobra/printError.go | 60 +++++ config/README.md | 119 ++++++++++ config/component.go | 89 +++++++ config/components/head/default.go | 45 ++++ config/components/head/errors.go | 71 ++++++ config/components/head/interface.go | 67 ++++++ config/components/head/model.go | 171 ++++++++++++++ config/components/http/default.go | 113 +++++++++ config/components/http/errors.go | 77 ++++++ config/components/http/interface.go | 87 +++++++ config/components/http/model.go | 306 ++++++++++++++++++++++++ config/components/log/default.go | 85 +++++++ config/components/log/errors.go | 71 ++++++ config/components/log/interface.go | 74 ++++++ config/components/log/model.go | 196 +++++++++++++++ config/components/tls/default.go | 83 +++++++ config/components/tls/errors.go | 71 ++++++ config/components/tls/interface.go | 66 ++++++ config/components/tls/model.go | 182 ++++++++++++++ config/cptList.go | 355 ++++++++++++++++++++++++++++ config/errors.go | 73 ++++++ config/interface.go | 158 +++++++++++++ config/model.go | 318 +++++++++++++++++++++++++ config/tools.go | 41 ++++ context/config.go | 71 +++--- errors/modules.go | 42 ++-- go.mod | 43 ++-- httpserver/config.go | 22 +- httpserver/run.go | 16 ++ version/license.go | 29 +++ version/version.go | 19 ++ viper/cleaner.go | 94 ++++++++ viper/config.go | 58 +++++ viper/errors.go | 85 +++++++ viper/interface.go | 61 +++++ viper/model.go | 127 ++++++++++ viper/remote.go | 61 +++++ viper/watch.go | 74 ++++++ 43 files changed, 4417 insertions(+), 87 deletions(-) create mode 100644 cobra/README.md create mode 100644 cobra/completion.go create mode 100644 cobra/configure.go create mode 100644 cobra/interface.go create mode 100644 cobra/model.go create mode 100644 cobra/printError.go create mode 100644 config/README.md create mode 100644 config/component.go create mode 100644 config/components/head/default.go create mode 100644 config/components/head/errors.go create mode 100644 config/components/head/interface.go create mode 100644 config/components/head/model.go create mode 100644 config/components/http/default.go create mode 100644 config/components/http/errors.go create mode 100644 config/components/http/interface.go create mode 100644 config/components/http/model.go create mode 100644 config/components/log/default.go create mode 100644 config/components/log/errors.go create mode 100644 config/components/log/interface.go create mode 100644 config/components/log/model.go create mode 100644 config/components/tls/default.go create mode 100644 config/components/tls/errors.go create mode 100644 config/components/tls/interface.go create mode 100644 config/components/tls/model.go create mode 100644 config/cptList.go create mode 100644 config/errors.go create mode 100644 config/interface.go create mode 100644 config/model.go create mode 100644 config/tools.go create mode 100644 viper/cleaner.go create mode 100644 viper/config.go create mode 100644 viper/errors.go create mode 100644 viper/interface.go create mode 100644 viper/model.go create mode 100644 viper/remote.go create mode 100644 viper/watch.go 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() + } + }) +}