diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 21b63a12..0f5c1f28 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -93,43 +93,21 @@ jobs: continue-on-error: false run: sudo apt-get install gcc-multilib gcc-mingw-w64 - - name: Test Build Linux/amd64 with suffix + - name: Test Build Linux/amd64 with suffix/CGO continue-on-error: false run: | - CGO_ENABLED=0 - GOOS=linux - GOARCH=amd64 - GOAMD64=v4 IGNORE_BUILD=$(sed '/^[[:space:]]*$/d' "build.$(go env | grep GOARCH | cut -d'=' -f2 | tr -d '"')" | tr '\n' '|') - go build -a -v -installsuffix cgo -ldflags "-w -s -extldflags '-static' " $(go list ./... | grep -vPi ${IGNORE_BUILD::-1}) - - - name: Test Build Linux/386 with suffix - continue-on-error: false - run: | - CGO_ENABLED=0 - GOOS=linux - GOARCH=386 - IGNORE_BUILD=$(sed '/^[[:space:]]*$/d' "build.$(go env | grep GOARCH | cut -d'=' -f2 | tr -d '"')" | tr '\n' '|') - go build -a -v -installsuffix cgo -ldflags "-w -s -extldflags '-static' " $(go list ./... | grep -vPi ${IGNORE_BUILD::-1}) + CGO_ENABLED=1 GOOS=linux GOARCH=amd64 GOAMD64=v4 go build -a -v -race -installsuffix cgo -ldflags "-w -s -extldflags '-static' " $(go list ./... | grep -vPi ${IGNORE_BUILD::-1}) - name: Test Build Windows/amd64 with CGO continue-on-error: false run: | - CC=/usr/bin/x86_64-w64-mingw32-gcc - CGO_ENABLED=1 - GOOS=windows - GOARCH=amd64 - GOAMD64=v4 IGNORE_BUILD=$(sed '/^[[:space:]]*$/d' "build.$(go env | grep GOARCH | cut -d'=' -f2 | tr -d '"')" | tr '\n' '|') - go build -a -v -ldflags "-w -s -extldflags '-static' " $(go list ./... | grep -vPi ${IGNORE_BUILD::-1}) + CC=/usr/bin/x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 GOAMD64=v4 go build -a -v -ldflags "-w -s -extldflags '-static' " $(go list ./... | grep -vPi ${IGNORE_BUILD::-1}) - - name: Test Build Windows/386 with CGO + - name: Test Build Darwin/arm64 with suffix/CGO continue-on-error: false run: | - CC=/usr/bin/i686-w64-mingw32-gcc - CGO_ENABLED=1 - GOOS=windows - GOARCH=386 IGNORE_BUILD=$(sed '/^[[:space:]]*$/d' "build.$(go env | grep GOARCH | cut -d'=' -f2 | tr -d '"')" | tr '\n' '|') - go build -a -v -ldflags "-w -s -extldflags '-static' " $(go list ./... | grep -vPi ${IGNORE_BUILD::-1}) + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 GOAMD64=v1 go build -a -v -ldflags "-w -s -extldflags '-static' " $(go list ./... | grep -vPi ${IGNORE_BUILD::-1}) diff --git a/aws/configAws/models.go b/aws/configAws/models.go index c20c0c3a..57c2fdbd 100644 --- a/aws/configAws/models.go +++ b/aws/configAws/models.go @@ -31,12 +31,12 @@ import ( "net" "net/url" - libsts "github.com/nabbar/golib/status/config" + moncfg "github.com/nabbar/golib/monitor/types" sdkaws "github.com/aws/aws-sdk-go-v2/aws" libval "github.com/go-playground/validator/v10" - "github.com/nabbar/golib/errors" - "github.com/nabbar/golib/httpcli" + liberr "github.com/nabbar/golib/errors" + libhtc "github.com/nabbar/golib/httpcli" ) type Model struct { @@ -47,8 +47,9 @@ type Model struct { } type ModelStatus struct { - Config Model `json:"config" yaml:"config" toml:"config" mapstructure:"config" validate:"required,dive"` - Status libsts.ConfigStatus `json:"status" yaml:"status" toml:"status" mapstructure:"status" validate:"required,dive"` + Config Model `json:"config" yaml:"config" toml:"config" mapstructure:"config" validate:"required,dive"` + HTTPClient libhtc.Options `json:"http-client" yaml:"http-client" toml:"http-client" mapstructure:"http-client" validate:"required,dive"` + Monitor moncfg.Config `json:"monitor" yaml:"monitor" toml:"monitor" mapstructure:"monitor" validate:"required,dive"` } type awsModel struct { @@ -56,7 +57,7 @@ type awsModel struct { retryer func() sdkaws.Retryer } -func (c *awsModel) Validate() errors.Error { +func (c *awsModel) Validate() liberr.Error { err := ErrorConfigValidator.Error(nil) if er := libval.New().Struct(c); er != nil { @@ -89,11 +90,11 @@ func (c *awsModel) SetCredentials(accessKey, secretKey string) { func (c *awsModel) ResetRegionEndpoint() { } -func (c *awsModel) RegisterRegionEndpoint(region string, endpoint *url.URL) errors.Error { +func (c *awsModel) RegisterRegionEndpoint(region string, endpoint *url.URL) liberr.Error { return nil } -func (c *awsModel) RegisterRegionAws(endpoint *url.URL) errors.Error { +func (c *awsModel) RegisterRegionAws(endpoint *url.URL) liberr.Error { return nil } @@ -108,7 +109,7 @@ func (c *awsModel) GetRegion() string { func (c *awsModel) SetEndpoint(endpoint *url.URL) { } -func (c awsModel) GetEndpoint() *url.URL { +func (c *awsModel) GetEndpoint() *url.URL { return nil } @@ -116,6 +117,18 @@ func (c *awsModel) ResolveEndpoint(service, region string) (sdkaws.Endpoint, err return sdkaws.Endpoint{}, ErrorEndpointInvalid.Error(nil) } +func (c *awsModel) ResolveEndpointWithOptions(service, region string, options ...interface{}) (sdkaws.Endpoint, error) { + return sdkaws.Endpoint{}, ErrorEndpointInvalid.Error(nil) +} + +func (c *awsModel) GetDisableHTTPS() bool { + return false +} + +func (c *awsModel) GetResolvedRegion() string { + return c.GetRegion() +} + func (c *awsModel) IsHTTPs() bool { return true } @@ -124,14 +137,14 @@ func (c *awsModel) SetRetryer(retryer func() sdkaws.Retryer) { c.retryer = retryer } -func (c awsModel) Check(ctx context.Context) errors.Error { +func (c *awsModel) Check(ctx context.Context) liberr.Error { var ( cfg *sdkaws.Config con net.Conn end sdkaws.Endpoint adr *url.URL err error - e errors.Error + e liberr.Error ) if cfg, e = c.GetConfig(ctx, nil); e != nil { @@ -142,7 +155,7 @@ func (c awsModel) Check(ctx context.Context) errors.Error { ctx = context.Background() } - if end, err = cfg.EndpointResolver.ResolveEndpoint("s3", c.GetRegion()); err != nil { + if end, err = cfg.EndpointResolverWithOptions.ResolveEndpoint("s3", c.GetRegion()); err != nil { return ErrorEndpointInvalid.ErrorParent(err) } @@ -155,8 +168,8 @@ func (c awsModel) Check(ctx context.Context) errors.Error { } d := net.Dialer{ - Timeout: httpcli.ClientTimeout5Sec, - KeepAlive: httpcli.ClientTimeout5Sec, + Timeout: libhtc.ClientTimeout5Sec, + KeepAlive: libhtc.ClientTimeout5Sec, } con, err = d.DialContext(ctx, "tcp", adr.Host) diff --git a/aws/configCustom/interface.go b/aws/configCustom/interface.go index 531c6a64..09ff8424 100644 --- a/aws/configCustom/interface.go +++ b/aws/configCustom/interface.go @@ -111,6 +111,7 @@ func (c *awsModel) GetConfig(ctx context.Context, cli *http.Client) (*sdkaws.Con cfg.Credentials = sdkcrd.NewStaticCredentialsProvider(c.AccessKey, c.SecretKey, "") cfg.Retryer = c.retryer cfg.EndpointResolver = sdkaws.EndpointResolverFunc(c.ResolveEndpoint) + cfg.EndpointResolverWithOptions = sdkaws.EndpointResolverWithOptionsFunc(c.ResolveEndpointWithOptions) cfg.Region = c.Region if cli != nil { diff --git a/aws/configCustom/models.go b/aws/configCustom/models.go index 662d7cf8..0036bd3d 100644 --- a/aws/configCustom/models.go +++ b/aws/configCustom/models.go @@ -32,12 +32,13 @@ import ( "net/url" "strings" + moncfg "github.com/nabbar/golib/monitor/types" + sdkaws "github.com/aws/aws-sdk-go-v2/aws" libval "github.com/go-playground/validator/v10" liberr "github.com/nabbar/golib/errors" libhtc "github.com/nabbar/golib/httpcli" liblog "github.com/nabbar/golib/logger" - libsts "github.com/nabbar/golib/status/config" ) type Model struct { @@ -49,8 +50,9 @@ type Model struct { } type ModelStatus struct { - Config Model `json:"config" yaml:"config" toml:"config" mapstructure:"config" validate:"required,dive"` - Status libsts.ConfigStatus `json:"status" yaml:"status" toml:"status" mapstructure:"status" validate:"required,dive"` + Config Model `json:"config" yaml:"config" toml:"config" mapstructure:"config" validate:"required,dive"` + HTTPClient libhtc.Options `json:"http-client" yaml:"http-client" toml:"http-client" mapstructure:"http-client" validate:"required,dive"` + Monitor moncfg.Config `json:"monitor" yaml:"monitor" toml:"monitor" mapstructure:"monitor" validate:"required,dive"` } type awsModel struct { @@ -220,11 +222,15 @@ func (c *awsModel) SetEndpoint(endpoint *url.URL) { c.Endpoint = strings.TrimSuffix(c.endpoint.String(), "/") } -func (c awsModel) GetEndpoint() *url.URL { +func (c *awsModel) GetEndpoint() *url.URL { return c.endpoint } func (c *awsModel) ResolveEndpoint(service, region string) (sdkaws.Endpoint, error) { + return c.ResolveEndpointWithOptions(service, region) +} + +func (c *awsModel) ResolveEndpointWithOptions(service, region string, options ...interface{}) (sdkaws.Endpoint, error) { if e, ok := c.mapRegion[region]; ok { return sdkaws.Endpoint{ URL: strings.TrimSuffix(e.String(), "/"), @@ -245,6 +251,14 @@ func (c *awsModel) ResolveEndpoint(service, region string) (sdkaws.Endpoint, err return sdkaws.Endpoint{}, ErrorEndpointInvalid.Error(nil) } +func (c *awsModel) GetDisableHTTPS() bool { + return false +} + +func (c *awsModel) GetResolvedRegion() string { + return c.GetRegion() +} + func (c *awsModel) IsHTTPs() bool { return strings.HasSuffix(strings.ToLower(c.endpoint.Scheme), "s") } @@ -253,7 +267,7 @@ func (c *awsModel) SetRetryer(retryer func() sdkaws.Retryer) { c.retryer = retryer } -func (c awsModel) Check(ctx context.Context) liberr.Error { +func (c *awsModel) Check(ctx context.Context) liberr.Error { var ( cfg *sdkaws.Config con net.Conn @@ -269,7 +283,7 @@ func (c awsModel) Check(ctx context.Context) liberr.Error { ctx = context.Background() } - if _, err = cfg.EndpointResolver.ResolveEndpoint("s3", c.GetRegion()); err != nil { + if _, err = cfg.EndpointResolverWithOptions.ResolveEndpoint("s3", c.GetRegion()); err != nil { return ErrorEndpointInvalid.ErrorParent(err) } diff --git a/aws/interface.go b/aws/interface.go index 3873cf48..96a82517 100644 --- a/aws/interface.go +++ b/aws/interface.go @@ -61,6 +61,9 @@ type Config interface { IsHTTPs() bool ResolveEndpoint(service, region string) (sdkaws.Endpoint, error) + ResolveEndpointWithOptions(service, region string, options ...interface{}) (sdkaws.Endpoint, error) + GetDisableHTTPS() bool + GetResolvedRegion() string SetRetryer(retryer func() sdkaws.Retryer) GetConfig(ctx context.Context, cli *http.Client) (*sdkaws.Config, liberr.Error) diff --git a/cache/expiration.go b/cache/expiration.go new file mode 100644 index 00000000..09c1c31b --- /dev/null +++ b/cache/expiration.go @@ -0,0 +1,27 @@ +/* + * 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 cache diff --git a/cache/interface.go b/cache/interface.go new file mode 100644 index 00000000..eaeea57c --- /dev/null +++ b/cache/interface.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 cache + +import ( + "context" + "sync" + "time" +) + +type FuncCache[T any] func() Cache[T] + +type Cache[T any] interface { + Clone(ctx context.Context, exp time.Duration) Cache[T] + Close() + Clean() + + Merge(c Cache[T]) + Walk(fct func(key any, val interface{}, exp time.Duration) bool) + + Load(key any) (val interface{}, exp time.Duration, ok bool) + Store(key any, val interface{}) time.Duration + Delete(key any) + + LoadOrStore(key any, val interface{}) (res interface{}, exp time.Duration, loaded bool) + LoadAndDelete(key any) (val interface{}, loaded bool) +} + +func New[T any](ctx context.Context, exp time.Duration) Cache[T] { + if ctx == nil { + ctx = context.Background() + } + + if exp < time.Microsecond { + return nil + } + + n := &cache[T]{ + Context: ctx, + w: sync.RWMutex{}, + m: sync.Map{}, + c: make(chan struct{}), + e: exp, + } + + go n.ticker(exp) + + return n +} diff --git a/cache/item.go b/cache/item.go new file mode 100644 index 00000000..c10a2380 --- /dev/null +++ b/cache/item.go @@ -0,0 +1,51 @@ +/* + * 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 cache + +import "time" + +type cacheItem struct { + t time.Time + v interface{} +} + +func store(val interface{}) *cacheItem { + return &cacheItem{ + t: time.Now(), + v: val, + } +} + +func parse(v any, exp time.Duration) (interface{}, time.Duration) { + if i, ok := v.(*cacheItem); !ok { + return nil, 0 + } else if e := exp - time.Since(i.t); e < time.Microsecond { + return nil, 0 + } else { + return i.v, e + } +} diff --git a/cache/model.go b/cache/model.go new file mode 100644 index 00000000..707f1d69 --- /dev/null +++ b/cache/model.go @@ -0,0 +1,218 @@ +/* + * 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 cache + +import ( + "context" + "sync" + "time" +) + +type cache[T any] struct { + context.Context + + w sync.RWMutex + m sync.Map + c chan struct{} + e time.Duration +} + +func (t *cache[T]) ticker(exp time.Duration) { + ticker := time.NewTicker(exp) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + t.expire() + + case <-t.Done(): + t.Clean() + return + + case <-t.c: + t.Clean() + return + } + } +} + +func (t *cache[T]) expire() { + t.w.RLock() + defer t.w.RUnlock() + + exp := t.e + + t.m.Range(func(key, value any) bool { + if v, _ := parse(value, exp); v == nil { + t.m.Delete(key) + } + return true + }) +} + +func (t *cache[T]) Clone(ctx context.Context, exp time.Duration) Cache[T] { + t.w.RLock() + defer t.w.RUnlock() + + if ctx == nil { + ctx = t.Context + } + + if exp < time.Microsecond { + exp = t.e + } + + n := &cache[T]{ + Context: ctx, + w: sync.RWMutex{}, + m: sync.Map{}, + c: make(chan struct{}), + e: exp, + } + + t.m.Range(func(key any, val interface{}) bool { + n.Store(key, val) + return true + }) + + go n.ticker(exp) + + return n +} + +func (t *cache[T]) Close() { + t.c <- struct{}{} +} + +func (t *cache[T]) Clean() { + t.w.Lock() + defer t.w.Unlock() + + t.m = sync.Map{} +} + +func (t *cache[T]) Merge(c Cache[T]) { + t.w.RLock() + defer t.w.RUnlock() + + c.Walk(func(key any, val interface{}, exp time.Duration) bool { + t.m.Store(key, &cacheItem{ + t: time.Now().Add(-exp), + v: nil, + }) + return true + }) +} + +func (t *cache[T]) Walk(fct func(key any, val interface{}, exp time.Duration) bool) { + t.w.RLock() + defer t.w.RUnlock() + + exp := t.e + + t.m.Range(func(key, value any) bool { + if v, e := parse(value, exp); v == nil { + t.m.Delete(key) + return true + } else { + return fct(key, v, e) + } + }) +} + +func (t *cache[T]) Load(key any) (val interface{}, exp time.Duration, ok bool) { + t.w.RLock() + defer t.w.RUnlock() + + var o any + + if o, ok = t.m.Load(key); !ok { + return nil, 0, false + } else if val, exp = parse(o, t.e); val == nil { + t.m.Delete(key) + return nil, 0, false + } else { + return val, exp, true + } +} + +func (t *cache[T]) Store(key any, val interface{}) time.Duration { + i := store(val) + e := time.Now() + + t.w.RLock() + defer t.w.RUnlock() + + t.m.Store(key, i) + return t.e - time.Since(e) +} + +func (t *cache[T]) Delete(key any) { + t.m.LoadAndDelete(key) +} + +func (t *cache[T]) LoadOrStore(key any, val interface{}) (res interface{}, exp time.Duration, loaded bool) { + if res, exp, loaded = t.Load(key); !loaded { + exp = t.Store(key, val) + return val, exp, false + } + + return res, exp, loaded +} + +func (t *cache[T]) LoadAndDelete(key any) (val interface{}, loaded bool) { + if val, _, loaded = t.Load(key); !loaded { + return nil, false + } + + t.w.RLock() + defer t.w.RUnlock() + + t.m.Delete(key) + return val, loaded +} + +func (t *cache[T]) Deadline() (deadline time.Time, ok bool) { + return t.Context.Deadline() +} + +func (t *cache[T]) Done() <-chan struct{} { + return t.Context.Done() +} + +func (t *cache[T]) Err() error { + return t.Context.Err() +} + +func (t *cache[T]) Value(key any) any { + if v, _, ok := t.Load(key); ok { + return v + } else { + return t.Context.Value(key) + } +} diff --git a/certificates/interface.go b/certificates/interface.go index 887ff883..3fd2d352 100644 --- a/certificates/interface.go +++ b/certificates/interface.go @@ -29,10 +29,14 @@ package certificates import ( "crypto/tls" "crypto/x509" + "net/http" liberr "github.com/nabbar/golib/errors" ) +type FctHttpClient func(def TLSConfig, servername string) *http.Client +type FctTLSDefault func() TLSConfig + type TLSConfig interface { AddRootCAString(rootCA string) bool AddRootCAFile(pemFile string) liberr.Error diff --git a/cobra/configure.go b/cobra/configure.go index d6c4c983..f33320ab 100644 --- a/cobra/configure.go +++ b/cobra/configure.go @@ -36,11 +36,10 @@ import ( "strings" "github.com/mitchellh/go-homedir" - "github.com/pelletier/go-toml" - "gopkg.in/yaml.v3" - liblog "github.com/nabbar/golib/logger" + "github.com/pelletier/go-toml" spfcbr "github.com/spf13/cobra" + "gopkg.in/yaml.v3" ) var cfgFile string diff --git a/config/components.go b/config/components.go new file mode 100644 index 00000000..7a62b8ea --- /dev/null +++ b/config/components.go @@ -0,0 +1,361 @@ +/* + * 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" + + _const "github.com/nabbar/golib/config/const" + + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + spfcbr "github.com/spf13/cobra" +) + +func (c *configModel) ComponentHas(key string) bool { + if i, l := c.cpt.Load(key); !l { + return false + } else { + _, k := i.(cfgtps.Component) + return k + } +} + +func (c *configModel) ComponentType(key string) string { + if v := c.ComponentGet(key); v == nil { + return "" + } else { + return v.Type() + } +} + +func (c *configModel) ComponentGet(key string) cfgtps.Component { + if i, l := c.cpt.Load(key); !l { + return nil + } else if v, k := i.(cfgtps.Component); !k { + return nil + } else { + return v + } +} + +func (c *configModel) ComponentDel(key string) { + c.cpt.Delete(key) +} + +func (c *configModel) ComponentSet(key string, cpt cfgtps.Component) { + cpt.Init(key, c.ctx.GetContext, c.ComponentGet, c.getViper, c.getVersion(), c.getDefaultLogger) + if f := c.getFctMonitorPool(); f != nil { + cpt.RegisterMonitorPool(f) + } else { + cpt.RegisterMonitorPool(c.getMonitorPool) + } + c.cpt.Store(key, cpt) +} + +func (c *configModel) ComponentList() map[string]cfgtps.Component { + var res = make(map[string]cfgtps.Component, 0) + c.cpt.Walk(func(key string, val interface{}) bool { + if v, k := val.(cfgtps.Component); k { + res[key] = v + } else { + c.cpt.Delete(key) + } + return true + }) + return res +} + +func (c *configModel) ComponentKeys() []string { + var res = make([]string, 0) + c.cpt.Walk(func(key string, val interface{}) bool { + if _, k := val.(cfgtps.Component); k { + res = append(res, key) + } else { + c.cpt.Delete(key) + } + return true + }) + return res +} + +func (c *configModel) ComponentStart() liberr.Error { + var err = ErrorComponentStart.Error(nil) + + c.cpt.Walk(func(key string, val interface{}) bool { + + if v, k := val.(cfgtps.Component); !k { + c.cpt.Delete(key) + } else if v == nil { + c.cpt.Delete(key) + } else if e := c.startComponent(key, v); e != nil { + err.AddParent(e) + } + + return true + }) + + if err.HasParent() { + return err + } + + return nil +} + +func (c *configModel) startComponent(key string, cpt cfgtps.Component) liberr.Error { + if cpt.IsStarted() { + return nil + } else if err := c.startDependencies(cpt.Dependencies()); err != nil { + return err + } else if err = cpt.Start(); err != nil { + return err + } else { + c.cpt.Store(key, cpt) + } + + return nil +} + +func (c *configModel) startDependencies(list []string) liberr.Error { + if len(list) < 1 { + return nil + } + + for _, d := range list { + if cpt := c.ComponentGet(d); cpt == nil { + return ErrorComponentNotFound.ErrorParent(fmt.Errorf("cvomponent '%s' not found", d)) + } else if cpt.IsStarted() { + continue + } else if err := c.startComponent(d, cpt); err != nil { + return err + } + } + + return nil +} + +func (c *configModel) ComponentIsStarted() bool { + isOk := false + + c.cpt.Walk(func(key string, val interface{}) bool { + if v, k := val.(cfgtps.Component); !k { + c.cpt.Delete(key) + } else if v == nil { + c.cpt.Delete(key) + } else if v.IsStarted() { + isOk = true + return false + } + + return true + }) + + return isOk +} + +func (c *configModel) ComponentReload() liberr.Error { + var err = ErrorComponentReload.Error(nil) + + c.cpt.Walk(func(key string, val interface{}) bool { + if v, k := val.(cfgtps.Component); !k { + c.cpt.Delete(key) + } else if v == nil { + c.cpt.Delete(key) + } else if e := c.reloadComponent(key, v); e != nil { + err.AddParent(e) + } + + return true + }) + + if err.HasParent() { + return err + } + + return nil +} + +func (c *configModel) reloadComponent(key string, cpt cfgtps.Component) liberr.Error { + if cpt.IsStarted() { + return nil + } else if err := c.reloadDependencies(cpt.Dependencies()); err != nil { + return err + } else if err = cpt.Start(); err != nil { + return err + } else { + c.cpt.Store(key, cpt) + } + + return nil +} + +func (c *configModel) reloadDependencies(list []string) liberr.Error { + if len(list) < 1 { + return nil + } + + for _, d := range list { + if cpt := c.ComponentGet(d); cpt == nil { + return ErrorComponentNotFound.ErrorParent(fmt.Errorf("cvomponent '%s' not found", d)) + } else if cpt.IsStarted() { + continue + } else if err := c.startComponent(d, cpt); err != nil { + return err + } + } + + return nil +} + +func (c *configModel) ComponentStop() { + c.cpt.Walk(func(key string, val interface{}) bool { + if v, k := val.(cfgtps.Component); !k { + c.cpt.Delete(key) + } else if v == nil { + c.cpt.Delete(key) + } else if v.IsStarted() { + v.Stop() + } + + return true + }) +} + +func (c *configModel) ComponentIsRunning(atLeast bool) bool { + isOk := false + + c.cpt.Walk(func(key string, val interface{}) bool { + v, k := val.(cfgtps.Component) + + if !k { + c.cpt.Delete(key) + return true + } else if v == nil { + c.cpt.Delete(key) + return true + } + + if v.IsRunning() { + isOk = true + } + + if atLeast && isOk { + return false + } else if !atLeast && !isOk { + return false + } + + return true + }) + + return isOk +} + +func (c *configModel) DefaultConfig() io.Reader { + var buffer = bytes.NewBuffer(make([]byte, 0)) + + buffer.WriteString("{") + buffer.WriteString("\n") + + n := buffer.Len() + + c.cpt.Walk(func(key string, val interface{}) bool { + v, k := val.(cfgtps.Component) + + if !k { + c.ComponentDel(key) + return true + } else if v == nil { + c.ComponentDel(key) + return true + } + + if p := v.DefaultConfig(_const.JSONIndent); len(p) > 0 { + if buffer.Len() > n { + buffer.WriteString(",") + buffer.WriteString("\n") + } + buffer.WriteString(fmt.Sprintf("%s\"%s\": ", _const.JSONIndent, key)) + buffer.Write(p) + } + + return true + }) + + buffer.WriteString("\n") + buffer.WriteString("}") + + var ( + cmp = bytes.NewBuffer(make([]byte, 0)) + ind = bytes.NewBuffer(make([]byte, 0)) + ) + + if err := json.Compact(cmp, buffer.Bytes()); err != nil { + return buffer + } else if err = json.Indent(ind, cmp.Bytes(), "", _const.JSONIndent); err != nil { + return buffer + } + + return ind +} + +func (c *configModel) RegisterFlag(Command *spfcbr.Command) error { + var err = ErrorComponentFlagError.Error(nil) + + for _, k := range c.ComponentKeys() { + if cpt := c.ComponentGet(k); cpt == nil { + continue + } else if e := cpt.RegisterFlag(Command); e != nil { + err.AddParent(e) + } else { + c.ComponentSet(k, cpt) + } + } + + if err.HasParent() { + return err + } + + return nil +} + +func (c *configModel) ComponentWalk(fct cfgtps.ComponentListWalkFunc) { + c.cpt.Walk(func(key string, val interface{}) bool { + if v, k := val.(cfgtps.Component); !k { + c.cpt.Delete(key) + return true + } else if v == nil { + c.cpt.Delete(key) + return true + } else { + return fct(key, v) + } + }) +} diff --git a/config/components/aws/client.go b/config/components/aws/client.go new file mode 100644 index 00000000..48e58860 --- /dev/null +++ b/config/components/aws/client.go @@ -0,0 +1,220 @@ +/* + * 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 aws + +import ( + "net/http" + + libaws "github.com/nabbar/golib/aws" + libtls "github.com/nabbar/golib/certificates" + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentAws) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentAws) _getTLS() libtls.TLSConfig { + o.m.RLock() + defer o.m.RUnlock() + + if o.t == nil { + return nil + } + + return o.t() +} + +func (o *componentAws) _getHttpClient() *http.Client { + o.m.RLock() + defer o.m.RUnlock() + + if o.c == nil { + return &http.Client{} + } + + return o.c() +} + +func (o *componentAws) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentAws) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentAws) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentAws) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentAws) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentAws) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentAws) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentAws) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentAws) _runCli() liberr.Error { + var prt = ErrorComponentReload + + if !o.IsStarted() { + prt = ErrorComponentStart + } + + if cfg, mon, htc, err := o._getConfig(); err != nil { + return prt.Error(err) + } else if cli, er := libaws.New(o.x.GetContext(), cfg, o._getHttpClient()); er != nil { + return prt.Error(er) + } else { + o.m.Lock() + o.a = cli + o.m.Unlock() + + if htc != nil { + o.RegisterHTTPClient(func() *http.Client { + if c, e := htc.GetClient(o._getTLS(), ""); e == nil { + return c + } + + return &http.Client{} + }) + } + + if e := o._registerMonitor(mon, cfg); e != nil { + return prt.ErrorParent(e) + } + } + + return nil +} + +func (o *componentAws) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/aws/component.go b/config/components/aws/component.go new file mode 100644 index 00000000..a059ef0c --- /dev/null +++ b/config/components/aws/component.go @@ -0,0 +1,155 @@ +/* + * 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 aws + +import ( + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "aws" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentAws) Type() string { + return ComponentType +} + +func (o *componentAws) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentAws) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentAws) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentAws) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o.a != nil +} + +func (o *componentAws) IsRunning() bool { + return o.IsStarted() +} + +func (o *componentAws) Start() liberr.Error { + return o._run() +} + +func (o *componentAws) Reload() liberr.Error { + return o._run() +} + +func (o *componentAws) Stop() { + o.m.Lock() + defer o.m.Unlock() + + o.a = nil + return +} + +func (o *componentAws) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = make([]string, 0) + + if o == nil { + return def + } else if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentAws) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentAws) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/aws/config.go b/config/components/aws/config.go new file mode 100644 index 00000000..adfb1b61 --- /dev/null +++ b/config/components/aws/config.go @@ -0,0 +1,236 @@ +/* + * 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 aws + +import ( + libaws "github.com/nabbar/golib/aws" + cfgstd "github.com/nabbar/golib/aws/configAws" + cfgcus "github.com/nabbar/golib/aws/configCustom" + liberr "github.com/nabbar/golib/errors" + libhtc "github.com/nabbar/golib/httpcli" + libmon "github.com/nabbar/golib/monitor/types" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +type _configFlag struct { + accessKey *string + secretKey *string + bucket *string + region *string + endpoint *string +} + +func (c *_configFlag) updCustom(cfg *cfgcus.Model) { + if c.accessKey != nil { + cfg.AccessKey = *c.accessKey + } + if c.secretKey != nil { + cfg.SecretKey = *c.secretKey + } + if c.bucket != nil { + cfg.Bucket = *c.bucket + } + if c.region != nil { + cfg.Region = *c.region + } + if c.endpoint != nil { + cfg.Endpoint = *c.endpoint + } +} + +func (c *_configFlag) updStandard(cfg *cfgstd.Model) { + if c.accessKey != nil { + cfg.AccessKey = *c.accessKey + } + if c.secretKey != nil { + cfg.SecretKey = *c.secretKey + } + if c.bucket != nil { + cfg.Bucket = *c.bucket + } + if c.region != nil { + cfg.Region = *c.region + } +} + +func (o *componentAws) RegisterFlag(Command *spfcbr.Command) error { + var ( + key = o._getKey() + vpr *spfvpr.Viper + ) + + if vpr = o._getSPFViper(); vpr == nil { + return ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } + + _ = Command.PersistentFlags().String(key+".access-key", "", "AWS Access Key") + _ = Command.PersistentFlags().String(key+".secret-key", "", "AWS Secret Key") + _ = Command.PersistentFlags().String(key+".bucket", "", "Bucket to use") + _ = Command.PersistentFlags().String(key+".region", "", "Region for bucket") + _ = Command.PersistentFlags().String(key+".endpoint", "", "Endpoint if necessary for the region") + + if err := vpr.BindPFlag(key+".access-key", Command.PersistentFlags().Lookup(key+".access-key")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".secret-key", Command.PersistentFlags().Lookup(key+".secret-key")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".bucket", Command.PersistentFlags().Lookup(key+".bucket")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".region", Command.PersistentFlags().Lookup(key+".region")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".endpoint", Command.PersistentFlags().Lookup(key+".endpoint")); err != nil { + return err + } + + return nil +} + +func (o *componentAws) _getConfig() (libaws.Config, *libmon.Config, *libhtc.Options, liberr.Error) { + var ( + key string + cfg libaws.Config + flg = o._getFlagUpdate() + mon *libmon.Config + htc *libhtc.Options + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, nil, nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, nil, nil, ErrorComponentNotInitialized.Error(nil) + } + + switch o.d { + case ConfigCustomStatus: + cnf := cfgcus.ModelStatus{} + if e := vpr.UnmarshalKey(key, &cnf); e != nil { + return nil, nil, nil, ErrorParamInvalid.ErrorParent(e) + } else { + flg.updCustom(&cnf.Config) + } + + if cfg, err = o.d.NewFromModel(cnf); err != nil { + return nil, nil, nil, err + } else { + mon = &cnf.Monitor + htc = &cnf.HTTPClient + } + + case ConfigCustom: + cnf := cfgcus.Model{} + if e := vpr.UnmarshalKey(key, &cnf); e != nil { + return nil, nil, nil, ErrorParamInvalid.ErrorParent(e) + } else { + flg.updCustom(&cnf) + } + + if cfg, err = o.d.NewFromModel(cnf); err != nil { + return nil, nil, nil, err + } else { + mon = nil + htc = nil + } + + case ConfigStandardStatus: + cnf := cfgstd.ModelStatus{} + if e := vpr.UnmarshalKey(key, &cnf); e != nil { + return nil, nil, nil, ErrorParamInvalid.ErrorParent(e) + } else { + flg.updStandard(&cnf.Config) + } + + if cfg, err = o.d.NewFromModel(cnf); err != nil { + return nil, nil, nil, err + } else { + mon = &cnf.Monitor + htc = &cnf.HTTPClient + } + + case ConfigStandard: + cnf := cfgstd.Model{} + if e := vpr.UnmarshalKey(key, &cnf); e != nil { + return nil, nil, nil, ErrorParamInvalid.ErrorParent(e) + } else { + flg.updStandard(&cnf) + } + + if cfg, err = o.d.NewFromModel(cnf); err != nil { + return nil, nil, nil, err + } else { + mon = nil + htc = nil + } + } + + if err = cfg.Validate(); err != nil { + return nil, nil, nil, ErrorConfigInvalid.Error(err) + } + + return cfg, mon, htc, nil +} + +func (o *componentAws) _getFlagUpdate() *_configFlag { + var ( + cfg = &_configFlag{ + accessKey: nil, + secretKey: nil, + bucket: nil, + region: nil, + endpoint: nil, + } + vpr *spfvpr.Viper + key string + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil + } else if key = o._getKey(); len(key) < 1 { + return nil + } + + if s := vpr.GetString(key + ".access-key"); s != "" { + cfg.accessKey = &s + } + if s := vpr.GetString(key + ".secret-key"); s != "" { + cfg.secretKey = &s + } + if s := vpr.GetString(key + ".bucket"); s != "" { + cfg.bucket = &s + } + if s := vpr.GetString(key + ".region"); s != "" { + cfg.region = &s + } + if s := vpr.GetString(key + ".endpoint"); s != "" { + cfg.endpoint = &s + } + + return cfg +} diff --git a/config/components/aws/default.go b/config/components/aws/default.go index bbbd1d33..c80b0da0 100644 --- a/config/components/aws/default.go +++ b/config/components/aws/default.go @@ -30,14 +30,9 @@ import ( "bytes" "encoding/json" - libaws "github.com/nabbar/golib/aws" - cfgstd "github.com/nabbar/golib/aws/configAws" - cfgcus "github.com/nabbar/golib/aws/configCustom" - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status/config" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgcst "github.com/nabbar/golib/config/const" + libhtc "github.com/nabbar/golib/httpcli" + montps "github.com/nabbar/golib/monitor/types" ) var _defaultConfigStandard = []byte(`{ @@ -49,12 +44,9 @@ var _defaultConfigStandard = []byte(`{ }`) var _defaultConfigStandardWithStatus = []byte(`{ - "bucket": "", - "accesskey": "", - "secretkey": "", - "region": "", - "endpoint": "", - "status":` + string(libsts.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` + "config":` + string(DefaultConfigStandard(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, + "http-client":` + string(libhtc.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, + "monitor":` + string(montps.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + ` }`) var _defaultConfigCustom = []byte(`{ @@ -66,12 +58,9 @@ var _defaultConfigCustom = []byte(`{ }`) var _defaultConfigCustomWithStatus = []byte(`{ - "bucket": "", - "accesskey": "", - "secretkey": "", - "region": "", - "endpoint": "", - "status":` + string(libsts.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` + "config":` + string(DefaultConfigCustom(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, + "http-client":` + string(libhtc.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, + "monitor":` + string(montps.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + ` }`) var _defaultConfig = _defaultConfigCustom @@ -96,145 +85,51 @@ func SetDefaultConfigCustom(withStatus bool) { } } -func DefaultConfig(indent string) []byte { +func DefaultConfigStandard(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfigStandard, indent, cfgcst.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentAws) DefaultConfig(indent string) []byte { - return DefaultConfig(indent) +func DefaultConfigStandardStatus(indent string) []byte { + var res = bytes.NewBuffer(make([]byte, 0)) + if err := json.Indent(res, _defaultConfigStandardWithStatus, indent, cfgcst.JSONIndent); err != nil { + return _defaultConfig + } else { + return res.Bytes() + } } -func (c *componentAws) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - _ = Command.PersistentFlags().String(c.key+".access-key", "", "AWS Access Key") - _ = Command.PersistentFlags().String(c.key+".secret-key", "", "AWS Secret Key") - _ = Command.PersistentFlags().String(c.key+".bucket", "", "Bucket to use") - _ = Command.PersistentFlags().String(c.key+".region", "", "Region for bucket") - _ = Command.PersistentFlags().String(c.key+".endpoint", "", "Endpoint if necessary for the region") - - if err := Viper.BindPFlag(c.key+".access-key", Command.PersistentFlags().Lookup(c.key+".access-key")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".secret-key", Command.PersistentFlags().Lookup(c.key+".secret-key")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".bucket", Command.PersistentFlags().Lookup(c.key+".bucket")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".region", Command.PersistentFlags().Lookup(c.key+".region")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".endpoint", Command.PersistentFlags().Lookup(c.key+".endpoint")); err != nil { - return err +func DefaultConfigCustom(indent string) []byte { + var res = bytes.NewBuffer(make([]byte, 0)) + if err := json.Indent(res, _defaultConfigCustom, indent, cfgcst.JSONIndent); err != nil { + return _defaultConfig + } else { + return res.Bytes() } - - return nil } -func (c *componentAws) _getConfig(getCfg libcfg.FuncComponentConfigGet) (libaws.Config, *libsts.ConfigStatus, liberr.Error) { - var ( - vpr = c.vpr() - cfg libaws.Config - sts *libsts.ConfigStatus - err liberr.Error - ) - - switch c.d { - case ConfigCustomStatus: - cnf := cfgcus.ModelStatus{} - if e := getCfg(c.key, &cnf); e != nil { - return nil, nil, ErrorParamInvalid.Error(e) - } - if s := vpr.GetString(c.key + ".access-key"); s != "" { - cnf.Config.AccessKey = s - } - if s := vpr.GetString(c.key + ".secret-key"); s != "" { - cnf.Config.SecretKey = s - } - if s := vpr.GetString(c.key + ".bucket"); s != "" { - cnf.Config.Bucket = s - } - if s := vpr.GetString(c.key + ".region"); s != "" { - cnf.Config.Region = s - } - if s := vpr.GetString(c.key + ".endpoint"); s != "" { - cnf.Config.Endpoint = s - } - if cfg, err = c.d.NewFromModel(cnf); err != nil { - return nil, nil, err - } else { - sts = &cnf.Status - } - case ConfigCustom: - cnf := cfgcus.Model{} - if e := getCfg(c.key, &cnf); e != nil { - return nil, nil, ErrorParamInvalid.Error(e) - } - if s := vpr.GetString(c.key + ".access-key"); s != "" { - cnf.AccessKey = s - } - if s := vpr.GetString(c.key + ".secret-key"); s != "" { - cnf.SecretKey = s - } - if s := vpr.GetString(c.key + ".bucket"); s != "" { - cnf.Bucket = s - } - if s := vpr.GetString(c.key + ".region"); s != "" { - cnf.Region = s - } - if s := vpr.GetString(c.key + ".endpoint"); s != "" { - cnf.Endpoint = s - } - if cfg, err = c.d.NewFromModel(cnf); err != nil { - return nil, nil, err - } - case ConfigStandardStatus: - cnf := cfgstd.ModelStatus{} - if e := getCfg(c.key, &cnf); e != nil { - return nil, nil, ErrorParamInvalid.Error(e) - } - if s := vpr.GetString(c.key + ".access-key"); s != "" { - cnf.Config.AccessKey = s - } - if s := vpr.GetString(c.key + ".secret-key"); s != "" { - cnf.Config.SecretKey = s - } - if s := vpr.GetString(c.key + ".bucket"); s != "" { - cnf.Config.Bucket = s - } - if s := vpr.GetString(c.key + ".region"); s != "" { - cnf.Config.Region = s - } - if cfg, err = c.d.NewFromModel(cnf); err != nil { - return nil, nil, err - } else { - sts = &cnf.Status - } - case ConfigStandard: - cnf := cfgstd.Model{} - if e := getCfg(c.key, &cnf); e != nil { - return nil, nil, ErrorParamInvalid.Error(e) - } - if s := vpr.GetString(c.key + ".access-key"); s != "" { - cnf.AccessKey = s - } - if s := vpr.GetString(c.key + ".secret-key"); s != "" { - cnf.SecretKey = s - } - if s := vpr.GetString(c.key + ".bucket"); s != "" { - cnf.Bucket = s - } - if s := vpr.GetString(c.key + ".region"); s != "" { - cnf.Region = s - } - if cfg, err = c.d.NewFromModel(cnf); err != nil { - return nil, nil, err - } +func DefaultConfigCustomStatus(indent string) []byte { + var res = bytes.NewBuffer(make([]byte, 0)) + if err := json.Indent(res, _defaultConfigCustomWithStatus, indent, cfgcst.JSONIndent); err != nil { + return _defaultConfig + } else { + return res.Bytes() } +} - if err = cfg.Validate(); err != nil { - return nil, nil, ErrorConfigInvalid.Error(err) +func DefaultConfig(indent string) []byte { + var res = bytes.NewBuffer(make([]byte, 0)) + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { + return _defaultConfig + } else { + return res.Bytes() } +} - return cfg, sts, nil +func (o *componentAws) DefaultConfig(indent string) []byte { + return DefaultConfig(indent) } diff --git a/config/components/aws/errors.go b/config/components/aws/errors.go index 710d9bce..c870e2f2 100644 --- a/config/components/aws/errors.go +++ b/config/components/aws/errors.go @@ -38,8 +38,8 @@ const ( ErrorParamInvalid ErrorComponentNotInitialized ErrorConfigInvalid - ErrorStartComponent - ErrorReloadComponent + ErrorComponentStart + ErrorComponentReload ErrorDependencyLogDefault ) @@ -60,9 +60,9 @@ func getMessage(code liberr.CodeError) (message string) { return "this component seems to not be correctly initialized" case ErrorConfigInvalid: return "invalid component config" - case ErrorStartComponent: + case ErrorComponentStart: return "cannot start component with config" - case ErrorReloadComponent: + case ErrorComponentReload: return "cannot reload component with new config" case ErrorDependencyLogDefault: return "cannot retrieve default Logger" diff --git a/config/components/aws/interface.go b/config/components/aws/interface.go index 2d38a516..5b1985bc 100644 --- a/config/components/aws/interface.go +++ b/config/components/aws/interface.go @@ -31,32 +31,36 @@ import ( "sync" libaws "github.com/nabbar/golib/aws" + libtls "github.com/nabbar/golib/certificates" libcfg "github.com/nabbar/golib/config" + cfgtps "github.com/nabbar/golib/config/types" liberr "github.com/nabbar/golib/errors" ) -const ( - ComponentType = "aws" -) +type ComponentAwsClient interface { + cfgtps.Component + RegisterHTTPClient(fct func() *http.Client) +} + +type ComponentAwsAPI interface { + cfgtps.Component + RegisterTLS(fct libtls.FctTLSDefault) +} type ComponentAws interface { - libcfg.Component - RegisterHTTPClient(fct func() *http.Client) + ComponentAwsClient + ComponentAwsAPI + GetAws() (libaws.AWS, liberr.Error) SetAws(a libaws.AWS) } func New(drv ConfigDriver) ComponentAws { return &componentAws{ - ctx: nil, - get: nil, - fsa: nil, - fsb: nil, - fra: nil, - frb: nil, - m: sync.Mutex{}, - d: drv, - a: nil, + m: sync.RWMutex{}, + x: nil, + d: drv, + a: nil, } } @@ -68,7 +72,7 @@ func RegisterNew(cfg libcfg.Config, drv ConfigDriver, key string) { cfg.ComponentSet(key, New(drv)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentAws { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentAws { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentAws); !ok { diff --git a/config/components/aws/model.go b/config/components/aws/model.go index f930a479..cd34a0dc 100644 --- a/config/components/aws/model.go +++ b/config/components/aws/model.go @@ -31,177 +31,50 @@ import ( "sync" libaws "github.com/nabbar/golib/aws" - libcfg "github.com/nabbar/golib/config" + libtls "github.com/nabbar/golib/certificates" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" - stscfg "github.com/nabbar/golib/status/config" + montps "github.com/nabbar/golib/monitor/types" ) type componentAws struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - sts libcfg.FuncRouteStatus - - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - fsi libsts.FctInfo - fsh libsts.FctHealth - - m sync.Mutex + m sync.RWMutex + x libctx.Config[uint8] d ConfigDriver + p montps.FuncPool c func() *http.Client + t libtls.FctTLSDefault a libaws.AWS - s stscfg.ConfigStatus -} - -func (c *componentAws) _getHttpClient() *http.Client { - if c.c == nil { - return &http.Client{} - } - - return c.c() -} - -func (c *componentAws) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.a != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } } -func (c *componentAws) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentAws) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - if cfg, sts, err := c._getConfig(getCfg); err != nil { - return err - } else if cli, er := libaws.New(c.ctx(), cfg, c._getHttpClient()); er != nil { - return er - } else { - c.a = cli - - if sts != nil && c.sts != nil && c.fsi != nil && c.fsh != nil { - sts.RegisterStatus(c.sts(), c.key, c.fsi, c.fsh) - } - } - - return nil -} - -func (c *componentAws) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentAws) Type() string { - return ComponentType -} - -func (c *componentAws) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentAws) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentAws) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentAws) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c.a != nil -} - -func (c *componentAws) IsRunning(atLeast bool) bool { - return c.IsStarted() -} - -func (c *componentAws) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentAws) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentAws) Stop() { - c.m.Lock() - defer c.m.Unlock() - - c.a = nil - return -} +func (o *componentAws) RegisterHTTPClient(fct func() *http.Client) { + o.m.Lock() + defer o.m.Unlock() -func (c *componentAws) Dependencies() []string { - return make([]string, 0) + o.c = fct } -func (c *componentAws) RegisterHTTPClient(fct func() *http.Client) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentAws) RegisterTLS(fct libtls.FctTLSDefault) { + o.m.Lock() + defer o.m.Unlock() - c.c = fct + o.t = fct } -func (c *componentAws) GetAws() (libaws.AWS, liberr.Error) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentAws) GetAws() (libaws.AWS, liberr.Error) { + o.m.RLock() + defer o.m.RUnlock() - if c.a == nil { + if o.a == nil { return nil, ErrorComponentNotInitialized.Error(nil) } else { - return c.a.Clone(c.ctx()) + return o.a.Clone(o.x.GetContext()) } } -func (c *componentAws) SetAws(a libaws.AWS) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentAws) SetAws(a libaws.AWS) { + o.m.Lock() + defer o.m.Unlock() - c.a = a + o.a = a } diff --git a/config/components/aws/monitor.go b/config/components/aws/monitor.go new file mode 100644 index 00000000..7d4dd442 --- /dev/null +++ b/config/components/aws/monitor.go @@ -0,0 +1,178 @@ +/* + * 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 aws + +import ( + "context" + "fmt" + "runtime" + + libaws "github.com/nabbar/golib/aws" + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" +) + +const ( + defaultNameMonitor = "AWS Client" +) + +func (o *componentAws) RegisterMonitorPool(fct montps.FuncPool) { + o.m.Lock() + defer o.m.Unlock() + + o.p = fct +} + +func (o *componentAws) _getMonitorPool() montps.Pool { + o.m.RLock() + defer o.m.RUnlock() + + if o.p == nil { + return nil + } else if p := o.p(); p == nil { + return nil + } else { + return p + } +} + +func (o *componentAws) _registerMonitor(cfg *montps.Config, aws libaws.Config) error { + var ( + e error + key = o._getKey() + inf moninf.Info + mon montps.Monitor + res = make(map[string]interface{}, 0) + vrs = o._getVersion() + ) + + if o._getMonitorPool() == nil { + return nil + } else if len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } else if cfg == nil { + return nil + } else if aws == nil { + return ErrorConfigInvalid.Error(nil) + } + + res["runtime"] = runtime.Version()[2:] + + if vrs != nil { + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + res["endpoint"] = aws.GetEndpoint().Host + res["region"] = aws.GetRegion() + } + + if inf, e = moninf.New(defaultNameMonitor); e != nil { + return e + } else if vrs != nil { + inf.RegisterName(func() (string, error) { + return fmt.Sprintf("%s %s", defaultNameMonitor, o._getKey()), nil + }) + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon = o._getMonitor(key, inf); mon == nil { + if mon, e = o._newMonitor(inf); e != nil { + return e + } else if mon == nil { + return nil + } + } + + if e = mon.SetConfig(o.x.GetContext, *cfg); e != nil { + return e + } + + mon.SetHealthCheck(o.HealthCheck) + + if e = mon.Restart(o.x.GetContext()); e != nil { + return e + } else if e = o._setMonitor(mon); e != nil { + return e + } + + return nil +} + +func (o *componentAws) _newMonitor(inf montps.Info) (montps.Monitor, error) { + if c, e := libmon.New(o.x.GetContext, inf); e != nil { + return nil, e + } else if c != nil { + c.RegisterLoggerDefault(o.getLogger) + return c, nil + } else { + return c, nil + } +} + +func (o *componentAws) _getMonitor(key string, inf montps.Info) montps.Monitor { + var ( + mon libmon.Monitor + pol = o._getMonitorPool() + ) + + if pol == nil { + return nil + } + + mon = pol.MonitorGet(key) + + if mon != nil { + mon.InfoUpd(inf) + mon.RegisterLoggerDefault(o.getLogger) + } + + return mon +} + +func (o *componentAws) _setMonitor(mon montps.Monitor) error { + var pol = o._getMonitorPool() + + if pol == nil { + return nil + } + + return pol.MonitorSet(mon) +} + +func (o *componentAws) HealthCheck(ctx context.Context) error { + o.m.RLock() + defer o.m.RUnlock() + + if !o.IsStarted() { + return fmt.Errorf("component not started") + } else { + return o.a.Config().Check(ctx) + } +} diff --git a/config/components/database/client.go b/config/components/database/client.go new file mode 100644 index 00000000..c0040e8d --- /dev/null +++ b/config/components/database/client.go @@ -0,0 +1,203 @@ +/* + * 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 database + +import ( + "context" + + cfgtps "github.com/nabbar/golib/config/types" + libdbs "github.com/nabbar/golib/database" + liberr "github.com/nabbar/golib/errors" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentDatabase) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentDatabase) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentDatabase) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentDatabase) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentDatabase) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentDatabase) _getContext() context.Context { + o.m.RLock() + defer o.m.RUnlock() + + return o.x.GetContext() +} + +func (o *componentDatabase) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentDatabase) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentDatabase) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentDatabase) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentDatabase) _runCli() liberr.Error { + var ( + err liberr.Error + prt = ErrorComponentReload + dbo libdbs.Database + cfg *libdbs.Config + ) + + if !o.IsStarted() { + prt = ErrorComponentStart + } + + if cfg, err = o._getConfig(); err != nil { + return prt.Error(err) + } + + if dbo, err = libdbs.New(cfg); err != nil { + return prt.Error(err) + } + + o.Stop() + + o.m.Lock() + o.d = dbo + o.m.Unlock() + + if e := o._registerMonitor(cfg); e != nil { + return prt.ErrorParent(e) + } + + return nil +} + +func (o *componentDatabase) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/database/component.go b/config/components/database/component.go new file mode 100644 index 00000000..09e4f461 --- /dev/null +++ b/config/components/database/component.go @@ -0,0 +1,168 @@ +/* + * 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 database + +import ( + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "database" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentDatabase) Type() string { + return ComponentType +} + +func (o *componentDatabase) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentDatabase) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentDatabase) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentDatabase) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o != nil && o.d != nil +} + +func (o *componentDatabase) IsRunning() bool { + if !o.IsStarted() { + return false + } else if db := o.GetDatabase(); db == nil { + return false + } else if e := db.CheckConn(); e != nil { + return false + } + + return true +} + +func (o *componentDatabase) Start() liberr.Error { + return o._run() +} + +func (o *componentDatabase) Reload() liberr.Error { + return o._run() +} + +func (o *componentDatabase) Stop() { + o.m.Lock() + defer o.m.Unlock() + + if o.d != nil { + o.d.Close() + } + + o.d = nil + + return +} + +func (o *componentDatabase) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = make([]string, 0) + + if o == nil { + return def + } else if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentDatabase) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentDatabase) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/database/config.go b/config/components/database/config.go new file mode 100644 index 00000000..e21c0eb9 --- /dev/null +++ b/config/components/database/config.go @@ -0,0 +1,191 @@ +/* + * 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 database + +import ( + "time" + + libdbs "github.com/nabbar/golib/database" + liberr "github.com/nabbar/golib/errors" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentDatabase) RegisterFlag(Command *spfcbr.Command) error { + var ( + key string + vpr *spfvpr.Viper + ) + + if vpr = o._getSPFViper(); vpr == nil { + return ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } + + _ = Command.PersistentFlags().String(key+".driver", "", "driver to use for the DSN. It must be one of mysql, psql, sqlite, sqlserver, clickhouse.") + _ = Command.PersistentFlags().String(key+".name", "", "string to identify the instance into status.") + _ = Command.PersistentFlags().String(key+".dsn", "", "string options to connect to database following database engine. See https://gorm.io/docs/connecting_to_the_database.html for more information.") + _ = Command.PersistentFlags().Bool(key+".skip-default-transaction", false, "disable the default transaction for single create, update, delete operations.") + _ = Command.PersistentFlags().Bool(key+".full-save-associations", false, "full save associations") + _ = Command.PersistentFlags().Bool(key+".dry-run", false, "generate sql without execute") + _ = Command.PersistentFlags().Bool(key+".prepare-stmt", false, "executes the given query in cached statement") + _ = Command.PersistentFlags().Bool(key+".disable-automatic-ping", false, "is used to disable the automatic ping to the database server") + _ = Command.PersistentFlags().Bool(key+".disable-foreign-key-constraint-when-migrating", false, "is used to disable the foreign key constraint when migrating the database") + _ = Command.PersistentFlags().Bool(key+".disable-nested-transaction", false, "disable nested transaction") + _ = Command.PersistentFlags().Bool(key+".allow-global-update", false, "allow global update") + _ = Command.PersistentFlags().Bool(key+".query-fields", false, "executes the SQL query with all fields of the table") + _ = Command.PersistentFlags().Int(key+".create-batch-size", 0, "default create batch size") + _ = Command.PersistentFlags().Bool(key+".enable-connection-pool", false, "is used to create a connection pool") + _ = Command.PersistentFlags().Int(key+".pool-max-idle-conns", 0, "sets the maximum number of connections idle in the connection pool if enable") + _ = Command.PersistentFlags().Int(key+".pool-max-open-conns", 0, "sets the maximum number of connections open in the connection pool if enable") + _ = Command.PersistentFlags().Duration(key+".pool-conn-max-lifetime", 5*time.Minute, "sets the maximum lifetime of connections in the connection pool if enable") + _ = Command.PersistentFlags().Bool(key+".disabled", false, "allow to disable a database connection without clean his configuration") + + if err := vpr.BindPFlag(key+".driver", Command.PersistentFlags().Lookup(key+".driver")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".name", Command.PersistentFlags().Lookup(key+".name")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".dsn", Command.PersistentFlags().Lookup(key+".dsn")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".skip-default-transaction", Command.PersistentFlags().Lookup(key+".skip-default-transaction")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".full-save-associations", Command.PersistentFlags().Lookup(key+".full-save-associations")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".dry-run", Command.PersistentFlags().Lookup(key+".dry-run")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".prepare-stmt", Command.PersistentFlags().Lookup(key+".prepare-stmt")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".disable-automatic-ping", Command.PersistentFlags().Lookup(key+".disable-automatic-ping")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".disable-foreign-key-constraint-when-migrating", Command.PersistentFlags().Lookup(key+".disable-foreign-key-constraint-when-migrating")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".disable-nested-transaction", Command.PersistentFlags().Lookup(key+".disable-nested-transaction")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".allow-global-update", Command.PersistentFlags().Lookup(key+".allow-global-update")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".query-fields", Command.PersistentFlags().Lookup(key+".query-fields")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".create-batch-size", Command.PersistentFlags().Lookup(key+".create-batch-size")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".enable-connection-pool", Command.PersistentFlags().Lookup(key+".enable-connection-pool")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".pool-max-idle-conns", Command.PersistentFlags().Lookup(key+".pool-max-idle-conns")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".pool-max-open-conns", Command.PersistentFlags().Lookup(key+".pool-max-open-conns")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".pool-conn-max-lifetime", Command.PersistentFlags().Lookup(key+".pool-conn-max-lifetime")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".disabled", Command.PersistentFlags().Lookup(key+".disabled")); err != nil { + return err + } + + return nil +} + +func (o *componentDatabase) _getConfig() (*libdbs.Config, liberr.Error) { + var ( + key string + cfg libdbs.Config + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } + + cfg.RegisterLogger(o.getLogger, o.li, o.ls) + cfg.RegisterContext(o.x.GetContext) + + if val := vpr.GetString(key + ".driver"); val != "" { + cfg.Driver = libdbs.DriverFromString(val) + } + if val := vpr.GetString(key + ".name"); val != "" { + cfg.Name = val + } + if val := vpr.GetString(key + ".dsn"); val != "" { + cfg.DSN = val + } + if val := vpr.GetBool(key + ".skip-default-transaction"); val { + cfg.SkipDefaultTransaction = true + } + if val := vpr.GetBool(key + ".full-save-associations"); val { + cfg.FullSaveAssociations = true + } + if val := vpr.GetBool(key + ".dry-run"); val { + cfg.DryRun = true + } + if val := vpr.GetBool(key + ".prepare-stmt"); val { + cfg.PrepareStmt = true + } + if val := vpr.GetBool(key + ".disable-automatic-ping"); val { + cfg.DisableAutomaticPing = true + } + if val := vpr.GetBool(key + ".disable-foreign-key-constraint-when-migrating"); val { + cfg.DisableForeignKeyConstraintWhenMigrating = true + } + if val := vpr.GetBool(key + ".disable-nested-transaction"); val { + cfg.DisableNestedTransaction = true + } + if val := vpr.GetBool(key + ".allow-global-update"); val { + cfg.AllowGlobalUpdate = true + } + if val := vpr.GetBool(key + ".query-fields"); val { + cfg.QueryFields = true + } + if val := vpr.GetInt(key + ".create-batch-size"); val != 0 { + cfg.CreateBatchSize = val + } + if val := vpr.GetBool(key + ".enable-connection-pool"); val { + cfg.EnableConnectionPool = true + } + if val := vpr.GetInt(key + ".pool-max-idle-conns"); val != 0 { + cfg.PoolMaxIdleConns = val + } + if val := vpr.GetInt(key + ".pool-max-open-conns"); val != 0 { + cfg.PoolMaxOpenConns = val + } + if val := vpr.GetDuration(key + ".pool-conn-max-lifetime"); val != 0 { + cfg.PoolConnMaxLifetime = val + } + if val := vpr.GetBool(key + ".disabled"); val { + cfg.Disabled = true + } + + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + return &cfg, nil +} diff --git a/config/components/database/default.go b/config/components/database/default.go index 5fc1b520..a2b7f0ad 100644 --- a/config/components/database/default.go +++ b/config/components/database/default.go @@ -29,15 +29,9 @@ package database import ( "bytes" "encoding/json" - "time" - libcfg "github.com/nabbar/golib/config" - libdbs "github.com/nabbar/golib/database" - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" - libsts "github.com/nabbar/golib/status/config" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgcst "github.com/nabbar/golib/config/const" + moncfg "github.com/nabbar/golib/monitor/types" ) var _defaultConfig = []byte(`{ @@ -59,7 +53,7 @@ var _defaultConfig = []byte(`{ "pool-max-open-conns": 0, "pool-conn-max-lifetime": "0s", "disabled": false, - "status": ` + string(libsts.DefaultConfig(libcfg.JSONIndent)) + ` + "monitor": ` + string(moncfg.DefaultConfig(cfgcst.JSONIndent)) + ` }`) func SetDefaultConfig(cfg []byte) { @@ -68,163 +62,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentDatabase) DefaultConfig(indent string) []byte { +func (o *componentDatabase) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentDatabase) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - _ = Command.PersistentFlags().String(c.key+".driver", "", "driver to use for the DSN. It must be one of mysql, psql, sqlite, sqlserver, clickhouse.") - _ = Command.PersistentFlags().String(c.key+".name", "", "string to identify the instance into status.") - _ = Command.PersistentFlags().String(c.key+".dsn", "", "string options to connect to database following database engine. See https://gorm.io/docs/connecting_to_the_database.html for more information.") - _ = Command.PersistentFlags().Bool(c.key+".skip-default-transaction", false, "disable the default transaction for single create, update, delete operations.") - _ = Command.PersistentFlags().Bool(c.key+".full-save-associations", false, "full save associations") - _ = Command.PersistentFlags().Bool(c.key+".dry-run", false, "generate sql without execute") - _ = Command.PersistentFlags().Bool(c.key+".prepare-stmt", false, "executes the given query in cached statement") - _ = Command.PersistentFlags().Bool(c.key+".disable-automatic-ping", false, "is used to disable the automatic ping to the database server") - _ = Command.PersistentFlags().Bool(c.key+".disable-foreign-key-constraint-when-migrating", false, "is used to disable the foreign key constraint when migrating the database") - _ = Command.PersistentFlags().Bool(c.key+".disable-nested-transaction", false, "disable nested transaction") - _ = Command.PersistentFlags().Bool(c.key+".allow-global-update", false, "allow global update") - _ = Command.PersistentFlags().Bool(c.key+".query-fields", false, "executes the SQL query with all fields of the table") - _ = Command.PersistentFlags().Int(c.key+".create-batch-size", 0, "default create batch size") - _ = Command.PersistentFlags().Bool(c.key+".enable-connection-pool", false, "is used to create a connection pool") - _ = Command.PersistentFlags().Int(c.key+".pool-max-idle-conns", 0, "sets the maximum number of connections idle in the connection pool if enable") - _ = Command.PersistentFlags().Int(c.key+".pool-max-open-conns", 0, "sets the maximum number of connections open in the connection pool if enable") - _ = Command.PersistentFlags().Duration(c.key+".pool-conn-max-lifetime", 5*time.Minute, "sets the maximum lifetime of connections in the connection pool if enable") - _ = Command.PersistentFlags().Bool(c.key+".disabled", false, "allow to disable a database connection without clean his configuration") - - if err := Viper.BindPFlag(c.key+".driver", Command.PersistentFlags().Lookup(c.key+".driver")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".name", Command.PersistentFlags().Lookup(c.key+".name")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".dsn", Command.PersistentFlags().Lookup(c.key+".dsn")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".skip-default-transaction", Command.PersistentFlags().Lookup(c.key+".skip-default-transaction")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".full-save-associations", Command.PersistentFlags().Lookup(c.key+".full-save-associations")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".dry-run", Command.PersistentFlags().Lookup(c.key+".dry-run")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".prepare-stmt", Command.PersistentFlags().Lookup(c.key+".prepare-stmt")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".disable-automatic-ping", Command.PersistentFlags().Lookup(c.key+".disable-automatic-ping")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".disable-foreign-key-constraint-when-migrating", Command.PersistentFlags().Lookup(c.key+".disable-foreign-key-constraint-when-migrating")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".disable-nested-transaction", Command.PersistentFlags().Lookup(c.key+".disable-nested-transaction")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".allow-global-update", Command.PersistentFlags().Lookup(c.key+".allow-global-update")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".query-fields", Command.PersistentFlags().Lookup(c.key+".query-fields")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".create-batch-size", Command.PersistentFlags().Lookup(c.key+".create-batch-size")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".enable-connection-pool", Command.PersistentFlags().Lookup(c.key+".enable-connection-pool")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".pool-max-idle-conns", Command.PersistentFlags().Lookup(c.key+".pool-max-idle-conns")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".pool-max-open-conns", Command.PersistentFlags().Lookup(c.key+".pool-max-open-conns")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".pool-conn-max-lifetime", Command.PersistentFlags().Lookup(c.key+".pool-conn-max-lifetime")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".disabled", Command.PersistentFlags().Lookup(c.key+".disabled")); err != nil { - return err - } else if err = libsts.RegisterFlag(c.key+".status", Command, Viper); err != nil { - return err - } - - return nil -} - -func (c *componentDatabase) _getConfig(getCfg libcfg.FuncComponentConfigGet) (libdbs.Config, liberr.Error) { - var ( - cnf = libdbs.Config{} - vpr = c.vpr() - ) - - if !c._CheckDep() { - return cnf, ErrorComponentNotInitialized.Error(nil) - } - - if err := getCfg(c.key, &cnf); err != nil { - return cnf, ErrorParamInvalid.Error(err) - } - - fct := func() liblog.Logger { - if l, e := c._GetLogger(); e != nil { - return liblog.GetDefault() - } else { - return l - } - } - - cnf.RegisterLogger(fct, c.li, c.ls) - cnf.RegisterContext(c.ctx) - - if val := vpr.GetString(c.key + ".driver"); val != "" { - cnf.Driver = libdbs.DriverFromString(val) - } - if val := vpr.GetString(c.key + ".name"); val != "" { - cnf.Name = val - } - if val := vpr.GetString(c.key + ".dsn"); val != "" { - cnf.DSN = val - } - if val := vpr.GetBool(c.key + ".skip-default-transaction"); val { - cnf.SkipDefaultTransaction = true - } - if val := vpr.GetBool(c.key + ".full-save-associations"); val { - cnf.FullSaveAssociations = true - } - if val := vpr.GetBool(c.key + ".dry-run"); val { - cnf.DryRun = true - } - if val := vpr.GetBool(c.key + ".prepare-stmt"); val { - cnf.PrepareStmt = true - } - if val := vpr.GetBool(c.key + ".disable-automatic-ping"); val { - cnf.DisableAutomaticPing = true - } - if val := vpr.GetBool(c.key + ".disable-foreign-key-constraint-when-migrating"); val { - cnf.DisableForeignKeyConstraintWhenMigrating = true - } - if val := vpr.GetBool(c.key + ".disable-nested-transaction"); val { - cnf.DisableNestedTransaction = true - } - if val := vpr.GetBool(c.key + ".allow-global-update"); val { - cnf.AllowGlobalUpdate = true - } - if val := vpr.GetBool(c.key + ".query-fields"); val { - cnf.QueryFields = true - } - if val := vpr.GetInt(c.key + ".create-batch-size"); val != 0 { - cnf.CreateBatchSize = val - } - if val := vpr.GetBool(c.key + ".enable-connection-pool"); val { - cnf.EnableConnectionPool = true - } - if val := vpr.GetInt(c.key + ".pool-max-idle-conns"); val != 0 { - cnf.PoolMaxIdleConns = val - } - if val := vpr.GetInt(c.key + ".pool-max-open-conns"); val != 0 { - cnf.PoolMaxOpenConns = val - } - if val := vpr.GetDuration(c.key + ".pool-conn-max-lifetime"); val != 0 { - cnf.PoolConnMaxLifetime = val - } - if val := vpr.GetBool(c.key + ".disabled"); val { - cnf.Disabled = true - } - - if err := cnf.Validate(); err != nil { - return cnf, ErrorConfigInvalid.Error(err) - } - - return cnf, nil -} diff --git a/config/components/database/errors.go b/config/components/database/errors.go index a4fa1ef9..a324baf0 100644 --- a/config/components/database/errors.go +++ b/config/components/database/errors.go @@ -38,8 +38,8 @@ const ( ErrorParamInvalid ErrorComponentNotInitialized ErrorConfigInvalid - ErrorStartDatabase - ErrorReloadDatabase + ErrorComponentStart + ErrorComponentReload ErrorDependencyLogDefault ) @@ -60,9 +60,9 @@ func getMessage(code liberr.CodeError) (message string) { return "this component seems to not be correctly initialized" case ErrorConfigInvalid: return "server invalid config" - case ErrorStartDatabase: + case ErrorComponentStart: return "cannot open database connection with config" - case ErrorReloadDatabase: + case ErrorComponentReload: return "cannot update database connection with new config" case ErrorDependencyLogDefault: return "cannot retrieve default Logger" diff --git a/config/components/database/interface.go b/config/components/database/interface.go index 7df52954..b54f2c96 100644 --- a/config/components/database/interface.go +++ b/config/components/database/interface.go @@ -31,27 +31,26 @@ import ( "time" libcfg "github.com/nabbar/golib/config" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" libdbs "github.com/nabbar/golib/database" ) -const ( - ComponentType = "database" -) - type ComponentDatabase interface { - libcfg.Component + cfgtps.Component - SetLOGKey(logKey string) SetLogOptions(ignoreRecordNotFoundError bool, slowThreshold time.Duration) GetDatabase() libdbs.Database SetDatabase(db libdbs.Database) } -func New(logKey string) ComponentDatabase { +func New(ctx libctx.FuncContext) ComponentDatabase { return &componentDatabase{ - m: sync.Mutex{}, - l: logKey, - d: nil, + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), + li: false, + ls: 0, + d: nil, } } @@ -59,11 +58,11 @@ func Register(cfg libcfg.Config, key string, cpt ComponentDatabase) { cfg.ComponentSet(key, cpt) } -func RegisterNew(cfg libcfg.Config, key, logKey string) { - cfg.ComponentSet(key, New(logKey)) +func RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string) { + cfg.ComponentSet(key, New(ctx)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentDatabase { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentDatabase { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentDatabase); !ok { diff --git a/config/components/database/model.go b/config/components/database/model.go index fbfa4b81..0c9b2c7f 100644 --- a/config/components/database/model.go +++ b/config/components/database/model.go @@ -30,212 +30,38 @@ import ( "sync" "time" - libcfg "github.com/nabbar/golib/config" - cptlog "github.com/nabbar/golib/config/components/log" + libctx "github.com/nabbar/golib/context" libdbs "github.com/nabbar/golib/database" - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" + montps "github.com/nabbar/golib/monitor/types" ) type componentDatabase struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex - l string + m sync.RWMutex + x libctx.Config[uint8] li bool ls time.Duration d libdbs.Database + p montps.FuncPool } -func (c *componentDatabase) _CheckDep() bool { - return c != nil && c.l != "" -} - -func (c *componentDatabase) _GetLogger() (liblog.Logger, liberr.Error) { - if !c._CheckDep() { - return nil, ErrorComponentNotInitialized.Error(nil) - } - - if i := cptlog.Load(c.get, c.l); 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 *componentDatabase) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - var isReload = c.IsStarted() - - c.m.Lock() - defer c.m.Unlock() - - if isReload { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentDatabase) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentDatabase) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - var ( - err liberr.Error - cnf libdbs.Config - ) - - if cnf, err = c._getConfig(getCfg); err != nil { - return err - } - - if c.d != nil { - c.d.Close() - } - - if c.d, err = libdbs.New(&cnf); err != nil { - return ErrorStartDatabase.Error(err) - } - - return nil -} - -func (c *componentDatabase) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - if !c._CheckDep() { - return ErrorComponentNotInitialized.Error(nil) - } - - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} -func (c *componentDatabase) Type() string { - return ComponentType -} - -func (c *componentDatabase) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentDatabase) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentDatabase) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentDatabase) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - if c == nil || c.d == nil { - return false - } - - if db := c.GetDatabase(); db == nil { - return false - } else if e := db.CheckConn(); e != nil { - return false - } - - return true -} - -func (c *componentDatabase) IsRunning(atLeast bool) bool { - return c.IsStarted() -} - -func (c *componentDatabase) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentDatabase) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentDatabase) Stop() { - if db := c.GetDatabase(); db != nil { - db.Close() - } -} - -func (c *componentDatabase) Dependencies() []string { - if c == nil { - return []string{cptlog.ComponentType} - } - - c.m.Lock() - defer c.m.Unlock() - - return []string{c.l} -} - -func (c *componentDatabase) SetLOGKey(logKey string) { - c.m.Lock() - defer c.m.Unlock() - - c.l = logKey -} - -func (c *componentDatabase) SetLogOptions(ignoreRecordNotFoundError bool, slowThreshold time.Duration) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentDatabase) SetLogOptions(ignoreRecordNotFoundError bool, slowThreshold time.Duration) { + o.m.Lock() + defer o.m.Unlock() - c.li = ignoreRecordNotFoundError - c.ls = slowThreshold + o.li = ignoreRecordNotFoundError + o.ls = slowThreshold } -func (c *componentDatabase) SetDatabase(db libdbs.Database) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentDatabase) SetDatabase(db libdbs.Database) { + o.m.Lock() + defer o.m.Unlock() - c.d = db + o.d = db } -func (c *componentDatabase) GetDatabase() libdbs.Database { - c.m.Lock() - defer c.m.Unlock() +func (o *componentDatabase) GetDatabase() libdbs.Database { + o.m.RLock() + defer o.m.RUnlock() - return c.d + return o.d } diff --git a/config/components/database/monitor.go b/config/components/database/monitor.go new file mode 100644 index 00000000..993996e9 --- /dev/null +++ b/config/components/database/monitor.go @@ -0,0 +1,133 @@ +/* + * 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 database + +import ( + libdbs "github.com/nabbar/golib/database" + libmon "github.com/nabbar/golib/monitor" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +func (o *componentDatabase) RegisterMonitorPool(fct montps.FuncPool) { + o.m.Lock() + defer o.m.Unlock() + + o.p = fct +} + +func (o *componentDatabase) _getMonitorPool() montps.Pool { + o.m.RLock() + defer o.m.RUnlock() + + if o.p == nil { + return nil + } else if p := o.p(); p == nil { + return nil + } else { + return p + } +} + +func (o *componentDatabase) _registerMonitor(cfg *libdbs.Config) error { + var ( + e error + key = o._getKey() + mon libmon.Monitor + vrs = o._getVersion() + ) + + if o._getMonitorPool() == nil { + return nil + } else if len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } else if cfg == nil { + return ErrorConfigInvalid.Error(nil) + } else if !o.IsStarted() { + return ErrorComponentStart.Error(nil) + } + + if mon = o._getMonitor(key); mon == nil { + if mon, e = o._newMonitor(vrs); e != nil { + return e + } else if mon == nil { + return nil + } + } + + if e = mon.SetConfig(o.x.GetContext, cfg.Monitor); e != nil { + return e + } + + if e = mon.Restart(o.x.GetContext()); e != nil { + return e + } else if e = o._setMonitor(mon); e != nil { + return e + } + + return nil +} + +func (o *componentDatabase) _newMonitor(vrs libver.Version) (libmon.Monitor, error) { + o.m.RLock() + defer o.m.RUnlock() + if c, e := o.d.Monitor(vrs); e != nil { + return nil, e + } else { + c.RegisterLoggerDefault(o.getLogger) + return c, nil + } +} + +func (o *componentDatabase) _getMonitor(key string) libmon.Monitor { + var ( + mon libmon.Monitor + pol = o._getMonitorPool() + ) + + if pol == nil { + return nil + } + + mon = pol.MonitorGet(key) + + if mon != nil { + mon.RegisterLoggerDefault(o.getLogger) + } + + return mon +} + +func (o *componentDatabase) _setMonitor(mon libmon.Monitor) error { + var pol = o._getMonitorPool() + + if pol == nil { + return nil + } + + return pol.MonitorSet(mon) +} diff --git a/config/components/head/client.go b/config/components/head/client.go new file mode 100644 index 00000000..22f2ac8e --- /dev/null +++ b/config/components/head/client.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 head + +import ( + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentHead) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentHead) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentHead) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentHead) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentHead) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentHead) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentHead) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentHead) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentHead) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentHead) _runCli() liberr.Error { + if cfg, err := o._getConfig(); err != nil { + return ErrorParamInvalid.Error(err) + } else { + o.m.Lock() + defer o.m.Unlock() + + o.h = cfg.New() + return nil + } +} + +func (o *componentHead) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/head/component.go b/config/components/head/component.go new file mode 100644 index 00000000..074189df --- /dev/null +++ b/config/components/head/component.go @@ -0,0 +1,155 @@ +/* + * 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 ( + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "head" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentHead) Type() string { + return ComponentType +} + +func (o *componentHead) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentHead) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentHead) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentHead) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o.h != nil +} + +func (o *componentHead) IsRunning() bool { + return o.IsStarted() +} + +func (o *componentHead) Start() liberr.Error { + return o._run() +} + +func (o *componentHead) Reload() liberr.Error { + return o._run() +} + +func (o *componentHead) Stop() { + o.m.Lock() + defer o.m.Unlock() + + o.h = nil + return +} + +func (o *componentHead) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = make([]string, 0) + + if o == nil { + return def + } else if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentHead) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentHead) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/head/config.go b/config/components/head/config.go new file mode 100644 index 00000000..6f774ce1 --- /dev/null +++ b/config/components/head/config.go @@ -0,0 +1,63 @@ +/* + * 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 ( + liberr "github.com/nabbar/golib/errors" + librtr "github.com/nabbar/golib/router" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentHead) RegisterFlag(Command *spfcbr.Command) error { + return nil +} + +func (o *componentHead) _getConfig() (*librtr.HeadersConfig, liberr.Error) { + var ( + key string + cfg librtr.HeadersConfig + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } + + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + return &cfg, nil +} diff --git a/config/components/head/default.go b/config/components/head/default.go index 4f2b7488..15ac9bc5 100644 --- a/config/components/head/default.go +++ b/config/components/head/default.go @@ -30,9 +30,7 @@ import ( "bytes" "encoding/json" - libcfg "github.com/nabbar/golib/config" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgcst "github.com/nabbar/golib/config/const" ) var _defaultConfig = []byte(`{ @@ -51,17 +49,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentHead) DefaultConfig(indent string) []byte { +func (o *componentHead) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentHead) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - return nil -} diff --git a/config/components/head/interface.go b/config/components/head/interface.go index 7c987b93..25022f64 100644 --- a/config/components/head/interface.go +++ b/config/components/head/interface.go @@ -27,16 +27,15 @@ package head import ( + "sync" + libcfg "github.com/nabbar/golib/config" + cfgtps "github.com/nabbar/golib/config/types" librtr "github.com/nabbar/golib/router" ) -const ( - ComponentType = "head" -) - type ComponentHead interface { - libcfg.Component + cfgtps.Component GetHeaders() librtr.Headers SetHeaders(head librtr.Headers) @@ -44,6 +43,7 @@ type ComponentHead interface { func New() ComponentHead { return &componentHead{ + m: sync.RWMutex{}, h: nil, } } @@ -56,7 +56,7 @@ func RegisterNew(cfg libcfg.Config, key string) { cfg.ComponentSet(key, New()) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentHead { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentHead { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentHead); !ok { diff --git a/config/components/head/model.go b/config/components/head/model.go index 1ad14d41..a752f0f5 100644 --- a/config/components/head/model.go +++ b/config/components/head/model.go @@ -29,139 +29,26 @@ package head import ( "sync" - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" + libctx "github.com/nabbar/golib/context" librtr "github.com/nabbar/golib/router" ) type componentHead struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex + m sync.RWMutex + x libctx.Config[uint8] h librtr.Headers } -func (c *componentHead) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.h != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentHead) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentHead) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - cnf := librtr.HeadersConfig{} - if err := getCfg(c.key, &cnf); err != nil { - return ErrorParamInvalid.Error(err) - } - - c.h = cnf.New() - return nil -} - -func (c *componentHead) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentHead) Type() string { - return ComponentType -} - -func (c *componentHead) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentHead) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentHead) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentHead) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c.h != nil -} - -func (c *componentHead) IsRunning(atLeast bool) bool { - return c.IsStarted() -} - -func (c *componentHead) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentHead) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentHead) Stop() { - -} - -func (c *componentHead) Dependencies() []string { - return []string{} -} - -func (c *componentHead) GetHeaders() librtr.Headers { - c.m.Lock() - defer c.m.Unlock() +func (o *componentHead) GetHeaders() librtr.Headers { + o.m.RLock() + defer o.m.RUnlock() - return c.h + return o.h } -func (c *componentHead) SetHeaders(head librtr.Headers) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentHead) SetHeaders(head librtr.Headers) { + o.m.Lock() + defer o.m.Unlock() - c.h = head + o.h = head } diff --git a/config/components/head/monitor.go b/config/components/head/monitor.go new file mode 100644 index 00000000..e8a6f08c --- /dev/null +++ b/config/components/head/monitor.go @@ -0,0 +1,34 @@ +/* + * 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 ( + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *componentHead) RegisterMonitorPool(fct montps.FuncPool) { +} diff --git a/config/components/http/client.go b/config/components/http/client.go new file mode 100644 index 00000000..085ee899 --- /dev/null +++ b/config/components/http/client.go @@ -0,0 +1,248 @@ +/* + * 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 ( + "context" + "net/http" + + libtls "github.com/nabbar/golib/certificates" + cpttls "github.com/nabbar/golib/config/components/tls" + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + htpool "github.com/nabbar/golib/httpserver/pool" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentHttp) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentHttp) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentHttp) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentHttp) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentHttp) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentHttp) _getContext() context.Context { + o.m.RLock() + defer o.m.RUnlock() + + return o.x.GetContext() +} + +func (o *componentHttp) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentHttp) _GetTLS() libtls.TLSConfig { + o.m.RLock() + defer o.m.RUnlock() + + if o.t == "" { + return nil + } + + if i := cpttls.Load(o._getFctCpt(), o.t); i == nil { + return nil + } else if tls := i.GetTLS(); tls == nil { + return nil + } else { + return tls + } +} + +func (o *componentHttp) _GetHandler() map[string]http.Handler { + o.m.RLock() + defer o.m.RUnlock() + + if o.h == nil { + return nil + } else { + return o.h() + } +} + +func (o *componentHttp) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentHttp) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentHttp) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentHttp) _runCli() liberr.Error { + var ( + e error + err liberr.Error + prt = ErrorComponentReload + pol htpool.Pool + cfg *htpool.Config + ) + + if !o.IsStarted() { + prt = ErrorComponentStart + } + + if cfg, err = o._getConfig(); err != nil { + return prt.Error(err) + } + + o.m.RLock() + defer o.m.RUnlock() + + if pol, err = cfg.Pool(o.x.GetContext, o._GetHandler, o.getLogger); err != nil { + return prt.ErrorParent(err) + } + + if o.s != nil && o.s.Len() > 0 { + if e = o.s.Merge(pol, o.getLogger); e != nil { + return prt.ErrorParent(e) + } + } else { + o.m.RUnlock() + o.m.Lock() + o.s = pol + o.m.Unlock() + o.m.RLock() + } + + if e = o.s.Restart(o.x.GetContext()); e != nil { + return prt.ErrorParent(e) + } + + // Implement wait notify on main call + //o.s.StopWaitNotify() + //o.s.StartWaitNotify(o.x.GetContext()) + + return o._registerMonitor(prt) +} + +func (o *componentHttp) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/http/component.go b/config/components/http/component.go new file mode 100644 index 00000000..16725f96 --- /dev/null +++ b/config/components/http/component.go @@ -0,0 +1,168 @@ +/* + * 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 ( + cpttls "github.com/nabbar/golib/config/components/tls" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "http" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentHttp) Type() string { + return ComponentType +} + +func (o *componentHttp) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentHttp) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentHttp) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentHttp) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o != nil && o.s != nil && o.t != "" && o.h != nil +} + +func (o *componentHttp) IsRunning() bool { + if !o.IsStarted() { + return false + } + + o.m.RLock() + defer o.m.RUnlock() + + return o.s.IsRunning() +} + +func (o *componentHttp) Start() liberr.Error { + return o._run() +} + +func (o *componentHttp) Reload() liberr.Error { + return o._run() +} + +func (o *componentHttp) Stop() { + o.m.Lock() + defer o.m.Unlock() + + _ = o.s.Stop(o.x.GetContext()) + o.s = nil + return +} + +func (o *componentHttp) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = []string{cpttls.ComponentType} + + if o == nil { + return def + } else if len(o.t) > 0 { + def = []string{o.t} + } + + if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentHttp) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentHttp) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/nutsdb/interface.go b/config/components/http/config.go similarity index 52% rename from config/components/nutsdb/interface.go rename to config/components/http/config.go index 4f3d49e0..2474cd42 100644 --- a/config/components/nutsdb/interface.go +++ b/config/components/http/config.go @@ -24,64 +24,50 @@ * */ -package nutsdb +package http import ( - "sync" - "time" + "fmt" - libcfg "github.com/nabbar/golib/config" - cptlog "github.com/nabbar/golib/config/components/log" liberr "github.com/nabbar/golib/errors" - libndb "github.com/nabbar/golib/nutsdb" - libsts "github.com/nabbar/golib/status" + htpool "github.com/nabbar/golib/httpserver/pool" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" ) -const ( - ComponentType = "nutsdb" -) - -type ComponentNutsDB interface { - libcfg.Component - - SetLogger(key string) - GetServer() (libndb.NutsDB, liberr.Error) - GetClient(tickSync time.Duration) (libndb.Client, liberr.Error) - SetStatusRouter(sts libsts.RouteStatus, prefix string) +func (o *componentHttp) RegisterFlag(Command *spfcbr.Command) error { + return nil } -func New(logKey string) ComponentNutsDB { - if logKey == "" { - logKey = cptlog.ComponentType - } +func (o *componentHttp) _getConfig() (*htpool.Config, liberr.Error) { + var ( + key string + cfg htpool.Config + vpr *spfvpr.Viper + err liberr.Error + ) - return &componentNutsDB{ - ctx: nil, - get: nil, - fsa: nil, - fsb: nil, - fra: nil, - frb: nil, - m: sync.Mutex{}, - l: logKey, - n: nil, + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) } -} -func Register(cfg libcfg.Config, key string, cpt ComponentNutsDB) { - cfg.ComponentSet(key, cpt) -} + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } -func RegisterNew(cfg libcfg.Config, key, logKey string) { - cfg.ComponentSet(key, New(logKey)) -} + cfg.SetDefaultTLS(o._GetTLS) + cfg.SetContext(o.x.GetContext) + cfg.SetHandlerFunc(o._GetHandler) -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentNutsDB { - if c := getCpt(key); c == nil { - return nil - } else if h, ok := c.(ComponentNutsDB); !ok { - return nil - } else { - return h + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } else if o.h == nil { + return nil, ErrorComponentNotInitialized.ErrorParent(fmt.Errorf("missing handler")) + } else if len(o.h()) < 1 { + return nil, ErrorComponentNotInitialized.ErrorParent(fmt.Errorf("missing handler")) } + + return &cfg, nil } diff --git a/config/components/http/default.go b/config/components/http/default.go index 35ac0300..3df7a864 100644 --- a/config/components/http/default.go +++ b/config/components/http/default.go @@ -30,21 +30,20 @@ import ( "bytes" "encoding/json" - libcfg "github.com/nabbar/golib/config" cpttls "github.com/nabbar/golib/config/components/tls" - libsts "github.com/nabbar/golib/status/config" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgcst "github.com/nabbar/golib/config/const" + cptlog "github.com/nabbar/golib/logger/config" + moncfg "github.com/nabbar/golib/monitor/types" ) var _defaultConfig = []byte(`[ { "disabled":false, "name":"status_http", - "handler_keys":"status", + "handler_key":"status", "listen":"0.0.0.0:6080", "expose":"http://0.0.0.0", - "status":` + string(libsts.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + `, + "monitor":` + string(moncfg.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, "read_timeout":"0s", "read_header_timeout":"0s", "write_timeout":"0s", @@ -57,15 +56,16 @@ var _defaultConfig = []byte(`[ "max_upload_buffer_per_connection":0, "max_upload_buffer_per_stream":0, "tls_mandatory":false, - "tls": ` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` + "tls":` + string(cpttls.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, + "logger":` + string(cptlog.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + ` }, { "disabled":false, "name":"api_http", - "handler_keys":"api", + "handler_key":"api", "listen":"0.0.0.0:7080", "expose":"http://0.0.0.0", - "status":` + string(libsts.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + `, + "monitor":` + string(moncfg.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, "read_timeout":"0s", "read_header_timeout":"0s", "write_timeout":"0s", @@ -78,15 +78,16 @@ var _defaultConfig = []byte(`[ "max_upload_buffer_per_connection":0, "max_upload_buffer_per_stream":0, "tls_mandatory":false, - "tls":` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` + "tls":` + string(cpttls.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, + "logger":` + string(cptlog.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + ` }, { "disabled":false, "name":"metrics_http", - "handler_keys":"metrics", + "handler_key":"metrics", "listen":"0.0.0.0:8080", "expose":"http://0.0.0.0", - "status":` + string(libsts.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + `, + "monitor":` + string(moncfg.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, "read_timeout":"0s", "read_header_timeout":"0s", "write_timeout":"0s", @@ -99,7 +100,8 @@ var _defaultConfig = []byte(`[ "max_upload_buffer_per_connection":0, "max_upload_buffer_per_stream":0, "tls_mandatory":false, - "tls":` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` + "tls":` + string(cpttls.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + `, + "logger":` + string(cptlog.DefaultConfig(cfgcst.JSONIndent+cfgcst.JSONIndent)) + ` } ]`) @@ -109,17 +111,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentHttp) DefaultConfig(indent string) []byte { +func (o *componentHttp) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentHttp) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - return nil -} diff --git a/config/components/http/errors.go b/config/components/http/errors.go index 465723f0..d95a83c8 100644 --- a/config/components/http/errors.go +++ b/config/components/http/errors.go @@ -38,10 +38,9 @@ const ( ErrorParamInvalid ErrorComponentNotInitialized ErrorConfigInvalid - ErrorStartComponent - ErrorReloadComponent + ErrorComponentStart + ErrorComponentReload ErrorDependencyTLSDefault - ErrorDependencyLogDefault ) func init() { @@ -61,14 +60,12 @@ func getMessage(code liberr.CodeError) (message string) { return "this component seems to not be correctly initialized" case ErrorConfigInvalid: return "invalid component config" - case ErrorStartComponent: + case ErrorComponentStart: return "cannot start component with config" - case ErrorReloadComponent: + case ErrorComponentReload: return "cannot reload component with new config" case ErrorDependencyTLSDefault: return "cannot retrieve TLS component" - case ErrorDependencyLogDefault: - return "cannot retrieve Logger Component" } return liberr.NullMessage diff --git a/config/components/http/interface.go b/config/components/http/interface.go index 62c83c3c..67230225 100644 --- a/config/components/http/interface.go +++ b/config/components/http/interface.go @@ -27,46 +27,41 @@ package http import ( - "net/http" "sync" libcfg "github.com/nabbar/golib/config" - libhts "github.com/nabbar/golib/httpserver" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + htpool "github.com/nabbar/golib/httpserver/pool" + srvtps "github.com/nabbar/golib/httpserver/types" ) const ( - DefaultTlsKey = "tls" - DefaultLogKey = "log" - ComponentType = "http" + DefaultTlsKey = "t" ) type ComponentHttp interface { - libcfg.Component + cfgtps.Component SetTLSKey(tlsKey string) - SetLOGKey(logKey string) - SetHandler(handler map[string]http.Handler) + SetHandler(fct srvtps.FuncHandler) - GetPool() libhts.PoolServer - SetPool(pool libhts.PoolServer) + GetPool() htpool.Pool + SetPool(pool htpool.Pool) } -func New(tlsKey, logKey string, handler map[string]http.Handler) ComponentHttp { +func New(ctx libctx.FuncContext, tlsKey string, hdl srvtps.FuncHandler) ComponentHttp { if tlsKey == "" { tlsKey = DefaultTlsKey } - if logKey == "" { - logKey = DefaultLogKey - } - return &componentHttp{ - m: sync.Mutex{}, - tls: tlsKey, - log: logKey, - run: false, - hand: handler, - pool: nil, + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), + t: tlsKey, + h: hdl, + s: nil, + p: nil, } } @@ -74,11 +69,11 @@ 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 RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string, tlsKey string, hdl srvtps.FuncHandler) { + cfg.ComponentSet(key, New(ctx, tlsKey, hdl)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentHttp { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentHttp { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentHttp); !ok { diff --git a/config/components/http/model.go b/config/components/http/model.go index 05487c86..387b7f86 100644 --- a/config/components/http/model.go +++ b/config/components/http/model.go @@ -27,302 +27,48 @@ package http import ( - "fmt" - "net/http" "sync" - 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" + libctx "github.com/nabbar/golib/context" + htpool "github.com/nabbar/golib/httpserver/pool" + srvtps "github.com/nabbar/golib/httpserver/types" + montps "github.com/nabbar/golib/monitor/types" ) type componentHttp struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - sts libcfg.FuncRouteStatus - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex - - 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) _GetTLS() (libtls.TLSConfig, liberr.Error) { - if !c._CheckDep() { - return nil, ErrorComponentNotInitialized.Error(nil) - } - - if i := cpttls.Load(c.get, 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() (liblog.Logger, liberr.Error) { - if !c._CheckDep() { - return nil, ErrorComponentNotInitialized.Error(nil) - } - - if i := cptlog.Load(c.get, 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(getCfg libcfg.FuncComponentConfigGet) (libhts.PoolServerConfig, liberr.Error) { - cnf := make(libhts.PoolServerConfig, 0) - - if !c._CheckDep() { - return cnf, ErrorComponentNotInitialized.Error(nil) - } - - if err := getCfg(c.key, &cnf); err != nil { - return cnf, ErrorParamInvalid.Error(err) - } - - if tls, err := c._GetTLS(); 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) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - var isReload = c.IsStarted() - - c.m.Lock() - defer c.m.Unlock() - - if isReload { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentHttp) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentHttp) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - var ( - err liberr.Error - cnf libhts.PoolServerConfig - ) - - if cnf, err = c._getPoolServerConfig(getCfg); err != nil { - return err - } - - if c.pool != nil { - if p, e := cnf.UpdatePoolServer(c.pool); e != nil { - return ErrorReloadComponent.Error(e) - } else { - c.pool = p - } - } else if p, e := cnf.PoolServer(); e != nil { - return ErrorReloadComponent.Error(e) - } else { - c.pool = p - } - - c.pool.SetLogger(func() liblog.Logger { - var ( - l liblog.Logger - e liberr.Error - ) - if l, e = c._GetLogger(); e != nil { - return liblog.GetDefault() - } else { - return l - } - }) - - if c.sts != nil { - c.pool.StatusRoute(c.key, c.sts()) - } - - if err = c.pool.ListenMultiHandler(c.hand); err != nil { - return ErrorStartComponent.Error(err) - } - - return nil -} - -func (c *componentHttp) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - if !c._CheckDep() { - return ErrorComponentNotInitialized.Error(nil) - } - - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentHttp) Type() string { - return ComponentType -} - -func (c *componentHttp) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr - c.sts = sts -} - -func (c *componentHttp) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentHttp) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentHttp) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c._CheckInit() && c.pool.IsRunning(true) -} - -func (c *componentHttp) IsRunning(atLeast bool) bool { - c.m.Lock() - defer c.m.Unlock() - - return c._CheckInit() && c.pool.IsRunning(atLeast) -} - -func (c *componentHttp) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentHttp) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentHttp) Stop() { - c.m.Lock() - defer c.m.Unlock() - - if c._CheckInit() { - c.pool.Shutdown() - } -} - -func (c *componentHttp) Dependencies() []string { - c.m.Lock() - defer c.m.Unlock() - - if !c._CheckDep() { - return []string{cpttls.ComponentType, cptlog.ComponentType} - } - - return []string{c.tls, c.log} -} - -func (c *componentHttp) SetTLSKey(tlsKey string) { - c.m.Lock() - defer c.m.Unlock() - - c.tls = tlsKey + m sync.RWMutex + x libctx.Config[uint8] + r bool + t string + h srvtps.FuncHandler + s htpool.Pool + p montps.FuncPool } -func (c *componentHttp) SetLOGKey(logKey string) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentHttp) SetTLSKey(tlsKey string) { + o.m.Lock() + defer o.m.Unlock() - c.log = logKey + o.t = tlsKey } -func (c *componentHttp) SetHandler(handler map[string]http.Handler) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentHttp) SetHandler(fct srvtps.FuncHandler) { + o.m.Lock() + defer o.m.Unlock() - c.hand = handler + o.h = fct } -func (c *componentHttp) GetPool() libhts.PoolServer { - c.m.Lock() - defer c.m.Unlock() +func (o *componentHttp) GetPool() htpool.Pool { + o.m.Lock() + defer o.m.Unlock() - return c.pool + return o.s } -func (c *componentHttp) SetPool(pool libhts.PoolServer) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentHttp) SetPool(pool htpool.Pool) { + o.m.Lock() + defer o.m.Unlock() - c.pool = pool + o.s = pool } diff --git a/config/components/http/monitor.go b/config/components/http/monitor.go new file mode 100644 index 00000000..1b076316 --- /dev/null +++ b/config/components/http/monitor.go @@ -0,0 +1,139 @@ +/* + * 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 ( + liberr "github.com/nabbar/golib/errors" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +func (o *componentHttp) RegisterMonitorPool(fct montps.FuncPool) { + o.m.Lock() + defer o.m.Unlock() + + o.p = fct +} + +func (o *componentHttp) _getMonitorPool() montps.Pool { + o.m.RLock() + defer o.m.RUnlock() + + if o.p == nil { + return nil + } else if p := o.p(); p == nil { + return nil + } else { + return p + } +} + +func (o *componentHttp) _registerMonitor(err liberr.CodeError) liberr.Error { + var ( + e error + key = o._getKey() + mon []montps.Monitor + vrs = o._getVersion() + ctx = o.x.GetContext + ) + + if o._getMonitorPool() == nil { + return nil + } else if len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } else if !o.IsStarted() { + return ErrorComponentStart.Error(nil) + } + + if mon, e = o._newMonitor(vrs); e != nil { + return err.ErrorParent(e) + } else if mon == nil { + return nil + } + + for _, m := range mon { + if old := o._getMonitor(m.Name()); old != nil { + old.InfoUpd(m.InfoGet()) + if e = old.SetConfig(ctx, m.GetConfig()); e == nil { + m = old + } + } + + if e = m.Restart(ctx()); e != nil { + return err.ErrorParent(e) + } else if e = o._setMonitor(m); e != nil { + return err.ErrorParent(e) + } + } + + return nil +} + +func (o *componentHttp) _newMonitor(vrs libver.Version) ([]montps.Monitor, error) { + o.m.RLock() + defer o.m.RUnlock() + + if c, e := o.s.Monitor(vrs); e != nil { + return nil, e + } else { + for k := range c { + if c[k] != nil { + c[k].RegisterLoggerDefault(o.getLogger) + } + } + return c, nil + } +} + +func (o *componentHttp) _getMonitor(key string) montps.Monitor { + var ( + mon montps.Monitor + pol = o._getMonitorPool() + ) + + if pol == nil { + return nil + } + + mon = pol.MonitorGet(key) + + if mon != nil { + mon.RegisterLoggerDefault(o.getLogger) + } + + return mon +} + +func (o *componentHttp) _setMonitor(mon montps.Monitor) error { + var pol = o._getMonitorPool() + + if pol == nil { + return nil + } + + return pol.MonitorSet(mon) +} diff --git a/config/components/ldap/client.go b/config/components/ldap/client.go new file mode 100644 index 00000000..4c3cf81f --- /dev/null +++ b/config/components/ldap/client.go @@ -0,0 +1,194 @@ +/* + * 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 ldap + +import ( + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + lbldap "github.com/nabbar/golib/ldap" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentLDAP) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentLDAP) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentLDAP) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentLDAP) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentLDAP) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentLDAP) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentLDAP) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentLDAP) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentLDAP) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentLDAP) _runCli() liberr.Error { + var ( + e error + err liberr.Error + cli *lbldap.HelperLDAP + cfg *lbldap.Config + ) + + o.m.RLock() + defer o.m.RUnlock() + + if cfg, err = o._getConfig(); err != nil { + return ErrorParamInvalid.Error(err) + } else if cli, e = lbldap.NewLDAP(o.x.GetContext(), cfg, o.a); e != nil { + return ErrorConfigInvalid.ErrorParent(e) + } else { + cli.SetLogger(o.getLogger) + } + + if o.l != nil { + o.l.Close() + } + + o.m.RUnlock() + o.m.Lock() + o.l = cli + o.c = cfg + o.m.Unlock() + o.m.RLock() + + return nil +} + +func (o *componentLDAP) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/ldap/component.go b/config/components/ldap/component.go new file mode 100644 index 00000000..9a028ba6 --- /dev/null +++ b/config/components/ldap/component.go @@ -0,0 +1,157 @@ +/* + * 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 ldap + +import ( + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "LDAP" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentLDAP) Type() string { + return ComponentType +} + +func (o *componentLDAP) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentLDAP) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentLDAP) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentLDAP) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o != nil && o.l != nil +} + +func (o *componentLDAP) IsRunning() bool { + return o.IsStarted() +} + +func (o *componentLDAP) Start() liberr.Error { + return o._run() +} + +func (o *componentLDAP) Reload() liberr.Error { + return o._run() +} + +func (o *componentLDAP) Stop() { + o.m.Lock() + defer o.m.Unlock() + + o.l.Close() + o.l = nil + + return +} + +func (o *componentLDAP) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = make([]string, 0) + + if o == nil { + return def + } else if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentLDAP) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentLDAP) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/ldap/config.go b/config/components/ldap/config.go new file mode 100644 index 00000000..a72dcb8e --- /dev/null +++ b/config/components/ldap/config.go @@ -0,0 +1,63 @@ +/* + * 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 ldap + +import ( + liberr "github.com/nabbar/golib/errors" + lbldap "github.com/nabbar/golib/ldap" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentLDAP) RegisterFlag(Command *spfcbr.Command) error { + return nil +} + +func (o *componentLDAP) _getConfig() (*lbldap.Config, liberr.Error) { + var ( + key string + cfg lbldap.Config + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } + + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + return &cfg, nil +} diff --git a/config/components/ldap/default.go b/config/components/ldap/default.go index 378c0c99..cd02cb62 100644 --- a/config/components/ldap/default.go +++ b/config/components/ldap/default.go @@ -30,9 +30,7 @@ import ( "bytes" "encoding/json" - libcfg "github.com/nabbar/golib/config" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgcst "github.com/nabbar/golib/config/const" ) var _defaultConfig = []byte(`{ @@ -50,17 +48,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentLDAP) DefaultConfig(indent string) []byte { +func (o *componentLDAP) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentLDAP) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - return nil -} diff --git a/config/components/ldap/interface.go b/config/components/ldap/interface.go index 1ccfdc34..49715842 100644 --- a/config/components/ldap/interface.go +++ b/config/components/ldap/interface.go @@ -30,25 +30,30 @@ import ( "sync" libcfg "github.com/nabbar/golib/config" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" lbldap "github.com/nabbar/golib/ldap" ) -const ( - ComponentType = "LDAP" -) - // @TODO: refactor LDAP Package type ComponentLDAP interface { - libcfg.Component + cfgtps.Component + + SetAttributes(att []string) + + GetConfig() *lbldap.Config + SetConfig(opt *lbldap.Config) - Config() *lbldap.Config - LDAP() *lbldap.HelperLDAP + GetLDAP() *lbldap.HelperLDAP + SetLDAP(l *lbldap.HelperLDAP) } -func New() ComponentLDAP { +func New(ctx libctx.FuncContext) ComponentLDAP { return &componentLDAP{ - m: sync.Mutex{}, + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), + c: nil, l: nil, } } @@ -57,11 +62,11 @@ func Register(cfg libcfg.Config, key string, cpt ComponentLDAP) { cfg.ComponentSet(key, cpt) } -func RegisterNew(cfg libcfg.Config, key string) { - cfg.ComponentSet(key, New()) +func RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string) { + cfg.ComponentSet(key, New(ctx)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentLDAP { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentLDAP { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentLDAP); !ok { diff --git a/config/components/ldap/model.go b/config/components/ldap/model.go index 769c6c5c..609b4ae5 100644 --- a/config/components/ldap/model.go +++ b/config/components/ldap/model.go @@ -27,173 +27,55 @@ package ldap import ( - "context" "sync" - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" + libctx "github.com/nabbar/golib/context" lbldap "github.com/nabbar/golib/ldap" ) type componentLDAP struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - sts libcfg.FuncRouteStatus - key string + m sync.RWMutex + x libctx.Config[uint8] - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex + a []string c *lbldap.Config l *lbldap.HelperLDAP } -func (c *componentLDAP) _GetContext() context.Context { - c.m.Lock() - defer c.m.Unlock() - - if c.ctx != nil { - if x := c.ctx(); x != nil { - return x - } - } - - return context.Background() -} +func (o *componentLDAP) GetConfig() *lbldap.Config { + o.m.RLock() + defer o.m.RUnlock() -func (c *componentLDAP) _CheckInit() bool { - return c != nil && c.l != nil + return o.c } -func (c *componentLDAP) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentLDAP) SetConfig(opt *lbldap.Config) { + o.m.Lock() + defer o.m.Unlock() - if c.l != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } + o.c = opt } -func (c *componentLDAP) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } +func (o *componentLDAP) GetLDAP() *lbldap.HelperLDAP { + o.m.RLock() + defer o.m.RUnlock() - return nil + return o.l } -func (c *componentLDAP) _runCli(ctx context.Context, getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - cfg := lbldap.Config{} - if err := getCfg(c.key, &cfg); err != nil { - return ErrorParamInvalid.Error(err) - } - - if l, e := lbldap.NewLDAP(ctx, &cfg, nil); e != nil { - return ErrorConfigInvalid.ErrorParent(e) - } else { - c.l = l - c.c = &cfg - } - - if c.sts != nil { - if s := c.sts(); s != nil { +func (o *componentLDAP) SetLDAP(l *lbldap.HelperLDAP) { + o.m.Lock() + defer o.m.Unlock() - } - } - - return nil + o.l = l } -func (c *componentLDAP) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - fb, fa := c._getFct() +func (o *componentLDAP) SetAttributes(att []string) { + o.m.Lock() + defer o.m.Unlock() - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(c._GetContext(), getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err + o.a = att + if o.l != nil { + o.l.Attributes = att } - - return nil -} - -func (c *componentLDAP) Type() string { - return ComponentType -} - -func (c *componentLDAP) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentLDAP) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentLDAP) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentLDAP) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c != nil && c.l != nil -} - -func (c *componentLDAP) IsRunning(atLeast bool) bool { - return c.IsStarted() -} - -func (c *componentLDAP) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentLDAP) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentLDAP) Stop() { - -} - -func (c *componentLDAP) Dependencies() []string { - return make([]string, 0) -} - -func (c *componentLDAP) Config() *lbldap.Config { - c.m.Lock() - defer c.m.Unlock() - - return c.c -} - -func (c *componentLDAP) LDAP() *lbldap.HelperLDAP { - c.m.Lock() - defer c.m.Unlock() - - return c.l } diff --git a/config/components/ldap/monitor.go b/config/components/ldap/monitor.go new file mode 100644 index 00000000..495f6179 --- /dev/null +++ b/config/components/ldap/monitor.go @@ -0,0 +1,34 @@ +/* + * 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 ldap + +import ( + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *componentLDAP) RegisterMonitorPool(fct montps.FuncPool) { +} diff --git a/config/components/log/client.go b/config/components/log/client.go new file mode 100644 index 00000000..7d69ac7b --- /dev/null +++ b/config/components/log/client.go @@ -0,0 +1,194 @@ +/* + * 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 ( + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentLog) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentLog) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentLog) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentLog) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentLog) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentLog) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentLog) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentLog) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentLog) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentLog) _runCli() liberr.Error { + var ( + e error + err liberr.Error + prt = ErrorReloadLog + cfg *liblog.Options + ) + + if !o.IsStarted() { + prt = ErrorStartLog + + o.m.Lock() + + o.l = liblog.New(o.x.GetContext) + o.l.SetLevel(o.v) + + o.m.Unlock() + } + + if cfg, err = o._getConfig(); err != nil { + return ErrorParamInvalid.Error(err) + } + + o.m.RLock() + defer o.m.RUnlock() + + if e = o.l.SetOptions(cfg); e != nil { + return prt.ErrorParent(e) + } + + return nil +} + +func (o *componentLog) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/log/component.go b/config/components/log/component.go new file mode 100644 index 00000000..55f805e2 --- /dev/null +++ b/config/components/log/component.go @@ -0,0 +1,156 @@ +/* + * 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 ( + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "head" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentLog) Type() string { + return ComponentType +} + +func (o *componentLog) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentLog) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentLog) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentLog) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o.l != nil +} + +func (o *componentLog) IsRunning() bool { + return o.IsStarted() +} + +func (o *componentLog) Start() liberr.Error { + return o._run() +} + +func (o *componentLog) Reload() liberr.Error { + return o._run() +} + +func (o *componentLog) Stop() { + o.m.Lock() + defer o.m.Unlock() + + _ = o.l.Close() + o.l = nil + return +} + +func (o *componentLog) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = make([]string, 0) + + if o == nil { + return def + } else if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentLog) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentLog) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/log/config.go b/config/components/log/config.go new file mode 100644 index 00000000..8bcfb6a8 --- /dev/null +++ b/config/components/log/config.go @@ -0,0 +1,120 @@ +/* + * 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 ( + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentLog) RegisterFlag(Command *spfcbr.Command) error { + var ( + key string + vpr *spfvpr.Viper + err error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } + + _ = Command.PersistentFlags().Bool(key+".disableStandard", false, "allow disabling to write log to standard output stdout/stderr.") + _ = Command.PersistentFlags().Bool(key+".disableStack", false, "allow to disable the goroutine id before each message") + _ = Command.PersistentFlags().Bool(key+".disableTimestamp", false, "allow to disable the timestamp before each message") + _ = Command.PersistentFlags().Bool(key+".enableTrace", true, "allow to add the origin caller/file/line of each message") + _ = Command.PersistentFlags().String(key+".traceFilter", "", "define the path to clean for trace") + _ = Command.PersistentFlags().Bool(key+".disableColor", false, "define if color could be use or not in messages format. If the running process is not a tty, no color will be used.") + + if err = vpr.BindPFlag(key+".disableStandard", Command.PersistentFlags().Lookup(key+".disableStandard")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".disableStack", Command.PersistentFlags().Lookup(key+".disableStack")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".disableTimestamp", Command.PersistentFlags().Lookup(key+".disableTimestamp")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".enableTrace", Command.PersistentFlags().Lookup(key+".enableTrace")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".traceFilter", Command.PersistentFlags().Lookup(key+".traceFilter")); err != nil { + return err + } else if err = vpr.BindPFlag(key+".disableColor", Command.PersistentFlags().Lookup(key+".disableColor")); err != nil { + return err + } + + return nil +} + +func (o *componentLog) _getConfig() (*liblog.Options, liberr.Error) { + var ( + key string + cfg liblog.Options + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } + + if val := vpr.GetBool(key + "disableStandard"); val { + cfg.DisableStandard = true + } + + if val := vpr.GetBool(key + "disableStack"); val { + cfg.DisableStack = true + } + + if val := vpr.GetBool(key + "disableTimestamp"); val { + cfg.DisableTimestamp = true + } + + if val := vpr.GetBool(key + "enableTrace"); val { + cfg.EnableTrace = true + } + + if val := vpr.GetString(key + "traceFilter"); val != "" { + cfg.TraceFilter = val + } + + if val := vpr.GetBool(key + "disableColor"); val { + cfg.DisableColor = true + } + + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + return &cfg, nil +} diff --git a/config/components/log/default.go b/config/components/log/default.go index 90756fad..620cad57 100644 --- a/config/components/log/default.go +++ b/config/components/log/default.go @@ -27,141 +27,9 @@ package log import ( - "bytes" - "encoding/json" - - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + logdef "github.com/nabbar/golib/logger/config" ) -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 SetDefaultConfig(cfg []byte) { - _defaultConfig = cfg -} - -func DefaultConfig(indent string) []byte { - var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { - return _defaultConfig - } else { - return res.Bytes() - } -} - -func (c *componentLog) DefaultConfig(indent string) []byte { - return DefaultConfig(indent) -} - -func (c *componentLog) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - _ = Command.PersistentFlags().Bool(c.key+".disableStandard", false, "allow disabling to write log to standard output stdout/stderr.") - _ = Command.PersistentFlags().Bool(c.key+".disableStack", false, "allow to disable the goroutine id before each message") - _ = Command.PersistentFlags().Bool(c.key+".disableTimestamp", false, "allow to disable the timestamp before each message") - _ = Command.PersistentFlags().Bool(c.key+".enableTrace", true, "allow to add the origin caller/file/line of each message") - _ = Command.PersistentFlags().String(c.key+".traceFilter", "", "define the path to clean for trace") - _ = Command.PersistentFlags().Bool(c.key+".disableColor", false, "define if color could be use or not in messages format. If the running process is not a tty, no color will be used.") - - if err := Viper.BindPFlag(c.key+".disableStandard", Command.PersistentFlags().Lookup(c.key+".disableStandard")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".disableStack", Command.PersistentFlags().Lookup(c.key+".disableStack")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".disableTimestamp", Command.PersistentFlags().Lookup(c.key+".disableTimestamp")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".enableTrace", Command.PersistentFlags().Lookup(c.key+".enableTrace")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".traceFilter", Command.PersistentFlags().Lookup(c.key+".traceFilter")); err != nil { - return err - } else if err = Viper.BindPFlag(c.key+".disableColor", Command.PersistentFlags().Lookup(c.key+".disableColor")); err != nil { - return err - } - - return nil -} - -func (c *componentLog) _GetOptions(getCfg libcfg.FuncComponentConfigGet) (*liblog.Options, liberr.Error) { - var ( - err liberr.Error - cfg = liblog.Options{} - vpr = c.vpr() - ) - - if err = getCfg(c.key, &cfg); err != nil { - return nil, ErrorParamInvalid.Error(err) - } - - if val := vpr.GetBool(c.key + "disableStandard"); val { - cfg.DisableStandard = true - } - if val := vpr.GetBool(c.key + "disableStack"); val { - cfg.DisableStack = true - } - if val := vpr.GetBool(c.key + "disableTimestamp"); val { - cfg.DisableTimestamp = true - } - if val := vpr.GetBool(c.key + "enableTrace"); val { - cfg.EnableTrace = true - } - if val := vpr.GetString(c.key + "traceFilter"); val != "" { - cfg.TraceFilter = val - } - if val := vpr.GetBool(c.key + "disableColor"); val { - cfg.DisableColor = true - } - - if err = cfg.Validate(); err != nil { - return nil, ErrorConfigInvalid.Error(err) - } - - return &cfg, nil +func (o *componentLog) DefaultConfig(indent string) []byte { + return logdef.DefaultConfig(indent) } diff --git a/config/components/log/interface.go b/config/components/log/interface.go index 6021e347..60731233 100644 --- a/config/components/log/interface.go +++ b/config/components/log/interface.go @@ -27,29 +27,38 @@ package log import ( + "sync" + libcfg "github.com/nabbar/golib/config" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" liblog "github.com/nabbar/golib/logger" ) const ( - DefaultLevel = liblog.InfoLevel - ComponentType = "log" + DefaultLevel = liblog.InfoLevel ) type ComponentLog interface { - libcfg.Component + cfgtps.Component Log() liblog.Logger SetLevel(lvl liblog.Level) + GetLevel() liblog.Level + SetField(fields liblog.Fields) + GetField() liblog.Fields + SetOptions(opt *liblog.Options) liberr.Error + GetOptions() *liblog.Options } -func New(lvl liblog.Level, defLogger func() liblog.Logger) ComponentLog { +func New(ctx libctx.FuncContext, lvl liblog.Level) ComponentLog { return &componentLog{ - d: defLogger, + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), l: nil, v: lvl, } @@ -59,11 +68,11 @@ 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 RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string, lvl liblog.Level) { + cfg.ComponentSet(key, New(ctx, lvl)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentLog { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentLog { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentLog); !ok { diff --git a/config/components/log/model.go b/config/components/log/model.go index 90db0aea..184911ac 100644 --- a/config/components/log/model.go +++ b/config/components/log/model.go @@ -29,210 +29,92 @@ package log import ( "sync" - libcfg "github.com/nabbar/golib/config" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" liblog "github.com/nabbar/golib/logger" ) type componentLog struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string + m sync.RWMutex + x libctx.Config[uint8] - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - d func() liblog.Logger - - m sync.Mutex l liblog.Logger v liblog.Level } -func (c *componentLog) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentLog) Log() liblog.Logger { + o.m.RLock() + defer o.m.RUnlock() - if c.l != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentLog) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) + if o.l != nil { + return o.l.Clone() } return nil } -func (c *componentLog) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() +func (o *componentLog) SetLevel(lvl liblog.Level) { + o.m.Lock() + defer o.m.Unlock() - if c.ctx == nil { - return ErrorComponentNotInitialized.Error(nil) - } - - if c.l == nil { - c.l = liblog.New(c.ctx()) - c.l.SetLevel(c.v) - } - - var ( - e error - - log liblog.Logger - cnf *liblog.Options - err liberr.Error - ) - - if log, e = c.l.Clone(); e != nil { - log = liblog.New(c.ctx()) - log.SetLevel(c.v) - } + o.v = lvl - if cnf, err = c._GetOptions(getCfg); err != nil { - return err - } else if cnf == nil { - return ErrorConfigInvalid.Error(nil) - } else if e = log.SetOptions(cnf); e != nil { - return ErrorReloadLog.Error(err) - } - - if c.l != nil { - _ = c.l.Close() + if o.l == nil { + return } - c.l = log - - return nil + o.l.SetLevel(lvl) } -func (c *componentLog) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} +func (o *componentLog) GetLevel() liblog.Level { + o.m.RLock() + defer o.m.RUnlock() -func (c *componentLog) Type() string { - return ComponentType + return o.v } -func (c *componentLog) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} +func (o *componentLog) SetField(fields liblog.Fields) { + o.m.Lock() + defer o.m.Unlock() -func (c *componentLog) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentLog) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentLog) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c.l != nil -} - -func (c *componentLog) IsRunning(atLeast bool) bool { - return c.IsStarted() -} - -func (c *componentLog) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentLog) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentLog) Stop() { - return -} - -func (c *componentLog) Dependencies() []string { - return make([]string, 0) -} - -func (c *componentLog) Log() liblog.Logger { - c.m.Lock() - defer c.m.Unlock() - - if c.l != nil { - if n, e := c.l.Clone(); e != nil { - return c.d() - } else { - return n - } + if o.l == nil { + return } - return c.d() + o.l.SetFields(fields) } -func (c *componentLog) SetLevel(lvl liblog.Level) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentLog) GetField() liblog.Fields { + o.m.RLock() + defer o.m.RUnlock() - c.v = lvl - - if c.l == nil { - return + if o.l == nil { + return nil } - c.l.SetLevel(lvl) + return o.l.GetFields() } -func (c *componentLog) SetField(fields liblog.Fields) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentLog) GetOptions() *liblog.Options { + o.m.RLock() + defer o.m.RUnlock() - if c.l == nil { - return + if o.l == nil { + return nil } - c.l.SetFields(fields) + return o.l.GetOptions() } -func (c *componentLog) SetOptions(opt *liblog.Options) liberr.Error { - c.m.Lock() - defer c.m.Unlock() +func (o *componentLog) SetOptions(opt *liblog.Options) liberr.Error { + o.m.Lock() + defer o.m.Unlock() - if c.l == nil { + if o.l == nil { return ErrorComponentNotInitialized.Error(nil) } - if e := c.l.SetOptions(opt); e != nil { + if e := o.l.SetOptions(opt); e != nil { return ErrorConfigInvalid.ErrorParent(e) } diff --git a/config/components/log/monitor.go b/config/components/log/monitor.go new file mode 100644 index 00000000..e44e7612 --- /dev/null +++ b/config/components/log/monitor.go @@ -0,0 +1,34 @@ +/* + * 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 ( + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *componentLog) RegisterMonitorPool(fct montps.FuncPool) { +} diff --git a/config/components/mail/client.go b/config/components/mail/client.go new file mode 100644 index 00000000..5f015731 --- /dev/null +++ b/config/components/mail/client.go @@ -0,0 +1,197 @@ +/* + * 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 mail + +import ( + "context" + + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + libmail "github.com/nabbar/golib/mail" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentMail) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentMail) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentMail) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentMail) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentMail) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentMail) _getContext() context.Context { + o.m.RLock() + defer o.m.RUnlock() + + return o.x.GetContext() +} + +func (o *componentMail) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentMail) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentMail) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentMail) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentMail) _runCli() liberr.Error { + var ( + err liberr.Error + prt = ErrorComponentReload + obj libmail.Mail + cfg *libmail.Config + ) + + if !o.IsStarted() { + prt = ErrorComponentStart + } + + if cfg, err = o._getConfig(); err != nil { + return prt.Error(err) + } else if obj, err = cfg.NewMailer(); err != nil { + return prt.Error(err) + } + + o.Stop() + + o.m.Lock() + o.e = obj + o.m.Unlock() + + return nil +} + +func (o *componentMail) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/mail/component.go b/config/components/mail/component.go new file mode 100644 index 00000000..c8122e99 --- /dev/null +++ b/config/components/mail/component.go @@ -0,0 +1,155 @@ +/* + * 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 mail + +import ( + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "smtp" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentMail) Type() string { + return ComponentType +} + +func (o *componentMail) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentMail) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentMail) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentMail) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o != nil && o.e != nil +} + +func (o *componentMail) IsRunning() bool { + return o.IsStarted() +} + +func (o *componentMail) Start() liberr.Error { + return o._run() +} + +func (o *componentMail) Reload() liberr.Error { + return o._run() +} + +func (o *componentMail) Stop() { + o.m.Lock() + defer o.m.Unlock() + + o.e = nil + return +} + +func (o *componentMail) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = make([]string, 0) + + if o == nil { + return def + } else if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentMail) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentMail) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/mail/config.go b/config/components/mail/config.go new file mode 100644 index 00000000..9cbe6707 --- /dev/null +++ b/config/components/mail/config.go @@ -0,0 +1,150 @@ +/* + * 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 mail + +import ( + liberr "github.com/nabbar/golib/errors" + libmail "github.com/nabbar/golib/mail" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentMail) RegisterFlag(Command *spfcbr.Command) error { + var ( + key string + vpr *spfvpr.Viper + ) + + if vpr = o._getSPFViper(); vpr == nil { + return ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } + + _ = Command.PersistentFlags().String(key+".charset", "", "define the charset to use into mail header") + _ = Command.PersistentFlags().String(key+".encoding", "", "define the encoding mode for contents of mail") + _ = Command.PersistentFlags().String(key+".priority", "", "define the priority of the mail") + _ = Command.PersistentFlags().String(key+".subject", "", "define the subject of the mail") + _ = Command.PersistentFlags().String(key+".from", "", "define the email use for sending the mail") + _ = Command.PersistentFlags().String(key+".sender", "", "define the email show as sender") + _ = Command.PersistentFlags().String(key+".replyTo", "", "define the email to use for reply") + + if err := vpr.BindPFlag(key+".charset", Command.PersistentFlags().Lookup(key+".charset")); err != nil { + return err + } + if err := vpr.BindPFlag(key+".encoding", Command.PersistentFlags().Lookup(key+".encoding")); err != nil { + return err + } + if err := vpr.BindPFlag(key+".priority", Command.PersistentFlags().Lookup(key+".priority")); err != nil { + return err + } + if err := vpr.BindPFlag(key+".subject", Command.PersistentFlags().Lookup(key+".subject")); err != nil { + return err + } + if err := vpr.BindPFlag(key+".from", Command.PersistentFlags().Lookup(key+".from")); err != nil { + return err + } + if err := vpr.BindPFlag(key+".sender", Command.PersistentFlags().Lookup(key+".sender")); err != nil { + return err + } + if err := vpr.BindPFlag(key+".replyTo", Command.PersistentFlags().Lookup(key+".replyTo")); err != nil { + return err + } + + return nil +} + +func (o *componentMail) _getConfig() (*libmail.Config, liberr.Error) { + var ( + key string + cfg libmail.Config + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } + + if len(cfg.Headers) < 1 { + cfg.Headers = make(map[string]string, 0) + } + + if len(cfg.To) < 1 { + cfg.To = make([]string, 0) + } + + if len(cfg.Cc) < 1 { + cfg.Cc = make([]string, 0) + } + + if len(cfg.Bcc) < 1 { + cfg.Bcc = make([]string, 0) + } + + if len(cfg.Attach) < 1 { + cfg.Attach = make([]libmail.ConfigFile, 0) + } + + if len(cfg.Inline) < 1 { + cfg.Inline = make([]libmail.ConfigFile, 0) + } + + if val := vpr.GetString(key + ".charset"); val != "" { + cfg.Charset = val + } + if val := vpr.GetString(key + ".encoding"); val != "" { + cfg.Encoding = val + } + if val := vpr.GetString(key + ".priority"); val != "" { + cfg.Priority = val + } + if val := vpr.GetString(key + ".subject"); val != "" { + cfg.Subject = val + } + if val := vpr.GetString(key + ".from"); val != "" { + cfg.From = val + } + if val := vpr.GetString(key + ".sender"); val != "" { + cfg.Sender = val + } + if val := vpr.GetString(key + ".replyTo"); val != "" { + cfg.ReplyTo = val + } + + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + return &cfg, nil +} diff --git a/config/components/mail/default.go b/config/components/mail/default.go index d38259ed..9cc19352 100644 --- a/config/components/mail/default.go +++ b/config/components/mail/default.go @@ -30,11 +30,7 @@ import ( "bytes" "encoding/json" - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" - libmail "github.com/nabbar/golib/mail" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgtps "github.com/nabbar/golib/config/const" ) var _defaultConfig = []byte(`{ @@ -80,111 +76,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgtps.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentMail) DefaultConfig(indent string) []byte { +func (o *componentMail) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentMail) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - _ = Command.PersistentFlags().String(c.key+".charset", "", "define the charset to use into mail header") - _ = Command.PersistentFlags().String(c.key+".encoding", "", "define the encoding mode for contents of mail") - _ = Command.PersistentFlags().String(c.key+".priority", "", "define the priority of the mail") - _ = Command.PersistentFlags().String(c.key+".subject", "", "define the subject of the mail") - _ = Command.PersistentFlags().String(c.key+".from", "", "define the email use for sending the mail") - _ = Command.PersistentFlags().String(c.key+".sender", "", "define the email show as sender") - _ = Command.PersistentFlags().String(c.key+".replyTo", "", "define the email to use for reply") - - if err := Viper.BindPFlag(c.key+".charset", Command.PersistentFlags().Lookup(c.key+".charset")); err != nil { - return err - } - if err := Viper.BindPFlag(c.key+".encoding", Command.PersistentFlags().Lookup(c.key+".encoding")); err != nil { - return err - } - if err := Viper.BindPFlag(c.key+".priority", Command.PersistentFlags().Lookup(c.key+".priority")); err != nil { - return err - } - if err := Viper.BindPFlag(c.key+".subject", Command.PersistentFlags().Lookup(c.key+".subject")); err != nil { - return err - } - if err := Viper.BindPFlag(c.key+".from", Command.PersistentFlags().Lookup(c.key+".from")); err != nil { - return err - } - if err := Viper.BindPFlag(c.key+".sender", Command.PersistentFlags().Lookup(c.key+".sender")); err != nil { - return err - } - if err := Viper.BindPFlag(c.key+".replyTo", Command.PersistentFlags().Lookup(c.key+".replyTo")); err != nil { - return err - } - - return nil -} - -func (c *componentMail) _getConfig(getCfg libcfg.FuncComponentConfigGet) (libmail.Config, liberr.Error) { - var ( - cfg = libmail.Config{} - vpr = c.vpr() - err liberr.Error - ) - - if len(cfg.Headers) < 1 { - cfg.Headers = make(map[string]string, 0) - } - - if len(cfg.To) < 1 { - cfg.To = make([]string, 0) - } - - if len(cfg.Cc) < 1 { - cfg.Cc = make([]string, 0) - } - - if len(cfg.Bcc) < 1 { - cfg.Bcc = make([]string, 0) - } - - if len(cfg.Attach) < 1 { - cfg.Attach = make([]libmail.ConfigFile, 0) - } - - if len(cfg.Inline) < 1 { - cfg.Inline = make([]libmail.ConfigFile, 0) - } - - if e := getCfg(c.key, &cfg); e != nil { - return cfg, ErrorParamInvalid.Error(e) - } - - if val := vpr.GetString(c.key + ".charset"); val != "" { - cfg.Charset = val - } - if val := vpr.GetString(c.key + ".encoding"); val != "" { - cfg.Encoding = val - } - if val := vpr.GetString(c.key + ".priority"); val != "" { - cfg.Priority = val - } - if val := vpr.GetString(c.key + ".subject"); val != "" { - cfg.Subject = val - } - if val := vpr.GetString(c.key + ".from"); val != "" { - cfg.From = val - } - if val := vpr.GetString(c.key + ".sender"); val != "" { - cfg.Sender = val - } - if val := vpr.GetString(c.key + ".replyTo"); val != "" { - cfg.ReplyTo = val - } - - if err = cfg.Validate(); err != nil { - return cfg, ErrorConfigInvalid.Error(err) - } - - return cfg, nil -} diff --git a/config/components/mail/errors.go b/config/components/mail/errors.go index 7115687b..d10b0b45 100644 --- a/config/components/mail/errors.go +++ b/config/components/mail/errors.go @@ -38,10 +38,8 @@ const ( ErrorParamInvalid ErrorComponentNotInitialized ErrorConfigInvalid - ErrorStartComponent - ErrorReloadComponent - ErrorDependencyTLSDefault - ErrorDependencyLogDefault + ErrorComponentStart + ErrorComponentReload ) func init() { @@ -61,14 +59,10 @@ func getMessage(code liberr.CodeError) (message string) { return "this component seems to not be correctly initialized" case ErrorConfigInvalid: return "invalid component config" - case ErrorStartComponent: + case ErrorComponentStart: return "cannot start component with config" - case ErrorReloadComponent: + case ErrorComponentReload: return "cannot reload component with new config" - case ErrorDependencyTLSDefault: - return "cannot retrieve TLS Component" - case ErrorDependencyLogDefault: - return "cannot retrieve Logger Component" } return liberr.NullMessage diff --git a/config/components/mail/interface.go b/config/components/mail/interface.go index 055ed912..6387229d 100644 --- a/config/components/mail/interface.go +++ b/config/components/mail/interface.go @@ -30,30 +30,23 @@ import ( "sync" libcfg "github.com/nabbar/golib/config" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" libmail "github.com/nabbar/golib/mail" ) -const ( - ComponentType = "smtp" -) - type ComponentMail interface { - libcfg.Component + cfgtps.Component GetMail() (libmail.Mail, liberr.Error) } -func New() ComponentMail { +func New(ctx libctx.FuncContext) ComponentMail { return &componentMail{ - ctx: nil, - get: nil, - fsa: nil, - fsb: nil, - fra: nil, - frb: nil, - m: sync.Mutex{}, - e: nil, + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), + e: nil, } } @@ -61,11 +54,11 @@ func Register(cfg libcfg.Config, key string, cpt ComponentMail) { cfg.ComponentSet(key, cpt) } -func RegisterNew(cfg libcfg.Config, key string) { - cfg.ComponentSet(key, New()) +func RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string) { + cfg.ComponentSet(key, New(ctx)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentMail { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentMail { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentMail); !ok { diff --git a/config/components/mail/model.go b/config/components/mail/model.go index 76eada7e..681d10e9 100644 --- a/config/components/mail/model.go +++ b/config/components/mail/model.go @@ -29,155 +29,24 @@ package mail import ( "sync" - libcfg "github.com/nabbar/golib/config" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" libmail "github.com/nabbar/golib/mail" ) type componentMail struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex + m sync.RWMutex + x libctx.Config[uint8] e libmail.Mail } -func (c *componentMail) _CheckDep() bool { - return c != nil -} - -func (c *componentMail) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.e != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentMail) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentMail) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - var ( - err liberr.Error - mlr libmail.Mail - cfg libmail.Config - ) - - if cfg, err = c._getConfig(getCfg); err != nil { - return err - } else if mlr, err = cfg.NewMailer(); err != nil { - return err - } else { - c.e = mlr - } - - return nil -} - -func (c *componentMail) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - if !c._CheckDep() { - return ErrorComponentNotInitialized.Error(nil) - } - - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentMail) Type() string { - return ComponentType -} - -func (c *componentMail) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentMail) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentMail) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentMail) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c._CheckDep() && c.e != nil -} - -func (c *componentMail) IsRunning(atLeast bool) bool { - return !c.IsStarted() -} - -func (c *componentMail) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentMail) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentMail) Stop() { - c.m.Lock() - defer c.m.Unlock() - - c.e = nil -} - -func (c *componentMail) Dependencies() []string { - return make([]string, 0) -} - -func (c *componentMail) GetMail() (libmail.Mail, liberr.Error) { - if !c.IsStarted() { +func (o *componentMail) GetMail() (libmail.Mail, liberr.Error) { + if !o.IsStarted() { return nil, ErrorComponentNotInitialized.Error(nil) } - c.m.Lock() - defer c.m.Unlock() + o.m.Lock() + defer o.m.Unlock() - return c.e.Clone(), nil + return o.e.Clone(), nil } diff --git a/config/components/mail/monitor.go b/config/components/mail/monitor.go new file mode 100644 index 00000000..a67bce6c --- /dev/null +++ b/config/components/mail/monitor.go @@ -0,0 +1,34 @@ +/* + * 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 mail + +import ( + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *componentMail) RegisterMonitorPool(fct montps.FuncPool) { +} diff --git a/config/components/natsServer/default.go b/config/components/natsServer/default.go deleted file mode 100644 index 49beb66f..00000000 --- a/config/components/natsServer/default.go +++ /dev/null @@ -1,316 +0,0 @@ -/* - * 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 natsServer - -import ( - "bytes" - "encoding/json" - - libcfg "github.com/nabbar/golib/config" - cpttls "github.com/nabbar/golib/config/components/tls" - liberr "github.com/nabbar/golib/errors" - libnat "github.com/nabbar/golib/nats" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" -) - -var _defaultConfig = []byte(`{ - "server":{ - "name":"node-0", - "host":"127.0.0.1", - "port":9000, - "client_advertise":"", - "http_host":"127.0.0.1", - "http_port":9200, - "https_port":0, - "http_base_path":"", - "prof_port":9300, - "pid_file":"", - "ports_file_dir":"", - "routes":[ - { - "Scheme":"nats", - "Opaque":"", - "User":{ - - }, - "Host":"127.0.0.1:9101", - "Path":"", - "RawPath":"", - "ForceQuery":false, - "RawQuery":"", - "Fragment":"", - "RawFragment":"" - }, - { - "Scheme":"nats", - "Opaque":"", - "User":{ - - }, - "Host":"127.0.0.1:9102", - "Path":"", - "RawPath":"", - "ForceQuery":false, - "RawQuery":"", - "Fragment":"", - "RawFragment":"" - }, - { - "Scheme":"nats", - "Opaque":"", - "User":{ - - }, - "Host":"127.0.0.1:9103", - "Path":"", - "RawPath":"", - "ForceQuery":false, - "RawQuery":"", - "Fragment":"", - "RawFragment":"" - } - ], - "routes_str":"nats://127.0.0.1:9101,nats://127.0.0.1:9102,nats://127.0.0.1:9103", - "no_log":true, - "username":"", - "password":"", - "token":"", - "jet_stream":true, - "jet_stream_max_memory":0, - "jet_stream_max_store":0, - "store_dir":"/path/to/working/folder", - "permission_store_dir":"0755", - "tags":[ - "" - ], - "tls":false, - "allow_no_tls":true, - "tls_timeout":0, - "tls_config":` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` - }, - "cluster":{ - "name":"Test-cluster", - "host":"127.0.0.1", - "port":9100, - "listen_str":"", - "advertise":"", - "no_advertise":false, - "connect_retries":5, - "username":"", - "password":"", - "auth_timeout":0, - "permissions":{ - "import":{ - "allow":null, - "deny":null - }, - "export":{ - "allow":null, - "deny":null - } - }, - "tls":false, - "tls_timeout":0, - "tls_config":` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` - }, - "gateways":{ - "name":"", - "host":"", - "port":0, - "username":"", - "password":"", - "auth_timeout":0, - "advertise":"", - "connect_retries":0, - "gateways":null, - "reject_unknown":false, - "tls":false, - "tls_timeout":0, - "tls_config":` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` - }, - "leaf":{ - "host":"", - "port":0, - "username":"", - "password":"", - "auth_timeout":0, - "advertise":"", - "no_advertise":false, - "account":"", - "users":null, - "reconnect_interval":0, - "remotes":null, - "tls":false, - "tls_timeout":0, - "tls_config":` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` - }, - "websockets":{ - "host":"", - "port":0, - "advertise":"", - "no_auth_user":"", - "jwt_cookie":"", - "username":"", - "password":"", - "token":"", - "auth_timeout":0, - "same_origin":false, - "allowed_origins":null, - "compression":false, - "no_tls":false, - "handshake_timeout":0, - "tls_config":` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` - }, - "mqtt":{ - "host":"", - "port":0, - "no_auth_user":"", - "username":"", - "password":"", - "token":"", - "auth_timeout":0, - "ack_wait":0, - "max_ack_pending":0, - "tls":false, - "tls_timeout":0, - "tls_config": ` + string(cpttls.DefaultConfig(libcfg.JSONIndent+libcfg.JSONIndent)) + ` - }, - "limits":{ - "max_conn":0, - "max_subs":0, - "ping_interval":0, - "max_pings_out":0, - "max_control_line":0, - "max_payload":0, - "max_pending":0, - "write_deadline":0, - "max_closed_clients":0, - "lame_duck_duration":0, - "lame_duck_grace_period":0, - "no_sublist_cache":false, - "no_header_support":false, - "disable_short_first_ping":false - }, - "logs":{ - "log_file":"/path/to/log/file.log", - "permission_folder":"0755", - "permission_file":"0644", - "syslog":false, - "remote_syslog":"", - "log_size_limit":0, - "max_traced_msg_len":0, - "connect_error_reports":0, - "reconnect_error_reports":0 - }, - "auth":{ - "nkeys":null, - "users":[ - { - "username":"username", - "password":"password", - "account":"cluster", - "connection_types":[ - "STANDARD", - "LEAFNODE", - "WEBSOCKET", - "MQTT" - ] - } - ], - "accounts":[ - { - "name":"cluster", - "permission":{ - "publish":{ - "allow":[ - ">", - "*" - ], - "deny":[] - }, - "subscribe":{ - "allow":[ - ">", - "*" - ], - "deny":[] - }, - "response":{ - "max_msgs":1000000000, - "expires":1 - } - } - } - ], - "auth_timeout":0, - "no_auth_user":"", - "system_account":"cluster", - "no_system_account":false, - "allow_new_accounts":true, - "trusted_keys":[], - "trusted_operators":[] - } -}`) - -func SetDefaultConfig(cfg []byte) { - _defaultConfig = cfg -} - -func DefaultConfig(indent string) []byte { - var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { - return _defaultConfig - } else { - return res.Bytes() - } -} - -func (c *componentNats) DefaultConfig(indent string) []byte { - return DefaultConfig(indent) -} - -func (c *componentNats) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - return nil -} - -func (c *componentNats) _getConfig(getCfg libcfg.FuncComponentConfigGet) (libnat.Config, liberr.Error) { - var ( - cfg = libnat.Config{} - err liberr.Error - ) - - if e := getCfg(c.key, &cfg); e != nil { - return cfg, ErrorParamInvalid.Error(e) - } - - if err = cfg.Validate(); err != nil { - return cfg, ErrorConfigInvalid.Error(err) - } - - return cfg, nil -} diff --git a/config/components/natsServer/model.go b/config/components/natsServer/model.go deleted file mode 100644 index b3a5b120..00000000 --- a/config/components/natsServer/model.go +++ /dev/null @@ -1,249 +0,0 @@ -/* - * 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 natsServer - -import ( - "sync" - - libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" - cpttls "github.com/nabbar/golib/config/components/tls" - liberr "github.com/nabbar/golib/errors" - libnat "github.com/nabbar/golib/nats" - libsts "github.com/nabbar/golib/status" - natsrv "github.com/nats-io/nats-server/v2/server" -) - -type componentNats struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex - t string - n libnat.Server -} - -func (c *componentNats) _CheckDep() bool { - return c != nil && c.t != "" -} - -func (c *componentNats) _GetTLS() (libtls.TLSConfig, liberr.Error) { - if !c._CheckDep() { - return nil, ErrorComponentNotInitialized.Error(nil) - } - - if i := cpttls.Load(c.get, c.t); 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 *componentNats) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.n != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentNats) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentNats) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - var ( - tls libtls.TLSConfig - err liberr.Error - cfg libnat.Config - opt *natsrv.Options - ) - - if cfg, err = c._getConfig(getCfg); err != nil { - return err - } - - if tls, err = c._GetTLS(); err != nil { - return err - } - - if opt, err = cfg.NatsOption(tls); err != nil { - return ErrorStartComponent.Error(err) - } - - if c.n != nil { - c.n.SetOptions(opt) - if err = c.n.Restart(c.ctx()); err != nil { - return ErrorReloadComponent.Error(err) - } - } else { - c.n = libnat.NewServer(opt, cfg.Status) - if err = c.n.Listen(c.ctx()); err != nil { - return err - } - } - - return nil -} - -func (c *componentNats) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - if !c._CheckDep() { - return ErrorComponentNotInitialized.Error(nil) - } - - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentNats) Type() string { - return ComponentType -} - -func (c *componentNats) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentNats) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentNats) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentNats) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c != nil && c.n != nil -} - -func (c *componentNats) IsRunning(atLeast bool) bool { - if c.IsStarted() { - c.m.Lock() - defer c.m.Unlock() - - return c.n.IsRunning() - } - - return false -} - -func (c *componentNats) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentNats) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentNats) Stop() { - if c.IsRunning(true) { - c.m.Lock() - defer c.m.Unlock() - - c.n.Shutdown() - } -} - -func (c *componentNats) Dependencies() []string { - c.m.Lock() - defer c.m.Unlock() - - if !c._CheckDep() { - return []string{cpttls.ComponentType} - } - - return []string{c.t} -} - -func (c *componentNats) SetTLSKey(tlsKey string) { - c.m.Lock() - defer c.m.Unlock() - - c.t = tlsKey -} - -func (c *componentNats) GetServer() (libnat.Server, liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c == nil { - return nil, ErrorComponentNotInitialized.Error(nil) - } - - return c.n, nil -} - -func (c *componentNats) SetStatusRouter(sts libsts.RouteStatus, prefix string) { - c.m.Lock() - defer c.m.Unlock() - - c.n.StatusRouter(sts, prefix) -} diff --git a/config/components/nutsdb/default.go b/config/components/nutsdb/default.go deleted file mode 100644 index cf483b99..00000000 --- a/config/components/nutsdb/default.go +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 nutsdb - -import ( - "bytes" - "encoding/json" - - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" - libndb "github.com/nabbar/golib/nutsdb" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" -) - -var _defaultConfig = []byte(`{ - "db":{ - "entry_idx_mode":0, - "rw_mode":0, - "segment_size":8388608, - "sync_enable":false, - "start_file_loading_mode":1 - }, - "cluster":{ - "node":{ - "deployment_id":0, - "wal_dir":"", - "node_host_dir":"", - "rtt_millisecond":200, - "raft_address":"localhost:9001", - "address_by_node_host_id":false, - "listen_address":"", - "mutual_tls":false, - "ca_file":"", - "cert_file":"", - "key_file":"", - "enable_metrics":true, - "max_send_queue_size":0, - "max_receive_queue_size":0, - "max_snapshot_send_bytes_per_second":0, - "max_snapshot_recv_bytes_per_second":0, - "notify_commit":false, - "gossip":{ - "bind_address":"", - "advertise_address":"", - "seed":null - }, - "expert":{ - "engine":{ - "exec_shards":0, - "commit_shards":0, - "apply_shards":0, - "snapshot_shards":0, - "close_shards":0 - }, - "test_node_host_id":0, - "test_gossip_probe_interval":0 - } - }, - "cluster":{ - "node_id":1, - "cluster_id":1, - "check_quorum":true, - "election_rtt":15, - "heartbeat_rtt":1, - "snapshot_entries":10, - "compaction_overhead":0, - "ordered_config_change":false, - "max_in_mem_log_size":0, - "snapshot_compression":0, - "entry_compression":0, - "disable_auto_compactions":true, - "is_observer":false, - "is_witness":false, - "quiesce":false - }, - "init_member":{ - "1":"localhost:9001" - } - }, - "directories":{ - "base":"/tmp/nutsdb/node-%d", - "sub_data":"data", - "sub_backup":"backup", - "sub_temp":"temp", - "wal_dir":"", - "host_dir":"", - "limit_number_backup":5, - "permission":504 - } -}`) - -func SetDefaultConfig(cfg []byte) { - _defaultConfig = cfg -} - -func DefaultConfig(indent string) []byte { - var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { - return _defaultConfig - } else { - return res.Bytes() - } -} - -func (c *componentNutsDB) DefaultConfig(indent string) []byte { - return DefaultConfig(indent) -} - -func (c *componentNutsDB) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - return nil -} - -func (c *componentNutsDB) _getConfig(getCfg libcfg.FuncComponentConfigGet) (libndb.Config, liberr.Error) { - var ( - cfg = libndb.Config{} - err liberr.Error - ) - - if e := getCfg(c.key, &cfg); e != nil { - return cfg, ErrorParamInvalid.Error(e) - } - - if err = cfg.Validate(); err != nil { - return cfg, ErrorConfigInvalid.Error(err) - } - - return cfg, nil -} diff --git a/config/components/nutsdb/model.go b/config/components/nutsdb/model.go deleted file mode 100644 index 9c33b2d2..00000000 --- a/config/components/nutsdb/model.go +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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 nutsdb - -import ( - "sync" - "time" - - libcfg "github.com/nabbar/golib/config" - cptlog "github.com/nabbar/golib/config/components/log" - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" - libndb "github.com/nabbar/golib/nutsdb" - libsts "github.com/nabbar/golib/status" -) - -type componentNutsDB struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex - l string - n libndb.NutsDB -} - -func (c *componentNutsDB) _CheckDep() bool { - return c != nil && c.l != "" -} - -func (c *componentNutsDB) _GetLogger() (liblog.Logger, liberr.Error) { - if !c._CheckDep() { - return nil, ErrorComponentNotInitialized.Error(nil) - } - - if i := cptlog.Load(c.get, c.l); 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 *componentNutsDB) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.n != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentNutsDB) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentNutsDB) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - var ( - err liberr.Error - cfg libndb.Config - ) - - if cfg, err = c._getConfig(getCfg); err != nil { - return err - } - - srv := libndb.New(cfg) - srv.SetLogger(func() liblog.Logger { - var ( - l liblog.Logger - e liberr.Error - ) - - if l, e = c._GetLogger(); e != nil { - return liblog.GetDefault() - } else { - return l - } - }) - - if err = srv.Listen(); err != nil { - return err - } - - if c.n != nil { - _ = c.n.Shutdown() - } - - c.n = srv - - return nil -} - -func (c *componentNutsDB) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - if !c._CheckDep() { - return ErrorComponentNotInitialized.Error(nil) - } - - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentNutsDB) Type() string { - return ComponentType -} - -func (c *componentNutsDB) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentNutsDB) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentNutsDB) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentNutsDB) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c._CheckDep() && c.n != nil -} - -func (c *componentNutsDB) IsRunning(atLeast bool) bool { - if c.IsStarted() { - c.m.Lock() - defer c.m.Unlock() - - return c.n.IsRunning() - } - - return false -} - -func (c *componentNutsDB) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentNutsDB) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentNutsDB) Stop() { - if c.IsRunning(true) { - c.m.Lock() - defer c.m.Unlock() - - _ = c.n.Shutdown() - } -} - -func (c *componentNutsDB) Dependencies() []string { - c.m.Lock() - defer c.m.Unlock() - - if !c._CheckDep() { - return []string{cptlog.ComponentType} - } - - return []string{c.l} -} - -func (c *componentNutsDB) GetServer() (libndb.NutsDB, liberr.Error) { - if c.IsStarted() { - c.m.Lock() - defer c.m.Unlock() - - return c.n, nil - } - - return nil, ErrorComponentNotInitialized.Error(nil) -} - -func (c *componentNutsDB) SetLogger(key string) { - c.m.Lock() - defer c.m.Unlock() - - c.l = key -} - -func (c *componentNutsDB) GetClient(tickSync time.Duration) (libndb.Client, liberr.Error) { - if c != nil && c.IsStarted() { - c.m.Lock() - defer c.m.Unlock() - - return c.n.Client(c.ctx(), tickSync), nil - } - - return nil, ErrorComponentNotInitialized.Error(nil) -} - -func (c *componentNutsDB) SetStatusRouter(sts libsts.RouteStatus, prefix string) { - c.m.Lock() - defer c.m.Unlock() - - c.n.StatusRouter(sts, prefix) -} diff --git a/config/components/old_cpt.tar.gz b/config/components/old_cpt.tar.gz new file mode 100644 index 00000000..aa705dea Binary files /dev/null and b/config/components/old_cpt.tar.gz differ diff --git a/config/components/request/client.go b/config/components/request/client.go new file mode 100644 index 00000000..f869a98d --- /dev/null +++ b/config/components/request/client.go @@ -0,0 +1,233 @@ +/* + * 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 request + +import ( + "context" + + libtls "github.com/nabbar/golib/certificates" + cpttls "github.com/nabbar/golib/config/components/tls" + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + libreq "github.com/nabbar/golib/request" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentRequest) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentRequest) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentRequest) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentRequest) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentRequest) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentRequest) _getContext() context.Context { + o.m.RLock() + defer o.m.RUnlock() + + return o.x.GetContext() +} + +func (o *componentRequest) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentRequest) _GetTLS() libtls.TLSConfig { + o.m.RLock() + defer o.m.RUnlock() + + if o.t == "" { + return nil + } + + if i := cpttls.Load(o._getFctCpt(), o.t); i == nil { + return nil + } else if tls := i.GetTLS(); tls == nil { + return nil + } else { + return tls + } +} + +func (o *componentRequest) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentRequest) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentRequest) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentRequest) _runCli() liberr.Error { + var ( + e error + err liberr.Error + prt = ErrorComponentReload + req libreq.Request + cfg *libreq.Options + ) + + if !o.IsStarted() { + prt = ErrorComponentStart + } else { + req = o.r + } + + if cfg, err = o._getConfig(); err != nil { + return prt.Error(err) + } + + cfg.SetDefaultTLS(o._GetTLS) + cfg.SetDefaultLog(o.getLogger) + + if req != nil { + req.RegisterDefaultLogger(o.getLogger) + if req, e = cfg.Update(o.x.GetContext, req); err != nil { + return prt.ErrorParent(e) + } + } else { + if req, e = cfg.New(o.x.GetContext); err != nil { + return prt.ErrorParent(e) + } + } + + o.m.Lock() + o.r = req + o.m.Unlock() + + if e = o._registerMonitor(cfg); e != nil { + return prt.ErrorParent(e) + } + + return nil +} + +func (o *componentRequest) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/request/component.go b/config/components/request/component.go new file mode 100644 index 00000000..a50940fb --- /dev/null +++ b/config/components/request/component.go @@ -0,0 +1,160 @@ +/* + * 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 request + +import ( + cpttls "github.com/nabbar/golib/config/components/tls" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "request" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentRequest) Type() string { + return ComponentType +} + +func (o *componentRequest) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentRequest) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentRequest) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentRequest) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o != nil && o.r != nil +} + +func (o *componentRequest) IsRunning() bool { + return o.IsStarted() +} + +func (o *componentRequest) Start() liberr.Error { + return o._run() +} + +func (o *componentRequest) Reload() liberr.Error { + return o._run() +} + +func (o *componentRequest) Stop() { + o.m.Lock() + defer o.m.Unlock() + + o.r = nil + return +} + +func (o *componentRequest) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = []string{cpttls.ComponentType} + + if o == nil { + return def + } else if len(o.t) > 0 { + def = []string{o.t} + } + + if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentRequest) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentRequest) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/request/config.go b/config/components/request/config.go new file mode 100644 index 00000000..8d7a26d2 --- /dev/null +++ b/config/components/request/config.go @@ -0,0 +1,63 @@ +/* + * 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 request + +import ( + liberr "github.com/nabbar/golib/errors" + libreq "github.com/nabbar/golib/request" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentRequest) RegisterFlag(Command *spfcbr.Command) error { + return nil +} + +func (o *componentRequest) _getConfig() (*libreq.Options, liberr.Error) { + var ( + key string + cfg libreq.Options + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } + + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + return &cfg, nil +} diff --git a/config/components/request/default.go b/config/components/request/default.go index 81ee6cf9..7e4799e8 100644 --- a/config/components/request/default.go +++ b/config/components/request/default.go @@ -30,25 +30,14 @@ import ( "bytes" "encoding/json" - libcfg "github.com/nabbar/golib/config" - cmptls "github.com/nabbar/golib/config/components/tls" - libsts "github.com/nabbar/golib/status/config" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgcst "github.com/nabbar/golib/config/const" + libhtc "github.com/nabbar/golib/httpcli" + moncfg "github.com/nabbar/golib/monitor/types" ) var _defaultConfig = []byte(`{ "endpoint":"https://endpoint.example.com/path", - "http_client": { - "timeout":"0s", - "http2": true, - "tls": ` + string(cmptls.DefaultConfig(" ")) + `, - "force_ip": { - "enable": false, - "net":"tcp", - "ip":"127.0.0.1:8080" - } - }, + "http_client": ` + string(libhtc.DefaultConfig(cfgcst.JSONIndent)) + `, "auth": { "basic":{ "enable": false, @@ -80,7 +69,7 @@ var _defaultConfig = []byte(`{ "contain": ["OK", "Done"], "not_contain": ["KO", "fail", "error"] }, - "status": ` + string(libsts.DefaultConfig(" ")) + ` + "monitor": ` + string(moncfg.DefaultConfig(cfgcst.JSONIndent)) + ` } }`) @@ -90,17 +79,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentRequest) DefaultConfig(indent string) []byte { +func (o *componentRequest) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentRequest) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - return nil -} diff --git a/config/components/request/errors.go b/config/components/request/errors.go index d335599d..877be699 100644 --- a/config/components/request/errors.go +++ b/config/components/request/errors.go @@ -39,6 +39,8 @@ const ( ErrorComponentNotInitialized ErrorConfigInvalid ErrorDependencyTLSDefault + ErrorComponentStart + ErrorComponentReload ) func init() { @@ -60,6 +62,10 @@ func getMessage(code liberr.CodeError) (message string) { return "server invalid config" case ErrorDependencyTLSDefault: return "cannot retrieve TLS component" + case ErrorComponentStart: + return "cannot start component with config" + case ErrorComponentReload: + return "cannot restart component with new config" } return liberr.NullMessage diff --git a/config/components/request/interface.go b/config/components/request/interface.go index ab134f1e..5c27576d 100644 --- a/config/components/request/interface.go +++ b/config/components/request/interface.go @@ -30,28 +30,24 @@ import ( "sync" libcfg "github.com/nabbar/golib/config" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" libreq "github.com/nabbar/golib/request" ) -const ( - ComponentType = "request" -) - type ComponentRequest interface { - libcfg.Component + cfgtps.Component - SetHTTPClient(fct libreq.FctHttpClient) SetDefaultTLS(key string) - Request() (libreq.Request, error) } -func New(tls string, cli libreq.FctHttpClient) ComponentRequest { +func New(ctx libctx.FuncContext, tls string) ComponentRequest { return &componentRequest{ - m: sync.Mutex{}, + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), r: nil, t: tls, - c: cli, } } @@ -59,11 +55,11 @@ func Register(cfg libcfg.Config, key string, cpt ComponentRequest) { cfg.ComponentSet(key, cpt) } -func RegisterNew(cfg libcfg.Config, key, tls string, cli libreq.FctHttpClient) { - cfg.ComponentSet(key, New(tls, cli)) +func RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key, tls string) { + cfg.ComponentSet(key, New(ctx, tls)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentRequest { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentRequest { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentRequest); !ok { diff --git a/config/components/request/model.go b/config/components/request/model.go index 7d291d7b..e0a9c32f 100644 --- a/config/components/request/model.go +++ b/config/components/request/model.go @@ -27,214 +27,31 @@ package request import ( - "context" "sync" - libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" - cpttls "github.com/nabbar/golib/config/components/tls" - liberr "github.com/nabbar/golib/errors" + libctx "github.com/nabbar/golib/context" + montps "github.com/nabbar/golib/monitor/types" libreq "github.com/nabbar/golib/request" ) type componentRequest struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - sts libcfg.FuncRouteStatus - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex + m sync.RWMutex + x libctx.Config[uint8] r libreq.Request t string - c libreq.FctHttpClient -} - -func (c *componentRequest) _GetContext() context.Context { - c.m.Lock() - defer c.m.Unlock() - - if c.ctx != nil { - if x := c.ctx(); x != nil { - return x - } - } - - return context.Background() -} - -func (c *componentRequest) _CheckInit() bool { - return c != nil && c.r != nil -} - -func (c *componentRequest) _GetTLS() libtls.TLSConfig { - if c.t == "" { - return nil - } - - if i := cpttls.Load(c.get, c.t); i == nil { - return nil - } else if tls := i.GetTLS(); tls == nil { - return nil - } else { - return tls - } -} - -func (c *componentRequest) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.r != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentRequest) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentRequest) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - cfg := libreq.Options{} - cfg.SetDefaultTLS(c._GetTLS) - - if err := getCfg(c.key, &cfg); err != nil { - return ErrorParamInvalid.Error(err) - } - - if c.r == nil { - if r, e := cfg.New(c._GetContext, c.c, c._GetTLS); e != nil { - return ErrorConfigInvalid.ErrorParent(e) - } else { - c.r = r - } - } else { - if r, e := cfg.Update(c.r, c._GetContext, c.c, c._GetTLS); e != nil { - return ErrorConfigInvalid.ErrorParent(e) - } else { - c.r = r - } - } - - if c.sts != nil { - if s := c.sts(); s != nil { - c.r.StatusRegister(s, c.key) - } - } - - return nil -} - -func (c *componentRequest) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentRequest) Type() string { - return ComponentType -} - -func (c *componentRequest) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr - c.sts = sts -} - -func (c *componentRequest) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentRequest) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentRequest) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c != nil && c.r != nil -} - -func (c *componentRequest) IsRunning(atLeast bool) bool { - return c.IsStarted() -} - -func (c *componentRequest) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentRequest) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentRequest) Stop() { - -} - -func (c *componentRequest) Dependencies() []string { - c.m.Lock() - defer c.m.Unlock() - - if c == nil || c.t == "" { - return []string{cpttls.ComponentType} - } - - return []string{c.t} -} - -func (c *componentRequest) SetHTTPClient(fct libreq.FctHttpClient) { - c.m.Lock() - defer c.m.Unlock() - - c.c = fct + p montps.FuncPool } -func (c *componentRequest) SetDefaultTLS(key string) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentRequest) SetDefaultTLS(key string) { + o.m.Lock() + defer o.m.Unlock() - c.t = key + o.t = key } -func (c *componentRequest) Request() (libreq.Request, error) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentRequest) Request() (libreq.Request, error) { + o.m.RLock() + defer o.m.RUnlock() - return c.r.Clone() + return o.r.Clone() } diff --git a/config/components/request/monitor.go b/config/components/request/monitor.go new file mode 100644 index 00000000..d8abb33d --- /dev/null +++ b/config/components/request/monitor.go @@ -0,0 +1,133 @@ +/* + * 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 request + +import ( + "context" + + montps "github.com/nabbar/golib/monitor/types" + libreq "github.com/nabbar/golib/request" + libver "github.com/nabbar/golib/version" +) + +func (o *componentRequest) RegisterMonitorPool(fct montps.FuncPool) { + o.m.Lock() + defer o.m.Unlock() + + o.p = fct +} + +func (o *componentRequest) _getMonitorPool() montps.Pool { + o.m.RLock() + defer o.m.RUnlock() + + if o.p == nil { + return nil + } else if p := o.p(); p == nil { + return nil + } else { + return p + } +} + +func (o *componentRequest) _registerMonitor(cfg *libreq.Options) error { + var ( + e error + key = o._getKey() + mon montps.Monitor + vrs = o._getVersion() + ctx = o._getContext() + ) + + if o._getMonitorPool() == nil { + return nil + } else if len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } else if cfg == nil { + return ErrorConfigInvalid.Error(nil) + } else if !o.IsStarted() { + return ErrorComponentStart.Error(nil) + } else if ctx == nil { + ctx = context.Background() + } + + if mon, e = o._newMonitor(ctx, vrs); e != nil { + return e + } + + if m := o._getMonitor(mon.Name()); m != nil { + mon = m + } + + if mon == nil { + return nil + } + + mon.RegisterLoggerDefault(o.getLogger) + + if e = mon.SetConfig(o.x.GetContext, cfg.Health.Monitor); e != nil { + return e + } + + if e = mon.Restart(o.x.GetContext()); e != nil { + return e + } else if e = o._setMonitor(mon); e != nil { + return e + } + + return nil +} + +func (o *componentRequest) _newMonitor(ctx context.Context, vrs libver.Version) (montps.Monitor, error) { + o.m.RLock() + defer o.m.RUnlock() + return o.r.Monitor(ctx, vrs) +} + +func (o *componentRequest) _getMonitor(key string) montps.Monitor { + var ( + mon montps.Monitor + pol = o._getMonitorPool() + ) + + if pol == nil { + return nil + } + + mon = pol.MonitorGet(key) + return mon +} + +func (o *componentRequest) _setMonitor(mon montps.Monitor) error { + var pol = o._getMonitorPool() + + if pol == nil { + return nil + } + + return pol.MonitorSet(mon) +} diff --git a/config/components/smtp/client.go b/config/components/smtp/client.go new file mode 100644 index 00000000..7d57ea1c --- /dev/null +++ b/config/components/smtp/client.go @@ -0,0 +1,233 @@ +/* + * 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 smtp + +import ( + "context" + "crypto/tls" + + libtls "github.com/nabbar/golib/certificates" + cpttls "github.com/nabbar/golib/config/components/tls" + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + moncfg "github.com/nabbar/golib/monitor/types" + lbsmtp "github.com/nabbar/golib/smtp" + smtpcf "github.com/nabbar/golib/smtp/config" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentSmtp) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentSmtp) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentSmtp) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentSmtp) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentSmtp) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentSmtp) _getContext() context.Context { + o.m.RLock() + defer o.m.RUnlock() + + return o.x.GetContext() +} + +func (o *componentSmtp) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentSmtp) _GetTLS() libtls.TLSConfig { + o.m.RLock() + defer o.m.RUnlock() + + if o.t == "" { + return nil + } + + if i := cpttls.Load(o._getFctCpt(), o.t); i == nil { + return nil + } else if tls := i.GetTLS(); tls == nil { + return nil + } else { + return tls + } +} + +func (o *componentSmtp) _GetTLSConfig(cfg libtls.Config) *tls.Config { + if i, e := cfg.NewFrom(o._GetTLS()); e != nil { + return &tls.Config{} + } else { + return i.TlsConfig("") + } +} + +func (o *componentSmtp) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentSmtp) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentSmtp) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentSmtp) _runCli() liberr.Error { + var ( + e error + err liberr.Error + prt = ErrorComponentReload + obj lbsmtp.SMTP + cfg smtpcf.Config + mon *moncfg.Config + ) + + if !o.IsStarted() { + prt = ErrorComponentStart + } + + if cfg, mon, err = o._getConfig(); err != nil { + return prt.Error(err) + } else if obj, err = lbsmtp.New(cfg, o._GetTLSConfig(cfg.GetTls())); err != nil { + return prt.Error(err) + } + + o.Stop() + + o.m.Lock() + o.s = obj + o.m.Unlock() + + if e = o._registerMonitor(mon); e != nil { + return prt.ErrorParent(e) + } + + return nil +} + +func (o *componentSmtp) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/smtp/component.go b/config/components/smtp/component.go new file mode 100644 index 00000000..1abbfa80 --- /dev/null +++ b/config/components/smtp/component.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 smtp + +import ( + cpttls "github.com/nabbar/golib/config/components/tls" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "smtp" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentSmtp) Type() string { + return ComponentType +} + +func (o *componentSmtp) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentSmtp) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentSmtp) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentSmtp) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o != nil && len(o.t) > 0 && o.s != nil +} + +func (o *componentSmtp) IsRunning() bool { + if !o.IsStarted() { + return false + } + + o.m.RLock() + defer o.m.RUnlock() + + return o.s.Check(o.x.GetContext()) == nil +} + +func (o *componentSmtp) Start() liberr.Error { + return o._run() +} + +func (o *componentSmtp) Reload() liberr.Error { + return o._run() +} + +func (o *componentSmtp) Stop() { + o.m.Lock() + defer o.m.Unlock() + + if o.s != nil { + o.s.Close() + } + + o.s = nil + return +} + +func (o *componentSmtp) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = []string{cpttls.ComponentType} + + if o == nil { + return def + } else if len(o.t) > 0 { + def = []string{o.t} + } + + if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentSmtp) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentSmtp) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/smtp/config.go b/config/components/smtp/config.go new file mode 100644 index 00000000..d67b6ffd --- /dev/null +++ b/config/components/smtp/config.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 smtp + +import ( + liberr "github.com/nabbar/golib/errors" + libmon "github.com/nabbar/golib/monitor/types" + smtpcf "github.com/nabbar/golib/smtp/config" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentSmtp) RegisterFlag(Command *spfcbr.Command) error { + var ( + key string + vpr *spfvpr.Viper + ) + + if vpr = o._getSPFViper(); vpr == nil { + return ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } + + _ = Command.PersistentFlags().String(key+".dsn", "", "A DSN like string to describe the smtp connection. Format allowed is [user[:password]@][net[(addr)]]/tlsmode[?param1=value1¶mN=valueN] ") + + if err := vpr.BindPFlag(key+".dsn", Command.PersistentFlags().Lookup(key+".dsn")); err != nil { + return err + } + + return nil +} + +func (o *componentSmtp) _getConfig() (smtpcf.Config, *libmon.Config, liberr.Error) { + var ( + key string + cfg smtpcf.ConfigModel + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, nil, ErrorParamInvalid.ErrorParent(e) + } + + if val := vpr.GetString(key + "dsn"); val != "" { + cfg.DSN = val + } + + if err = cfg.Validate(); err != nil { + return nil, nil, ErrorConfigInvalid.Error(err) + } + + if c, e := cfg.Config(); e != nil { + return nil, nil, e + } else { + return c, &cfg.Monitor, nil + } +} diff --git a/config/components/smtp/default.go b/config/components/smtp/default.go index 7a082720..129cd907 100644 --- a/config/components/smtp/default.go +++ b/config/components/smtp/default.go @@ -30,20 +30,15 @@ import ( "bytes" "encoding/json" - libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" cpttls "github.com/nabbar/golib/config/components/tls" - liberr "github.com/nabbar/golib/errors" - libsmtp "github.com/nabbar/golib/smtp" - libsts "github.com/nabbar/golib/status/config" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgcst "github.com/nabbar/golib/config/const" + moncfg "github.com/nabbar/golib/monitor/types" ) var _defaultConfig = []byte(`{ "dsn": "", - "tls": ` + string(cpttls.DefaultConfig(libcfg.JSONIndent)) + `, - "status": ` + string(libsts.DefaultConfig(libcfg.JSONIndent)) + ` + "tls": ` + string(cpttls.DefaultConfig(cfgcst.JSONIndent)) + `, + "monitor": ` + string(moncfg.DefaultConfig(cfgcst.JSONIndent)) + ` }`) func SetDefaultConfig(cfg []byte) { @@ -52,58 +47,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentSmtp) DefaultConfig(indent string) []byte { +func (o *componentSmtp) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentSmtp) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - _ = Command.PersistentFlags().String(c.key+".dsn", "", "A DSN like string to describe the smtp connection. Format allowed is [user[:password]@][net[(addr)]]/tlsmode[?param1=value1¶mN=valueN] ") - - if err := Viper.BindPFlag(c.key+".dsn", Command.PersistentFlags().Lookup(c.key+".dsn")); err != nil { - return err - } - - return nil -} - -func (c *componentSmtp) _getConfig(getCfg libcfg.FuncComponentConfigGet) (libsmtp.ConfigModel, liberr.Error) { - var ( - cfg = libsmtp.ConfigModel{} - vpr = c.vpr() - err liberr.Error - ) - - if e := getCfg(c.key, &cfg); e != nil { - return cfg, ErrorParamInvalid.Error(e) - } - - if val := vpr.GetString(c.key + "dsn"); val != "" { - cfg.DSN = val - } - - if err = cfg.Validate(); err != nil { - return cfg, ErrorConfigInvalid.Error(err) - } - - cfg.RegisterDefaultTLS(func() libtls.TLSConfig { - var ( - t libtls.TLSConfig - e liberr.Error - ) - - if t, e = c._GetTLS(); e != nil { - return t - } else { - return nil - } - }) - - return cfg, nil -} diff --git a/config/components/smtp/errors.go b/config/components/smtp/errors.go index f37d3a1f..15fd2cc7 100644 --- a/config/components/smtp/errors.go +++ b/config/components/smtp/errors.go @@ -38,10 +38,8 @@ const ( ErrorParamInvalid ErrorComponentNotInitialized ErrorConfigInvalid - ErrorStartComponent - ErrorReloadComponent - ErrorDependencyTLSDefault - ErrorDependencyLogDefault + ErrorComponentStart + ErrorComponentReload ) func init() { @@ -61,14 +59,10 @@ func getMessage(code liberr.CodeError) (message string) { return "this component seems to not be correctly initialized" case ErrorConfigInvalid: return "invalid component config" - case ErrorStartComponent: + case ErrorComponentStart: return "cannot start component with config" - case ErrorReloadComponent: + case ErrorComponentReload: return "cannot reload component with new config" - case ErrorDependencyTLSDefault: - return "cannot retrieve TLS Component" - case ErrorDependencyLogDefault: - return "cannot retrieve Logger Component" } return liberr.NullMessage diff --git a/config/components/smtp/interface.go b/config/components/smtp/interface.go index 77f3a19c..c31792a4 100644 --- a/config/components/smtp/interface.go +++ b/config/components/smtp/interface.go @@ -31,38 +31,29 @@ import ( libcfg "github.com/nabbar/golib/config" cpttls "github.com/nabbar/golib/config/components/tls" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libsmtp "github.com/nabbar/golib/smtp" - libsts "github.com/nabbar/golib/status" -) - -const ( - ComponentType = "smtp" + lbsmtp "github.com/nabbar/golib/smtp" ) type ComponentSMTP interface { - libcfg.Component + cfgtps.Component SetTLSKey(tlsKey string) - GetSMTP() (libsmtp.SMTP, liberr.Error) - SetStatusRouter(sts libsts.RouteStatus, prefix string) + GetSMTP() (lbsmtp.SMTP, liberr.Error) } -func New(tlsKey string) ComponentSMTP { +func New(ctx libctx.FuncContext, tlsKey string) ComponentSMTP { if tlsKey == "" { tlsKey = cpttls.ComponentType } return &componentSmtp{ - ctx: nil, - get: nil, - fsa: nil, - fsb: nil, - fra: nil, - frb: nil, - m: sync.Mutex{}, - t: tlsKey, - s: nil, + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), + t: tlsKey, + s: nil, } } @@ -70,11 +61,11 @@ func Register(cfg libcfg.Config, key string, cpt ComponentSMTP) { cfg.ComponentSet(key, cpt) } -func RegisterNew(cfg libcfg.Config, key, tlsKey string) { - cfg.ComponentSet(key, New(tlsKey)) +func RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key, tlsKey string) { + cfg.ComponentSet(key, New(ctx, tlsKey)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentSMTP { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentSMTP { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentSMTP); !ok { diff --git a/config/components/smtp/model.go b/config/components/smtp/model.go index a29883c7..52bc0028 100644 --- a/config/components/smtp/model.go +++ b/config/components/smtp/model.go @@ -29,214 +29,34 @@ package smtp import ( "sync" - libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" - cpttls "github.com/nabbar/golib/config/components/tls" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" + montps "github.com/nabbar/golib/monitor/types" libsmtp "github.com/nabbar/golib/smtp" - libsts "github.com/nabbar/golib/status" ) type componentSmtp struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex + m sync.RWMutex + x libctx.Config[uint8] + p montps.FuncPool t string s libsmtp.SMTP } -func (c *componentSmtp) _CheckDep() bool { - return c != nil && c.t != "" -} - -func (c *componentSmtp) _GetTLS() (libtls.TLSConfig, liberr.Error) { - if !c._CheckDep() { - return nil, ErrorComponentNotInitialized.Error(nil) - } - - if i := cpttls.Load(c.get, c.t); 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 *componentSmtp) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.s != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentSmtp) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentSmtp) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() +func (o *componentSmtp) SetTLSKey(tlsKey string) { + o.m.Lock() + defer o.m.Unlock() - var ( - err liberr.Error - cli libsmtp.SMTP - cfg libsmtp.ConfigModel - ) - - if cfg, err = c._getConfig(getCfg); err != nil { - return err - } - - if cli, err = cfg.GetSMTP(); err != nil { - if c.s != nil { - return ErrorReloadComponent.Error(err) - } - return ErrorStartComponent.Error(err) - } - - if c.s != nil { - _ = c.s.Close - } - - c.s = cli - return nil -} - -func (c *componentSmtp) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - if !c._CheckDep() { - return ErrorComponentNotInitialized.Error(nil) - } - - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil + o.t = tlsKey } -func (c *componentSmtp) Type() string { - return ComponentType -} - -func (c *componentSmtp) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentSmtp) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentSmtp) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentSmtp) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c._CheckDep() && c.s != nil -} - -func (c *componentSmtp) IsRunning(atLeast bool) bool { - if !c.IsStarted() { - return false - } - - c.m.Lock() - defer c.m.Unlock() - - e := c.s.Check(c.ctx()) - return e == nil -} - -func (c *componentSmtp) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentSmtp) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentSmtp) Stop() { - if !c.IsStarted() { - return - } - - c.m.Lock() - defer c.m.Unlock() - - c.s.Close() -} - -func (c *componentSmtp) Dependencies() []string { - c.m.Lock() - defer c.m.Unlock() - - if !c._CheckDep() { - return []string{cpttls.ComponentType} - } - - return []string{c.t} -} - -func (c *componentSmtp) SetTLSKey(tlsKey string) { - c.m.Lock() - defer c.m.Unlock() - - c.t = tlsKey -} - -func (c *componentSmtp) GetSMTP() (libsmtp.SMTP, liberr.Error) { - if !c.IsStarted() { +func (o *componentSmtp) GetSMTP() (libsmtp.SMTP, liberr.Error) { + if !o.IsStarted() { return nil, ErrorComponentNotInitialized.Error(nil) } - c.m.Lock() - defer c.m.Unlock() - - return c.s.Clone(), nil -} - -func (c *componentSmtp) SetStatusRouter(sts libsts.RouteStatus, prefix string) { - c.m.Lock() - defer c.m.Unlock() + o.m.Lock() + defer o.m.Unlock() - c.s.StatusRouter(sts, prefix) + return o.s.Clone(), nil } diff --git a/config/components/smtp/monitor.go b/config/components/smtp/monitor.go new file mode 100644 index 00000000..a7bb8b05 --- /dev/null +++ b/config/components/smtp/monitor.go @@ -0,0 +1,129 @@ +/* + * 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 smtp + +import ( + "context" + + libctx "github.com/nabbar/golib/context" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +func (o *componentSmtp) RegisterMonitorPool(fct montps.FuncPool) { + o.m.Lock() + defer o.m.Unlock() + + o.p = fct +} + +func (o *componentSmtp) _getMonitorPool() montps.Pool { + o.m.RLock() + defer o.m.RUnlock() + + if o.p == nil { + return nil + } else if p := o.p(); p == nil { + return nil + } else { + return p + } +} + +func (o *componentSmtp) _registerMonitor(cfg *montps.Config) error { + var ( + e error + key = o._getKey() + mon montps.Monitor + vrs = o._getVersion() + ctx = o._getContext + ) + + if o._getMonitorPool() == nil { + return nil + } else if len(key) < 1 { + return ErrorComponentNotInitialized.Error(nil) + } else if cfg == nil { + return ErrorConfigInvalid.Error(nil) + } else if !o.IsStarted() { + return ErrorComponentStart.Error(nil) + } else if ctx == nil { + ctx = context.Background + } + + if mon = o._getMonitor(key); mon == nil { + if mon, e = o._newMonitor(ctx, vrs); e != nil { + return e + } else if mon == nil { + return nil + } + } + + mon.RegisterLoggerDefault(o.getLogger) + + if e = mon.SetConfig(o.x.GetContext, *cfg); e != nil { + return e + } + + if e = mon.Restart(o.x.GetContext()); e != nil { + return e + } else if e = o._setMonitor(mon); e != nil { + return e + } + + return nil +} + +func (o *componentSmtp) _newMonitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) { + o.m.RLock() + defer o.m.RUnlock() + return o.s.Monitor(ctx, vrs) +} + +func (o *componentSmtp) _getMonitor(key string) montps.Monitor { + var ( + mon montps.Monitor + pol = o._getMonitorPool() + ) + + if pol == nil { + return nil + } + + mon = pol.MonitorGet(key) + return mon +} + +func (o *componentSmtp) _setMonitor(mon montps.Monitor) error { + var pol = o._getMonitorPool() + + if pol == nil { + return nil + } + + return pol.MonitorSet(mon) +} diff --git a/config/components/tls/client.go b/config/components/tls/client.go new file mode 100644 index 00000000..09bd5ed6 --- /dev/null +++ b/config/components/tls/client.go @@ -0,0 +1,188 @@ +/* + * 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" + cfgtps "github.com/nabbar/golib/config/types" + liberr "github.com/nabbar/golib/errors" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvbr "github.com/spf13/viper" +) + +func (o *componentTls) _getKey() string { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptKey); !l { + return "" + } else if i == nil { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *componentTls) _getFctVpr() libvpr.FuncViper { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctViper); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(libvpr.FuncViper); !k { + return nil + } else { + return f + } +} + +func (o *componentTls) _getViper() libvpr.Viper { + if f := o._getFctVpr(); f == nil { + return nil + } else if v := f(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentTls) _getSPFViper() *spfvbr.Viper { + if f := o._getViper(); f == nil { + return nil + } else if v := f.Viper(); v == nil { + return nil + } else { + return v + } +} + +func (o *componentTls) _getFctCpt() cfgtps.FuncCptGet { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyFctGetCpt); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptGet); !k { + return nil + } else { + return f + } +} + +func (o *componentTls) _getVersion() libver.Version { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(keyCptVersion); !l { + return nil + } else if i == nil { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else { + return v + } +} + +func (o *componentTls) _getFct() (cfgtps.FuncCptEvent, cfgtps.FuncCptEvent) { + if o.IsStarted() { + return o._getFctEvt(keyFctRelBef), o._getFctEvt(keyFctRelAft) + } else { + return o._getFctEvt(keyFctStaBef), o._getFctEvt(keyFctStaAft) + } +} + +func (o *componentTls) _getFctEvt(key uint8) cfgtps.FuncCptEvent { + o.m.RLock() + defer o.m.RUnlock() + + if i, l := o.x.Load(key); !l { + return nil + } else if i == nil { + return nil + } else if f, k := i.(cfgtps.FuncCptEvent); !k { + return nil + } else { + return f + } +} + +func (o *componentTls) _runFct(fct func(cpt cfgtps.Component) liberr.Error) liberr.Error { + if fct != nil { + return fct(o) + } + + return nil +} + +func (o *componentTls) _runCli() liberr.Error { + var ( + err liberr.Error + prt = ErrorComponentReload + tls libtls.TLSConfig + cfg *libtls.Config + ) + + if !o.IsStarted() { + prt = ErrorComponentStart + } + + if cfg, err = o._getConfig(); err != nil { + return prt.Error(err) + } else if tls, err = cfg.New(); err != nil { + return prt.Error(err) + } + + o.m.Lock() + defer o.m.Unlock() + + o.t = tls + o.c = cfg + + return nil +} + +func (o *componentTls) _run() liberr.Error { + fb, fa := o._getFct() + + if err := o._runFct(fb); err != nil { + return err + } else if err = o._runCli(); err != nil { + return err + } else if err = o._runFct(fa); err != nil { + return err + } + + return nil +} diff --git a/config/components/tls/component.go b/config/components/tls/component.go new file mode 100644 index 00000000..6d7722ad --- /dev/null +++ b/config/components/tls/component.go @@ -0,0 +1,155 @@ +/* + * 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 ( + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" +) + +const ( + ComponentType = "tls" + + keyCptKey = iota + 1 + keyCptDependencies + keyFctViper + keyFctGetCpt + keyCptVersion + keyCptLogger + keyFctStaBef + keyFctStaAft + keyFctRelBef + keyFctRelAft + keyFctMonitorPool +) + +func (o *componentTls) Type() string { + return ComponentType +} + +func (o *componentTls) Init(key string, ctx libctx.FuncContext, get cfgtps.FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) { + o.m.Lock() + defer o.m.Unlock() + + if o.x == nil { + o.x = libctx.NewConfig[uint8](ctx) + } else { + x := libctx.NewConfig[uint8](ctx) + x.Merge(o.x) + o.x = x + } + + o.x.Store(keyCptKey, key) + o.x.Store(keyFctGetCpt, get) + o.x.Store(keyFctViper, vpr) + o.x.Store(keyCptVersion, vrs) + o.x.Store(keyCptLogger, log) +} + +func (o *componentTls) RegisterFuncStart(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctStaBef, before) + o.x.Store(keyFctStaAft, after) +} + +func (o *componentTls) RegisterFuncReload(before, after cfgtps.FuncCptEvent) { + o.x.Store(keyFctRelBef, before) + o.x.Store(keyFctRelAft, after) +} + +func (o *componentTls) IsStarted() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o.t != nil +} + +func (o *componentTls) IsRunning() bool { + return o.IsStarted() +} + +func (o *componentTls) Start() liberr.Error { + return o._run() +} + +func (o *componentTls) Reload() liberr.Error { + return o._run() +} + +func (o *componentTls) Stop() { + o.m.Lock() + defer o.m.Unlock() + + o.t = nil + return +} + +func (o *componentTls) Dependencies() []string { + o.m.RLock() + defer o.m.RUnlock() + + var def = make([]string, 0) + + if o == nil { + return def + } else if o.x == nil { + return def + } else if i, l := o.x.Load(keyCptDependencies); !l { + return def + } else if v, k := i.([]string); !k { + return def + } else if len(v) > 0 { + return v + } else { + return def + } +} + +func (o *componentTls) SetDependencies(d []string) liberr.Error { + o.m.RLock() + defer o.m.RUnlock() + + if o.x == nil { + return ErrorComponentNotInitialized.Error(nil) + } else { + o.x.Store(keyCptDependencies, d) + return nil + } +} + +func (o *componentTls) getLogger() liblog.Logger { + if i, l := o.x.Load(keyCptLogger); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} diff --git a/config/components/tls/config.go b/config/components/tls/config.go new file mode 100644 index 00000000..5e85c7b8 --- /dev/null +++ b/config/components/tls/config.go @@ -0,0 +1,63 @@ +/* + * 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" + liberr "github.com/nabbar/golib/errors" + spfcbr "github.com/spf13/cobra" + spfvpr "github.com/spf13/viper" +) + +func (o *componentTls) RegisterFlag(Command *spfcbr.Command) error { + return nil +} + +func (o *componentTls) _getConfig() (*libtls.Config, liberr.Error) { + var ( + key string + cfg libtls.Config + vpr *spfvpr.Viper + err liberr.Error + ) + + if vpr = o._getSPFViper(); vpr == nil { + return nil, ErrorComponentNotInitialized.Error(nil) + } else if key = o._getKey(); len(key) < 1 { + return nil, ErrorComponentNotInitialized.Error(nil) + } + + if e := vpr.UnmarshalKey(key, &cfg); e != nil { + return nil, ErrorParamInvalid.ErrorParent(e) + } + + if err = cfg.Validate(); err != nil { + return nil, ErrorConfigInvalid.Error(err) + } + + return &cfg, nil +} diff --git a/config/components/tls/default.go b/config/components/tls/default.go index f74ddd3d..4682b65d 100644 --- a/config/components/tls/default.go +++ b/config/components/tls/default.go @@ -30,11 +30,7 @@ import ( "bytes" "encoding/json" - libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" - spfcbr "github.com/spf13/cobra" - spfvbr "github.com/spf13/viper" + cfgtps "github.com/nabbar/golib/config/const" ) var _defaultConfig = []byte(`{ @@ -100,31 +96,13 @@ func SetDefaultConfig(cfg []byte) { func DefaultConfig(indent string) []byte { var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { + if err := json.Indent(res, _defaultConfig, indent, cfgtps.JSONIndent); err != nil { return _defaultConfig } else { return res.Bytes() } } -func (c *componentTls) DefaultConfig(indent string) []byte { +func (o *componentTls) DefaultConfig(indent string) []byte { return DefaultConfig(indent) } - -func (c *componentTls) RegisterFlag(Command *spfcbr.Command, Viper *spfvbr.Viper) error { - return nil -} - -func (c *componentTls) _getConfig(getCfg libcfg.FuncComponentConfigGet) (*libtls.Config, liberr.Error) { - cfg := libtls.Config{} - - if err := getCfg(c.key, &cfg); err != nil { - return nil, ErrorParamInvalid.Error(err) - } - - if err := cfg.Validate(); err != nil { - return nil, ErrorConfigInvalid.Error(err) - } - - return &cfg, nil -} diff --git a/config/components/tls/interface.go b/config/components/tls/interface.go index 01eabf41..0b02f060 100644 --- a/config/components/tls/interface.go +++ b/config/components/tls/interface.go @@ -27,24 +27,27 @@ package tls import ( + "sync" + libtls "github.com/nabbar/golib/certificates" libcfg "github.com/nabbar/golib/config" -) - -const ( - ComponentType = "tls" + cfgtps "github.com/nabbar/golib/config/types" + libctx "github.com/nabbar/golib/context" ) type ComponentTlS interface { - libcfg.Component + cfgtps.Component Config() *libtls.Config GetTLS() libtls.TLSConfig SetTLS(tls libtls.TLSConfig) } -func New() ComponentTlS { +func New(ctx libctx.FuncContext) ComponentTlS { return &componentTls{ + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), t: nil, + c: nil, } } @@ -52,11 +55,11 @@ 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 RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string) { + cfg.ComponentSet(key, New(ctx)) } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentTlS { +func Load(getCpt cfgtps.FuncCptGet, key string) ComponentTlS { if c := getCpt(key); c == nil { return nil } else if h, ok := c.(ComponentTlS); !ok { diff --git a/config/components/tls/model.go b/config/components/tls/model.go index 596950dc..ac0b1ab8 100644 --- a/config/components/tls/model.go +++ b/config/components/tls/model.go @@ -30,158 +30,33 @@ import ( "sync" libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" + libctx "github.com/nabbar/golib/context" ) type componentTls struct { - ctx libcfg.FuncContext - get libcfg.FuncComponentGet - vpr libcfg.FuncComponentViper - key string - - fsa func(cpt libcfg.Component) liberr.Error - fsb func(cpt libcfg.Component) liberr.Error - fra func(cpt libcfg.Component) liberr.Error - frb func(cpt libcfg.Component) liberr.Error - - m sync.Mutex + m sync.RWMutex + x libctx.Config[uint8] t libtls.TLSConfig c *libtls.Config } -func (c *componentTls) _getFct() (func(cpt libcfg.Component) liberr.Error, func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - if c.t != nil { - return c.frb, c.fra - } else { - return c.fsb, c.fsa - } -} - -func (c *componentTls) _runFct(fct func(cpt libcfg.Component) liberr.Error) liberr.Error { - if fct != nil { - return fct(c) - } - - return nil -} - -func (c *componentTls) _runCli(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - var ( - err liberr.Error - cfg *libtls.Config - tls libtls.TLSConfig - ) - - if cfg, err = c._getConfig(getCfg); err != nil { - return err - } else if tls, err = cfg.New(); err != nil { - if c.t != nil { - return ErrorComponentReload.Error(err) - } - return ErrorComponentStart.Error(err) - } else { - c.t = tls - c.c = cfg - } - - return nil -} - -func (c *componentTls) _run(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - fb, fa := c._getFct() - - if err := c._runFct(fb); err != nil { - return err - } else if err = c._runCli(getCfg); err != nil { - return err - } else if err = c._runFct(fa); err != nil { - return err - } - - return nil -} - -func (c *componentTls) Type() string { - return ComponentType -} - -func (c *componentTls) Init(key string, ctx libcfg.FuncContext, get libcfg.FuncComponentGet, vpr libcfg.FuncComponentViper, sts libcfg.FuncRouteStatus) { - c.m.Lock() - defer c.m.Unlock() - - c.key = key - c.ctx = ctx - c.get = get - c.vpr = vpr -} - -func (c *componentTls) RegisterFuncStart(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.fsb = before - c.fsa = after -} - -func (c *componentTls) RegisterFuncReload(before, after func(cpt libcfg.Component) liberr.Error) { - c.m.Lock() - defer c.m.Unlock() - - c.frb = before - c.fra = after -} - -func (c *componentTls) IsStarted() bool { - c.m.Lock() - defer c.m.Unlock() - - return c.t != nil -} - -func (c *componentTls) IsRunning(atLeast bool) bool { - return c.IsStarted() -} - -func (c *componentTls) Start(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentTls) Reload(getCfg libcfg.FuncComponentConfigGet) liberr.Error { - return c._run(getCfg) -} - -func (c *componentTls) Stop() { - return -} - -func (c *componentTls) Dependencies() []string { - return make([]string, 0) -} - -func (c *componentTls) Config() *libtls.Config { - c.m.Lock() - defer c.m.Unlock() +func (o *componentTls) Config() *libtls.Config { + o.m.Lock() + defer o.m.Unlock() - return c.c + return o.c } -func (c *componentTls) GetTLS() libtls.TLSConfig { - c.m.Lock() - defer c.m.Unlock() +func (o *componentTls) GetTLS() libtls.TLSConfig { + o.m.Lock() + defer o.m.Unlock() - return c.t + return o.t } -func (c *componentTls) SetTLS(tls libtls.TLSConfig) { - c.m.Lock() - defer c.m.Unlock() +func (o *componentTls) SetTLS(tls libtls.TLSConfig) { + o.m.Lock() + defer o.m.Unlock() - c.t = tls + o.t = tls } diff --git a/config/components/tls/monitor.go b/config/components/tls/monitor.go new file mode 100644 index 00000000..b200517f --- /dev/null +++ b/config/components/tls/monitor.go @@ -0,0 +1,34 @@ +/* + * 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 ( + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *componentTls) RegisterMonitorPool(fct montps.FuncPool) { +} diff --git a/config/const/const.go b/config/const/const.go new file mode 100644 index 00000000..847f7d56 --- /dev/null +++ b/config/const/const.go @@ -0,0 +1,31 @@ +/* + * 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 _const + +const ( + JSONIndent = " " +) diff --git a/config/context.go b/config/context.go new file mode 100644 index 00000000..0f98be16 --- /dev/null +++ b/config/context.go @@ -0,0 +1,65 @@ +/* + * 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 ( + libctx "github.com/nabbar/golib/context" +) + +func (c *configModel) Context() libctx.Config[string] { + return c.ctx +} + +func (c *configModel) CancelAdd(fct ...func()) { + c.m.Lock() + defer c.m.Unlock() + + c.fcnl = append(c.fcnl, fct...) +} + +func (c *configModel) CancelClean() { + c.m.Lock() + defer c.m.Unlock() + + c.fcnl = make([]func(), 0) +} + +func (c *configModel) cancel() { + if l := c.getCancelCustom(); len(l) > 0 { + for _, f := range l { + f() + } + } + + c.Stop() +} + +func (c *configModel) getCancelCustom() []func() { + c.m.RLock() + defer c.m.RUnlock() + return c.fcnl +} diff --git a/config/cptList.go b/config/cptList.go deleted file mode 100644 index d83e889c..00000000 --- a/config/cptList.go +++ /dev/null @@ -1,414 +0,0 @@ -/* - * 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" - "time" - - "golang.org/x/exp/slices" - - liberr "github.com/nabbar/golib/errors" - spfcbr "github.com/spf13/cobra" - spfvpr "github.com/spf13/viper" -) - -const JSONIndent = " " - -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(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(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 - - // RegisterFlag can be called to register flag to a spf cobra command and link it with viper - // to retrieve it into the config viper. - // The key will be use to stay config organisation by compose flag as key.config_key. - RegisterFlag(Command *spfcbr.Command, Viper *spfvpr.Viper) error -} - -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) - } - - delete(c.l, key) -} - -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, 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 { - - var err liberr.Error - - for retry := 0; retry < 3; retry++ { - - if err = c.startOne(k, getCfg); err == nil { - break - } - - time.Sleep(100 * time.Millisecond) - } - - if err != nil { - return err - } - } - } - - if err := cpt.Start(getCfg); err != nil { - return err - } else { - c.ComponentSet(key, cpt) - } - - return nil -} - -func (c *componentList) ComponentStart(getCfg FuncComponentConfigGet) liberr.Error { - for _, key := range c.ComponentKeys() { - if err := c.startOne(key, 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, getCfg FuncComponentConfigGet) ([]string, liberr.Error) { - var ( - err = ErrorComponentReload.Error(nil) - e 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 slices.Contains(isReload, key) { - return isReload, nil - } - - if dep := cpt.Dependencies(); len(dep) > 0 { - for _, k := range dep { - if isReload, e = c.reloadOne(isReload, k, getCfg); e != nil { - err.AddParentError(e) - } - } - } - - if e = cpt.Reload(getCfg); e != nil { - er := ErrorComponentReload.ErrorParent(fmt.Errorf("component: %s", key)) - er.AddParentError(e) - err.AddParentError(er) - } else { - c.ComponentSet(key, cpt) - isReload = append(isReload, key) - } - - if !err.HasParent() { - err = nil - } - - return isReload, err -} - -func (c *componentList) ComponentReload(getCfg FuncComponentConfigGet) liberr.Error { - var ( - err = ErrorComponentReload.Error(nil) - e liberr.Error - key string - - isReload = make([]string, 0) - ) - - for _, key = range c.ComponentKeys() { - if isReload, e = c.reloadOne(isReload, key, getCfg); e != nil { - err.AddParent(e) - } - } - - if !err.HasParent() { - err = nil - } - - return err -} - -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(JSONIndent); len(p) > 0 { - if buffer.Len() > n { - buffer.WriteString(",") - buffer.WriteString("\n") - } - buffer.WriteString(fmt.Sprintf("%s\"%s\": ", JSONIndent, k)) - buffer.Write(p) - } - } - - buffer.WriteString("\n") - buffer.WriteString("}") - - var ( - cmp = bytes.NewBuffer(make([]byte, 0)) - ind = bytes.NewBuffer(make([]byte, 0)) - ) - - if err := json.Compact(cmp, buffer.Bytes()); err != nil { - return buffer - } else if err = json.Indent(ind, cmp.Bytes(), "", JSONIndent); err != nil { - return buffer - } - - return ind -} - -func (c *componentList) RegisterFlag(Command *spfcbr.Command, Viper *spfvpr.Viper) error { - var err = ErrorComponentFlagError.Error(nil) - - for _, k := range c.ComponentKeys() { - if cpt := c.ComponentGet(k); cpt == nil { - continue - } else if e := cpt.RegisterFlag(Command, Viper); e != nil { - err.AddParent(e) - } else { - c.ComponentSet(k, cpt) - } - } - - if err.HasParent() { - return err - } - - return nil -} diff --git a/config/components/natsServer/interface.go b/config/events.go similarity index 55% rename from config/components/natsServer/interface.go rename to config/events.go index 4d40f3a3..c01c1e42 100644 --- a/config/components/natsServer/interface.go +++ b/config/events.go @@ -24,62 +24,53 @@ * */ -package natsServer +package config import ( - "sync" + "os" - libcfg "github.com/nabbar/golib/config" liberr "github.com/nabbar/golib/errors" - libnat "github.com/nabbar/golib/nats" - libsts "github.com/nabbar/golib/status" ) -const ( - DefaultTlsKey = "tls" - ComponentType = "natsServer" -) +func (c *configModel) Start() liberr.Error { + if err := c.runFuncStartBefore(); err != nil { + return err + } -type ComponentNats interface { - libcfg.Component + if err := c.ComponentStart(); err != nil { + return err + } + + if err := c.runFuncStartAfter(); err != nil { + return err + } - SetTLSKey(tlsKey string) - GetServer() (libnat.Server, liberr.Error) - SetStatusRouter(sts libsts.RouteStatus, prefix string) + return nil } -func New(tlsKey string) ComponentNats { - if tlsKey == "" { - tlsKey = DefaultTlsKey +func (c *configModel) Reload() liberr.Error { + if err := c.runFuncReloadBefore(); err != nil { + return err } - return &componentNats{ - ctx: nil, - get: nil, - fsa: nil, - fsb: nil, - fra: nil, - frb: nil, - m: sync.Mutex{}, - t: tlsKey, - n: nil, + if err := c.ComponentReload(); err != nil { + return err + } + + if err := c.runFuncReloadAfter(); err != nil { + return err } -} -func Register(cfg libcfg.Config, key string, cpt ComponentNats) { - cfg.ComponentSet(key, cpt) + return nil } -func RegisterNew(cfg libcfg.Config, key, tlsKey string) { - cfg.ComponentSet(key, New(tlsKey)) +func (c *configModel) Stop() { + _ = c.runFuncStopBefore() + c.ComponentStop() + _ = c.runFuncStopAfter() } -func Load(getCpt libcfg.FuncComponentGet, key string) ComponentNats { - if c := getCpt(key); c == nil { - return nil - } else if h, ok := c.(ComponentNats); !ok { - return nil - } else { - return h - } +func (c *configModel) Shutdown(code int) { + c.cancel() + os.Exit(code) } diff --git a/config/interface.go b/config/interface.go index ba332a03..0f621585 100644 --- a/config/interface.go +++ b/config/interface.go @@ -33,102 +33,96 @@ import ( "sync" "syscall" + liblog "github.com/nabbar/golib/logger" + + cfgtps "github.com/nabbar/golib/config/types" libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" + libver "github.com/nabbar/golib/version" libvpr "github.com/nabbar/golib/viper" - spfvpr "github.com/spf13/viper" ) -type FuncContext func() context.Context -type FuncRouteStatus func() libsts.RouteStatus -type FuncComponentGet func(key string) Component -type FuncComponentViper func() *spfvpr.Viper -type FuncComponentConfigGet func(key string, model interface{}) liberr.Error +type FuncEvent func() 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{} + // Context return the config context instance + Context() libctx.Config[string] - // ContextSetCancel allow to register a custom function called on cancel context. + // CancelAdd allow to register a slice of 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()) + CancelAdd(fct ...func()) + + // CancelClean allow clear the all Cancel func registered into slice + CancelClean() /* - // Section Event : github.com/nabbar/golib/config + // Section Manage : github.com/nabbar/golib/config */ - // RegisterFuncViper is used to expose golib Viper instance to all config component. - // With this function, the component can load his own config part and start or reload. - RegisterFuncViper(fct func() libvpr.Viper) - - // RegisterFuncRouteStatus is used to expose golib Status Router instance to all config component. - // With this function, the component can register component status for router status and expose his own health. - RegisterFuncRouteStatus(fct FuncRouteStatus) - // 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 + // Reload triggers the Reload function of each registered Component. + Reload() liberr.Error + + // Stop will trigger the stop function of all registered component. + // All component must stop cleanly. + Stop() + + // Shutdown will trigger all stop function. + // This function will call the Stop function and the private function cancel. + // This will stop all process and do like a SIGTERM/SIGINT signal. + // This will finish by an os.Exit with the given parameter code. + Shutdown(code int) + + /* + // Section Events : github.com/nabbar/golib/config + */ + + // RegisterFuncViper is used to expose golib Viper instance to all config component. + // With this function, the component can load his own config part and start or reload. + RegisterFuncViper(fct libvpr.FuncViper) + // 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) + RegisterFuncStartBefore(fct FuncEvent) // 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 + RegisterFuncStartAfter(fct FuncEvent) // 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) + RegisterFuncReloadBefore(fct FuncEvent) // 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() + RegisterFuncReloadAfter(fct FuncEvent) // 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()) + RegisterFuncStopBefore(fct FuncEvent) // 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()) + RegisterFuncStopAfter(fct FuncEvent) - // Shutdown will trigger all stop function. - // This function will call the Stop function and the private function cancel. - // This will stop all process and do like a SIGTERM/SIGINT signal. - // This will finish by an os.Exit with the given parameter code. - Shutdown(code int) + // RegisterDefaultLogger allow to register a func to return a default logger. + // This logger can be used by component to extend config ot to log message. + RegisterDefaultLogger(fct liblog.FuncLog) /* // Section Component : github.com/nabbar/golib/config */ - ComponentList + cfgtps.ComponentList + cfgtps.ComponentMonitor } var ( @@ -138,31 +132,43 @@ var ( 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) +func Shutdown() { + cnl() +} - select { - case <-quit: - cnl() - case <-ctx.Done(): - cnl() - } - }() +func WaitNotify() { + // 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 { +func New(vrs libver.Version) Config { + fct := func() context.Context { + return ctx + } + c := &configModel{ - m: sync.Mutex{}, - ctx: libctx.NewConfig(ctx), - cpt: newComponentList(), + m: sync.RWMutex{}, + ctx: libctx.NewConfig[string](fct), + cpt: libctx.NewConfig[string](fct), + fct: libctx.NewConfig[uint8](fct), + fcnl: nil, } + c.RegisterVersion(vrs) + go func() { select { case <-c.ctx.Done(): diff --git a/config/manage.go b/config/manage.go new file mode 100644 index 00000000..ae79e1c3 --- /dev/null +++ b/config/manage.go @@ -0,0 +1,216 @@ +/* + * 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" + liblog "github.com/nabbar/golib/logger" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" + spfvpr "github.com/spf13/viper" +) + +func (c *configModel) RegisterVersion(vrs libver.Version) { + c.fct.Store(fctVersion, vrs) +} + +func (c *configModel) getVersion() libver.Version { + if i, l := c.fct.Load(fctVersion); !l { + return nil + } else if v, k := i.(libver.Version); !k { + return nil + } else if v == nil { + return nil + } else { + return v + } +} + +func (c *configModel) RegisterFuncViper(fct libvpr.FuncViper) { + c.fct.Store(fctViper, fct) +} + +func (c *configModel) getViper() libvpr.Viper { + if i, l := c.fct.Load(fctViper); !l { + return nil + } else if v, k := i.(libvpr.FuncViper); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) getSPFViper() *spfvpr.Viper { + if v := c.getViper(); v == nil { + return nil + } else { + return v.Viper() + } +} + +func (c *configModel) RegisterFuncStartBefore(fct FuncEvent) { + c.fct.Store(fctStartBefore, fct) +} + +func (c *configModel) runFuncStartBefore() liberr.Error { + if i, l := c.fct.Load(fctStartBefore); !l { + return nil + } else if v, k := i.(FuncEvent); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) RegisterFuncStartAfter(fct FuncEvent) { + c.fct.Store(fctStartAfter, fct) +} + +func (c *configModel) runFuncStartAfter() liberr.Error { + if i, l := c.fct.Load(fctStartAfter); !l { + return nil + } else if v, k := i.(FuncEvent); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) RegisterFuncReloadBefore(fct FuncEvent) { + c.fct.Store(fctReloadBefore, fct) +} + +func (c *configModel) runFuncReloadBefore() liberr.Error { + if i, l := c.fct.Load(fctReloadBefore); !l { + return nil + } else if v, k := i.(FuncEvent); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) RegisterFuncReloadAfter(fct FuncEvent) { + c.fct.Store(fctReloadAfter, fct) +} + +func (c *configModel) runFuncReloadAfter() liberr.Error { + if i, l := c.fct.Load(fctReloadAfter); !l { + return nil + } else if v, k := i.(FuncEvent); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) RegisterFuncStopBefore(fct FuncEvent) { + c.fct.Store(fctStopBefore, fct) +} + +func (c *configModel) runFuncStopBefore() liberr.Error { + if i, l := c.fct.Load(fctStopBefore); !l { + return nil + } else if v, k := i.(FuncEvent); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) RegisterFuncStopAfter(fct FuncEvent) { + c.fct.Store(fctStopAfter, fct) +} + +func (c *configModel) runFuncStopAfter() liberr.Error { + if i, l := c.fct.Load(fctStopAfter); !l { + return nil + } else if v, k := i.(FuncEvent); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) RegisterDefaultLogger(fct liblog.FuncLog) { + c.fct.Store(fctLoggerDef, fct) +} + +func (c *configModel) getDefaultLogger() liblog.Logger { + if i, l := c.fct.Load(fctLoggerDef); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} + +func (c *configModel) RegisterMonitorPool(p montps.FuncPool) { + c.fct.Store(fctMonitorPool, p) +} + +func (c *configModel) getFctMonitorPool() montps.FuncPool { + if i, l := c.fct.Load(fctMonitorPool); !l { + return nil + } else if v, k := i.(montps.FuncPool); !k { + return nil + } else if v == nil { + return nil + } else { + return v + } +} + +func (c *configModel) getMonitorPool() montps.Pool { + if i, l := c.fct.Load(fctMonitorPool); !l { + return nil + } else if v, k := i.(montps.FuncPool); !k { + return nil + } else if v == nil { + return nil + } else { + return v() + } +} diff --git a/config/model.go b/config/model.go index 2aa43957..1d7f41a8 100644 --- a/config/model.go +++ b/config/model.go @@ -27,279 +27,43 @@ package config import ( - "context" - "fmt" - "io" - "os" "sync" libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libvpr "github.com/nabbar/golib/viper" - spfcbr "github.com/spf13/cobra" - spfvpr "github.com/spf13/viper" ) -type configModel struct { - m sync.Mutex +const ( + fctViper uint8 = iota + 1 + fctStartBefore + fctStartAfter + fctReloadBefore + fctReloadAfter + fctStopBefore + fctStopAfter + fctVersion + fctLoggerDef + fctMonitorPool +) - ctx libctx.Config - fcnl func() +type configModel struct { + m sync.RWMutex - cpt ComponentList + ctx libctx.Config[string] + cpt libctx.Config[string] + fct libctx.Config[uint8] - fctGolibStatus FuncRouteStatus - fctGolibViper func() libvpr.Viper - fctStartBefore func() liberr.Error - fctStartAfter func() liberr.Error - fctReloadBefore func() liberr.Error - fctReloadAfter func() liberr.Error - fctStopBefore func() - fctStopAfter func() + fcnl []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) 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) RegisterFuncRouteStatus(fct FuncRouteStatus) { - c.fctGolibStatus = fct -} - -func (c *configModel) Start() liberr.Error { - c.m.Lock() - defer c.m.Unlock() - - if c.fctStartBefore != nil { - if err := c.fctStartBefore(); err != nil { - return err - } - } - - if err := c.cpt.ComponentStart(c._ComponentGetConfig); err != nil { - return err - } - - if c.fctStartAfter != nil { - if err := c.fctStartAfter(); err != nil { - return err - } + if vpr := c.getViper(); vpr == nil { + return ErrorConfigMissingViper.Error(nil) + } else if vip := vpr.Viper(); vip == nil { + return ErrorConfigMissingViper.Error(nil) + } else if err := vpr.Viper().UnmarshalKey(key, model); err != nil { + return ErrorComponentConfigError.ErrorParent(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() - - if c.fctReloadBefore != nil { - if err := c.fctReloadBefore(); err != nil { - return err - } - } - - if err := c.cpt.ComponentReload(c._ComponentGetConfig); 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) Shutdown(code int) { - c.cancel() - os.Exit(code) -} - -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) { - fv := func() *spfvpr.Viper { - if c.fctGolibViper == nil { - return nil - } else if vpr := c.fctGolibViper(); vpr == nil { - return nil - } else { - return vpr.Viper() - } - } - - cpt.Init(key, c.Context, c.ComponentGet, fv, c.fctGolibStatus) - 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(getCfg FuncComponentConfigGet) liberr.Error { - return c.cpt.ComponentStart(getCfg) -} - -func (c *configModel) ComponentIsStarted() bool { - return c.cpt.ComponentIsStarted() -} - -func (c *configModel) ComponentReload(getCfg FuncComponentConfigGet) liberr.Error { - return c.cpt.ComponentReload(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() -} - -func (c *configModel) RegisterFlag(Command *spfcbr.Command, Viper *spfvpr.Viper) error { - return c.cpt.RegisterFlag(Command, Viper) -} diff --git a/config/component.go b/config/types/component.go similarity index 72% rename from config/component.go rename to config/types/component.go index 613b3a72..687a2272 100644 --- a/config/component.go +++ b/config/types/component.go @@ -24,31 +24,27 @@ * */ -package config +package types import ( + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" + libvpr "github.com/nabbar/golib/viper" spfcbr "github.com/spf13/cobra" - spfvpr "github.com/spf13/viper" ) -type Component interface { - // Type return the component type. - Type() string - - // Init is called by Config to register some function and value to the component instance. - Init(key string, ctx FuncContext, get FuncComponentGet, vpr FuncComponentViper, sts FuncRouteStatus) +type FuncCptGet func(key string) Component +type FuncCptEvent func(cpt Component) liberr.Error +type ComponentEvent interface { // RegisterFuncStart is called to register the function to be called before and after the start function. - RegisterFuncStart(before, after func(cpt Component) liberr.Error) + RegisterFuncStart(before, after FuncCptEvent) // RegisterFuncReload is called to register the function to be called before and after the reload function. - RegisterFuncReload(before, after func(cpt Component) liberr.Error) - - // RegisterFlag can be called to register flag to a spf cobra command and link it with viper - // to retrieve it into the config viper. - // The key will be use to stay config organisation by compose flag as key.config_key. - RegisterFlag(Command *spfcbr.Command, Viper *spfvpr.Viper) error + RegisterFuncReload(before, after FuncCptEvent) // 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. @@ -58,22 +54,43 @@ type Component interface { // 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 + IsRunning() bool // 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(getCfg FuncComponentConfigGet) liberr.Error + Start() liberr.Error // 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(getCfg FuncComponentConfigGet) liberr.Error + Reload() 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() +} + +type ComponentViper interface { + // RegisterFlag can be called to register flag to a spf cobra command and link it with viper + // to retrieve it into the config viper. + // The key will be use to stay config organisation by compose flag as key.config_key. + RegisterFlag(Command *spfcbr.Command) error +} + +type ComponentMonitor interface { + // RegisterMonitorPool is called to register the function to register a monitor into a pool. + // This function enable the monitoring of the component. + RegisterMonitorPool(p montps.FuncPool) +} + +type Component interface { + // Type return the component type. + Type() string + + // Init is called by Config to register some function and value to the component instance. + Init(key string, ctx libctx.FuncContext, get FuncCptGet, vpr libvpr.FuncViper, vrs libver.Version, log liblog.FuncLog) // DefaultConfig is called by Config.GetDefault. // It must return a slice of byte containing the default json config for this component. @@ -82,4 +99,13 @@ type Component interface { // 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 + + // SetDependencies allow to customize the dependencies for the current component. + // The custom dependencies will replace the default dependencies. + // Take care to be sure to include the default dependencies into the custom given as params. + SetDependencies(d []string) liberr.Error + + ComponentViper + ComponentEvent + ComponentMonitor } diff --git a/config/types/componentList.go b/config/types/componentList.go new file mode 100644 index 00000000..6769e7d4 --- /dev/null +++ b/config/types/componentList.go @@ -0,0 +1,99 @@ +/* + * 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 types + +import ( + "io" + + liberr "github.com/nabbar/golib/errors" + spfcbr "github.com/spf13/cobra" +) + +// ComponentListWalkFunc is used for ComponentList.Walk. +// For each component the function is call with the component key and the component as params. +// If the function return false, the loop is breaking, other else, the loop will run the function +// until the end of the component list. +type ComponentListWalkFunc func(key string, cpt Component) bool + +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 + + // ComponentWalk run a function on each component + ComponentWalk(fct ComponentListWalkFunc) + + // 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() 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() 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 asLeast params is false and at least one component return false, this func return false. + // Otherwise, if the params is true, if at least one component is true, this func return true. + ComponentIsRunning(atLeast bool) bool + + // DefaultConfig aggregates all registered components' default config + // Returns a filled buffer with a complete config json model + DefaultConfig() io.Reader + + // RegisterFlag can be called to register flag to a spf cobra command and link it with viper + // to retrieve it into the config viper. + // The key will be use to stay config organisation by compose flag as key.config_key. + RegisterFlag(Command *spfcbr.Command) error +} diff --git a/context/config.go b/context/config.go index 5e262ee8..1482c744 100644 --- a/context/config.go +++ b/context/config.go @@ -27,93 +27,223 @@ package context import ( "context" "sync" - "sync/atomic" + "time" + + "golang.org/x/exp/slices" ) -type Config interface { +type FuncContext func() context.Context +type FuncContextConfig[T comparable] func() Config[T] +type FuncWalk[T comparable] func(key T, val interface{}) bool + +type MapManage[T comparable] interface { + Clean() + Load(key T) (val interface{}, ok bool) + Store(key T, cfg interface{}) + Delete(key T) +} + +type Context interface { + GetContext() context.Context +} + +type Config[T comparable] interface { context.Context + MapManage[T] + Context - Merge(cfg Config) bool + Clone(ctx context.Context) Config[T] + Merge(cfg Config[T]) bool + Walk(fct FuncWalk[T]) bool + WalkLimit(fct FuncWalk[T], validKeys ...T) bool - Store(key string, cfg interface{}) - Load(key string) interface{} + LoadOrStore(key T, cfg interface{}) (val interface{}, loaded bool) + LoadAndDelete(key T) (val interface{}, loaded bool) } -func NewConfig(ctx context.Context) Config { - return &configContext{ - Context: ctx, - cfg: make(map[string]*atomic.Value, 0), +func NewConfig[T comparable](ctx FuncContext) Config[T] { + if ctx == nil { + ctx = context.Background + } + + return &configContext[T]{ + Context: ctx(), + n: sync.RWMutex{}, + m: sync.Map{}, + x: ctx, } } -type configContext struct { +type configContext[T comparable] struct { context.Context - m sync.Mutex - cfg map[string]*atomic.Value + n sync.RWMutex + m sync.Map + x FuncContext } -func (c configContext) Load(key string) interface{} { - c.m.Lock() - defer c.m.Unlock() - - var ( - v *atomic.Value - i interface{} - ok bool - ) - - if c.cfg == nil { - c.cfg = make(map[string]*atomic.Value, 0) - } else if v, ok = c.cfg[key]; !ok || v == nil { - return nil - } else if i = v.Load(); i != nil { - return i +func (c *configContext[T]) Delete(key T) { + if c.Err() != nil { + c.Clean() + return } - return nil + c.n.RLock() + defer c.n.RUnlock() + + c.m.Delete(key) } -func (c configContext) Store(key string, cfg interface{}) { - c.m.Lock() - defer c.m.Unlock() +func (c *configContext[T]) Load(key T) (val interface{}, ok bool) { + c.n.RLock() + defer c.n.RUnlock() - var ok bool + return c.m.Load(key) +} - if c.cfg == nil { - c.cfg = make(map[string]*atomic.Value, 0) +func (c *configContext[T]) Store(key T, cfg interface{}) { + if c.Err() != nil { + c.Clean() + return } - if _, ok = c.cfg[key]; !ok { - c.cfg[key] = new(atomic.Value) + c.n.RLock() + defer c.n.RUnlock() + + c.m.Store(key, cfg) +} + +func (c *configContext[T]) LoadOrStore(key T, cfg interface{}) (val interface{}, loaded bool) { + if c.Err() != nil { + c.Clean() + return nil, false } - c.cfg[key].Store(cfg) + c.n.RLock() + defer c.n.RUnlock() + + return c.m.LoadOrStore(key, cfg) +} + +func (c *configContext[T]) LoadAndDelete(key T) (val interface{}, loaded bool) { + c.n.RLock() + defer c.n.RUnlock() + + return c.m.LoadAndDelete(key) } -func (c *configContext) Merge(cfg Config) bool { - var ( - x *configContext - ok bool - ) +func (c *configContext[T]) Walk(fct FuncWalk[T]) bool { + return c.WalkLimit(fct) +} - if x, ok = cfg.(*configContext); !ok { +func (c *configContext[T]) WalkLimit(fct FuncWalk[T], validKeys ...T) bool { + c.n.RLock() + defer c.n.RUnlock() + + c.m.Range(func(key, value any) bool { + if i, k := key.(T); !k { + return true + } else if len(validKeys) < 1 { + return fct(i, value) + } else if slices.Contains(validKeys, i) { + return fct(i, value) + } + return true + }) + + return true +} + +func (c *configContext[T]) Merge(cfg Config[T]) bool { + if c.Err() != nil { + c.Clean() + return false + } else if cfg == nil { return false } - x.m.Lock() - defer x.m.Unlock() + c.n.RLock() + defer c.n.RUnlock() - for k, v := range x.cfg { - if k == "" || v == nil { - continue - } + cfg.Walk(func(key T, val interface{}) bool { + c.m.Store(key, val) + return true + }) - if i := v.Load(); i == nil { - continue - } else { - c.Store(k, i) + return true +} + +func (c *configContext[T]) Clean() { + c.n.Lock() + defer c.n.Unlock() + + c.m = sync.Map{} +} + +func (c *configContext[T]) Clone(ctx context.Context) Config[T] { + if c.Err() != nil { + c.Clean() + return nil + } + + c.n.RLock() + defer c.n.RUnlock() + + if ctx == nil { + ctx = c.x() + } + + if ctx == nil { + ctx = c.Context + } + + n := &configContext[T]{ + Context: ctx, + n: sync.RWMutex{}, + m: sync.Map{}, + x: c.x, + } + + c.m.Range(func(key any, val interface{}) bool { + if i, k := key.(T); k { + n.Store(i, val) } + return true + }) + + return n +} + +func (c *configContext[T]) Deadline() (deadline time.Time, ok bool) { + return c.Context.Deadline() +} + +func (c *configContext[T]) Done() <-chan struct{} { + return c.Context.Done() +} + +func (c *configContext[T]) Err() error { + return c.Context.Err() +} + +func (c *configContext[T]) Value(key any) any { + if i, k := key.(T); !k { + return c.Context.Value(key) + } else if v, ok := c.Load(i); ok { + return v + } else { + return c.Context.Value(key) } +} - return true +func (c *configContext[T]) GetContext() context.Context { + c.n.RLock() + defer c.n.RUnlock() + + if c.x == nil { + return context.Background() + } else if x := c.x(); x == nil { + return context.Background() + } else { + return x + } } diff --git a/context/gin/interface.go b/context/gin/interface.go new file mode 100644 index 00000000..c6241b6c --- /dev/null +++ b/context/gin/interface.go @@ -0,0 +1,63 @@ +/*********************************************************************************************************************** + * + * 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 gin + +import ( + "context" + "os" + "time" + + "github.com/gin-gonic/gin" + liblog "github.com/nabbar/golib/logger" +) + +type GinTonic interface { + context.Context + + //generic + GinContext() *gin.Context + CancelOnSignal(s ...os.Signal) + + //gin context metadata + Set(key string, value interface{}) + Get(key string) (value interface{}, exists bool) + MustGet(key string) interface{} + GetString(key string) (s string) + GetBool(key string) (b bool) + GetInt(key string) (i int) + GetInt64(key string) (i64 int64) + GetFloat64(key string) (f64 float64) + GetTime(key string) (t time.Time) + GetDuration(key string) (d time.Duration) + GetStringSlice(key string) (ss []string) + GetStringMap(key string) (sm map[string]interface{}) + GetStringMapString(key string) (sms map[string]string) + GetStringMapStringSlice(key string) (smss map[string][]string) + + SetLogger(log liblog.FuncLog) +} diff --git a/context/ginTonic.go b/context/gin/model.go similarity index 82% rename from context/ginTonic.go rename to context/gin/model.go index 842cf96d..82e37cb1 100644 --- a/context/ginTonic.go +++ b/context/gin/model.go @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package context +package gin import ( "context" @@ -34,36 +34,8 @@ import ( liblog "github.com/nabbar/golib/logger" ) -type GinTonic interface { - context.Context - - //generic - GinContext() *gin.Context - CancelOnSignal(s ...os.Signal) - - //gin context metadata - Set(key string, value interface{}) - Get(key string) (value interface{}, exists bool) - MustGet(key string) interface{} - GetString(key string) (s string) - GetBool(key string) (b bool) - GetInt(key string) (i int) - GetInt64(key string) (i64 int64) - GetFloat64(key string) (f64 float64) - GetTime(key string) (t time.Time) - GetDuration(key string) (d time.Duration) - GetStringSlice(key string) (ss []string) - GetStringMap(key string) (sm map[string]interface{}) - GetStringMapString(key string) (sms map[string]string) - GetStringMapStringSlice(key string) (smss map[string][]string) - - SetLogger(log FuncLogger) -} - -type FuncLogger func() liblog.Logger - type ctxGinTonic struct { - l FuncLogger + l liblog.FuncLog g *gin.Context x context.Context c context.CancelFunc @@ -100,7 +72,7 @@ func NewGinTonic(c *gin.Context) GinTonic { } } -func (c *ctxGinTonic) SetLogger(fct FuncLogger) { +func (c *ctxGinTonic) SetLogger(fct liblog.FuncLog) { c.l = fct } diff --git a/database/config.go b/database/config.go index 8af53124..38459dcc 100644 --- a/database/config.go +++ b/database/config.go @@ -27,14 +27,14 @@ package database import ( - "context" "database/sql" "fmt" "time" - libsts "github.com/nabbar/golib/status/config" + moncfg "github.com/nabbar/golib/monitor/types" libval "github.com/go-playground/validator/v10" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" liblog "github.com/nabbar/golib/logger" gormdb "gorm.io/gorm" @@ -98,10 +98,12 @@ type Config struct { // Disabled allow to disable a database connection without clean his configuration. Disabled bool `mapstructure:"disabled" json:"disabled" yaml:"disabled" toml:"disabled"` - // Status defined the router status configuration - Status libsts.ConfigStatus `mapstructure:"status" json:"status" yaml:"status" toml:"status"` + // Monitor defined the monitoring configuration + Monitor moncfg.Config `mapstructure:"monitor" json:"monitor" yaml:"monitor" toml:"monitor"` - ctx func() context.Context + //@TODO : implement logger options with new logger + + ctx libctx.FuncContext flog func() gorlog.Interface } @@ -133,11 +135,11 @@ func (c *Config) RegisterLogger(fct func() liblog.Logger, ignoreRecordNotFoundEr } } -func (c *Config) RegisterGORMLogger(fct func() gorlog.Interface) { +func (c *Config) RegisterGORMLogger(fct FuncGormLog) { c.flog = fct } -func (c *Config) RegisterContext(fct func() context.Context) { +func (c *Config) RegisterContext(fct libctx.FuncContext) { c.ctx = fct } diff --git a/database/interface.go b/database/interface.go index 1c1f2204..2fde62fb 100644 --- a/database/interface.go +++ b/database/interface.go @@ -32,16 +32,17 @@ import ( "sync/atomic" "time" - libcfg "github.com/nabbar/golib/config" - liblog "github.com/nabbar/golib/logger" - gorlog "gorm.io/gorm/logger" - + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - - libsts "github.com/nabbar/golib/status" + liblog "github.com/nabbar/golib/logger" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" gormdb "gorm.io/gorm" + gorlog "gorm.io/gorm/logger" ) +type FuncGormLog func() gorlog.Interface + type Database interface { GetDB() *gormdb.DB SetDb(db *gormdb.DB) @@ -51,13 +52,11 @@ type Database interface { CheckConn() liberr.Error Config() *gormdb.Config - RegisterContext(fct libcfg.FuncContext) + RegisterContext(fct libctx.FuncContext) RegisterLogger(fct func() liblog.Logger, ignoreRecordNotFoundError bool, slowThreshold time.Duration) RegisterGORMLogger(fct func() gorlog.Interface) - StatusInfo() (name string, release string, hash string) - StatusHealth() error - StatusRouter(sts libsts.RouteStatus, prefix string) liberr.Error + Monitor(vrs libver.Version) (montps.Monitor, error) } func New(cfg *Config) (Database, liberr.Error) { diff --git a/database/model.go b/database/model.go index 3584157f..dea68fe4 100644 --- a/database/model.go +++ b/database/model.go @@ -28,23 +28,18 @@ package database import ( "context" - "fmt" "os" "os/signal" - "runtime" - "strings" "sync" "sync/atomic" "syscall" "time" - libcfg "github.com/nabbar/golib/config" - liblog "github.com/nabbar/golib/logger" - gorlog "gorm.io/gorm/logger" - + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" + liblog "github.com/nabbar/golib/logger" gormdb "gorm.io/gorm" + gorlog "gorm.io/gorm/logger" ) const unknown = "unknown" @@ -155,41 +150,6 @@ func (d *database) CheckConn() liberr.Error { return nil } -func (d *database) StatusInfo() (name string, release string, hash string) { - hash = "" - release = strings.TrimLeft(strings.ToLower(runtime.Version()), "go") - - cfg := d.getConfig() - if cfg == nil { - name = unknown - } else { - name = fmt.Sprintf("%s (%s)", cfg.Name, cfg.Driver.String()) - } - - return name, release, hash -} - -func (d *database) StatusHealth() error { - return d.CheckConn() -} - -func (d *database) StatusRouter(sts libsts.RouteStatus, prefix string) liberr.Error { - cfg := d.getConfig() - if cfg == nil { - return ErrorDatabaseNotInitialized.Error(nil) - } - - if prefix != "" { - prefix = fmt.Sprintf("%s - %s (%s)", prefix, cfg.Name, cfg.Driver.String()) - } else { - prefix = fmt.Sprintf("%s (%s)", cfg.Name, cfg.Driver.String()) - } - - cfg.Status.RegisterStatus(sts, prefix, d.StatusInfo, d.StatusHealth) - - return nil -} - func (d *database) Config() *gormdb.Config { cfg := d.getConfig() if cfg == nil { @@ -199,7 +159,7 @@ func (d *database) Config() *gormdb.Config { return cfg.Config() } -func (d *database) RegisterContext(fct libcfg.FuncContext) { +func (d *database) RegisterContext(fct libctx.FuncContext) { cfg := d.getConfig() if cfg == nil { return diff --git a/database/monitor.go b/database/monitor.go new file mode 100644 index 00000000..a1936996 --- /dev/null +++ b/database/monitor.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 database + +import ( + "context" + "fmt" + "runtime" + + libctx "github.com/nabbar/golib/context" + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +const ( + defaultNameMonitor = "DB Client" +) + +func (d *database) HealthCheck(ctx context.Context) error { + return d.CheckConn() +} + +func (d *database) Monitor(vrs libver.Version) (montps.Monitor, error) { + var ( + e error + inf moninf.Info + mon montps.Monitor + res = make(map[string]interface{}, 0) + ctx libctx.FuncContext + cfg = d.getConfig() + ) + + if cfg == nil { + return nil, fmt.Errorf("cannot load config") + } + + res["runtime"] = runtime.Version()[2:] + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + + if inf, e = moninf.New(defaultNameMonitor); e != nil { + return nil, e + } else { + inf.RegisterName(func() (string, error) { + return fmt.Sprintf("%s %s [%s]", defaultNameMonitor, cfg.Driver.String(), cfg.Name), nil + }) + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon, e = libmon.New(ctx, inf); e != nil { + return nil, e + } + + if e = mon.SetConfig(cfg.ctx, cfg.Monitor); e != nil { + return nil, e + } + + mon.SetHealthCheck(d.HealthCheck) + + if e = mon.Start(ctx()); e != nil { + return nil, e + } + + return mon, nil +} diff --git a/errors/errors.go b/errors/errors.go index 6871221e..8dcfd13d 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -86,6 +86,8 @@ type errors struct { t runtime.Frame } +type FuncMap func(e Error) bool + type Error interface { //IsCodeError check if the given error code is matching with the current Error IsCodeError(code CodeError) bool @@ -102,6 +104,10 @@ type Error interface { HasError(err error) bool //HasParent check if the current Error has any valid parent HasParent() bool + //GetParent return a slice of Error interface for each parent error with or without the first error. + GetParent(withMainError bool) []Error + //Map run a function on each func and parent. If the function return false, the loop stop. + Map(fct FuncMap) bool //AddParent will add all no empty given error into parent of the current Error pointer AddParent(parent ...error) @@ -359,11 +365,46 @@ func (e *errors) HasParent() bool { return len(e.p) > 0 } +func (e *errors) GetParent(withMainError bool) []Error { + var res = make([]Error, 0) + + if withMainError { + res = append(res, &errors{ + c: e.c, + e: e.e, + p: nil, + t: e.t, + }) + } + + if len(e.p) > 0 { + for _, er := range e.p { + res = append(res, er.GetParent(true)...) + } + } + + return res +} + func (e *errors) SetParent(parent ...error) { e.p = make([]Error, 0) e.AddParent(parent...) } +func (e *errors) Map(fct FuncMap) bool { + if !fct(e) { + return false + } else if len(e.p) > 0 { + for _, er := range e.p { + if !er.Map(fct) { + return false + } + } + } + + return true +} + func (e *errors) AddParentError(parent ...Error) { for _, p := range parent { if p != nil { @@ -538,7 +579,7 @@ func (e *errors) Return(r Return) { e.ReturnParent(r.AddParent) } -func (e errors) ReturnError(f ReturnError) { +func (e *errors) ReturnError(f ReturnError) { if e.t.File != "" { f(int(e.c), e.e, e.t.File, e.t.Line) } else { @@ -546,7 +587,7 @@ func (e errors) ReturnError(f ReturnError) { } } -func (e errors) ReturnParent(f ReturnError) { +func (e *errors) ReturnParent(f ReturnError) { for _, p := range e.p { p.ReturnError(f) p.ReturnParent(f) diff --git a/errors/modules.go b/errors/modules.go index 1d9c6108..9b4258f8 100644 --- a/errors/modules.go +++ b/errors/modules.go @@ -27,35 +27,41 @@ package errors const ( - MinPkgArchive = 100 - MinPkgArtifact = 200 - MinPkgCertificate = 300 - MinPkgCluster = 400 - MinPkgConfig = 500 - MinPkgConsole = 800 - MinPkgCrypt = 900 - MinPkgDatabase = 1000 - MinPkgFTPClient = 1100 - MinPkgHttpCli = 1200 - MinPkgHttpServer = 1300 - MinPkgIOUtils = 1400 - MinPkgLDAP = 1500 - MinPkgLogger = 1600 - MinPkgMail = 1700 - MinPkgMailer = 1800 - MinPkgMailPooler = 1900 - MinPkgNetwork = 2000 - MinPkgNats = 2100 - MinPkgNutsDB = 2200 - MinPkgOAuth = 2300 - MinPkgAws = 2400 - MinPkgRequest = 2500 - MinPkgRouter = 2600 - MinPkgSemaphore = 2700 - MinPkgSMTP = 2800 - MinPkgStatic = 2900 - MinPkgVersion = 3000 - MinPkgViper = 3100 + MinPkgArchive = 100 + MinPkgArtifact = 200 + MinPkgCertificate = 300 + MinPkgCluster = 400 + MinPkgConfig = 500 + MinPkgConsole = 800 + MinPkgCrypt = 900 + MinPkgDatabase = 1000 + MinPkgFTPClient = 1100 + MinPkgHttpCli = 1200 + MinPkgHttpServer = 1300 + MinPkgHttpServerPool = 1320 + MinPkgIOUtils = 1400 + MinPkgLDAP = 1500 + MinPkgLogger = 1600 + MinPkgMail = 1700 + MinPkgMailer = 1800 + MinPkgMailPooler = 1900 + MinPkgMonitor = 2000 + MinPkgMonitorCfg = 2020 + MinPkgMonitorPool = 2100 + MinPkgNetwork = 2200 + MinPkgNats = 2300 + MinPkgNutsDB = 2400 + MinPkgOAuth = 2500 + MinPkgAws = 2600 + MinPkgRequest = 2700 + MinPkgRouter = 2800 + MinPkgSemaphore = 2900 + MinPkgSMTP = 3000 + MinPkgSMTPConfig = 3050 + MinPkgStatic = 3100 + MinPkgStatus = 3200 + MinPkgVersion = 3300 + MinPkgViper = 3400 MinAvailable = 4000 diff --git a/errors/return.go b/errors/return.go index 85b72a57..9f10de1d 100644 --- a/errors/return.go +++ b/errors/return.go @@ -42,6 +42,13 @@ type Return interface { AddParent(code int, msg string, file string, line int) } +type ReturnGin interface { + Return + + GinTonicAbort(ctx *gin.Context, httpCode int) + GinTonicErrorAbort(ctx *gin.Context, httpCode int) +} + type DefaultReturn struct { Code string Message string diff --git a/go.mod b/go.mod index befe35db..36d616bf 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module github.com/nabbar/golib go 1.19 require ( - github.com/aws/aws-sdk-go-v2 v1.17.2 - github.com/aws/aws-sdk-go-v2/config v1.18.4 - github.com/aws/aws-sdk-go-v2/credentials v1.13.4 - github.com/aws/aws-sdk-go-v2/service/iam v1.18.24 - github.com/aws/aws-sdk-go-v2/service/s3 v1.29.5 - github.com/bits-and-blooms/bitset v1.4.0 + github.com/aws/aws-sdk-go-v2 v1.17.5 + github.com/aws/aws-sdk-go-v2/config v1.18.14 + github.com/aws/aws-sdk-go-v2/credentials v1.13.14 + github.com/aws/aws-sdk-go-v2/service/iam v1.19.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.30.4 + github.com/bits-and-blooms/bitset v1.5.0 github.com/c-bata/go-prompt v0.2.6 - github.com/fatih/color v1.13.0 + github.com/fatih/color v1.14.1 github.com/fsnotify/fsnotify v1.6.0 github.com/fxamacker/cbor/v2 v2.4.0 - github.com/gin-gonic/gin v1.8.1 + github.com/gin-gonic/gin v1.9.0 github.com/go-ldap/ldap/v3 v3.4.4 - github.com/go-playground/validator/v10 v10.11.1 + github.com/go-playground/validator/v10 v10.11.2 github.com/google/go-github/v33 v33.0.0 github.com/hashicorp/go-hclog v1.4.0 - github.com/hashicorp/go-retryablehttp v0.7.1 + github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/jlaffaye/ftp v0.1.0 @@ -26,84 +26,88 @@ require ( github.com/matcornic/hermes/v2 v2.1.0 github.com/mattn/go-colorable v0.1.13 github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/mapstructure v1.5.0 github.com/nats-io/jwt/v2 v2.3.0 - github.com/nats-io/nats-server/v2 v2.9.8 - github.com/nats-io/nats.go v1.21.0 - github.com/onsi/ginkgo/v2 v2.5.1 - github.com/onsi/gomega v1.24.1 + github.com/nats-io/nats-server/v2 v2.9.14 + github.com/nats-io/nats.go v1.23.0 + github.com/onsi/ginkgo/v2 v2.8.3 + github.com/onsi/gomega v1.27.1 github.com/pelletier/go-toml v1.9.5 - github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 github.com/shirou/gopsutil v3.21.11+incompatible github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/jwalterweatherman v1.1.0 - github.com/spf13/viper v1.14.0 + github.com/spf13/viper v1.15.0 github.com/vbauerster/mpb/v5 v5.4.0 - github.com/xanzy/go-gitlab v0.76.0 + github.com/xanzy/go-gitlab v0.80.2 github.com/xhit/go-simple-mail v2.2.2+incompatible github.com/xujiajun/nutsdb v0.11.1 github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db - golang.org/x/net v0.4.0 - golang.org/x/oauth2 v0.3.0 + golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb + golang.org/x/net v0.7.0 + golang.org/x/oauth2 v0.5.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.3.0 - golang.org/x/term v0.3.0 + golang.org/x/sys v0.5.0 + golang.org/x/term v0.5.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/clickhouse v0.5.0 - gorm.io/driver/mysql v1.4.4 - gorm.io/driver/postgres v1.4.5 - gorm.io/driver/sqlite v1.4.3 - gorm.io/driver/sqlserver v1.4.1 - gorm.io/gorm v1.24.2 + gorm.io/driver/mysql v1.4.7 + gorm.io/driver/postgres v1.4.8 + gorm.io/driver/sqlite v1.4.4 + gorm.io/driver/sqlserver v1.4.2 + gorm.io/gorm v1.24.5 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/ClickHouse/ch-go v0.50.0 // indirect - github.com/ClickHouse/clickhouse-go/v2 v2.4.3 // indirect + github.com/ClickHouse/ch-go v0.52.1 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.6.2 // indirect + github.com/DataDog/zstd v1.5.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect - github.com/PuerkitoBio/goquery v1.8.0 // indirect - github.com/VictoriaMetrics/metrics v1.23.0 // indirect + github.com/PuerkitoBio/goquery v1.8.1 // indirect + github.com/VictoriaMetrics/metrics v1.23.1 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/andybalholm/brotli v1.0.4 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.26 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.27 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.17 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.21 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.21 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.20 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.26 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.17.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.23 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.4 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect + github.com/bytedance/sonic v1.8.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cockroachdb/errors v1.9.0 // indirect - github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cockroachdb/errors v1.9.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20210331181633-27fc006b8bfb // indirect github.com/cockroachdb/redact v1.1.3 // indirect - github.com/getsentry/sentry-go v0.15.0 // indirect + github.com/getsentry/sentry-go v0.18.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.6.1 // indirect github.com/go-logr/logr v1.2.3 // indirect 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/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect @@ -113,6 +117,7 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -127,49 +132,45 @@ require ( github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.13.0 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.13.0 // indirect - github.com/jackc/pgx/v4 v4.17.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.0 // indirect github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/ratelimit v1.0.2 // indirect - github.com/klauspost/compress v1.15.12 // indirect + github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kr/pretty v0.3.1 // 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.6 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/mattn/go-tty v0.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/microsoft/go-mssqldb v0.18.0 // indirect + github.com/microsoft/go-mssqldb v0.20.0 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // 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 github.com/nats-io/nkeys v0.3.0 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/paulmach/orb v0.7.1 // indirect + github.com/paulmach/orb v0.9.0 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect - github.com/rivo/uniseg v0.4.3 // indirect + github.com/prometheus/common v0.40.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect @@ -179,8 +180,9 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect - github.com/subosito/gotenv v1.4.1 // indirect - github.com/ugorji/go/codec v1.2.7 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.10 // indirect github.com/valyala/fastrand v1.1.0 // indirect github.com/valyala/histogram v1.2.0 // indirect github.com/vanng822/css v1.0.1 // indirect @@ -188,15 +190,15 @@ 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 - go.opentelemetry.io/otel v1.11.2 // indirect - go.opentelemetry.io/otel/trace v1.11.2 // indirect - golang.org/x/crypto v0.3.0 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/text v0.5.0 // indirect + go.opentelemetry.io/otel v1.13.0 // indirect + go.opentelemetry.io/otel/trace v1.13.0 // indirect + golang.org/x/arch v0.2.0 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.4.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/httpcli/cli.go b/httpcli/cli.go index 189583bb..09a8dd48 100644 --- a/httpcli/cli.go +++ b/httpcli/cli.go @@ -48,6 +48,8 @@ const ( ClientNetworkUDP = "udp" ) +type FctHttpClient func() *http.Client + func GetClient(serverName string) *http.Client { c, e := GetClientTimeout(serverName, true, 0) diff --git a/httpcli/options.go b/httpcli/options.go index d59da400..e9b4308f 100644 --- a/httpcli/options.go +++ b/httpcli/options.go @@ -27,10 +27,15 @@ package httpcli import ( + "bytes" + "encoding/json" "fmt" "net/http" "time" + cmptls "github.com/nabbar/golib/config/components/tls" + cfgcst "github.com/nabbar/golib/config/const" + libval "github.com/go-playground/validator/v10" libtls "github.com/nabbar/golib/certificates" @@ -55,6 +60,27 @@ type Options struct { ForceIP OptionForceIP `json:"force_ip" yaml:"force_ip" toml:"force_ip" mapstructure:"force_ip"` } +func DefaultConfig(indent string) []byte { + var ( + res = bytes.NewBuffer(make([]byte, 0)) + def = []byte(`{ + "timeout":"0s", + "http2": true, + "tls": ` + string(cmptls.DefaultConfig(cfgcst.JSONIndent)) + `, + "force_ip": { + "enable": false, + "net":"tcp", + "ip":"127.0.0.1:8080" + } +}`) + ) + if err := json.Indent(res, def, indent, cfgcst.JSONIndent); err != nil { + return def + } else { + return res.Bytes() + } +} + func (o Options) Validate() liberr.Error { var e = ErrorValidatorError.Error(nil) diff --git a/httpserver/config.go b/httpserver/config.go index c6402e26..57ff0c4e 100644 --- a/httpserver/config.go +++ b/httpserver/config.go @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2020 Nicolas JUHEL + * 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 @@ -34,139 +34,70 @@ import ( "strings" "time" - libsts "github.com/nabbar/golib/status/config" + srvtps "github.com/nabbar/golib/httpserver/types" + + moncfg "github.com/nabbar/golib/monitor/types" libval "github.com/go-playground/validator/v10" libtls "github.com/nabbar/golib/certificates" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" ) -type MapUpdPoolServerConfig func(cfg ServerConfig) ServerConfig -type MapRunPoolServerConfig func(cfg ServerConfig) -type PoolServerConfig []ServerConfig - -func (p PoolServerConfig) PoolServer() (PoolServer, liberr.Error) { - var ( - r = NewPool() - e = ErrorPoolAdd.Error(nil) - ) - - p.MapRun(func(cfg ServerConfig) { - var err liberr.Error - - if r, err = r.Add(cfg.Server()); err != nil { - e.AddParentError(err) - } - }) - - if !e.HasParent() { - e = nil - } - - return r, e -} - -func (p PoolServerConfig) UpdatePoolServer(pSrv PoolServer) (PoolServer, liberr.Error) { - var e = ErrorPoolAdd.Error(nil) - - p.MapRun(func(cfg ServerConfig) { - var err liberr.Error - - if pSrv, err = pSrv.Add(cfg.Server()); err != nil { - e.AddParentError(err) - } - }) - - if !e.HasParent() { - e = nil - } - - return pSrv, e -} - -func (p PoolServerConfig) Validate() liberr.Error { - var e = ErrorPoolValidate.Error(nil) - - p.MapRun(func(cfg ServerConfig) { - var err liberr.Error - - if err = cfg.Validate(); err != nil { - e.AddParentError(err) - } - }) - - if !e.HasParent() { - e = nil - } - - return e -} - -func (p PoolServerConfig) MapUpdate(f MapUpdPoolServerConfig) PoolServerConfig { - var r = make(PoolServerConfig, 0) - - if p != nil { - r = p - } - - for i, c := range r { - r[i] = f(c) - } - - return r -} - -func (p PoolServerConfig) MapRun(f MapRunPoolServerConfig) PoolServerConfig { - var r = make(PoolServerConfig, 0) - - if p != nil { - r = p - } - - for _, c := range r { - f(c) - } - - return r -} +const ( + cfgConfig = "cfgConfig" + cfgName = "cfgName" + cfgListen = "cfgListen" + cfgExpose = "cfgExpose" + cfgHandler = "cfgHandler" + cfgHandlerKey = "cfgHandlerKey" + cfgDisabled = "cfgDisabled" + cfgMonitor = "cfgMonitor" + cfgTLS = "cfgTLS" + cfgTLSMandatory = "cfgTLSMandatory" + cfgServerOptions = "cfgServerOptions" +) // nolint #maligned -type ServerConfig struct { +type Config struct { - // Name is the name of the current server - // the configuration allow multipke server, which each one must be identify by a name + // Name is the name of the current srv + // the configuration allow multipke srv, which each one must be identify by a name // If not defined, will use the listen address Name string `mapstructure:"name" json:"name" yaml:"name" toml:"name" validate:"required"` // Listen is the local address (ip, hostname, unix socket, ...) with a port - // The server will bind with this address only and listen for the port defined + // The srv will bind with this address only and listen for the port defined Listen string `mapstructure:"listen" json:"listen" yaml:"listen" toml:"listen" validate:"required,hostname_port"` - // Expose is the address use to call this server. This can be allow to use a single fqdn to multiple server" + // Expose is the address use to call this srv. This can be allow to use a single fqdn to multiple srv" Expose string `mapstructure:"expose" json:"expose" yaml:"expose" toml:"expose" validate:"required,url"` - // HandlerKeys is an options to associate current server with a specifc handler defined by the key - // This allow to defined multiple server in only one config for different handler to start multiple api - HandlerKeys string `mapstructure:"handler_keys" json:"handler_keys" yaml:"handler_keys" toml:"handler_keys"` + // HandlerKey is an options to associate current srv with a specifc handler defined by the key + // This key allow to defined multiple srv in only one config for different handler to start multiple api + HandlerKey string `mapstructure:"handler_key" json:"handler_key" yaml:"handler_key" toml:"handler_key"` //private - getTLSDefault func() libtls.TLSConfig + getTLSDefault libtls.FctTLSDefault //private - getParentContext func() context.Context + getParentContext libctx.FuncContext - // Enabled allow to disable a server without clean his configuration + //private + getHandlerFunc srvtps.FuncHandler + + // Enabled allow to disable a srv without clean his configuration Disabled bool `mapstructure:"disabled" json:"disabled" yaml:"disabled" toml:"disabled"` - // Mandator - // y defined if the component for status is mandatory or not - Status libsts.ConfigStatus `mapstructure:"status" json:"status" yaml:"status" toml:"status"` + // Monitor defined the monitoring options to monitor the status & metrics about the health of this srv + Monitor moncfg.Config `mapstructure:"monitor" json:"monitor" yaml:"monitor" toml:"monitor"` - // TLSMandatory is a flag to defined that TLS must be valid to start current server. + // TLSMandatory is a flag to defined that TLS must be valid to start current srv. TLSMandatory bool `mapstructure:"tls_mandatory" json:"tls_mandatory" yaml:"tls_mandatory" toml:"tls_mandatory"` - // TLS is the tls configuration for this server. - // To allow tls on this server, at least the TLS Config option InheritDefault must be at true and the default TLS config must be set. + // TLS is the tls configuration for this srv. + // To allow tls on this srv, at least the TLS Config option InheritDefault must be at true and the default TLS config must be set. // If you don't want any tls config, just omit or set an empty struct. TLS libtls.Config `mapstructure:"tls" json:"tls" yaml:"tls" toml:"tls"` @@ -196,7 +127,7 @@ type ServerConfig struct { WriteTimeout time.Duration `mapstructure:"write_timeout" json:"write_timeout" yaml:"write_timeout" toml:"write_timeout"` // MaxHeaderBytes controls the maximum number of bytes the - // server will read parsing the request header's keys and + // srv will read parsing the request header's keys and // values, including the request line. It does not limit the // size of the request body. // If zero, DefaultMaxHeaderBytes is used. @@ -218,7 +149,7 @@ type ServerConfig struct { MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams" json:"max_concurrent_streams" yaml:"max_concurrent_streams" toml:"max_concurrent_streams"` // MaxReadFrameSize optionally specifies the largest frame - // this server is willing to read. A valid value is between + // this srv is willing to read. A valid value is between // 16k and 16M, inclusive. If zero or otherwise invalid, a // default value is used. MaxReadFrameSize uint32 `mapstructure:"max_read_frame_size" json:"max_read_frame_size" yaml:"max_read_frame_size" toml:"max_read_frame_size"` @@ -250,10 +181,13 @@ type ServerConfig struct { // resource-constrained environments or servers in the process of // shutting down should disable them. DisableKeepAlive bool `mapstructure:"disable_keep_alive" json:"disable_keep_alive" yaml:"disable_keep_alive" toml:"disable_keep_alive"` + + // Logger is used to define the logger options. + Logger liblog.Options `mapstructure:"logger" json:"logger" yaml:"logger" toml:"logger"` } -func (c *ServerConfig) Clone() ServerConfig { - return ServerConfig{ +func (c *Config) Clone() Config { + return Config{ Disabled: c.Disabled, getTLSDefault: c.getTLSDefault, getParentContext: c.getParentContext, @@ -272,7 +206,7 @@ func (c *ServerConfig) Clone() ServerConfig { Name: c.Name, Listen: c.Listen, Expose: c.Expose, - HandlerKeys: strings.ToLower(c.HandlerKeys), + HandlerKey: strings.ToLower(c.HandlerKey), TLSMandatory: c.TLSMandatory, TLS: libtls.Config{ CurveList: c.TLS.CurveList, @@ -290,25 +224,23 @@ func (c *ServerConfig) Clone() ServerConfig { DynamicSizingDisable: c.TLS.DynamicSizingDisable, SessionTicketDisable: c.TLS.SessionTicketDisable, }, - Status: libsts.ConfigStatus{ - Mandatory: c.Status.Mandatory, - MessageOK: c.Status.MessageOK, - MessageKO: c.Status.MessageKO, - CacheTimeoutInfo: c.Status.CacheTimeoutInfo, - CacheTimeoutHealth: c.Status.CacheTimeoutHealth, - }, + Monitor: c.Monitor.Clone(), } } -func (c *ServerConfig) SetDefaultTLS(f func() libtls.TLSConfig) { +func (c *Config) RegisterHandlerFunc(hdl srvtps.FuncHandler) { + c.getHandlerFunc = hdl +} + +func (c *Config) SetDefaultTLS(f libtls.FctTLSDefault) { c.getTLSDefault = f } -func (c *ServerConfig) SetParentContext(f func() context.Context) { +func (c *Config) SetContext(f libctx.FuncContext) { c.getParentContext = f } -func (c *ServerConfig) GetTLS() (libtls.TLSConfig, liberr.Error) { +func (c *Config) GetTLS() (libtls.TLSConfig, liberr.Error) { var def libtls.TLSConfig if c.TLS.InheritDefault && c.getTLSDefault != nil { @@ -318,15 +250,25 @@ func (c *ServerConfig) GetTLS() (libtls.TLSConfig, liberr.Error) { return c.TLS.NewFrom(def) } -func (c *ServerConfig) IsTLS() bool { - if ssl, err := c.GetTLS(); err == nil && ssl != nil && ssl.LenCertificatePair() > 0 { +func (c *Config) CheckTLS() (libtls.TLSConfig, liberr.Error) { + if ssl, err := c.GetTLS(); err == nil { + return nil, err + } else if ssl != nil && ssl.LenCertificatePair() > 0 { + return nil, ErrorServerValidate.ErrorParent(fmt.Errorf("not certificates defined")) + } else { + return ssl, nil + } +} + +func (c *Config) IsTLS() bool { + if _, err := c.CheckTLS(); err == nil { return true } return false } -func (c *ServerConfig) getContext() context.Context { +func (c *Config) context() context.Context { var ctx context.Context if c.getParentContext != nil { @@ -340,7 +282,7 @@ func (c *ServerConfig) getContext() context.Context { return ctx } -func (c *ServerConfig) GetListen() *url.URL { +func (c *Config) GetListen() *url.URL { var ( err error add *url.URL @@ -367,7 +309,7 @@ func (c *ServerConfig) GetListen() *url.URL { return add } -func (c *ServerConfig) GetExpose() *url.URL { +func (c *Config) GetExpose() *url.URL { var ( err error add *url.URL @@ -392,11 +334,11 @@ func (c *ServerConfig) GetExpose() *url.URL { return add } -func (c *ServerConfig) GetHandlerKey() string { - return c.HandlerKeys +func (c *Config) GetHandlerKey() string { + return c.HandlerKey } -func (c *ServerConfig) Validate() liberr.Error { +func (c *Config) Validate() liberr.Error { err := ErrorServerValidate.Error(nil) if er := libval.New().Struct(c); er != nil { @@ -418,6 +360,146 @@ func (c *ServerConfig) Validate() liberr.Error { } -func (c *ServerConfig) Server() Server { - return NewServer(c) +func (c *Config) Server(defLog liblog.FuncLog) (Server, error) { + return New(*c, defLog) +} + +func (o *srv) GetConfig() *Config { + if i, l := o.c.Load(cfgConfig); !l { + return nil + } else if v, k := i.(Config); !k { + return nil + } else { + return &v + } +} + +func (o *srv) makeOptServer(cfg Config) *optServer { + return &optServer{ + ReadTimeout: cfg.ReadTimeout, + ReadHeaderTimeout: cfg.ReadHeaderTimeout, + WriteTimeout: cfg.WriteTimeout, + MaxHeaderBytes: cfg.MaxHeaderBytes, + MaxHandlers: cfg.MaxHandlers, + MaxConcurrentStreams: cfg.MaxConcurrentStreams, + MaxReadFrameSize: cfg.MaxReadFrameSize, + PermitProhibitedCipherSuites: cfg.PermitProhibitedCipherSuites, + IdleTimeout: cfg.IdleTimeout, + MaxUploadBufferPerConnection: cfg.MaxUploadBufferPerConnection, + MaxUploadBufferPerStream: cfg.MaxUploadBufferPerStream, + DisableKeepAlive: cfg.DisableKeepAlive, + } +} + +func (o *srv) SetConfig(cfg Config, defLog liblog.FuncLog) error { + if e := o.cfgSetTLS(&cfg); e != nil { + return e + } else if e = o.setLogger(defLog, cfg.Logger); e != nil { + return e + } + + if o.HandlerHas(cfg.HandlerKey) { + o.HandlerStoreFct(cfg.HandlerKey) + } else { + return ErrorServerValidate.ErrorParent(fmt.Errorf("handler is missing or not existing")) + } + + o.c.Store(cfgName, cfg.Name) + o.c.Store(cfgListen, cfg.GetListen()) + o.c.Store(cfgExpose, cfg.GetExpose()) + o.c.Store(cfgDisabled, cfg.Disabled) + o.c.Store(cfgServerOptions, o.makeOptServer(cfg)) + o.c.Store(cfgConfig, cfg) + + return nil +} + +func (o *srv) setLogger(def liblog.FuncLog, opt liblog.Options) error { + o.m.Lock() + defer o.m.Unlock() + + var l liblog.Logger + + if def != nil { + if n := def(); n != nil { + l = n + } + } + + if l == nil { + l = liblog.GetDefault() + } + + if e := l.SetOptions(&opt); e == nil { + o.l = func() liblog.Logger { + return l + } + return nil + } else if o.l == nil { + o.l = liblog.GetDefault + return e + } else { + return e + } +} + +func (o *srv) logger() liblog.Logger { + o.m.RLock() + defer o.m.RUnlock() + + var log liblog.Logger + + if o.l != nil { + log = o.l() + } + + if log == nil { + log = liblog.GetDefault() + } + + log.SetFields(log.GetFields().Add("bind", o.GetBindable())) + return log +} + +func (o *srv) cfgSetTLS(cfg *Config) error { + o.c.Store(cfgTLSMandatory, cfg.TLSMandatory) + if t, e := cfg.CheckTLS(); e != nil && cfg.TLSMandatory { + return e + } else if e != nil { + o.c.Delete(cfgTLS) + return nil + } else { + o.c.Store(cfgTLS, t) + return nil + } +} + +func (o *srv) cfgGetTLS() libtls.TLSConfig { + if i, l := o.c.Load(cfgTLS); !l { + return nil + } else if v, k := i.(libtls.TLSConfig); !k { + return nil + } else { + return v + } +} + +func (o *srv) cfgTLSMandatory() bool { + if i, l := o.c.Load(cfgTLSMandatory); !l { + return false + } else if v, k := i.(bool); !k { + return false + } else { + return v + } +} + +func (o *srv) cfgGetServer() *optServer { + if i, l := o.c.Load(cfgServerOptions); !l { + return &optServer{} + } else if v, k := i.(*optServer); !k { + return &optServer{} + } else { + return v + } } diff --git a/httpserver/error.go b/httpserver/error.go index 4889d25d..9e336aa8 100644 --- a/httpserver/error.go +++ b/httpserver/error.go @@ -35,13 +35,9 @@ import ( const ( ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgHttpServer ErrorHTTP2Configure - ErrorPoolAdd - ErrorPoolValidate - ErrorPoolListen ErrorServerValidate + ErrorServerStart ErrorPortUse - ErrorServerDisabled - ErrorServerOffline ) func init() { @@ -56,21 +52,13 @@ func getMessage(code liberr.CodeError) (message string) { case ErrorParamEmpty: return "given parameters is empty" case ErrorHTTP2Configure: - return "cannot initialize http2 over http server" - case ErrorPoolAdd: - return "cannot add server on pool" - case ErrorPoolValidate: - return "at least one config server seems to be not valid" - case ErrorPoolListen: - return "at least one server has listen error" + return "cannot initialize http2 over http srv" case ErrorServerValidate: - return "config server seems to be not valid" + return "config srv seems to be not valid" + case ErrorServerStart: + return "server killed : server start but not listen" case ErrorPortUse: - return "server port is still used" - case ErrorServerDisabled: - return "server disabled" - case ErrorServerOffline: - return "server offline" + return "srv port is still used" } return liberr.NullMessage diff --git a/httpserver/handler.go b/httpserver/handler.go new file mode 100644 index 00000000..c0565595 --- /dev/null +++ b/httpserver/handler.go @@ -0,0 +1,103 @@ +/* + * 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 httpserver + +import ( + "net/http" + + srvtps "github.com/nabbar/golib/httpserver/types" +) + +func (o *srv) Handler(h srvtps.FuncHandler) { + o.m.Lock() + defer o.m.Unlock() + o.h = h +} + +func (o *srv) HandlerGet(key string) http.Handler { + o.m.RLock() + defer o.m.RUnlock() + + if o.h == nil { + return srvtps.NewBadHandler() + } else if l := o.h(); len(l) < 1 { + return srvtps.NewBadHandler() + } else if h, k := l[key]; !k { + return srvtps.NewBadHandler() + } else { + return h + } +} + +func (o *srv) HandlerGetValidKey() string { + if i, l := o.c.Load(cfgHandler); !l { + return srvtps.BadHandlerName + } else if _, f := i.(*srvtps.BadHandler); f { + return srvtps.BadHandlerName + } else if i == nil { + return srvtps.BadHandlerName + } else if i, l = o.c.Load(cfgHandlerKey); !l { + return srvtps.BadHandlerName + } else if v, k := i.(string); !k { + return srvtps.BadHandlerName + } else { + return v + } +} + +func (o *srv) HandlerHas(key string) bool { + o.m.RLock() + defer o.m.RUnlock() + + if o.h == nil { + return false + } else if l := o.h(); len(l) < 1 { + return false + } else { + _, k := l[key] + return k + } +} + +func (o *srv) HandlerStoreFct(key string) { + o.c.Store(cfgHandler, func() http.Handler { + return o.HandlerGet(key) + }) + o.c.Store(cfgHandlerKey, key) +} + +func (o *srv) HandlerLoadFct() http.Handler { + if i, l := o.c.Load(cfgHandler); !l { + return srvtps.NewBadHandler() + } else if v, k := i.(func() http.Handler); !k { + return srvtps.NewBadHandler() + } else if h := v(); h == nil { + return srvtps.NewBadHandler() + } else { + return h + } +} diff --git a/httpserver/info.go b/httpserver/info.go new file mode 100644 index 00000000..c44c0d8a --- /dev/null +++ b/httpserver/info.go @@ -0,0 +1,79 @@ +/* + * 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 httpserver + +import "net/url" + +func (o *srv) GetName() string { + if i, l := o.c.Load(cfgName); !l { + return o.GetBindable() + } else if v, k := i.(string); !k { + return o.GetBindable() + } else { + return v + } +} + +func (o *srv) GetBindable() string { + if i, l := o.c.Load(cfgListen); !l { + return "" + } else if v, k := i.(*url.URL); !k { + return "" + } else { + return v.Host + } +} + +func (o *srv) GetExpose() string { + if i, l := o.c.Load(cfgExpose); !l { + return o.GetBindable() + } else if v, k := i.(*url.URL); !k { + return o.GetBindable() + } else { + return v.Host + } +} + +func (o *srv) IsDisable() bool { + if i, l := o.c.Load(cfgDisabled); !l { + return false + } else if v, k := i.(bool); !k { + return false + } else { + return v + } +} + +func (o *srv) IsTLS() bool { + if o.cfgTLSMandatory() { + return true + } else if s := o.cfgGetTLS(); s != nil && s.LenCertificatePair() > 0 { + return true + } else { + return false + } +} diff --git a/httpserver/interface.go b/httpserver/interface.go new file mode 100644 index 00000000..6d3fa151 --- /dev/null +++ b/httpserver/interface.go @@ -0,0 +1,82 @@ +/* + * 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 httpserver + +import ( + "sync" + + liblog "github.com/nabbar/golib/logger" + + libctx "github.com/nabbar/golib/context" + srvrun "github.com/nabbar/golib/httpserver/run" + srvtps "github.com/nabbar/golib/httpserver/types" + montps "github.com/nabbar/golib/monitor/types" + libsrv "github.com/nabbar/golib/server" + libver "github.com/nabbar/golib/version" +) + +type Info interface { + GetName() string + GetBindable() string + GetExpose() string + + IsDisable() bool + IsTLS() bool +} + +type Server interface { + libsrv.Server + libsrv.WaitNotify + + Info + + Handler(h srvtps.FuncHandler) + Merge(s Server, def liblog.FuncLog) error + + GetConfig() *Config + SetConfig(cfg Config, defLog liblog.FuncLog) error + + Monitor(vrs libver.Version) (montps.Monitor, error) +} + +func New(cfg Config, defLog liblog.FuncLog) (Server, error) { + s := &srv{ + m: sync.RWMutex{}, + r: nil, + c: libctx.NewConfig[string](cfg.getParentContext), + } + + s.Handler(cfg.getHandlerFunc) + + if e := s.SetConfig(cfg, defLog); e != nil { + return nil, e + } else { + s.r = srvrun.New() + } + + return s, nil +} diff --git a/httpserver/model.go b/httpserver/model.go new file mode 100644 index 00000000..78bcfc11 --- /dev/null +++ b/httpserver/model.go @@ -0,0 +1,48 @@ +/* + * 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 httpserver + +import ( + "sync" + + libctx "github.com/nabbar/golib/context" + srvrun "github.com/nabbar/golib/httpserver/run" + srvtps "github.com/nabbar/golib/httpserver/types" + liblog "github.com/nabbar/golib/logger" +) + +type srv struct { + m sync.RWMutex + h srvtps.FuncHandler + l liblog.FuncLog + c libctx.Config[string] + r srvrun.Run +} + +func (o *srv) Merge(s Server, def liblog.FuncLog) error { + return o.SetConfig(*s.GetConfig(), def) +} diff --git a/httpserver/monitor.go b/httpserver/monitor.go new file mode 100644 index 00000000..68775be4 --- /dev/null +++ b/httpserver/monitor.go @@ -0,0 +1,104 @@ +/* + * 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 httpserver + +import ( + "context" + "fmt" + "runtime" + + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +const ( + defaultNameMonitor = "HTTP Server" +) + +func (o *srv) HealthCheck(ctx context.Context) error { + o.m.RLock() + defer o.m.RUnlock() + + if o.r == nil { + return fmt.Errorf("server is not running") + } else if o.r.IsRunning() { + return nil + } else if e := o.r.GetError(); e != nil { + return e + } else { + return fmt.Errorf("server is not running") + } +} + +func (o *srv) Monitor(vrs libver.Version) (montps.Monitor, error) { + var ( + e error + inf moninf.Info + mon montps.Monitor + cfg *Config + res = make(map[string]interface{}, 0) + ) + + if cfg = o.GetConfig(); cfg == nil { + return nil, fmt.Errorf("cannot load config") + } + + res["runtime"] = runtime.Version()[2:] + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + res["handler"] = o.HandlerGetValidKey() + + if inf, e = moninf.New(defaultNameMonitor); e != nil { + return nil, e + } else { + inf.RegisterName(func() (string, error) { + return fmt.Sprintf("%s [%s]", defaultNameMonitor, o.GetBindable()), nil + }) + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon, e = libmon.New(o.c.GetContext, inf); e != nil { + return nil, e + } + + mon.SetHealthCheck(o.HealthCheck) + + if e = mon.SetConfig(o.c.GetContext, cfg.Monitor); e != nil { + return nil, e + } + + if e = mon.Start(o.c.GetContext()); e != nil { + return nil, e + } + + return mon, nil +} diff --git a/httpserver/pool.go b/httpserver/pool.go deleted file mode 100644 index 3950456f..00000000 --- a/httpserver/pool.go +++ /dev/null @@ -1,473 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 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 httpserver - -import ( - "context" - "fmt" - "net/http" - "os" - "os/signal" - "regexp" - "strings" - "syscall" - - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" - libsem "github.com/nabbar/golib/semaphore" - libsts "github.com/nabbar/golib/status" -) - -type FieldType uint8 - -const ( - HandlerDefault = "default" - FieldName FieldType = iota - FieldBind - FieldExpose -) - -type MapUpdPoolServer func(srv Server) Server -type MapRunPoolServer func(srv Server) - -type pool []Server - -type PoolServer interface { - Add(srv ...Server) (PoolServer, liberr.Error) - Get(bindAddress string) Server - Del(bindAddress string) PoolServer - Has(bindAddress string) bool - Len() int - SetLogger(log liblog.FuncLog) - - MapRun(f MapRunPoolServer) - MapUpd(f MapUpdPoolServer) - - List(fieldFilter, fieldReturn FieldType, pattern, regex string) []string - Filter(field FieldType, pattern, regex string) PoolServer - - IsRunning(asLeast bool) bool - WaitNotify(ctx context.Context, cancel context.CancelFunc) - - Listen(handler http.Handler) liberr.Error - ListenMultiHandler(handler map[string]http.Handler) liberr.Error - Restart() - Shutdown() - - StatusInfo(bindAddress string) (name string, release string, hash string) - StatusHealth(bindAddress string) error - StatusRoute(prefix string, sts libsts.RouteStatus) -} - -func NewPool(srv ...Server) PoolServer { - p, _ := make(pool, 0).Add(srv...) - return p -} - -func (p pool) MapRun(f MapRunPoolServer) { - if p == nil { - return - } - - for _, s := range p { - f(s) - } -} - -func (p pool) MapUpd(f MapUpdPoolServer) { - if p == nil { - return - } - - for i, s := range p { - p[i] = f(s) - } -} - -func (p pool) Add(srv ...Server) (PoolServer, liberr.Error) { - var r = make(pool, 0) - - if p != nil { - r = p - } - - for _, s := range srv { - if !r.Has(s.GetBindable()) { - r = append(r, s) - continue - } - - for _, x := range r { - if x.GetBindable() != s.GetBindable() { - continue - } else if !x.Merge(s) { - r = r.Del(s.GetBindable()).(pool) - r = append(r, s) - break - } - } - } - - return r, nil -} - -func (p pool) Get(bindAddress string) Server { - if !p.Has(bindAddress) { - return nil - } - - for _, s := range p { - if s.GetBindable() == bindAddress { - return s - } - } - - return nil -} - -func (p pool) Del(bindAddress string) PoolServer { - if !p.Has(bindAddress) { - return p - } - - var r = make(pool, 0) - - for _, s := range p { - if s.GetBindable() != bindAddress { - r = append(r, s) - } - - if s.IsRunning() { - s.Shutdown() - } - } - - return r -} - -func (p pool) Has(bindAddress string) bool { - if p.Len() < 1 { - return false - } - - for _, s := range p { - if s.GetBindable() == bindAddress { - return true - } - } - - return false -} - -func (p pool) Len() int { - if p == nil { - return 0 - } - - return len(p) -} - -func (p pool) SetLogger(log liblog.FuncLog) { - p.MapUpd(func(srv Server) Server { - srv.SetLogger(log) - return srv - }) -} - -func (p pool) List(fieldFilter, fieldReturn FieldType, pattern, regex string) []string { - var ( - r = make([]string, 0) - f string - ) - - if p.Len() < 1 { - return r - } - - pattern = strings.ToLower(pattern) - - p.MapRun(func(srv Server) { - switch fieldFilter { - case FieldBind: - f = srv.GetBindable() - case FieldExpose: - f = srv.GetExpose() - case FieldName: - f = srv.GetName() - default: - f = srv.GetName() - } - - f = strings.ToLower(f) - - if pattern != "" && strings.Contains(f, pattern) { - switch fieldReturn { - case FieldBind: - r = append(r, srv.GetBindable()) - case FieldExpose: - r = append(r, srv.GetExpose()) - case FieldName: - r = append(r, srv.GetName()) - default: - r = append(r, srv.GetName()) - } - - return - } - - if regex == "" { - return - } - - if ok, err := regexp.MatchString(regex, srv.GetName()); err == nil && ok { - switch fieldReturn { - case FieldBind: - r = append(r, srv.GetBindable()) - case FieldExpose: - r = append(r, srv.GetExpose()) - case FieldName: - r = append(r, srv.GetName()) - default: - r = append(r, srv.GetName()) - } - } - }) - - return r -} - -func (p pool) Filter(field FieldType, pattern, regex string) PoolServer { - if p.Len() < 1 { - return nil - } - - var ( - r = make(pool, 0) - f string - ) - - pattern = strings.ToLower(pattern) - - p.MapRun(func(srv Server) { - switch field { - case FieldBind: - f = srv.GetBindable() - case FieldExpose: - f = srv.GetExpose() - case FieldName: - f = srv.GetName() - default: - f = srv.GetName() - } - - f = strings.ToLower(f) - - if pattern != "" && strings.Contains(f, pattern) { - r = append(r, srv) - return - } - - if regex == "" { - return - } - - if ok, err := regexp.MatchString(regex, srv.GetName()); err == nil && ok { - r = append(r, srv) - } - }) - - return r -} - -func (p pool) IsRunning(atLeast bool) bool { - if p.Len() < 1 { - return false - } - - var r = false - - for _, s := range p { - if s.IsRunning() { - r = true - continue - } - - if !atLeast { - return false - } - } - - return r -} - -func (p pool) WaitNotify(ctx context.Context, cancel context.CancelFunc) { - // 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: - p.Shutdown() - if cancel != nil { - cancel() - } - case <-ctx.Done(): - p.Shutdown() - if cancel != nil { - cancel() - } - } -} - -func (p pool) Listen(handler http.Handler) liberr.Error { - return p.ListenMultiHandler(map[string]http.Handler{HandlerDefault: handler}) -} - -func (p pool) ListenMultiHandler(handler map[string]http.Handler) liberr.Error { - if p.Len() < 1 { - return nil - } - - var e liberr.Error - - e = ErrorPoolListen.Error(nil) - liblog.InfoLevel.Log("Calling listen for All Servers") - - p.MapRun(func(srv Server) { - if len(handler) < 1 { - e.AddParentError(srv.Listen(nil)) - } else { - for k := range handler { - if len(handler) == 1 { - e.AddParentError(srv.Listen(handler[k])) - break - } else if strings.ToLower(k) == srv.GetHandlerKey() { - e.AddParentError(srv.Listen(handler[k])) - break - } - } - } - }) - - liblog.InfoLevel.Log("End of Calling listen for All Servers") - - if !e.HasParent() { - e = nil - } - - return e -} - -func (p pool) runMapCommand(f func(sem libsem.Sem, srv Server)) { - if p.Len() < 1 { - return - } - - var ( - s libsem.Sem - x context.Context - c context.CancelFunc - ) - - x, c = context.WithTimeout(context.Background(), timeoutShutdown) - - defer func() { - c() - s.DeferMain() - }() - - s = libsem.NewSemaphoreWithContext(x, 0) - - p.MapRun(func(srv Server) { - _ = s.NewWorker() - go func(sem libsem.Sem, srv Server) { - f(sem, srv) - }(s, srv) - }) - - _ = s.WaitAll() -} - -func (p pool) runMapRestart(sem libsem.Sem, srv Server) { - defer func() { - if sem != nil { - sem.DeferWorker() - } - }() - - if srv != nil { - srv.Restart() - } -} - -func (p pool) runMapShutdown(sem libsem.Sem, srv Server) { - defer func() { - if sem != nil { - sem.DeferWorker() - } - }() - - if srv != nil { - srv.Shutdown() - } -} - -func (p pool) Restart() { - p.runMapCommand(p.runMapRestart) -} - -func (p pool) Shutdown() { - p.runMapCommand(p.runMapShutdown) -} - -func (p pool) StatusInfo(bindAddress string) (name string, release string, hash string) { - if s := p.Get(bindAddress); s != nil { - return s.StatusInfo() - } - - return fmt.Sprintf("missing server '%s'", bindAddress), "", "" -} - -func (p pool) StatusHealth(bindAddress string) error { - if s := p.Get(bindAddress); s != nil { - return s.StatusHealth() - } - - //nolint #goerr113 - return fmt.Errorf("missing server '%s'", bindAddress) -} - -func (p pool) StatusRoute(keyPrefix string, sts libsts.RouteStatus) { - p.MapRun(func(srv Server) { - bind := srv.GetBindable() - name := fmt.Sprintf("%s-%s", keyPrefix, bind) - sts.ComponentNew(name, srv.StatusComponent(name)) - }) -} diff --git a/httpserver/pool/config.go b/httpserver/pool/config.go new file mode 100644 index 00000000..89eb9827 --- /dev/null +++ b/httpserver/pool/config.go @@ -0,0 +1,112 @@ +/* + * 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 pool + +import ( + libtls "github.com/nabbar/golib/certificates" + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + libhtp "github.com/nabbar/golib/httpserver" + srvtps "github.com/nabbar/golib/httpserver/types" + liblog "github.com/nabbar/golib/logger" +) + +type Config []libhtp.Config +type FuncWalkConfig func(cfg libhtp.Config) bool + +func (p Config) SetHandlerFunc(hdl srvtps.FuncHandler) { + for i, c := range p { + c.RegisterHandlerFunc(hdl) + p[i] = c + } +} + +func (p Config) SetDefaultTLS(f libtls.FctTLSDefault) { + for i, c := range p { + c.SetDefaultTLS(f) + p[i] = c + } +} + +func (p Config) SetContext(f libctx.FuncContext) { + for i, c := range p { + c.SetContext(f) + p[i] = c + } +} + +func (p Config) Pool(ctx libctx.FuncContext, hdl srvtps.FuncHandler, defLog liblog.FuncLog) (Pool, liberr.Error) { + var ( + r = New(ctx, hdl) + e = ErrorPoolAdd.Error(nil) + ) + + p.Walk(func(cfg libhtp.Config) bool { + if err := r.StoreNew(cfg, defLog); err != nil { + e.AddParent(err) + } + return true + }) + + if !e.HasParent() { + e = nil + } + + return r, e +} + +func (p Config) Walk(fct FuncWalkConfig) { + if fct == nil { + return + } + + for _, c := range p { + if !fct(c) { + return + } + } +} + +func (p Config) Validate() liberr.Error { + var e = ErrorPoolValidate.Error(nil) + + p.Walk(func(cfg libhtp.Config) bool { + var err liberr.Error + + if err = cfg.Validate(); err != nil { + e.AddParentError(err) + } + + return true + }) + + if !e.HasParent() { + e = nil + } + + return e +} diff --git a/config/components/nutsdb/errors.go b/httpserver/pool/error.go similarity index 62% rename from config/components/nutsdb/errors.go rename to httpserver/pool/error.go index 02794d19..4016087c 100644 --- a/config/components/nutsdb/errors.go +++ b/httpserver/pool/error.go @@ -24,28 +24,27 @@ * */ -package nutsdb +package pool import ( "fmt" - libcfg "github.com/nabbar/golib/config" liberr "github.com/nabbar/golib/errors" ) const ( - ErrorParamEmpty liberr.CodeError = iota + libcfg.MinErrorComponentNutsDB - ErrorParamInvalid - ErrorComponentNotInitialized - ErrorConfigInvalid - ErrorStartComponent - ErrorReloadComponent - ErrorDependencyLogDefault + ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgHttpServerPool + ErrorPoolAdd + ErrorPoolValidate + ErrorPoolStart + ErrorPoolStop + ErrorPoolRestart + ErrorPoolMonitor ) func init() { if liberr.ExistInMapMessage(ErrorParamEmpty) { - panic(fmt.Errorf("error code collision with package golib/config/components/nutsdb")) + panic(fmt.Errorf("error code collision with package golib/httpserver")) } liberr.RegisterIdFctMessage(ErrorParamEmpty, getMessage) } @@ -53,19 +52,19 @@ func init() { func getMessage(code liberr.CodeError) (message string) { switch code { case ErrorParamEmpty: - return "at least one given parameters is empty" - case ErrorParamInvalid: - return "at least one given parameters is invalid" - case ErrorComponentNotInitialized: - return "this component seems to not be correctly initialized" - case ErrorConfigInvalid: - return "invalid component config" - case ErrorStartComponent: - return "cannot start component with config" - case ErrorReloadComponent: - return "cannot reload component with new config" - case ErrorDependencyLogDefault: - return "cannot retrieve default Logger" + return "given parameters is empty" + case ErrorPoolAdd: + return "cannot add server on pool" + case ErrorPoolValidate: + return "at least one config server seems to be not valid" + case ErrorPoolStart: + return "at least one server has listen error" + case ErrorPoolStop: + return "at least one server has shutdown error" + case ErrorPoolRestart: + return "at least one server has restart error" + case ErrorPoolMonitor: + return "at least one server has monitor error" } return liberr.NullMessage diff --git a/httpserver/pool/interface.go b/httpserver/pool/interface.go new file mode 100644 index 00000000..688fa52a --- /dev/null +++ b/httpserver/pool/interface.go @@ -0,0 +1,93 @@ +/* + * 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 pool + +import ( + "context" + "sync" + + liblog "github.com/nabbar/golib/logger" + + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + libhtp "github.com/nabbar/golib/httpserver" + srvtps "github.com/nabbar/golib/httpserver/types" + montps "github.com/nabbar/golib/monitor/types" + libsrv "github.com/nabbar/golib/server" + libver "github.com/nabbar/golib/version" +) + +type FuncWalk func(bindAddress string, srv libhtp.Server) bool + +type Manage interface { + Walk(fct FuncWalk) bool + WalkLimit(fct FuncWalk, onlyBindAddress ...string) bool + + Clean() + Load(bindAddress string) libhtp.Server + Store(srv libhtp.Server) + Delete(bindAddress string) + + StoreNew(cfg libhtp.Config, defLog liblog.FuncLog) error + LoadAndDelete(bindAddress string) (val libhtp.Server, loaded bool) +} + +type Filter interface { + Has(bindAddress string) bool + Len() int + List(fieldFilter, fieldReturn srvtps.FieldType, pattern, regex string) []string + Filter(field srvtps.FieldType, pattern, regex string) Pool +} + +type Pool interface { + libsrv.Server + libsrv.WaitNotify + + Manage + Filter + + Clone(ctx context.Context) Pool + Merge(p Pool, def liblog.FuncLog) error + Handler(fct srvtps.FuncHandler) + Monitor(vrs libver.Version) ([]montps.Monitor, liberr.Error) +} + +func New(ctx libctx.FuncContext, hdl srvtps.FuncHandler, srv ...libhtp.Server) Pool { + p := &pool{ + m: sync.RWMutex{}, + p: libctx.NewConfig[string](ctx), + h: hdl, + } + + for _, s := range srv { + if s != nil { + p.Store(s) + } + } + + return p +} diff --git a/httpserver/pool/list.go b/httpserver/pool/list.go new file mode 100644 index 00000000..84297ee6 --- /dev/null +++ b/httpserver/pool/list.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 pool + +import ( + "regexp" + "strings" + + liblog "github.com/nabbar/golib/logger" + + libhtp "github.com/nabbar/golib/httpserver" + srvtps "github.com/nabbar/golib/httpserver/types" +) + +func (o *pool) Clean() { + o.p.Clean() +} + +func (o *pool) Walk(fct FuncWalk) bool { + return o.WalkLimit(fct) +} + +func (o *pool) WalkLimit(fct FuncWalk, onlyBindAddress ...string) bool { + if fct == nil { + return false + } + + return o.p.WalkLimit(func(key string, val interface{}) bool { + if v, k := val.(libhtp.Server); !k { + return true + } else { + return fct(key, v) + } + }, onlyBindAddress...) +} + +func (o *pool) Load(bindAddress string) libhtp.Server { + if i, l := o.p.Load(bindAddress); !l { + return nil + } else if v, k := i.(libhtp.Server); !k { + return nil + } else { + return v + } +} + +func (o *pool) Store(srv libhtp.Server) { + o.p.Store(srv.GetBindable(), srv) +} + +func (o *pool) StoreNew(cfg libhtp.Config, defLog liblog.FuncLog) error { + if s, e := libhtp.New(cfg, defLog); e != nil { + return e + } else { + o.Store(s) + return nil + } +} + +func (o *pool) Delete(bindAddress string) { + o.p.Delete(bindAddress) +} + +func (o *pool) LoadAndDelete(bindAddress string) (val libhtp.Server, loaded bool) { + if i, l := o.p.LoadAndDelete(bindAddress); !l { + return nil, false + } else if v, k := i.(libhtp.Server); !k { + return nil, false + } else { + return v, true + } +} + +func (o *pool) Has(bindAddress string) bool { + if i, l := o.p.Load(bindAddress); !l { + return false + } else { + _, ok := i.(libhtp.Server) + return ok + } +} + +func (o *pool) Len() int { + var cnt int + + o.p.Walk(func(key string, val interface{}) bool { + cnt++ + return true + }) + + return cnt +} + +func (o *pool) Filter(field srvtps.FieldType, pattern, regex string) Pool { + var ( + r = o.Clone(nil) + f string + ) + + r.Clean() + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + switch field { + case srvtps.FieldBind: + f = srv.GetBindable() + case srvtps.FieldExpose: + f = srv.GetExpose() + default: + f = srv.GetName() + } + + var found = false + + if len(pattern) > 0 && strings.EqualFold(f, pattern) { + found = true + } else if len(regex) > 0 { + if ok, err := regexp.MatchString(regex, f); err == nil && ok { + found = true + } + } + + if found { + r.Store(srv) + } + + return true + }) + + return r +} + +func (o *pool) List(fieldFilter, fieldReturn srvtps.FieldType, pattern, regex string) []string { + var r = make([]string, 0) + + o.Filter(fieldFilter, pattern, regex).Walk(func(bindAddress string, srv libhtp.Server) bool { + switch fieldReturn { + case srvtps.FieldBind: + r = append(r, srv.GetBindable()) + case srvtps.FieldExpose: + r = append(r, srv.GetExpose()) + default: + r = append(r, srv.GetName()) + } + return true + }) + + return r +} diff --git a/httpserver/pool/model.go b/httpserver/pool/model.go new file mode 100644 index 00000000..c8242c8d --- /dev/null +++ b/httpserver/pool/model.go @@ -0,0 +1,132 @@ +/* + * 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 pool + +import ( + "context" + "net/http" + "sync" + + liblog "github.com/nabbar/golib/logger" + + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + libhtp "github.com/nabbar/golib/httpserver" + srvtps "github.com/nabbar/golib/httpserver/types" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +type pool struct { + m sync.RWMutex + p libctx.Config[string] + h srvtps.FuncHandler +} + +func (o *pool) Clone(ctx context.Context) Pool { + return &pool{ + m: sync.RWMutex{}, + p: o.p.Clone(ctx), + h: o.h, + } +} + +func (o *pool) Merge(p Pool, def liblog.FuncLog) error { + var err error + + p.Walk(func(bindAddress string, srv libhtp.Server) bool { + if s := o.Get(bindAddress); s == nil { + o.Store(srv) + } else if e := s.Merge(srv, def); e != nil { + err = e + return false + } else { + o.Store(s) + } + return true + }) + + return err +} + +func (o *pool) Get(adr string) libhtp.Server { + if i, l := o.p.Load(adr); !l { + return nil + } else if v, k := i.(libhtp.Server); !k { + return nil + } else { + return v + } +} + +func (o *pool) Handler(fct srvtps.FuncHandler) { + o.m.Lock() + defer o.m.Unlock() + o.h = fct +} + +func (o *pool) handler(name string) http.Handler { + o.m.RLock() + defer o.m.RUnlock() + + if o.h == nil { + return srvtps.NewBadHandler() + } else if h := o.h(); h == nil { + return srvtps.NewBadHandler() + } else if f, k := h[name]; !k { + return srvtps.NewBadHandler() + } else { + return f + } +} + +func (o *pool) context() context.Context { + return o.p.GetContext() +} + +func (o *pool) Monitor(vrs libver.Version) ([]montps.Monitor, liberr.Error) { + var ( + res = make([]montps.Monitor, 0) + err = ErrorPoolMonitor.Error(nil) + ) + + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + if p, e := srv.Monitor(vrs); e != nil { + err.AddParent(e) + } else { + res = append(res, p) + } + + return true + }) + + if !err.HasParent() { + err = nil + } + + return res, err +} diff --git a/httpserver/pool/server.go b/httpserver/pool/server.go new file mode 100644 index 00000000..1eed1586 --- /dev/null +++ b/httpserver/pool/server.go @@ -0,0 +1,108 @@ +/* + * 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 pool + +import ( + "context" + + libhtp "github.com/nabbar/golib/httpserver" +) + +func (o *pool) Start(ctx context.Context) error { + var err = ErrorPoolStart.Error(nil) + + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + if e := srv.Start(ctx); e != nil { + err.AddParent(e) + } else { + o.Store(srv) + } + + return true + }) + + if !err.HasParent() { + err = nil + } + + return err +} + +func (o *pool) Stop(ctx context.Context) error { + var err = ErrorPoolStop.Error(nil) + + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + if e := srv.Stop(ctx); e != nil { + err.AddParent(e) + } else { + o.Store(srv) + } + + return true + }) + + if !err.HasParent() { + err = nil + } + + return err +} + +func (o *pool) Restart(ctx context.Context) error { + var err = ErrorPoolRestart.Error(nil) + + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + if e := srv.Restart(ctx); e != nil { + err.AddParent(e) + } else { + o.Store(srv) + } + + return true + }) + + if !err.HasParent() { + err = nil + } + + return err +} + +func (o *pool) IsRunning() bool { + var run = false + + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + if srv.IsRunning() { + run = true + return false + } + + return true + }) + + return run +} diff --git a/httpserver/pool/waitNotify.go b/httpserver/pool/waitNotify.go new file mode 100644 index 00000000..eb5850e4 --- /dev/null +++ b/httpserver/pool/waitNotify.go @@ -0,0 +1,47 @@ +/* + * 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 pool + +import ( + "context" + + libhtp "github.com/nabbar/golib/httpserver" +) + +func (o *pool) StartWaitNotify(ctx context.Context) { + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + srv.StartWaitNotify(ctx) + return true + }) +} + +func (o *pool) StopWaitNotify() { + o.Walk(func(bindAddress string, srv libhtp.Server) bool { + srv.StopWaitNotify() + return true + }) +} diff --git a/httpserver/run.go b/httpserver/run.go deleted file mode 100644 index 7da1eadd..00000000 --- a/httpserver/run.go +++ /dev/null @@ -1,423 +0,0 @@ -/*********************************************************************************************************************** - * - * MIT License - * - * Copyright (c) 2021 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 httpserver - -import ( - "context" - "errors" - "fmt" - "log" - "net" - "net/http" - "os" - "os/signal" - "sync" - "sync/atomic" - "syscall" - "time" - - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" - "golang.org/x/net/http2" -) - -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 - snm string // server name - bnd string // server bind - tls bool // is tls server or tls mandatory - srv *http.Server // golang http server - ctx context.Context // context - cnl context.CancelFunc // cancel func of context -} - -type run interface { - IsRunning() bool - GetError() error - WaitNotify() - Listen(cfg *ServerConfig, handler http.Handler) liberr.Error - Restart(cfg *ServerConfig) - Shutdown() -} - -func newRun(log liblog.FuncLog) run { - return &srvRun{ - m: sync.Mutex{}, - log: log, - err: new(atomic.Value), - run: new(atomic.Value), - srv: nil, - } -} - -func (s *srvRun) getRunning() bool { - if s.run == nil { - return false - } else if i := s.run.Load(); i == nil { - return false - } else if b, ok := i.(bool); !ok { - return false - } else { - return b - } -} - -func (s *srvRun) setRunning(state bool) { - s.run.Store(state) -} - -func (s *srvRun) IsRunning() bool { - return s.getRunning() -} - -func (s *srvRun) getErr() error { - if s.err == nil { - return nil - } else if i := s.err.Load(); i == nil { - return nil - } else if e, ok := i.(error); !ok { - return nil - } else if e.Error() == "" { - return nil - } else { - return e - } -} - -func (s *srvRun) setErr(e error) { - if e != nil { - s.err.Store(e) - } else { - //nolint #goerr113 - s.err.Store(errors.New("")) - } -} - -func (s *srvRun) GetError() error { - return s.getErr() -} - -func (s *srvRun) WaitNotify() { - if !s.IsRunning() { - return - } - - // 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: - s.Shutdown() - case <-s.ctx.Done(): - if s.IsRunning() { - s.Shutdown() - } else if s.srv != nil { - s.srvShutdown() - } - } -} - -func (s *srvRun) Merge(srv Server) bool { - panic("implement me") -} - -func (s *srvRun) getLogger() liblog.Logger { - var _log liblog.Logger - - if s.log == nil { - _log = liblog.GetDefault() - } else if l := s.log(); l == nil { - _log = liblog.GetDefault() - } else { - _log = l - } - - _log.SetFields(_log.GetFields().Add("server_name", s.snm).Add("server_bind", s.bnd).Add("server_tls", s.tls)) - return _log -} - -// nolint #gocognit -func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error { - ssl, err := cfg.GetTLS() - if err != nil { - return err - } - - sTls := cfg.TLSMandatory - bind := cfg.GetListen().Host - name := cfg.Name - if name == "" { - name = bind - } - - s.snm = name - s.bnd = bind - s.tls = sTls || ssl.LenCertificatePair() > 0 - - var ( - l = s.getLogger() - ) - - srv := &http.Server{ - Addr: cfg.GetListen().Host, - ErrorLog: l.GetStdLogger(liblog.ErrorLevel, log.LstdFlags|log.Lmicroseconds), - } - - if cfg.ReadTimeout > 0 { - srv.ReadTimeout = cfg.ReadTimeout - } - - if cfg.ReadHeaderTimeout > 0 { - srv.ReadHeaderTimeout = cfg.ReadHeaderTimeout - } - - if cfg.WriteTimeout > 0 { - srv.WriteTimeout = cfg.WriteTimeout - } - - if cfg.MaxHeaderBytes > 0 { - srv.MaxHeaderBytes = cfg.MaxHeaderBytes - } - - if cfg.IdleTimeout > 0 { - srv.IdleTimeout = cfg.IdleTimeout - } - - if cfg.DisableKeepAlive { - srv.SetKeepAlivesEnabled(false) - } else { - srv.SetKeepAlivesEnabled(true) - } - - if ssl.LenCertificatePair() > 0 { - srv.TLSConfig = ssl.TlsConfig("") - } - - if handler != nil { - srv.Handler = handler - } else if s.srv != nil { - srv.Handler = s.srv.Handler - } - - s2 := &http2.Server{} - - if cfg.MaxHandlers > 0 { - s2.MaxHandlers = cfg.MaxHandlers - } - - if cfg.MaxConcurrentStreams > 0 { - s2.MaxConcurrentStreams = cfg.MaxConcurrentStreams - } - - if cfg.PermitProhibitedCipherSuites { - s2.PermitProhibitedCipherSuites = true - } - - if cfg.IdleTimeout > 0 { - s2.IdleTimeout = cfg.IdleTimeout - } - - if cfg.MaxUploadBufferPerConnection > 0 { - s2.MaxUploadBufferPerConnection = cfg.MaxUploadBufferPerConnection - } - - if cfg.MaxUploadBufferPerStream > 0 { - s2.MaxUploadBufferPerStream = cfg.MaxUploadBufferPerStream - } - - if e := http2.ConfigureServer(srv, s2); e != nil { - s.setErr(e) - return ErrorHTTP2Configure.ErrorParent(e) - } - - if s.IsRunning() { - s.Shutdown() - } - - for i := 0; i < 5; i++ { - if e := s.PortInUse(cfg.Listen); e != nil { - s.Shutdown() - } else { - break - } - } - - if s.ctx != nil && s.ctx.Err() == nil && s.cnl != nil { - s.cnl() - s.ctx = nil - s.cnl = nil - } - - 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, _log liblog.Logger, name, host string, tlsMandatory bool) { - ent := _log.Entry(liblog.InfoLevel, "server stopped") - - defer func() { - ent.Log() - - if _log != nil { - _ = _log.Close() - } - - if ctx != nil && cnl != nil && ctx.Err() == nil { - cnl() - } - - 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() - - if ssl.LenCertificatePair() > 0 { - s.setRunning(true) - er = s.srv.ListenAndServeTLS("", "") - } else if tlsMandatory { - //nolint #goerr113 - er = fmt.Errorf("missing valid server certificates") - } else { - s.setRunning(true) - er = s.srv.ListenAndServe() - } - - if er != nil && ctx.Err() != nil && ctx.Err().Error() == er.Error() { - return - } else if er != nil && errors.Is(er, http.ErrServerClosed) { - return - } else if er != nil { - s.setErr(er) - ent.Level = liblog.ErrorLevel - ent.ErrorAdd(true, er) - } - }(s.ctx, s.cnl, l, name, bind, sTls) - - return nil -} - -func (s *srvRun) Restart(cfg *ServerConfig) { - _ = s.Listen(cfg, nil) -} - -func (s *srvRun) Shutdown() { - s.srvShutdown() - s.setRunning(false) - - if s.cnl != nil && s.ctx != nil && s.ctx.Err() == nil { - s.cnl() - } -} - -func (s *srvRun) srvShutdown() { - ctx, cancel := context.WithTimeout(context.Background(), timeoutShutdown) - _log := s.getLogger() - - defer func() { - 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) - } - - if _log != nil { - _ = _log.Close() - } - }() - - if s.srv != nil { - err := s.srv.Shutdown(ctx) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - _log.Entry(liblog.ErrorLevel, "Shutdown server").ErrorAdd(true, err).Check(liblog.InfoLevel) - } - } -} - -func (s *srvRun) PortInUse(listen string) liberr.Error { - var ( - dia = net.Dialer{} - con net.Conn - err error - ctx context.Context - cnl context.CancelFunc - ) - - defer func() { - if cnl != nil { - cnl() - } - if con != nil { - _ = con.Close() - } - }() - - ctx, cnl = context.WithTimeout(context.TODO(), _TimeoutWaitingPortFreeing) - con, err = dia.DialContext(ctx, "tcp", listen) - - if con != nil { - _ = con.Close() - con = nil - } - - cnl() - cnl = nil - - if err != nil { - return nil - } - - return ErrorPortUse.Error(nil) -} diff --git a/httpserver/run/interface.go b/httpserver/run/interface.go new file mode 100644 index 00000000..66e7d1bd --- /dev/null +++ b/httpserver/run/interface.go @@ -0,0 +1,53 @@ +/* + * 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 run + +import ( + "net/http" + "sync" + + liblog "github.com/nabbar/golib/logger" + libsrv "github.com/nabbar/golib/server" +) + +type Run interface { + libsrv.Server + libsrv.WaitNotify + + GetError() error + SetServer(srv *http.Server, log liblog.FuncLog, tls bool) +} + +func New() Run { + return &sRun{ + m: sync.RWMutex{}, + ctx: nil, + cnl: nil, + log: nil, + srv: nil, + } +} diff --git a/httpserver/run/model.go b/httpserver/run/model.go new file mode 100644 index 00000000..35c7e00c --- /dev/null +++ b/httpserver/run/model.go @@ -0,0 +1,119 @@ +/* + * 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 run + +import ( + "context" + "net" + "net/http" + "sync" + + liblog "github.com/nabbar/golib/logger" +) + +type sRun struct { + m sync.RWMutex + err error + chn chan struct{} + ctx context.Context + cnl context.CancelFunc + log liblog.FuncLog + srv *http.Server + run bool + tls bool +} + +func (o *sRun) logger() liblog.Logger { + o.m.RLock() + defer o.m.RUnlock() + + if o.log == nil { + return liblog.GetDefault() + } else if l := o.log(); l == nil { + return liblog.GetDefault() + } else { + return l + } +} + +func (o *sRun) SetServer(srv *http.Server, log liblog.FuncLog, tls bool) { + o.m.Lock() + defer o.m.Unlock() + + srv.BaseContext = func(listener net.Listener) context.Context { + return o.getContext() + } + + o.srv = srv + o.log = log + o.tls = tls +} + +func (o *sRun) getServer() *http.Server { + o.m.RLock() + defer o.m.RUnlock() + return o.srv +} + +func (o *sRun) delServer() { + o.m.Lock() + defer o.m.Unlock() + if o.srv != nil { + o.logger().Entry(liblog.ErrorLevel, "closing server").ErrorAdd(true, o.srv.Close()).Check(liblog.NilLevel) + o.srv = nil + } +} + +func (o *sRun) getContext() context.Context { + o.m.RLock() + defer o.m.RUnlock() + return o.ctx +} + +func (o *sRun) getCancel() context.CancelFunc { + o.m.RLock() + defer o.m.RUnlock() + return o.cnl +} + +func (o *sRun) isTLS() bool { + o.m.RLock() + defer o.m.RUnlock() + return o.tls +} + +func (o *sRun) GetError() error { + o.m.RLock() + defer o.m.RUnlock() + return o.err +} + +func (o *sRun) setError(err error) { + o.m.Lock() + defer o.m.Unlock() + o.err = err +} diff --git a/httpserver/run/server.go b/httpserver/run/server.go new file mode 100644 index 00000000..0be67f28 --- /dev/null +++ b/httpserver/run/server.go @@ -0,0 +1,136 @@ +/* + * 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 run + +import ( + "context" + "errors" + "fmt" + "net/http" + + srvtps "github.com/nabbar/golib/httpserver/types" + liblog "github.com/nabbar/golib/logger" +) + +func (o *sRun) Start(ctx context.Context) error { + o.m.Lock() + defer o.m.Unlock() + + if o == nil || o.srv == nil { + return fmt.Errorf("invalid instance") + } + + o.ctx, o.cnl = context.WithCancel(ctx) + go o.runServer() + return nil +} + +func (o *sRun) runServer() { + defer func() { + cnl := o.getCancel() + if cnl != nil { + cnl() + } + + o.setRunning(false) + o.logger().Entry(liblog.InfoLevel, "server stopped").Log() + }() + + var err error + o.logger().Entry(liblog.InfoLevel, "Server is starting").Log() + o.setError(nil) + o.setRunning(true) + + if o.isTLS() { + err = o.getServer().ListenAndServeTLS("", "") + } else { + err = o.getServer().ListenAndServe() + } + + if err != nil { + x := o.getContext() + if x != nil && x.Err() != nil && errors.Is(err, x.Err()) { + err = nil + } else if errors.Is(err, http.ErrServerClosed) { + err = nil + } + } + + o.setError(err) + o.logger().Entry(liblog.InfoLevel, "Server return an error").ErrorAdd(true, err).Check(liblog.NilLevel) +} + +func (o *sRun) Stop(ctx context.Context) error { + var cnl context.CancelFunc + + if ctx.Err() != nil { + ctx = context.Background() + } + + if t, ok := ctx.Deadline(); !ok || t.IsZero() { + ctx, cnl = context.WithTimeout(ctx, srvtps.TimeoutWaitingStop) + } + + defer func() { + if cnl != nil { + cnl() + } + //o.delServer() + }() + + var err error + if o.IsRunning() { + o.StopWaitNotify() + if srv := o.getServer(); srv != nil { + err = srv.Shutdown(ctx) + } + } + + if err != nil && !errors.Is(err, http.ErrServerClosed) { + err = nil + } + + o.logger().Entry(liblog.ErrorLevel, "Shutting down server").ErrorAdd(true, err).Check(liblog.NilLevel) + return err +} + +func (o *sRun) Restart(ctx context.Context) error { + _ = o.Stop(ctx) + return o.Start(ctx) +} + +func (o *sRun) IsRunning() bool { + o.m.RLock() + defer o.m.RUnlock() + return o.run +} + +func (o *sRun) setRunning(flag bool) { + o.m.Lock() + defer o.m.Unlock() + o.run = flag +} diff --git a/httpserver/run/waitNotify.go b/httpserver/run/waitNotify.go new file mode 100644 index 00000000..f2ba638c --- /dev/null +++ b/httpserver/run/waitNotify.go @@ -0,0 +1,79 @@ +/* + * 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 run + +import ( + "context" + "os" + "os/signal" + "syscall" +) + +func (o *sRun) StartWaitNotify(ctx context.Context) { + if !o.IsRunning() { + return + } + + // 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) + + o.initChan() + select { + case <-quit: + _ = o.Stop(ctx) + return + case <-o.getContext().Done(): + if o.IsRunning() { + _ = o.Stop(ctx) + } + return + case <-o.getChan(): + return + } +} + +func (o *sRun) StopWaitNotify() { + o.m.Lock() + defer o.m.Unlock() + o.chn <- struct{}{} +} + +func (o *sRun) initChan() { + o.m.Lock() + defer o.m.Unlock() + o.chn = make(chan struct{}) +} + +func (o *sRun) getChan() <-chan struct{} { + o.m.RLock() + defer o.m.RUnlock() + return o.chn +} diff --git a/httpserver/server.go b/httpserver/server.go index 63c605bf..a9ddbd6d 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2020 Nicolas JUHEL + * 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 @@ -27,243 +27,152 @@ package httpserver import ( + "context" "fmt" + "log" + "net" "net/http" - "runtime" - "strings" - "sync/atomic" "time" - "github.com/nabbar/golib/status/config" - - liblog "github.com/nabbar/golib/logger" - liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" -) - -const ( - timeoutShutdown = 10 * time.Second + srvtps "github.com/nabbar/golib/httpserver/types" + liblog "github.com/nabbar/golib/logger" ) -type server struct { - log *atomic.Value - run *atomic.Value - cfg *atomic.Value -} - -type Server interface { - SetLogger(log liblog.FuncLog) - - GetConfig() *ServerConfig - SetConfig(cfg *ServerConfig) bool - - GetName() string - GetBindable() string - GetExpose() string - GetHandlerKey() string - - IsRunning() bool - IsTLS() bool - WaitNotify() - Merge(srv Server) bool - - Listen(handler http.Handler) liberr.Error - Restart() - Shutdown() - - StatusInfo() (name string, release string, hash string) - StatusHealth() error - StatusComponent(key string) libsts.Component -} - -func NewServer(cfg *ServerConfig) Server { - c := new(atomic.Value) - c.Store(cfg.Clone()) - - return &server{ - log: new(atomic.Value), - cfg: c, - run: new(atomic.Value), +func (o *srv) Start(ctx context.Context) error { + ssl := o.cfgGetTLS() + if ssl == nil && o.IsTLS() { + return ErrorServerValidate.ErrorParent(fmt.Errorf("TLS Config is not well defined")) } -} - -func (s *server) SetLogger(log liblog.FuncLog) { - s.log.Store(log) -} -func (s *server) GetLogger() liblog.FuncLog { - if i := s.log.Load(); i == nil { - return nil - } else if f, ok := i.(liblog.FuncLog); ok { - return f + bind := o.GetBindable() + name := o.GetName() + if name == "" { + name = bind } - return nil -} - -func (s *server) GetConfig() *ServerConfig { - if s.cfg == nil { - return nil - } else if i := s.cfg.Load(); i == nil { - return nil - } else if c, ok := i.(ServerConfig); !ok { - return nil - } else { - return &c + s := &http.Server{ + Addr: bind, + ErrorLog: o.logger().GetStdLogger(liblog.ErrorLevel, log.LstdFlags|log.Lmicroseconds), + Handler: o.HandlerLoadFct(), } -} -func (s *server) IsDisabled() bool { - if c := s.GetConfig(); c != nil { - return c.Disabled + if ssl != nil && ssl.LenCertificatePair() > 0 { + s.TLSConfig = ssl.TlsConfig("") } - return true -} - -func (s *server) SetConfig(cfg *ServerConfig) bool { - if cfg == nil { - return false + if e := o.cfgGetServer().initServer(s); e != nil { + return e } - if s.cfg == nil { - s.cfg = new(atomic.Value) + if o.IsRunning() { + _ = o.Stop(ctx) } - c := cfg.Clone() - - if c.Name == "" { - c.Name = c.GetListen().Host + for i := 0; i < 5; i++ { + if e := o.PortInUse(o.GetBindable()); e != nil { + _ = o.Stop(ctx) + } else { + break + } } - s.cfg.Store(cfg.Clone()) - return true -} + o.m.RLock() + defer o.m.RUnlock() -func (s *server) getRun() run { - if s.run == nil { - return newRun(s.GetLogger()) - } else if i := s.run.Load(); i == nil { - return newRun(s.GetLogger()) - } else if r, ok := i.(run); !ok { - return newRun(s.GetLogger()) - } else { - return r - } -} - -func (s *server) setRun(r run) { - s.run.Store(r) -} + o.r.SetServer(s, o.logger, ssl != nil) -func (s *server) getErr() error { - if r := s.getRun(); r == nil { - return nil - } else { - return r.GetError() + if e := o.r.Start(ctx); e != nil { + return e } -} -func (s server) GetName() string { - return s.GetConfig().Name -} + for i := 0; i < 5; i++ { + ok := false + ts := time.Now() -func (s *server) GetBindable() string { - return s.GetConfig().GetListen().Host -} + for { + time.Sleep(100 * time.Millisecond) + ok = o.r.IsRunning() -func (s *server) GetExpose() string { - return s.GetConfig().GetExpose().String() -} + if ok { + break + } else if time.Since(ts) > 10*time.Second { + break + } + } -func (s *server) GetHandlerKey() string { - return s.GetConfig().GetHandlerKey() -} + if !ok { + _ = o.r.Restart(ctx) + } else { + ts = time.Now() -func (s *server) IsRunning() bool { - return s.getRun().IsRunning() -} + for { + time.Sleep(100 * time.Millisecond) -func (s *server) IsTLS() bool { - return s.GetConfig().IsTLS() -} + if e := o.PortInUse(o.GetBindable()); e != nil { + return nil + } else if time.Since(ts) > 10*time.Second { + break + } + } -func (s *server) Listen(handler http.Handler) liberr.Error { - if s.IsDisabled() && !s.IsRunning() { - return nil - } else if s.IsDisabled() { - s.Shutdown() - return nil + _ = o.r.Restart(ctx) + } } - r := s.getRun() - e := r.Listen(s.GetConfig(), handler) - s.setRun(r) - - runtime.GC() + _ = o.r.Stop(ctx) + return ErrorServerStart.Error(nil) +} - return e +func (o *srv) Stop(ctx context.Context) error { + o.m.RLock() + defer o.m.RUnlock() + return o.r.Stop(ctx) } -func (s *server) WaitNotify() { - r := s.getRun() - r.WaitNotify() +func (o *srv) Restart(ctx context.Context) error { + _ = o.Stop(ctx) + return o.Start(ctx) } -func (s *server) Restart() { - _ = s.Listen(nil) +func (o *srv) IsRunning() bool { + o.m.RLock() + defer o.m.RUnlock() + return o.r.IsRunning() } -func (s *server) Shutdown() { - if s.IsDisabled() && !s.IsRunning() { - return - } +func (o *srv) PortInUse(listen string) liberr.Error { + var ( + dia = net.Dialer{} + con net.Conn + err error + ctx context.Context + cnl context.CancelFunc + ) - r := s.getRun() - r.Shutdown() - s.setRun(r) -} + defer func() { + if cnl != nil { + cnl() + } + if con != nil { + _ = con.Close() + } + }() -func (s *server) Merge(srv Server) bool { - if x, ok := srv.(*server); !ok { - return false - } else { - return s.SetConfig(x.GetConfig()) - } -} + ctx, cnl = context.WithTimeout(context.TODO(), srvtps.TimeoutWaitingPortFreeing) + con, err = dia.DialContext(ctx, "tcp", listen) -func (s *server) StatusInfo() (name string, release string, hash string) { - vers := strings.TrimLeft(runtime.Version(), "go") - vers = strings.TrimLeft(vers, "Go") - vers = strings.TrimLeft(vers, "GO") + if con != nil { + _ = con.Close() + con = nil + } - return fmt.Sprintf("%s [%s]", s.GetName(), s.GetBindable()), runtime.Version()[2:], "" -} + cnl() + cnl = nil -func (s *server) StatusHealth() error { - if !s.IsDisabled() && s.IsRunning() { + if err != nil { return nil - } else if s.IsDisabled() { - return ErrorServerDisabled.Error(nil) - } else if e := s.getErr(); e != nil { - return e - } else { - return ErrorServerOffline.Error(nil) } -} -func (s *server) StatusComponent(key string) libsts.Component { - if cfg := s.GetConfig(); cfg == nil { - cnf := config.ConfigStatus{ - Mandatory: true, - MessageOK: "", - MessageKO: "", - CacheTimeoutInfo: 30 * time.Second, - CacheTimeoutHealth: 5 * time.Second, - } - return cnf.Component(key, s.StatusInfo, s.StatusHealth) - } else { - return cfg.Status.Component(key, s.StatusInfo, s.StatusHealth) - } + return ErrorPortUse.Error(nil) } diff --git a/httpserver/serverOpt.go b/httpserver/serverOpt.go new file mode 100644 index 00000000..13a6b2e2 --- /dev/null +++ b/httpserver/serverOpt.go @@ -0,0 +1,110 @@ +/* + * 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 httpserver + +import ( + "net/http" + "time" + + liberr "github.com/nabbar/golib/errors" + "golang.org/x/net/http2" +) + +type optServer struct { + ReadTimeout time.Duration + ReadHeaderTimeout time.Duration + WriteTimeout time.Duration + MaxHeaderBytes int + MaxHandlers int + MaxConcurrentStreams uint32 + MaxReadFrameSize uint32 + PermitProhibitedCipherSuites bool + IdleTimeout time.Duration + MaxUploadBufferPerConnection int32 + MaxUploadBufferPerStream int32 + DisableKeepAlive bool +} + +func (o *optServer) initServer(s *http.Server) liberr.Error { + if o.ReadTimeout > 0 { + s.ReadTimeout = o.ReadTimeout + } + + if o.ReadHeaderTimeout > 0 { + s.ReadHeaderTimeout = o.ReadHeaderTimeout + } + + if o.WriteTimeout > 0 { + s.WriteTimeout = o.WriteTimeout + } + + if o.MaxHeaderBytes > 0 { + s.MaxHeaderBytes = o.MaxHeaderBytes + } + + if o.IdleTimeout > 0 { + s.IdleTimeout = o.IdleTimeout + } + + if o.DisableKeepAlive { + s.SetKeepAlivesEnabled(false) + } else { + s.SetKeepAlivesEnabled(true) + } + + s2 := &http2.Server{} + + if o.MaxHandlers > 0 { + s2.MaxHandlers = o.MaxHandlers + } + + if o.MaxConcurrentStreams > 0 { + s2.MaxConcurrentStreams = o.MaxConcurrentStreams + } + + if o.PermitProhibitedCipherSuites { + s2.PermitProhibitedCipherSuites = true + } + + if o.IdleTimeout > 0 { + s2.IdleTimeout = o.IdleTimeout + } + + if o.MaxUploadBufferPerConnection > 0 { + s2.MaxUploadBufferPerConnection = o.MaxUploadBufferPerConnection + } + + if o.MaxUploadBufferPerStream > 0 { + s2.MaxUploadBufferPerStream = o.MaxUploadBufferPerStream + } + + if e := http2.ConfigureServer(s, s2); e != nil { + return ErrorHTTP2Configure.ErrorParent(e) + } + + return nil +} diff --git a/httpserver/types/const.go b/httpserver/types/const.go new file mode 100644 index 00000000..a1d00d61 --- /dev/null +++ b/httpserver/types/const.go @@ -0,0 +1,35 @@ +/* + * 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 types + +import "time" + +const ( + TimeoutWaitingPortFreeing = 500 * time.Microsecond + TimeoutWaitingStop = 10 * time.Second + BadHandlerName = "no handler" +) diff --git a/httpserver/types/fields.go b/httpserver/types/fields.go new file mode 100644 index 00000000..99a2b255 --- /dev/null +++ b/httpserver/types/fields.go @@ -0,0 +1,36 @@ +/* + * 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 types + +type FieldType uint8 + +const ( + HandlerDefault = "default" + FieldName FieldType = iota + FieldBind + FieldExpose +) diff --git a/httpserver/types/handler.go b/httpserver/types/handler.go new file mode 100644 index 00000000..7409fcef --- /dev/null +++ b/httpserver/types/handler.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 types + +import "net/http" + +type FuncHandler func() map[string]http.Handler + +func NewBadHandler() http.Handler { + return &BadHandler{} +} + +type BadHandler struct{} + +func (o BadHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(http.StatusInternalServerError) +} diff --git a/httpserver/waitNotify.go b/httpserver/waitNotify.go new file mode 100644 index 00000000..80380303 --- /dev/null +++ b/httpserver/waitNotify.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 httpserver + +import "context" + +func (o *srv) StartWaitNotify(ctx context.Context) { + o.m.RLock() + defer o.m.RUnlock() + go o.r.StartWaitNotify(ctx) +} + +func (o *srv) StopWaitNotify() { + o.m.RLock() + defer o.m.RUnlock() + go o.r.StopWaitNotify() +} diff --git a/ioutils/fileDescriptor.go b/ioutils/fileDescriptor.go index 2c1de57c..38c9db27 100644 --- a/ioutils/fileDescriptor.go +++ b/ioutils/fileDescriptor.go @@ -25,31 +25,29 @@ package ioutils -import . "github.com/nabbar/golib/errors" +import liberr "github.com/nabbar/golib/errors" -/** - * SystemFileDescriptor is returning current Limit & max system limit for file descriptor (open file or I/O resource) currently set in the system - * This function return the current setting (current number of file descriptor and the max value) if the newValue given is zero - * Otherwise if the newValue is more than the current system limit, try to change the current limit in the system for this process only - * - * For Windows build, please follow this note : - * 1) install package gcc-multilib gcc-mingw-w64 to build C source with GCC - * you will having this binaries - * - i686-w64-mingw32* for 32-bit Windows; - * - x86_64-w64-mingw32* for 64-bit Windows. - * locate you binaries gcc mingw path and note it: - * - win32 : updatedb && locate i686-w64-mingw32-gcc - * - win64 : updatedb && locate x86_64-w64-mingw32-gcc - * 2) if you have an error in the build, or if the .o object file is not present in golib/njg-ioutils/maxstdio/, run this step - * - go to golib/njg-ioutils/maxstdio folder - * - gcc -c maxstdio.c - * 3) for Win32 use this env var in prefix of your go build command (recommend to use -a flag) : - * CC=/usr/bin/i686-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -a -v ... - * 4) for win64 use this env var in prefix of your go build command (recommend to use -a flag) : - * CC=/usr/bin/x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -a -v ... - * - * Normally no problem will be result in the build. - */ -func SystemFileDescriptor(newValue int) (current int, max int, err Error) { +// SystemFileDescriptor is returning current Limit & max system limit for file descriptor (open file or I/O resource) currently set in the system +// This function return the current setting (current number of file descriptor and the max value) if the newValue given is zero +// Otherwise if the newValue is more than the current system limit, try to change the current limit in the system for this process only +// +// For Windows build, please follow this note : +// 1) install package gcc-multilib gcc-mingw-w64 to build C source with GCC +// you will having this binaries +// - i686-w64-mingw32* for 32-bit Windows; +// - x86_64-w64-mingw32* for 64-bit Windows. +// locate you binaries gcc mingw path and note it: +// - win32 : updatedb && locate i686-w64-mingw32-gcc +// - win64 : updatedb && locate x86_64-w64-mingw32-gcc +// 2) if you have an error in the build, or if the .o object file is not present in golib/njg-ioutils/maxstdio/, run this step +// - go to golib/njg-ioutils/maxstdio folder +// - gcc -x maxstdio.x +// 3) for Win32 use this env var in prefix of your go build command (recommend to use -a flag) : +// CC=/usr/bin/i686-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -a -v ... +// 4) for win64 use this env var in prefix of your go build command (recommend to use -a flag) : +// CC=/usr/bin/x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -a -v ... +// +// Normally no problem will be result in the build. +func SystemFileDescriptor(newValue int) (current int, max int, err liberr.Error) { return systemFileDescriptor(newValue) } diff --git a/logger/closer.go b/ioutils/mapCloser/interface.go similarity index 58% rename from logger/closer.go rename to ioutils/mapCloser/interface.go index 47d29c26..e9ce8baa 100644 --- a/logger/closer.go +++ b/ioutils/mapCloser/interface.go @@ -25,92 +25,40 @@ * **********************************************************************************************************************/ -package logger +package mapCloser import ( - "fmt" "io" - "strings" - "sync" + "sync/atomic" + + libctx "github.com/nabbar/golib/context" ) -type _Closer interface { - Add(clo io.Closer) +type Closer interface { + Add(clo ...io.Closer) Get() []io.Closer Len() int + Clean() - Clone() _Closer + Clone() Closer Close() error } -func _NewCloser() _Closer { - return &closer{ - m: sync.Mutex{}, - c: make([]io.Closer, 0), +func New(ctx libctx.FuncContext) Closer { + c := &closer{ + i: new(atomic.Uint64), + x: libctx.NewConfig[uint64](ctx), } -} -type closer struct { - m sync.Mutex - c []io.Closer -} + c.i.Store(0) -func (c *closer) Add(clo io.Closer) { - lst := append(c.Get(), clo) - - c.m.Lock() - defer c.m.Unlock() - - c.c = lst -} - -func (c *closer) Get() []io.Closer { - res := make([]io.Closer, 0) - - c.m.Lock() - defer c.m.Unlock() - - if len(c.c) > 0 { - for _, i := range c.c { - if i == nil { - continue - } - res = append(res, i) + go func() { + select { + case <-c.x.Done(): + _ = c.Close() + return } - } - - return res -} - -func (c *closer) Len() int { - c.m.Lock() - defer c.m.Unlock() - return len(c.c) -} - -func (c *closer) Clean() { - c.m.Lock() - defer c.m.Unlock() - - c.c = make([]io.Closer, 0) -} - -func (c *closer) Clone() _Closer { - o := _NewCloser() - for _, i := range c.Get() { - o.Add(i) - } - return o -} - -func (c *closer) Close() error { - var e = make([]string, 0) - - for _, i := range c.Get() { - if err := i.Close(); err != nil { - e = append(e, err.Error()) - } - } + }() - return fmt.Errorf("%s", strings.Join(e, ", ")) + return c } diff --git a/ioutils/mapCloser/model.go b/ioutils/mapCloser/model.go new file mode 100644 index 00000000..7a6ce367 --- /dev/null +++ b/ioutils/mapCloser/model.go @@ -0,0 +1,164 @@ +/*********************************************************************************************************************** + * + * MIT License + * + * Copyright (c) 2021 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 mapCloser + +import ( + "fmt" + "io" + "math" + "strings" + "sync/atomic" + + libctx "github.com/nabbar/golib/context" +) + +type closer struct { + i *atomic.Uint64 + x libctx.Config[uint64] +} + +func (o *closer) idx() uint64 { + return o.i.Load() +} + +func (o *closer) idxInc() uint64 { + o.i.Add(1) + return o.idx() +} + +func (o *closer) Add(clo ...io.Closer) { + if o == nil { + return + } else if o.x == nil { + return + } else if o.x.Err() != nil { + return + } + + for _, c := range clo { + o.x.Store(o.idxInc(), c) + } +} + +func (o *closer) Get() []io.Closer { + var res = make([]io.Closer, 0) + + if o == nil { + return res + } else if o.x == nil { + return res + } else if o.x.Err() != nil { + return res + } + + o.x.Walk(func(key uint64, val interface{}) bool { + if val == nil { + return true + } + if v, k := val.(io.Closer); !k { + return true + } else { + res = append(res, v) + return true + } + }) + return res +} + +func (o *closer) Len() int { + i := o.idx() + + if i > math.MaxInt { + // overflow + return math.MaxInt + } else { + return int(i) + } +} + +func (o *closer) Len64() uint64 { + return o.idx() +} + +func (o *closer) Clean() { + if o == nil { + return + } else if o.x == nil { + return + } else if o.x.Err() != nil { + return + } + + o.i.Store(0) + o.x.Clean() +} + +func (o *closer) Clone() Closer { + if o == nil { + return nil + } else if o.x == nil { + return nil + } else if o.x.Err() != nil { + return nil + } + + i := new(atomic.Uint64) + i.Store(o.idx()) + + return &closer{ + i: i, + x: o.x.Clone(nil), + } +} + +func (o *closer) Close() error { + var e = make([]string, 0) + + if o == nil { + return fmt.Errorf("not initialized") + } else if o.x == nil { + return fmt.Errorf("not initialized") + } else if o.x.Err() != nil { + return o.x.Err() + } + + o.x.Walk(func(key uint64, val interface{}) bool { + if c, k := val.(io.Closer); !k { + return true + } else if err := c.Close(); err != nil { + e = append(e, err.Error()) + } + return true + }) + + if len(e) > 0 { + return fmt.Errorf("%s", strings.Join(e, ", ")) + } + + return nil +} diff --git a/ldap/ldap.go b/ldap/ldap.go index 22afe79d..9a05ce1f 100644 --- a/ldap/ldap.go +++ b/ldap/ldap.go @@ -39,7 +39,7 @@ import ( liblog "github.com/nabbar/golib/logger" ) -type FuncLogger func() liblog.Logger +type FuncLogger liblog.FuncLog // HelperLDAP struct use to manage connection to server and request it. type HelperLDAP struct { @@ -51,7 +51,7 @@ type HelperLDAP struct { bindDN string bindPass string ctx context.Context - log FuncLogger + log liblog.FuncLog } // NewLDAP build a new LDAP helper based on config struct given. @@ -71,7 +71,7 @@ func NewLDAP(ctx context.Context, cnf *Config, attributes []string) (*HelperLDAP } // SetLogger is used to specify the logger to be used for debug messgae -func (lc *HelperLDAP) SetLogger(fct FuncLogger) { +func (lc *HelperLDAP) SetLogger(fct liblog.FuncLog) { lc.log = fct } diff --git a/logger/compat.go b/logger/compat.go index ecf20968..2987cda4 100644 --- a/logger/compat.go +++ b/logger/compat.go @@ -38,7 +38,7 @@ import ( var defaultLogger Logger func init() { - defaultLogger = New(context.Background()) + defaultLogger = New(context.Background) defaultLogger.SetLevel(InfoLevel) } diff --git a/logger/config/default.go b/logger/config/default.go new file mode 100644 index 00000000..9dd61636 --- /dev/null +++ b/logger/config/default.go @@ -0,0 +1,97 @@ +/* + * 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" + + cfgcst "github.com/nabbar/golib/config/const" +) + +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 SetDefaultConfig(cfg []byte) { + _defaultConfig = cfg +} + +func DefaultConfig(indent string) []byte { + var res = bytes.NewBuffer(make([]byte, 0)) + if err := json.Indent(res, _defaultConfig, indent, cfgcst.JSONIndent); err != nil { + return _defaultConfig + } else { + return res.Bytes() + } +} diff --git a/logger/entry.go b/logger/entry.go index 38803363..a923d41d 100644 --- a/logger/entry.go +++ b/logger/entry.go @@ -94,13 +94,13 @@ func (e *Entry) SetGinContext(ctx *gin.Context) *Entry { // FieldAdd allow to add one couple key/val as type string/interface into the custom field of the entry. func (e *Entry) FieldAdd(key string, val interface{}) *Entry { - e.Fields = e.Fields.Add(key, val) + e.Fields.Add(key, val) return e } // FieldMerge allow to merge a Field pointer into the custom field of the entry. func (e *Entry) FieldMerge(fields Fields) *Entry { - e.Fields = e.Fields.Merge(fields) + e.Fields.Merge(fields) return e } @@ -111,7 +111,10 @@ func (e *Entry) FieldSet(fields Fields) *Entry { } func (e *Entry) FieldClean(keys ...string) *Entry { - e.Fields = e.Fields.Clean(keys...) + for _, k := range keys { + e.Fields.Delete(k) + } + return e } @@ -183,14 +186,28 @@ func (e *Entry) _logClean() { } func (e *Entry) Log() { - if e.clean { + if e == nil { + return + } else if e.Fields == nil { + return + } else if e.Fields.Err() != nil { + return + } else if e.clean { e._logClean() return + } else if e.gin != nil && len(e.Error) > 0 { + for _, err := range e.Error { + _ = e.gin.Error(err) + } + } + + if e.Level == NilLevel { + return } var ( ent *logrus.Entry - tag = NewFields().Add(FieldLevel, e.Level.String()) + tag = NewFields(e.Fields.GetContext).Add(FieldLevel, e.Level.String()) log *logrus.Logger ) @@ -233,9 +250,7 @@ func (e *Entry) Log() { tag = tag.Add(FieldData, e.Data) } - if len(e.Fields) > 0 { - tag = tag.Merge(e.Fields) - } + tag.Merge(e.Fields) if e.log == nil { return @@ -245,16 +260,6 @@ func (e *Entry) Log() { ent = log.WithFields(tag.Logrus()) } - if e.gin != nil && len(e.Error) > 0 { - for _, err := range e.Error { - _ = e.gin.Error(err) - } - } - - if e.Level == NilLevel { - return - } - ent.Log(e.Level.Logrus()) if e.Level <= FatalLevel { diff --git a/logger/fields.go b/logger/fields.go index 41e00591..c0a03d34 100644 --- a/logger/fields.go +++ b/logger/fields.go @@ -27,81 +27,83 @@ package logger -import "github.com/sirupsen/logrus" - -type Fields map[string]interface{} - -func NewFields() Fields { - return make(Fields) +import ( + "context" + "encoding/json" + + libctx "github.com/nabbar/golib/context" + "github.com/sirupsen/logrus" +) + +type Fields interface { + libctx.Config[string] + json.Marshaler + json.Unmarshaler + + FieldsClone(ctx context.Context) Fields + Add(key string, val interface{}) Fields + Logrus() logrus.Fields + Map(fct func(key string, val interface{}) interface{}) Fields } -func (f Fields) clone() map[string]interface{} { - res := make(map[string]interface{}, 0) - - if len(f) > 0 { - for k, v := range f { - res[k] = v - } +func NewFields(ctx libctx.FuncContext) Fields { + return &fldModel{ + libctx.NewConfig[string](ctx), } - - return res } -func (f Fields) Add(key string, val interface{}) Fields { - res := f.clone() - res[key] = val - - return res +type fldModel struct { + libctx.Config[string] } -func (f Fields) Map(fct func(key string, val interface{}) interface{}) Fields { - res := f.clone() - - for k, v := range res { - if v = fct(k, v); v != nil { - res[k] = v - } - } - - return res +func (o *fldModel) Add(key string, val interface{}) Fields { + o.Store(key, val) + return o } -func (f Fields) Merge(other Fields) Fields { - if len(other) < 1 { - return f - } else if len(f) < 1 { - return other - } - - res := f.clone() - - other.Map(func(key string, val interface{}) interface{} { +func (o *fldModel) Logrus() logrus.Fields { + var res = make(logrus.Fields, 0) + o.Walk(func(key string, val interface{}) bool { res[key] = val - return nil + return true }) - return res } -func (f Fields) Clean(keys ...string) Fields { - res := make(map[string]interface{}, 0) +func (o *fldModel) Map(fct func(key string, val interface{}) interface{}) Fields { + o.Walk(func(key string, val interface{}) bool { + o.Store(key, fct(key, val)) + return true + }) + return o +} + +func (o *fldModel) MarshalJSON() ([]byte, error) { + return json.Marshal(o.Logrus()) +} - if len(keys) > 0 { - f.Map(func(key string, val interface{}) interface{} { - for _, kk := range keys { - if kk == key { - return nil - } - } +func (o *fldModel) UnmarshalJSON(bytes []byte) error { + var l = make(logrus.Fields) - res[key] = val - return nil - }) + if e := json.Unmarshal(bytes, &l); e != nil { + return e + } else if len(l) > 0 { + for k, v := range l { + o.Store(k, v) + } } - return res + return nil } -func (f Fields) Logrus() logrus.Fields { - return f.clone() +func (o *fldModel) FieldsClone(ctx context.Context) Fields { + if o == nil { + return nil + } else if o.Config == nil { + return nil + } else { + return &fldModel{ + o.Config.Clone(ctx), + } + } } diff --git a/logger/hclog.go b/logger/hclog.go index f6c2fa03..cfcd2814 100644 --- a/logger/hclog.go +++ b/logger/hclog.go @@ -43,128 +43,128 @@ type _hclog struct { l Logger } -func (l *_hclog) Log(level hclog.Level, msg string, args ...interface{}) { +func (o *_hclog) Log(level hclog.Level, msg string, args ...interface{}) { switch level { case hclog.NoLevel, hclog.Off: return case hclog.Trace: - l.l.Debug(msg, nil, args...) + o.l.Debug(msg, nil, args...) case hclog.Debug: - l.l.Debug(msg, nil, args...) + o.l.Debug(msg, nil, args...) case hclog.Info: - l.l.Info(msg, nil, args...) + o.l.Info(msg, nil, args...) case hclog.Warn: - l.l.Warning(msg, nil, args...) + o.l.Warning(msg, nil, args...) case hclog.Error: - l.l.Error(msg, nil, args...) + o.l.Error(msg, nil, args...) } } -func (l *_hclog) Trace(msg string, args ...interface{}) { - l.l.Debug(msg, nil, args...) +func (o *_hclog) Trace(msg string, args ...interface{}) { + o.l.Debug(msg, nil, args...) } -func (l *_hclog) Debug(msg string, args ...interface{}) { - l.l.Debug(msg, nil, args...) +func (o *_hclog) Debug(msg string, args ...interface{}) { + o.l.Debug(msg, nil, args...) } -func (l *_hclog) Info(msg string, args ...interface{}) { - l.l.Info(msg, nil, args...) +func (o *_hclog) Info(msg string, args ...interface{}) { + o.l.Info(msg, nil, args...) } -func (l *_hclog) Warn(msg string, args ...interface{}) { - l.l.Warning(msg, nil, args...) +func (o *_hclog) Warn(msg string, args ...interface{}) { + o.l.Warning(msg, nil, args...) } -func (l *_hclog) Error(msg string, args ...interface{}) { - l.l.Error(msg, nil, args...) +func (o *_hclog) Error(msg string, args ...interface{}) { + o.l.Error(msg, nil, args...) } -func (l *_hclog) IsTrace() bool { - return l.l.GetOptions().EnableTrace +func (o *_hclog) IsTrace() bool { + return o.l.GetOptions().EnableTrace } -func (l *_hclog) IsDebug() bool { - return l.l.GetLevel() >= DebugLevel +func (o *_hclog) IsDebug() bool { + return o.l.GetLevel() >= DebugLevel } -func (l *_hclog) IsInfo() bool { - return l.l.GetLevel() >= InfoLevel +func (o *_hclog) IsInfo() bool { + return o.l.GetLevel() >= InfoLevel } -func (l *_hclog) IsWarn() bool { - return l.l.GetLevel() >= WarnLevel +func (o *_hclog) IsWarn() bool { + return o.l.GetLevel() >= WarnLevel } -func (l *_hclog) IsError() bool { - return l.l.GetLevel() >= ErrorLevel +func (o *_hclog) IsError() bool { + return o.l.GetLevel() >= ErrorLevel } -func (l *_hclog) ImpliedArgs() []interface{} { - fields := l.l.GetFields() +func (o *_hclog) ImpliedArgs() []interface{} { + fields := o.l.GetFields() - if a, ok := fields[HCLogArgs]; !ok { + if i, l := fields.Load(HCLogArgs); !l { return make([]interface{}, 0) - } else if s, ok := a.([]interface{}); ok { - return s + } else if v, k := i.([]interface{}); k { + return v } return make([]interface{}, 0) } -func (l *_hclog) With(args ...interface{}) hclog.Logger { - l.l.SetFields(l.l.GetFields().Add(HCLogArgs, args)) - return l +func (o *_hclog) With(args ...interface{}) hclog.Logger { + o.l.SetFields(o.l.GetFields().Add(HCLogArgs, args)) + return o } -func (l *_hclog) Name() string { - fields := l.l.GetFields() +func (o *_hclog) Name() string { + fields := o.l.GetFields() - if a, ok := fields[HCLogName]; !ok { + if i, l := fields.Load(HCLogName); !l { return "" - } else if s, ok := a.(string); ok { - return s + } else if v, k := i.(string); k { + return v } return "" } -func (l *_hclog) Named(name string) hclog.Logger { - l.l.SetFields(l.l.GetFields().Add(HCLogName, name)) - return l +func (o *_hclog) Named(name string) hclog.Logger { + o.l.SetFields(o.l.GetFields().Add(HCLogName, name)) + return o } -func (l *_hclog) ResetNamed(name string) hclog.Logger { - l.l.SetFields(l.l.GetFields().Add(HCLogName, name)) - return l +func (o *_hclog) ResetNamed(name string) hclog.Logger { + o.l.SetFields(o.l.GetFields().Add(HCLogName, name)) + return o } -func (l *_hclog) SetLevel(level hclog.Level) { +func (o *_hclog) SetLevel(level hclog.Level) { switch level { case hclog.NoLevel, hclog.Off: - l.l.SetLevel(NilLevel) + o.l.SetLevel(NilLevel) case hclog.Trace: - opt := l.l.GetOptions() + opt := o.l.GetOptions() opt.EnableTrace = true - _ = l.l.SetOptions(opt) - l.l.SetLevel(DebugLevel) + _ = o.l.SetOptions(opt) + o.l.SetLevel(DebugLevel) case hclog.Debug: - l.l.SetLevel(DebugLevel) + o.l.SetLevel(DebugLevel) case hclog.Info: - l.l.SetLevel(InfoLevel) + o.l.SetLevel(InfoLevel) case hclog.Warn: - l.l.SetLevel(WarnLevel) + o.l.SetLevel(WarnLevel) case hclog.Error: - l.l.SetLevel(ErrorLevel) + o.l.SetLevel(ErrorLevel) } } -func (l *_hclog) GetLevel() hclog.Level { - switch l.l.GetLevel() { +func (o *_hclog) GetLevel() hclog.Level { + switch o.l.GetLevel() { case NilLevel: return hclog.NoLevel case DebugLevel: - opt := l.l.GetOptions() + opt := o.l.GetOptions() if opt.EnableTrace { return hclog.Trace } else { @@ -181,7 +181,7 @@ func (l *_hclog) GetLevel() hclog.Level { } } -func (l *_hclog) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { +func (o *_hclog) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { var lvl Level switch opts.ForceLevel { case hclog.NoLevel, hclog.Off: @@ -198,9 +198,9 @@ func (l *_hclog) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { lvl = ErrorLevel } - return l.l.GetStdLogger(lvl, 0) + return o.l.GetStdLogger(lvl, 0) } -func (l *_hclog) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { - return l.l +func (o *_hclog) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { + return o.l } diff --git a/logger/interface.go b/logger/interface.go index b3e7260e..57713e68 100644 --- a/logger/interface.go +++ b/logger/interface.go @@ -28,19 +28,21 @@ package logger import ( - "context" "io" "log" "sync" - "sync/atomic" "time" + libctx "github.com/nabbar/golib/context" + iotclo "github.com/nabbar/golib/ioutils/mapCloser" + "github.com/hashicorp/go-hclog" jww "github.com/spf13/jwalterweatherman" ) type FuncLog func() Logger +type FuncOpt func() *Options type Logger interface { io.WriteCloser @@ -72,7 +74,7 @@ type Logger interface { GetFields() Fields //Clone allow to duplicate the logger with a copy of the logger - Clone() (Logger, error) + Clone() Logger //SetSPF13Level allow to plus spf13 logger (jww) to this logger SetSPF13Level(lvl Level, log *jww.Notepad) @@ -124,19 +126,15 @@ type Logger interface { } // New return a new logger interface pointer -func New(ctx context.Context) Logger { - lvl := new(atomic.Value) - lvl.Store(InfoLevel) - - return &logger{ - x: ctx, - n: nil, - m: &sync.Mutex{}, - l: lvl, - o: new(atomic.Value), - s: new(atomic.Value), - f: new(atomic.Value), - w: new(atomic.Value), - c: _NewCloser(), +func New(ctx libctx.FuncContext) Logger { + l := &logger{ + m: sync.RWMutex{}, + x: libctx.NewConfig[uint8](ctx), + f: NewFields(ctx), + c: iotclo.New(ctx), } + + l.SetLevel(InfoLevel) + + return l } diff --git a/logger/interface_test.go b/logger/interface_test.go index 7c3b59db..80d86c97 100644 --- a/logger/interface_test.go +++ b/logger/interface_test.go @@ -52,7 +52,7 @@ func getLogFileTemp() string { var _ = Describe("Logger", func() { Context("Create New Logger with Default Config", func() { It("Must succeed", func() { - log := logger.New(GetContext()) + log := logger.New(GetContext) log.SetLevel(logger.DebugLevel) err := log.SetOptions(&logger.Options{}) Expect(err).ToNot(HaveOccurred()) @@ -61,7 +61,7 @@ var _ = Describe("Logger", func() { }) Context("Create New Logger with Default Config and trace", func() { It("Must succeed", func() { - log := logger.New(GetContext()) + log := logger.New(GetContext) log.SetLevel(logger.DebugLevel) err := log.SetOptions(&logger.Options{ EnableTrace: true, @@ -72,19 +72,19 @@ var _ = Describe("Logger", func() { }) Context("Create New Logger with field", func() { It("Must succeed", func() { - log := logger.New(GetContext()) + log := logger.New(GetContext) log.SetLevel(logger.DebugLevel) err := log.SetOptions(&logger.Options{ EnableTrace: true, }) - log.SetFields(logger.NewFields().Add("test-field", "ok")) + log.SetFields(logger.NewFields(GetContext).Add("test-field", "ok")) Expect(err).ToNot(HaveOccurred()) log.LogDetails(logger.InfoLevel, "test logger with field", nil, nil, nil) }) }) Context("Create New Logger with file", func() { It("Must succeed", func() { - log := logger.New(GetContext()) + log := logger.New(GetContext) log.SetLevel(logger.DebugLevel) fsp, err := GetTempFile() @@ -114,13 +114,13 @@ var _ = Describe("Logger", func() { }) Expect(err).ToNot(HaveOccurred()) - log.SetFields(logger.NewFields().Add("test-field", "ok")) + log.SetFields(logger.NewFields(GetContext).Add("test-field", "ok")) log.LogDetails(logger.InfoLevel, "test logger with field", nil, nil, nil) }) }) Context("Create New Logger with file in multithreading mode", func() { It("Must succeed", func() { - log := logger.New(GetContext()) + log := logger.New(GetContext) log.SetLevel(logger.DebugLevel) fsp, err := GetTempFile() @@ -150,12 +150,11 @@ var _ = Describe("Logger", func() { }) Expect(err).ToNot(HaveOccurred()) - log.SetFields(logger.NewFields().Add("test-field", "ok")) + log.SetFields(logger.NewFields(GetContext).Add("test-field", "ok")) log.LogDetails(logger.InfoLevel, "test logger with field", nil, nil, nil) var sub logger.Logger - sub, err = log.Clone() - Expect(err).ToNot(HaveOccurred()) + sub = log.Clone() go func(log logger.Logger) { defer func() { @@ -163,13 +162,13 @@ var _ = Describe("Logger", func() { Expect(se).ToNot(HaveOccurred()) }() - log.SetFields(logger.NewFields().Add("logger", "sub")) + log.SetFields(logger.NewFields(GetContext).Add("logger", "sub")) for i := 0; i < 10; i++ { log.Entry(logger.InfoLevel, "test multithreading logger").FieldAdd("id", i).Log() } }(sub) - log.SetFields(logger.NewFields().Add("logger", "main")) + log.SetFields(logger.NewFields(GetContext).Add("logger", "main")) sem := libsem.NewSemaphoreWithContext(context.Background(), 0) defer sem.DeferMain() diff --git a/logger/iowritecloser.go b/logger/iowritecloser.go index 57a55700..c2286e10 100644 --- a/logger/iowritecloser.go +++ b/logger/iowritecloser.go @@ -27,58 +27,52 @@ package logger -import "sync/atomic" +import "fmt" -func (l *logger) Close() error { - l.m.Lock() - defer func() { - l.m.Unlock() - l.cancelCall() - }() +func (o *logger) Close() error { + if o == nil { + return fmt.Errorf("not initialized") + } else if o.c == nil { + return fmt.Errorf("not initialized") + } - _ = l.c.Close() - l.c = _NewCloser() + _ = o.c.Close() + o.c.Clean() return nil } -func (l *logger) Write(p []byte) (n int, err error) { - l.newEntry(l.GetIOWriterLevel(), string(p), nil, l.GetFields(), nil).Log() - return len(p), nil -} - -func (l *logger) SetIOWriterLevel(lvl Level) { - if l == nil { +func (o *logger) Write(p []byte) (n int, err error) { + if o == nil { + return + } else if o.x == nil { return } - l.m.Lock() - defer l.m.Unlock() + o.newEntry(o.GetIOWriterLevel(), string(p), nil, o.GetFields(), nil).Log() + return len(p), nil +} - if l.w == nil { - l.w = new(atomic.Value) +func (o *logger) SetIOWriterLevel(lvl Level) { + if o == nil { + return + } else if o.x == nil { + return } - l.w.Store(lvl) + o.x.Store(keyWriter, lvl) } -func (l *logger) GetIOWriterLevel() Level { - if l == nil { +func (o *logger) GetIOWriterLevel() Level { + if o == nil { return NilLevel - } - - l.m.Lock() - defer l.m.Unlock() - - if l.w == nil { - l.w = new(atomic.Value) - } - - if i := l.w.Load(); i == nil { + } else if o.x == nil { + return NilLevel + } else if i, l := o.x.Load(keyWriter); !l { return NilLevel - } else if o, ok := i.(Level); ok { - return o + } else if v, k := i.(Level); !k { + return NilLevel + } else { + return v } - - return NilLevel } diff --git a/logger/log.go b/logger/log.go index cdd29632..784faac7 100644 --- a/logger/log.go +++ b/logger/log.go @@ -32,87 +32,90 @@ import ( "time" ) -func (l *logger) Debug(message string, data interface{}, args ...interface{}) { - l.newEntry(DebugLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() +func (o *logger) Debug(message string, data interface{}, args ...interface{}) { + o.newEntry(DebugLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() } -func (l *logger) Info(message string, data interface{}, args ...interface{}) { - l.newEntry(InfoLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() +func (o *logger) Info(message string, data interface{}, args ...interface{}) { + o.newEntry(InfoLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() } -func (l *logger) Warning(message string, data interface{}, args ...interface{}) { - l.newEntry(WarnLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() +func (o *logger) Warning(message string, data interface{}, args ...interface{}) { + o.newEntry(WarnLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() } -func (l *logger) Error(message string, data interface{}, args ...interface{}) { - l.newEntry(ErrorLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() +func (o *logger) Error(message string, data interface{}, args ...interface{}) { + o.newEntry(ErrorLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() } -func (l *logger) Fatal(message string, data interface{}, args ...interface{}) { - l.newEntry(FatalLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() +func (o *logger) Fatal(message string, data interface{}, args ...interface{}) { + o.newEntry(FatalLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() } -func (l *logger) Panic(message string, data interface{}, args ...interface{}) { - l.newEntry(PanicLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() +func (o *logger) Panic(message string, data interface{}, args ...interface{}) { + o.newEntry(PanicLevel, fmt.Sprintf(message, args...), nil, nil, data).Log() } -func (l *logger) LogDetails(lvl Level, message string, data interface{}, err []error, fields Fields, args ...interface{}) { - l.newEntry(lvl, fmt.Sprintf(message, args...), err, fields, data).Log() +func (o *logger) LogDetails(lvl Level, message string, data interface{}, err []error, fields Fields, args ...interface{}) { + o.newEntry(lvl, fmt.Sprintf(message, args...), err, fields, data).Log() } -func (l *logger) CheckError(lvlKO, lvlOK Level, message string, err ...error) bool { - ent := l.newEntry(lvlKO, message, err, nil, nil) +func (o *logger) CheckError(lvlKO, lvlOK Level, message string, err ...error) bool { + ent := o.newEntry(lvlKO, message, err, nil, nil) return ent.Check(lvlOK) } -func (l *logger) Entry(lvl Level, message string, args ...interface{}) *Entry { - return l.newEntry(lvl, fmt.Sprintf(message, args...), nil, nil, nil) +func (o *logger) Entry(lvl Level, message string, args ...interface{}) *Entry { + return o.newEntry(lvl, fmt.Sprintf(message, args...), nil, nil, nil) } -func (l *logger) Access(remoteAddr, remoteUser string, localtime time.Time, latency time.Duration, method, request, proto string, status int, size int64) *Entry { - return l.newEntryClean(fmt.Sprintf("%s - %s [%s] [%s] \"%s %s %s\" %d %d", remoteAddr, remoteUser, localtime.Format(time.RFC1123Z), latency.String(), method, request, proto, status, size)) +func (o *logger) Access(remoteAddr, remoteUser string, localtime time.Time, latency time.Duration, method, request, proto string, status int, size int64) *Entry { + return o.newEntryClean(fmt.Sprintf("%s - %s [%s] [%s] \"%s %s %s\" %d %d", remoteAddr, remoteUser, localtime.Format(time.RFC1123Z), latency.String(), method, request, proto, status, size)) } -func (l *logger) newEntry(lvl Level, message string, err []error, fields Fields, data interface{}) *Entry { - opt := l.GetOptions() - cLv := l.GetLevel() +func (o *logger) newEntry(lvl Level, message string, err []error, fields Fields, data interface{}) *Entry { + opt := o.GetOptions() + cLv := o.GetLevel() - if cLv == NilLevel || lvl > cLv { - return &Entry{} + var ent = &Entry{ + clean: false, + Time: time.Time{}, + Level: lvl, + Stack: 0, + Caller: "", + File: "", + Line: 0, + Error: err, + Fields: o.GetFields().FieldsClone(nil), } - var ent = &Entry{ - log: l.getLog, - clean: false, - Time: time.Time{}, - Level: lvl, - Stack: 0, - Caller: "", - File: "", - Line: 0, - Message: message, - Error: err, - Data: data, - Fields: NewFields().Merge(l.GetFields()).Merge(fields), + if cLv == NilLevel || lvl > cLv { + return ent + } else { + ent.log = o.getLogrus + ent.Message = message + ent.Data = data } + ent.Fields.Merge(fields) + if !opt.DisableTimestamp { ent.Time = time.Now() } if !opt.DisableStack { - ent.Stack = l.getStack() + ent.Stack = o.getStack() } if opt.EnableTrace { - frm := l.getCaller() + frm := o.getCaller() if frm.Function != "" { ent.Caller = frm.Function } if frm.File != "" { - ent.File = l.filterPath(frm.File) + ent.File = o.filterPath(frm.File) } if frm.Line > 0 { @@ -123,11 +126,12 @@ func (l *logger) newEntry(lvl Level, message string, err []error, fields Fields, return ent } -func (l *logger) newEntryClean(message string) *Entry { +func (o *logger) newEntryClean(message string) *Entry { var ent = &Entry{ - log: l.getLog, + log: o.getLogrus, clean: true, Message: message, + Fields: NewFields(o.x.GetContext), } return ent diff --git a/logger/logger.go b/logger/logger.go index adf5e4c3..b6069e04 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -35,19 +35,19 @@ import ( jww "github.com/spf13/jwalterweatherman" ) -func (l *logger) GetStdLogger(lvl Level, logFlags int) *log.Logger { - l.SetIOWriterLevel(lvl) - return log.New(l, "", logFlags) +func (o *logger) GetStdLogger(lvl Level, logFlags int) *log.Logger { + o.SetIOWriterLevel(lvl) + return log.New(o, "", logFlags) } -func (l *logger) SetStdLogger(lvl Level, logFlags int) { - l.SetIOWriterLevel(lvl) - log.SetOutput(l) +func (o *logger) SetStdLogger(lvl Level, logFlags int) { + o.SetIOWriterLevel(lvl) + log.SetOutput(o) log.SetPrefix("") log.SetFlags(logFlags) } -func (l *logger) SetSPF13Level(lvl Level, log *jww.Notepad) { +func (o *logger) SetSPF13Level(lvl Level, log *jww.Notepad) { var ( fOutLog func(handle io.Writer) fLvl func(threshold jww.Threshold) @@ -68,39 +68,39 @@ func (l *logger) SetSPF13Level(lvl Level, log *jww.Notepad) { fLvl(jww.LevelCritical) case DebugLevel: - fOutLog(l) - if opt := l.GetOptions(); opt.EnableTrace { + fOutLog(o) + if opt := o.GetOptions(); opt.EnableTrace { fLvl(jww.LevelTrace) } else { fLvl(jww.LevelDebug) } case InfoLevel: - fOutLog(l) + fOutLog(o) fLvl(jww.LevelInfo) case WarnLevel: - fOutLog(l) + fOutLog(o) fLvl(jww.LevelWarn) case ErrorLevel: - fOutLog(l) + fOutLog(o) fLvl(jww.LevelError) case FatalLevel: - fOutLog(l) + fOutLog(o) fLvl(jww.LevelFatal) case PanicLevel: - fOutLog(l) + fOutLog(o) fLvl(jww.LevelCritical) } } -func (l *logger) SetHashicorpHCLog() { +func (o *logger) SetHashicorpHCLog() { hclog.SetDefault(&_hclog{ - l: l, + l: o, }) } -func (l *logger) NewHashicorpHCLog() hclog.Logger { +func (o *logger) NewHashicorpHCLog() hclog.Logger { return &_hclog{ - l: l, + l: o, } } diff --git a/logger/manage.go b/logger/manage.go new file mode 100644 index 00000000..2e645f6e --- /dev/null +++ b/logger/manage.go @@ -0,0 +1,173 @@ +/*********************************************************************************************************************** + * + * MIT License + * + * Copyright (c) 2021 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 logger + +import ( + "context" + "io" + "sync" + + iotclo "github.com/nabbar/golib/ioutils/mapCloser" + "github.com/sirupsen/logrus" +) + +func (o *logger) Clone() Logger { + return &logger{ + x: o.x.Clone(nil), + m: sync.RWMutex{}, + f: o.f.FieldsClone(nil), + c: o.c.Clone(), + } +} + +func (o *logger) SetLevel(lvl Level) { + o.x.Store(keyLevel, lvl) + o.setLogrusLevel(o.GetLevel()) + + if opt := o.GetOptions(); opt.change != nil { + opt.change(o) + } +} + +func (o *logger) GetLevel() Level { + if o == nil { + return NilLevel + } else if o.x == nil { + return NilLevel + } else if i, l := o.x.Load(keyLevel); !l { + return NilLevel + } else if v, k := i.(Level); !k { + return NilLevel + } else { + return v + } +} + +func (o *logger) SetFields(field Fields) { + if o == nil { + return + } else { + o.f.Clean() + } + + if field != nil { + o.f.Merge(field) + } +} + +func (o *logger) GetFields() Fields { + if o == nil { + return NewFields(context.Background) + } + + return o.f +} + +func (o *logger) SetOptions(opt *Options) error { + var ( + ctx, cnl = context.WithCancel(o.x.GetContext()) + lvl = o.GetLevel() + obj = logrus.New() + ) + + defer cnl() + + o.optionsMerge(opt) + + obj.SetLevel(lvl.Logrus()) + obj.SetFormatter(o.defaultFormatter(opt)) + obj.SetOutput(io.Discard) // Send all logs to nowhere by default + + clo := iotclo.New(func() context.Context { + return ctx + }) + + if !opt.DisableStandard { + obj.AddHook(NewHookStandard(*opt, StdOut, []logrus.Level{ + logrus.InfoLevel, + logrus.DebugLevel, + logrus.TraceLevel, + })) + + obj.AddHook(NewHookStandard(*opt, StdErr, []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + })) + } + + if len(opt.LogFile) > 0 { + for _, fopt := range opt.LogFile { + if hook, err := NewHookFile(fopt, o.defaultFormatterNoColor()); err != nil { + return err + } else { + clo.Add(hook) + hook.RegisterHook(obj) + } + } + } + + if len(opt.LogSyslog) > 0 { + for _, lopt := range opt.LogSyslog { + if hook, err := NewHookSyslog(lopt, o.defaultFormatterNoColor()); err != nil { + return err + } else { + clo.Add(hook) + hook.RegisterHook(obj) + } + } + } + + o.x.Store(keyOptions, opt) + o.x.Store(keyLogrus, obj) + + _ = o.Close() + o.c.Add(clo.Get()...) + clo.Clean() + + if opt.init != nil { + opt.init(o) + } + + return nil +} + +func (o *logger) GetOptions() *Options { + if o == nil { + return &Options{} + } else if o.x == nil { + return &Options{} + } else if i, l := o.x.Load(keyOptions); !l { + return &Options{} + } else if v, k := i.(*Options); !k { + return &Options{} + } else { + return v + } +} diff --git a/logger/model.go b/logger/model.go index 7d985ff9..5d770e6b 100644 --- a/logger/model.go +++ b/logger/model.go @@ -30,22 +30,28 @@ package logger import ( "bytes" "context" - "io/ioutil" "path" "reflect" "runtime" "strconv" "strings" "sync" - "sync/atomic" "time" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - + iotclo "github.com/nabbar/golib/ioutils/mapCloser" "github.com/sirupsen/logrus" ) const ( + keyContext = iota + KeyCancel + keyLevel + keyOptions + keyLogrus + keyWriter + _TraceFilterMod = "/pkg/mod/" _TraceFilterVendor = "/vendor/" ) @@ -53,15 +59,10 @@ const ( var _selfPackage = path.Base(reflect.TypeOf(logger{}).PkgPath()) type logger struct { - x context.Context - n context.CancelFunc - m sync.Locker - l *atomic.Value //current level set for this logger - o *atomic.Value //options - s *atomic.Value //logrus logger - f *atomic.Value //defaults fields - w *atomic.Value //io writer level - c _Closer // closer + m sync.RWMutex + x libctx.Config[uint8] // cf const key... + f Fields // fields map + c iotclo.Closer } func defaultFormatter() logrus.TextFormatter { @@ -84,7 +85,7 @@ func defaultFormatter() logrus.TextFormatter { } } -func (l *logger) defaultFormatter(opt *Options) logrus.Formatter { +func (o *logger) defaultFormatter(opt *Options) logrus.Formatter { f := defaultFormatter() if opt != nil && opt.DisableColor { @@ -99,7 +100,7 @@ func (l *logger) defaultFormatter(opt *Options) logrus.Formatter { return &f } -func (l *logger) defaultFormatterNoColor() logrus.Formatter { +func (o *logger) defaultFormatterNoColor() logrus.Formatter { f := defaultFormatter() f.ForceColors = false f.EnvironmentOverrideColors = false @@ -107,315 +108,109 @@ func (l *logger) defaultFormatterNoColor() logrus.Formatter { return &f } -func (l *logger) Clone() (Logger, error) { - c := &logger{ - x: l.contextGet(), - n: nil, - m: &sync.Mutex{}, - l: new(atomic.Value), - o: new(atomic.Value), - s: new(atomic.Value), - f: new(atomic.Value), - w: new(atomic.Value), - c: _NewCloser(), - } - - c.setLoggerMutex(l.GetLevel()) - c.SetIOWriterLevel(l.GetIOWriterLevel()) - c.SetFields(l.GetFields()) - - if err := c.SetOptions(l.GetOptions()); err != nil { - return nil, err - } - - return c, nil -} - -func (l *logger) contextGet() context.Context { - l.m.Lock() - defer l.m.Unlock() - - if l.x == nil { - l.x = context.Background() - } - - return l.x -} - -func (l *logger) contextNew() context.Context { - ctx, cnl := context.WithCancel(l.contextGet()) - - l.m.Lock() - l.n = cnl - l.m.Unlock() +func (o *logger) contextNew() context.Context { + ctx, cnl := context.WithCancel(o.x.GetContext()) + o.x.Store(KeyCancel, cnl) return ctx } -func (l *logger) cancelCall() { - l.m.Lock() - defer l.m.Unlock() - - if l.n == nil { +func (o *logger) cancelCall() { + if i, l := o.x.LoadAndDelete(KeyCancel); !l { return - } - - l.n() - l.n = nil -} - -func (l *logger) cancelClear() { - l.m.Lock() - - if l.n == nil { - l.m.Unlock() + } else if i == nil { return - } - - l.n() - l.m.Unlock() -} - -func (l *logger) setLoggerMutex(lvl Level) { - if l == nil { + } else if c, k := i.(context.CancelFunc); !k { return + } else { + c() } - - l.m.Lock() - defer l.m.Unlock() - - if l.l == nil { - l.l = new(atomic.Value) - } - - l.l.Store(lvl) -} - -func (l *logger) SetLevel(lvl Level) { - l.setLoggerMutex(lvl) - l.setLogrusLevel(l.GetLevel()) - - if opt := l.GetOptions(); opt.change != nil { - opt.change(l) - } -} - -func (l *logger) GetLevel() Level { - if l == nil { - return NilLevel - } - - l.m.Lock() - defer l.m.Unlock() - - if l.l == nil { - l.l = new(atomic.Value) - } - - if i := l.l.Load(); i == nil { - return NilLevel - } else if o, ok := i.(Level); ok { - return o - } - - return NilLevel } -func (l *logger) SetFields(field Fields) { - if l == nil { +func (o *logger) optionsMerge(opt *Options) { + if !opt.InheritDefault { return } - l.m.Lock() - defer l.m.Unlock() - - if l.f == nil { - l.f = new(atomic.Value) - } - - l.f.Store(field) -} + var no Options -func (l *logger) GetFields() Fields { - if l == nil { - return NewFields() + if opt.opts != nil { + no = *opt.opts() + } else if i, l := o.x.Load(keyOptions); !l { + return + } else if v, k := i.(*Options); !k { + return + } else if v.opts != nil { + no = *v.opts() + } else { + no = *v } - l.m.Lock() - defer l.m.Unlock() - - if l.f == nil { - l.f = new(atomic.Value) + if opt.DisableStandard { + no.DisableStandard = true } - if i := l.f.Load(); i == nil { - return NewFields() - } else if o, ok := i.(Fields); ok { - return o + if opt.DisableStack { + no.DisableStack = true } - return NewFields() -} - -func (l *logger) setOptionsMutex(opt *Options) error { - lvl := l.GetLevel() - - go func() { - var ctx = l.contextNew() - - defer func() { - l.cancelClear() - }() - - select { - case <-ctx.Done(): - _ = l.Close() - return - } - }() - - obj := logrus.New() - obj.SetLevel(lvl.Logrus()) - obj.SetFormatter(l.defaultFormatter(opt)) - obj.SetOutput(ioutil.Discard) // Send all logs to nowhere by default - clo := _NewCloser() - - if !opt.DisableStandard { - obj.AddHook(NewHookStandard(*opt, StdOut, []logrus.Level{ - logrus.InfoLevel, - logrus.DebugLevel, - logrus.TraceLevel, - })) - - obj.AddHook(NewHookStandard(*opt, StdErr, []logrus.Level{ - logrus.PanicLevel, - logrus.FatalLevel, - logrus.ErrorLevel, - logrus.WarnLevel, - })) + if opt.DisableTimestamp { + no.DisableTimestamp = true } - if len(opt.LogFile) > 0 { - for _, fopt := range opt.LogFile { - if hook, err := NewHookFile(fopt, l.defaultFormatterNoColor()); err != nil { - return err - } else { - clo.Add(hook) - hook.RegisterHook(obj) - } - } + if opt.EnableTrace { + no.EnableTrace = true } - if len(opt.LogSyslog) > 0 { - for _, lopt := range opt.LogSyslog { - if hook, err := NewHookSyslog(lopt, l.defaultFormatterNoColor()); err != nil { - return err - } else { - clo.Add(hook) - hook.RegisterHook(obj) - } - } + if len(opt.TraceFilter) > 0 { + no.TraceFilter = opt.TraceFilter } - l.setOptions(opt) - - l.m.Lock() - defer l.m.Unlock() - - _ = l.c.Close() - l.c = clo - - l.s = new(atomic.Value) - l.s.Store(obj) - - return nil -} - -func (l *logger) SetOptions(opt *Options) error { - if err := l.setOptionsMutex(opt); err != nil { - return err + if opt.DisableColor { + no.DisableColor = true } - if opt.init != nil { - opt.init(l) - } - - return nil -} - -func (l *logger) GetOptions() *Options { - if l == nil { - return &Options{} + if opt.EnableAccessLog { + no.EnableAccessLog = true } - l.m.Lock() - defer l.m.Unlock() - - if l.o == nil { - l.o = new(atomic.Value) + if opt.LogFileExtend { + no.LogFile = append(no.LogFile, opt.LogFile...) + } else { + no.LogFile = opt.LogFile } - if i := l.o.Load(); i == nil { - return &Options{} - } else if o, ok := i.(*Options); ok { - return o + if opt.LogSyslogExtend { + no.LogSyslog = append(no.LogSyslog, opt.LogSyslog...) + } else { + no.LogSyslog = opt.LogSyslog } - return &Options{} + *opt = no } -func (l *logger) setOptions(opt *Options) { - if l == nil { +func (o *logger) setLogrusLevel(lvl Level) { + if i, l := o.x.Load(keyLogrus); !l { return - } - - l.m.Lock() - defer l.m.Unlock() - - if l.o == nil { - l.o = new(atomic.Value) - } - - l.o.Store(opt) -} - -func (l *logger) setLogrusLevel(lvl Level) { - if _log := l.getLog(); _log != nil { - _log.SetLevel(lvl.Logrus()) - - l.m.Lock() - defer l.m.Unlock() - - if l.s == nil { - return - } - - l.s.Store(_log) + } else if v, k := i.(*logrus.Logger); !k { + return + } else { + v.SetLevel(lvl.Logrus()) + o.x.Store(keyLogrus, v) } } -func (l *logger) getLog() *logrus.Logger { - if l == nil { - return nil - } - - l.m.Lock() - defer l.m.Unlock() - - if l.s == nil { - l.s = new(atomic.Value) - } - - if i := l.s.Load(); i == nil { +func (o *logger) getLogrus() *logrus.Logger { + if i, l := o.x.Load(keyLogrus); !l { return nil - } else if o, ok := i.(*logrus.Logger); !ok { + } else if v, k := i.(*logrus.Logger); !k { return nil } else { - return o + return v } } -func (l *logger) getStack() uint64 { +func (o *logger) getStack() uint64 { b := make([]byte, 64) b = b[:runtime.Stack(b, false)] @@ -429,7 +224,7 @@ func (l *logger) getStack() uint64 { return n } -func (l *logger) getCaller() runtime.Frame { +func (o *logger) getCaller() runtime.Frame { // Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need. programCounters := make([]uintptr, 10, 255) n := runtime.Callers(1, programCounters) @@ -453,7 +248,7 @@ func (l *logger) getCaller() runtime.Frame { return runtime.Frame{Function: "unknown", File: "unknown", Line: 0} } -func (l *logger) filterPath(pathname string) string { +func (o *logger) filterPath(pathname string) string { pathname = liberr.ConvPathFromLocal(pathname) if i := strings.LastIndex(pathname, _TraceFilterMod); i != -1 { @@ -466,7 +261,7 @@ func (l *logger) filterPath(pathname string) string { pathname = pathname[i:] } - opt := l.GetOptions() + opt := o.GetOptions() if opt.TraceFilter != "" { if i := strings.LastIndex(pathname, opt.TraceFilter); i != -1 { diff --git a/logger/options.go b/logger/options.go index b2fff262..13fa2227 100644 --- a/logger/options.go +++ b/logger/options.go @@ -99,6 +99,8 @@ type OptionsFile struct { EnableAccessLog bool `json:"enableAccessLog,omitempty" yaml:"enableAccessLog,omitempty" toml:"enableAccessLog,omitempty" mapstructure:"enableAccessLog,omitempty"` } +type OptionsFiles []OptionsFile + type OptionsSyslog struct { // LogLevel define the allowed level of log for this syslog. LogLevel []string `json:"logLevel,omitempty" yaml:"logLevel,omitempty" toml:"logLevel,omitempty" mapstructure:"logLevel,omitempty"` @@ -133,7 +135,12 @@ type OptionsSyslog struct { EnableAccessLog bool `json:"enableAccessLog,omitempty" yaml:"enableAccessLog,omitempty" toml:"enableAccessLog,omitempty" mapstructure:"enableAccessLog,omitempty"` } +type OptionsSyslogs []OptionsSyslog + type Options struct { + // InheritDefault define if the current options will override a default options + InheritDefault bool `json:"inheritDefault" yaml:"inheritDefault" toml:"inheritDefault" mapstructure:"inheritDefault"` + // DisableStandard allow disabling to write log to standard output stdout/stderr. DisableStandard bool `json:"disableStandard,omitempty" yaml:"disableStandard,omitempty" toml:"disableStandard,omitempty" mapstructure:"disableStandard,omitempty"` @@ -156,26 +163,42 @@ type Options struct { // EnableAccessLog allow to add all message from api router for access log and error log. EnableAccessLog bool `json:"enableAccessLog,omitempty" yaml:"enableAccessLog,omitempty" toml:"enableAccessLog,omitempty" mapstructure:"enableAccessLog,omitempty"` + // LogFileExtend define if the logFile given is in addition of default LogFile or a replacement. + LogFileExtend bool `json:"logFileExtend,omitempty" yaml:"logFileExtend,omitempty" toml:"logFileExtend,omitempty" mapstructure:"logFileExtend,omitempty"` + // LogFile define a list of log file configuration to allow log to files. - LogFile []OptionsFile `json:"logFile,omitempty" yaml:"logFile,omitempty" toml:"logFile,omitempty" mapstructure:"logFile,omitempty"` + LogFile OptionsFiles `json:"logFile,omitempty" yaml:"logFile,omitempty" toml:"logFile,omitempty" mapstructure:"logFile,omitempty"` + + // LogSyslogExtend define if the logFile given is in addition of default LogSyslog or a replacement. + LogSyslogExtend bool `json:"logSyslogExtend,omitempty" yaml:"logSyslogExtend,omitempty" toml:"logSyslogExtend,omitempty" mapstructure:"logSyslogExtend,omitempty"` // LogSyslog define a list of syslog configuration to allow log to syslog. - LogSyslog []OptionsSyslog `json:"logSyslog,omitempty" yaml:"logSyslog,omitempty" toml:"logSyslog,omitempty" mapstructure:"logSyslog,omitempty"` + LogSyslog OptionsSyslogs `json:"logSyslog,omitempty" yaml:"logSyslog,omitempty" toml:"logSyslog,omitempty" mapstructure:"logSyslog,omitempty"` // custom function handler. init FuncCustomConfig change FuncCustomConfig + + // default options + opts FuncOpt +} + +// RegisterDefaultFunc allow to register a function called to retrieve default options for inheritDefault. +// If not set, the previous options will be used as default options. +// To clean function, just call RegisterDefaultFunc with nil as param. +func (o *Options) RegisterDefaultFunc(fct FuncOpt) { + o.opts = fct } // RegisterFuncUpdateLogger allow to register a function called when init or update of logger. // To clean function, just call RegisterFuncUpdateLogger with nil as param. -func (o Options) RegisterFuncUpdateLogger(fct FuncCustomConfig) { +func (o *Options) RegisterFuncUpdateLogger(fct FuncCustomConfig) { o.init = fct } // RegisterFuncUpdateLevel allow to register a function called when init or update level // To clean function, just call RegisterFuncUpdateLevel with nil as param. -func (o Options) RegisterFuncUpdateLevel(fct FuncCustomConfig) { +func (o *Options) RegisterFuncUpdateLevel(fct FuncCustomConfig) { o.change = fct } @@ -200,3 +223,64 @@ func (o *Options) Validate() liberr.Error { return e } + +func (o *Options) Clone() Options { + return Options{ + DisableStandard: o.DisableStandard, + DisableStack: o.DisableStack, + DisableTimestamp: o.DisableTimestamp, + EnableTrace: o.EnableTrace, + TraceFilter: o.TraceFilter, + DisableColor: o.DisableColor, + EnableAccessLog: o.EnableAccessLog, + LogFile: o.LogFile.Clone(), + LogSyslog: o.LogSyslog.Clone(), + init: o.init, + change: o.change, + } +} + +func (o OptionsFile) Clone() OptionsFile { + return OptionsFile{ + LogLevel: o.LogLevel, + Filepath: o.Filepath, + Create: o.Create, + CreatePath: o.CreatePath, + FileMode: o.FileMode, + PathMode: o.PathMode, + DisableStack: o.DisableStack, + DisableTimestamp: o.DisableTimestamp, + EnableTrace: o.EnableTrace, + EnableAccessLog: o.EnableAccessLog, + } +} + +func (o OptionsFiles) Clone() OptionsFiles { + var c = make([]OptionsFile, 0) + for _, i := range o { + c = append(c, i.Clone()) + } + return c +} + +func (o OptionsSyslog) Clone() OptionsSyslog { + return OptionsSyslog{ + LogLevel: o.LogLevel, + Network: o.Network, + Host: o.Host, + Facility: o.Facility, + Tag: o.Tag, + DisableStack: o.DisableStack, + DisableTimestamp: o.DisableTimestamp, + EnableTrace: o.EnableTrace, + EnableAccessLog: o.EnableAccessLog, + } +} + +func (o OptionsSyslogs) Clone() OptionsSyslogs { + var c = make([]OptionsSyslog, 0) + for _, i := range o { + c = append(c, i.Clone()) + } + return c +} diff --git a/mailPooler/pooler.go b/mailPooler/interface.go similarity index 100% rename from mailPooler/pooler.go rename to mailPooler/interface.go diff --git a/mailPooler/model.go b/mailPooler/model.go index f0b2a741..1f1b0f7f 100644 --- a/mailPooler/model.go +++ b/mailPooler/model.go @@ -27,15 +27,14 @@ package mailPooler import ( "context" + "crypto/tls" "errors" "io" "net/smtp" - "runtime" - "strings" liberr "github.com/nabbar/golib/errors" libsmtp "github.com/nabbar/golib/smtp" - libsts "github.com/nabbar/golib/status" + smtpcf "github.com/nabbar/golib/smtp/config" ) type pooler struct { @@ -107,30 +106,10 @@ func (p *pooler) Clone() libsmtp.SMTP { return p.NewPooler() } -func (p *pooler) StatusInfo() (name string, release string, hash string) { - if p.s == nil { - hash = "" - release = strings.TrimLeft(strings.ToLower(runtime.Version()), "go") - name = "SMTP Pooler unknown" - - return name, release, hash - } - - return p.s.StatusInfo() -} - -func (p *pooler) StatusHealth() error { - if p.s == nil { - return ErrorParamEmpty.ErrorParent(errors.New("smtp client is not define")) - } - - return p.s.StatusHealth() -} - -func (p *pooler) StatusRouter(sts libsts.RouteStatus, prefix string) { - if p.s == nil { - return +func (p *pooler) UpdConfig(cfg smtpcf.SMTP, tslConfig *tls.Config) { + if p.s != nil { + p.s.UpdConfig(cfg, tslConfig) + } else { + p.s, _ = libsmtp.New(cfg, tslConfig) } - - p.s.StatusRouter(sts, prefix) } diff --git a/mailPooler/monitor.go b/mailPooler/monitor.go new file mode 100644 index 00000000..b9d995b9 --- /dev/null +++ b/mailPooler/monitor.go @@ -0,0 +1,43 @@ +/* + * 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 mailPooler + +import ( + "fmt" + + libctx "github.com/nabbar/golib/context" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +func (p *pooler) Monitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) { + if p.s == nil { + return nil, fmt.Errorf("SMTP client not defined") + } + + return p.s.Monitor(ctx, vrs) +} diff --git a/monitor/encode.go b/monitor/encode.go new file mode 100644 index 00000000..416dfffa --- /dev/null +++ b/monitor/encode.go @@ -0,0 +1,121 @@ +/* + * 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 monitor + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + moninf "github.com/nabbar/golib/monitor/types" + + monsts "github.com/nabbar/golib/monitor/status" +) + +const ( + encTextSepStatus = ": " + encTextSepPart = " | " + encTextSepTime = " / " +) + +type Encode interface { + String() string + Bytes() []byte +} + +type encodeModel struct { + Status monsts.Status + Name string + Info moninf.Info + + Latency string + Uptime string + Downtime string + + Message string +} + +func (e *encodeModel) Bytes() []byte { + return []byte(e.String()) +} + +func (e *encodeModel) String() string { + return e.Status.String() + encTextSepStatus + e.stringPart() +} + +func (e *encodeModel) stringDuration() string { + part := append(make([]string, 0), e.Latency, e.Uptime, e.Downtime) + return strings.Join(part, encTextSepTime) +} + +func (e *encodeModel) stringName() string { + var inf string + + if e.Info != nil { + i, _ := e.Info.MarshalText() + inf = string(i) + } + + if len(inf) > 0 { + return fmt.Sprintf("%s (%s)", e.Name, inf) + } else { + return e.Name + } +} + +func (e *encodeModel) stringPart() string { + item := make([]string, 0) + item = append(item, e.stringName()) + item = append(item, e.stringDuration()) + + if len(e.Message) > 0 { + item = append(item, e.Message) + } + + return strings.Join(item, encTextSepPart) +} + +func (o *mon) getEncodeModel() Encode { + return &encodeModel{ + Status: o.Status(), + Name: o.Name(), + Info: o.InfoGet(), + Latency: o.Latency().Truncate(time.Millisecond).String(), + Uptime: o.Uptime().Truncate(time.Second).String(), + Downtime: o.Downtime().Truncate(time.Second).String(), + Message: o.Message(), + } +} + +func (o *mon) MarshalText() (text []byte, err error) { + return o.getEncodeModel().Bytes(), nil +} + +func (o *mon) MarshalJSON() (text []byte, err error) { + return json.Marshal(o.getEncodeModel()) +} diff --git a/config/components/natsServer/errors.go b/monitor/error.go similarity index 59% rename from config/components/natsServer/errors.go rename to monitor/error.go index a7ba6601..d00a61fd 100644 --- a/config/components/natsServer/errors.go +++ b/monitor/error.go @@ -24,29 +24,25 @@ * */ -package natsServer +package monitor import ( "fmt" - libcfg "github.com/nabbar/golib/config" liberr "github.com/nabbar/golib/errors" ) const ( - ErrorParamEmpty liberr.CodeError = iota + libcfg.MinErrorComponentNats - ErrorParamInvalid - ErrorComponentNotInitialized - ErrorConfigInvalid - ErrorStartComponent - ErrorReloadComponent - ErrorDependencyTLSDefault - ErrorDependencyLogDefault + ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgMonitor + ErrorMissingHealthCheck + ErrorValidatorError + ErrorLoggerError + ErrorTimeout ) func init() { if liberr.ExistInMapMessage(ErrorParamEmpty) { - panic(fmt.Errorf("error code collision with package golib/config/components/natsServer")) + panic(fmt.Errorf("error code collision with package golib/logger")) } liberr.RegisterIdFctMessage(ErrorParamEmpty, getMessage) } @@ -54,21 +50,15 @@ func init() { func getMessage(code liberr.CodeError) (message string) { switch code { case ErrorParamEmpty: - return "at least one given parameters is empty" - case ErrorParamInvalid: - return "at least one given parameters is invalid" - case ErrorComponentNotInitialized: - return "this component seems to not be correctly initialized" - case ErrorConfigInvalid: - return "invalid component config" - case ErrorStartComponent: - return "cannot start component with config" - case ErrorReloadComponent: - return "cannot reload component with new config" - case ErrorDependencyTLSDefault: - return "cannot retrieve TLS component" - case ErrorDependencyLogDefault: - return "cannot retrieve Logger Component" + return "given parameters is empty" + case ErrorMissingHealthCheck: + return "missing healthcheck" + case ErrorValidatorError: + return "invalid config" + case ErrorLoggerError: + return "cannot initialize logger" + case ErrorTimeout: + return "timeout error" } return liberr.NullMessage diff --git a/monitor/info/encode.go b/monitor/info/encode.go new file mode 100644 index 00000000..70967db2 --- /dev/null +++ b/monitor/info/encode.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 info + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +type Encode interface { + String() string + Bytes() []byte +} + +type encodingModel struct { + Name string + Info map[string]interface{} +} + +func (e *encodingModel) stringInfo() string { + var ( + buf = bytes.NewBuffer(make([]byte, 0)) + ) + + for _, i := range e.Info { + buf.WriteString(fmt.Sprintf("%v", i)) + } + + return buf.String() +} + +func (e *encodingModel) stringClean(str string) string { + str = strings.Replace(str, "\n", " ", -1) + str = strings.Replace(str, "\r", "", -1) + return str +} + +func (e *encodingModel) String() string { + if i := e.stringInfo(); len(i) > 0 { + return e.stringClean(fmt.Sprintf("%s (%s)", e.Name, i)) + } else { + return e.stringClean(e.Name) + } +} + +func (e *encodingModel) Bytes() []byte { + return []byte(e.String()) +} + +func (o *inf) getEncodeModel() Encode { + return &encodingModel{ + Name: o.Name(), + Info: o.Info(), + } +} + +func (o *inf) MarshalText() (text []byte, err error) { + return o.getEncodeModel().Bytes(), nil +} + +func (o *inf) MarshalJSON() ([]byte, error) { + return json.Marshal(o.getEncodeModel()) +} diff --git a/monitor/info/info.go b/monitor/info/info.go new file mode 100644 index 00000000..7cc9a449 --- /dev/null +++ b/monitor/info/info.go @@ -0,0 +1,70 @@ +/* + * 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 info + +func (o *inf) defaultName() string { + if i, l := o.v.Load(keyDefName); !l { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (o *inf) Name() string { + if i, l := o.v.Load(keyName); !l { + return o.getName() + } else if v, k := i.(string); !k { + return o.getName() + } else { + return v + } +} + +func (o *inf) Info() map[string]interface{} { + var res = make(map[string]interface{}, 0) + + o.v.Range(func(key, value any) bool { + if v, k := key.(string); !k { + return true + } else if v == keyDefName { + return true + } else if v == keyName { + return true + } else { + res[v] = value + return true + } + }) + + if len(res) < 1 { + return o.getInfo() + } + + return res +} diff --git a/monitor/info/interface.go b/monitor/info/interface.go new file mode 100644 index 00000000..405bbdd2 --- /dev/null +++ b/monitor/info/interface.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 info + +import ( + "fmt" + "sync" + + montps "github.com/nabbar/golib/monitor/types" +) + +type FuncName func() (string, error) +type FuncInfo func() (map[string]interface{}, error) + +type Info interface { + montps.Info + + RegisterName(fct FuncName) + RegisterInfo(fct FuncInfo) +} + +func New(defaultName string) (Info, error) { + i := &inf{ + m: sync.RWMutex{}, + fi: nil, + fn: nil, + v: sync.Map{}, + } + + if len(defaultName) < 1 { + return nil, fmt.Errorf("default name cannot be empty") + } + + i.v.Store(keyDefName, defaultName) + return i, nil +} diff --git a/monitor/info/model.go b/monitor/info/model.go new file mode 100644 index 00000000..f13a943e --- /dev/null +++ b/monitor/info/model.go @@ -0,0 +1,148 @@ +/* + * 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 info + +import "sync" + +const ( + keyDefName = "__keyDefaultName__" + keyName = "__keyName__" +) + +type inf struct { + m sync.RWMutex + v sync.Map + + fi FuncInfo + ri bool + + fn FuncName + rn bool +} + +func (o *inf) RegisterName(fct FuncName) { + o.m.Lock() + defer o.m.Unlock() + + o.v.Delete(keyName) + o.fn = fct + o.rn = true +} + +func (o *inf) RegisterInfo(fct FuncInfo) { + o.m.Lock() + defer o.m.Unlock() + + o.v.Range(func(key, value any) bool { + if s, k := key.(string); !k { + o.v.Delete(key) + return true + } else if s == keyName { + return true + } else if s == keyDefName { + return true + } else { + o.v.Delete(key) + return true + } + }) + + o.fi = fct + o.ri = true +} + +func (o *inf) isName() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o.rn && o.fn != nil +} + +func (o *inf) isInfo() bool { + o.m.RLock() + defer o.m.RUnlock() + + return o.ri && o.fi != nil +} + +func (o *inf) callName() (string, error) { + o.m.RLock() + defer o.m.RUnlock() + + if v, e := o.fn(); e != nil { + return o.defaultName(), e + } else { + o.v.Store(keyName, v) + return v, nil + } +} + +func (o *inf) callInfo() (map[string]interface{}, error) { + o.m.RLock() + defer o.m.RUnlock() + + if i, e := o.fi(); e != nil { + return nil, e + } else { + for k, v := range i { + o.v.Store(k, v) + } + return i, nil + } +} + +func (o *inf) getName() string { + if !o.isName() { + return o.defaultName() + } + + i, e := o.callName() + + if e == nil { + o.m.Lock() + defer o.m.Unlock() + o.rn = false + } + + return i +} + +func (o *inf) getInfo() map[string]interface{} { + if !o.isInfo() { + return nil + } + + i, e := o.callInfo() + + if e == nil { + o.m.Lock() + defer o.m.Unlock() + o.ri = false + } + + return i +} diff --git a/monitor/interface.go b/monitor/interface.go new file mode 100644 index 00000000..9cee5c66 --- /dev/null +++ b/monitor/interface.go @@ -0,0 +1,55 @@ +/* + * 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 monitor + +import ( + "context" + "fmt" + "sync" + + libctx "github.com/nabbar/golib/context" + montps "github.com/nabbar/golib/monitor/types" +) + +type Monitor interface { + montps.Monitor +} + +func New(ctx libctx.FuncContext, info montps.Info) (montps.Monitor, error) { + if info == nil { + return nil, fmt.Errorf("info cannot be nil") + } else if ctx == nil { + ctx = context.Background + } + + return &mon{ + m: sync.RWMutex{}, + i: info, + x: libctx.NewConfig[string](ctx), + s: make(chan struct{}), + }, nil +} diff --git a/monitor/internalConfig.go b/monitor/internalConfig.go new file mode 100644 index 00000000..c99a3940 --- /dev/null +++ b/monitor/internalConfig.go @@ -0,0 +1,295 @@ +/* + * 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 monitor + +import ( + "time" + + libctx "github.com/nabbar/golib/context" + + "github.com/nabbar/golib/monitor/types" + + monsts "github.com/nabbar/golib/monitor/status" + + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" +) + +type runCfg struct { + checkTimeout time.Duration + intervalCheck time.Duration + intervalFall time.Duration + intervalRise time.Duration + fallCountKO uint8 + fallCountWarn uint8 + riseCountKO uint8 + riseCountWarn uint8 +} + +func (o *mon) defConfig() *runCfg { + cfg := &runCfg{} + + if cfg.checkTimeout < 5*time.Second { + cfg.checkTimeout = 5 * time.Second + } + + if cfg.intervalCheck < time.Second { + cfg.intervalCheck = time.Second + } + + if cfg.intervalFall < time.Second { + cfg.intervalFall = cfg.intervalCheck + } + + if cfg.intervalRise < time.Second { + cfg.intervalRise = cfg.intervalCheck + } + + if cfg.fallCountKO < 1 { + cfg.fallCountKO = 1 + } + + if cfg.fallCountWarn < 1 { + cfg.fallCountWarn = 1 + } + + if cfg.riseCountKO < 1 { + cfg.riseCountKO = 1 + } + + if cfg.riseCountWarn < 1 { + cfg.riseCountWarn = 1 + } + + o.x.Store(keyConfig, cfg) + return cfg +} + +func (o *mon) RegisterLoggerDefault(fct liblog.FuncLog) { + o.x.Store(keyLoggerDef, fct) +} + +func (o *mon) getLoggerDefault() liblog.Logger { + if i, l := o.x.Load(keyLoggerDef); !l { + return nil + } else if v, k := i.(liblog.FuncLog); !k { + return nil + } else { + return v() + } +} + +func (o *mon) SetConfig(ctx libctx.FuncContext, cfg types.Config) liberr.Error { + if ctx == nil { + ctx = o.x.GetContext + } + + if err := cfg.Validate(); err != nil { + return err + } + + if len(cfg.Name) < 1 { + o.x.Store(keyName, defaultMonitorName) + } else { + o.x.Store(keyName, cfg.Name) + } + + cnf := &runCfg{ + checkTimeout: cfg.CheckTimeout, + intervalCheck: cfg.IntervalCheck, + intervalFall: cfg.IntervalFall, + intervalRise: cfg.IntervalRise, + fallCountKO: cfg.FallCountKO, + fallCountWarn: cfg.FallCountWarn, + riseCountKO: cfg.RiseCountKO, + riseCountWarn: cfg.RiseCountWarn, + } + + if cnf.checkTimeout < 5*time.Second { + cnf.checkTimeout = 5 * time.Second + } + + if cnf.intervalCheck < time.Second { + cnf.intervalCheck = time.Second + } + + if cnf.intervalFall < time.Second { + cnf.intervalFall = cnf.intervalCheck + } + + if cnf.intervalRise < time.Second { + cnf.intervalRise = cnf.intervalCheck + } + + if cnf.fallCountKO < 1 { + cnf.fallCountKO = 1 + } + + if cnf.fallCountWarn < 1 { + cnf.fallCountWarn = 1 + } + + if cnf.riseCountKO < 1 { + cnf.riseCountKO = 1 + } + + if cnf.riseCountWarn < 1 { + cnf.riseCountWarn = 1 + } + + o.x.Store(keyConfig, cnf) + + var n liblog.Logger + + if l := o.getLoggerDefault(); l == nil { + n = liblog.New(ctx) + } else { + n = l + } + + if e := n.SetOptions(&cfg.Logger); e != nil { + return ErrorLoggerError.ErrorParent(e) + } + + f := n.GetFields() + n.SetFields(f.Add(LogFieldProcess, LogValueProcess).Add(LogFieldName, cfg.Name)) + n.SetLevel(liblog.GetCurrentLevel()) + + if l := o.getLog(); l != nil { + _ = l.Close() + } + + o.x.Store(keyLogger, n) + return nil +} + +func (o *mon) GetConfig() types.Config { + cfg := o.getCfg() + if cfg == nil { + cfg = &runCfg{} + } + + opt := o.getLogger().GetOptions() + if opt == nil { + opt = &liblog.Options{} + } + + return types.Config{ + Name: o.getName(), + CheckTimeout: cfg.checkTimeout, + IntervalCheck: cfg.intervalCheck, + IntervalFall: cfg.intervalFall, + IntervalRise: cfg.intervalRise, + FallCountKO: cfg.fallCountKO, + FallCountWarn: cfg.fallCountWarn, + RiseCountKO: cfg.riseCountKO, + RiseCountWarn: cfg.riseCountWarn, + Logger: *opt, + } +} + +func (o *mon) getName() string { + if i, l := o.x.Load(keyName); !l { + return defaultMonitorName + } else if v, k := i.(string); !k { + return defaultMonitorName + } else { + return v + } +} + +func (o *mon) getCfg() *runCfg { + if i, l := o.x.Load(keyConfig); !l { + return o.defConfig() + } else if v, k := i.(*runCfg); !k { + return o.defConfig() + } else { + return v + } +} + +func (o *mon) getLog() liblog.Logger { + if i, l := o.x.Load(keyLogger); !l { + return nil + } else if v, k := i.(liblog.Logger); !k { + return nil + } else { + return v + } +} + +func (o *mon) getLogger() liblog.Logger { + i := o.getLog() + + if i == nil { + return liblog.GetDefault() + } else { + return i + } +} + +func (o *mon) getFct() types.HealthCheck { + if i, l := o.x.Load(keyHealthCheck); !l { + return nil + } else if v, k := i.(types.HealthCheck); !k { + return nil + } else { + return v + } +} + +func (o *mon) getLastCheck() *lastRun { + if i, l := o.x.Load(keyLastRun); !l { + return &lastRun{ + status: monsts.KO, + runtime: time.Now(), + isRise: false, + isFall: false, + } + } else if v, k := i.(*lastRun); !k { + return &lastRun{ + status: monsts.KO, + runtime: time.Now(), + isRise: false, + isFall: false, + } + } else { + return v + } +} + +func (o *mon) setLastCheck(m middleWare) error { + e := m.Next() + l := &lastRun{ + status: o.Status(), + runtime: time.Now(), + isRise: o.IsRise(), + isFall: o.IsFall(), + } + o.x.Store(keyLastRun, l) + return e +} diff --git a/monitor/last.go b/monitor/last.go new file mode 100644 index 00000000..bde2310c --- /dev/null +++ b/monitor/last.go @@ -0,0 +1,40 @@ +/* + * 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 monitor + +import ( + "time" + + monsts "github.com/nabbar/golib/monitor/status" +) + +type lastRun struct { + status monsts.Status + runtime time.Time + isRise bool + isFall bool +} diff --git a/monitor/metrics.go b/monitor/metrics.go new file mode 100644 index 00000000..338c9c6e --- /dev/null +++ b/monitor/metrics.go @@ -0,0 +1,218 @@ +/* + * 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 monitor + +import ( + "context" + "time" + + "golang.org/x/exp/slices" + + libprm "github.com/nabbar/golib/prometheus" + + monsts "github.com/nabbar/golib/monitor/status" +) + +func (o *mon) RegisterMetricsName(names ...string) { + o.x.Store(keyMetricsName, names) +} + +func (o *mon) RegisterMetricsAddName(names ...string) { + var n []string + if i, l := o.x.Load(keyMetricsName); !l || i == nil { + n = make([]string, 0) + } else if v, k := i.([]string); !k { + n = make([]string, 0) + } else { + n = v + } + + for _, i := range names { + if !slices.Contains(n, i) { + n = append(n, i) + } + } + + o.x.Store(keyMetricsName, n) +} + +func (o *mon) RegisterCollectMetrics(fct libprm.FuncCollectMetrics) { + o.x.Store(keyMetricsFunc, fct) +} + +func (o *mon) CollectLatency() time.Duration { + if i, l := o.x.LoadAndDelete(keyMetricLatency); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) CollectUpTime() time.Duration { + if i, l := o.x.LoadAndDelete(keyMetricUpTime); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) CollectDownTime() time.Duration { + if i, l := o.x.LoadAndDelete(keyMetricDownTime); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) CollectRiseTime() time.Duration { + if i, l := o.x.LoadAndDelete(keyMetricRiseTime); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) CollectFallTime() time.Duration { + if i, l := o.x.LoadAndDelete(keyMetricFallTime); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) CollectStatus() (sts string, rise bool, fall bool) { + return o.Status().String(), o.IsRise(), o.IsFall() +} + +func (o *mon) collectMetrics(ctx context.Context) { + var ( + n []string + f libprm.FuncCollectMetrics + ) + + if i, l := o.x.Load(keyMetricsName); !l || i == nil { + return + } else if v, k := i.([]string); !k { + return + } else { + n = v + } + + if i, l := o.x.Load(keyMetricsFunc); !l { + return + } else if v, k := i.(libprm.FuncCollectMetrics); !k { + return + } else { + f = v + } + + if len(n) < 1 || f == nil { + return + } + + f(ctx, n...) +} + +func (o *mon) setLatency(m middleWare) error { + var ts = time.Now() + + ret := m.Next() + d := time.Since(ts) + + o.x.Store(keyMetricLatency, d) + + return ret +} + +func (o *mon) setUpTime(m middleWare) error { + ret := m.Next() + + if o.Status() != monsts.OK { + return ret + } + + last := o.getLastCheck() + + if last.status != monsts.OK { + return ret + } + + d := time.Since(last.runtime) + o.Uptime() + o.x.Store(keyMetricUpTime, d) + return ret +} + +func (o *mon) setDownTime(m middleWare) error { + ret := m.Next() + + if o.Status() != monsts.KO { + return ret + } + + last := o.getLastCheck() + if last.status != monsts.KO { + return ret + } + + d := time.Since(last.runtime) + o.Downtime() + o.x.Store(keyMetricDownTime, d) + return ret +} + +func (o *mon) setRiseTime(m middleWare) error { + ret := m.Next() + + if !o.IsRise() { + return ret + } + + last := o.getLastCheck() + o.x.Store(keyMetricRiseTime, o.CollectRiseTime()+time.Since(last.runtime)) + return ret +} + +func (o *mon) setFallTime(m middleWare) error { + ret := m.Next() + + if !o.IsFall() { + return ret + } + + last := o.getLastCheck() + o.x.Store(keyMetricFallTime, o.CollectFallTime()+time.Since(last.runtime)) + return ret +} diff --git a/monitor/middleware.go b/monitor/middleware.go new file mode 100644 index 00000000..3cabce04 --- /dev/null +++ b/monitor/middleware.go @@ -0,0 +1,97 @@ +/* + * 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 monitor + +import ( + "context" + + "github.com/nabbar/golib/monitor/types" +) + +type fctMiddleWare func(m middleWare) error + +type middleWare interface { + Context() context.Context + Config() *runCfg + Run(ctx context.Context) + Next() error + Add(fct fctMiddleWare) +} + +type mdl struct { + ctx context.Context + cfg *runCfg + crs int + mdl []fctMiddleWare +} + +func newMiddleware(cfg *runCfg, fct types.HealthCheck) middleWare { + o := &mdl{ + ctx: nil, + cfg: cfg, + crs: 0, + mdl: make([]fctMiddleWare, 0), + } + + o.Add(func(m middleWare) error { + return fct(m.Context()) + }) + + return o +} + +func (m *mdl) Context() context.Context { + return m.ctx +} + +func (m *mdl) Config() *runCfg { + return m.cfg +} + +func (m *mdl) Run(ctx context.Context) { + var cnl context.CancelFunc + + m.ctx, cnl = context.WithTimeout(ctx, m.cfg.checkTimeout) + defer cnl() + + m.crs = len(m.mdl) - 1 + _ = m.Next() +} + +func (m *mdl) Next() error { + m.crs-- + + if m.crs >= 0 && m.crs < len(m.mdl) { + return m.mdl[m.crs](m) + } + + return nil +} + +func (m *mdl) Add(fct fctMiddleWare) { + m.mdl = append(m.mdl, fct) +} diff --git a/monitor/model.go b/monitor/model.go new file mode 100644 index 00000000..5afe4440 --- /dev/null +++ b/monitor/model.go @@ -0,0 +1,97 @@ +/* + * 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 monitor + +import ( + "context" + "sync" + + libctx "github.com/nabbar/golib/context" + liberr "github.com/nabbar/golib/errors" + montps "github.com/nabbar/golib/monitor/types" +) + +const ( + defaultMonitorName = "not named" + + keyName = "keyName" + keyConfig = "keyConfig" + keyLogger = "keyLogger" + keyLoggerDef = "keyLoggerDefault" + keyHealthCheck = "keyFct" + keyStatus = "keySts" + keyMessage = "keyMsg" + keyRise = "keyRise" + keyFall = "keyFall" + keyRun = "keyRun" + keyLastRun = "keyLastRun" + + keyMetricsName = "keyMetricsName" + keyMetricsFunc = "keyMetricsFunc" + + keyMetricLatency = "metricLatency" + keyMetricUpTime = "metricUpTime" + keyMetricDownTime = "metricDownTime" + keyMetricRiseTime = "metricRiseTime" + keyMetricFallTime = "metricFallTime" + + LogFieldProcess = "process" + LogValueProcess = "monitor" + LogFieldName = "name" +) + +type mon struct { + m sync.RWMutex + i montps.Info + x libctx.Config[string] + s chan struct{} +} + +func (o *mon) SetHealthCheck(fct montps.HealthCheck) { + o.x.Store(keyHealthCheck, fct) +} + +func (o *mon) GetHealthCheck() montps.HealthCheck { + return o.getFct() +} + +func (o *mon) Clone(ctx context.Context) (montps.Monitor, liberr.Error) { + n := &mon{} + n.x = o.x.Clone(ctx) + + if o.IsRunning() { + if e := n.Start(ctx); e != nil { + if err, ok := e.(liberr.Error); ok { + return nil, err + } else { + return nil, ErrorTimeout.ErrorParent(e) + } + } + } + + return n, nil +} diff --git a/status/response.go b/monitor/pool/encode.go similarity index 63% rename from status/response.go rename to monitor/pool/encode.go index c38b401a..0bb07b91 100644 --- a/status/response.go +++ b/monitor/pool/encode.go @@ -21,58 +21,46 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * + * */ -package status - -import "sync" +package pool -const DefMessageOK = "OK" -const DefMessageKO = "KO" +import ( + "bytes" + "encoding/json" -type Response struct { - InfoResponse - StatusResponse + montps "github.com/nabbar/golib/monitor/types" +) - m sync.Mutex - Components []CptResponse `json:"components"` -} +func (o *pool) MarshalText() (text []byte, err error) { + var buf = bytes.NewBuffer(make([]byte, 0)) -func (r Response) IsOk() bool { - if len(r.Components) < 1 { - return true - } - - for _, c := range r.Components { - if c.Status != DefMessageOK { + o.MonitorWalk(func(name string, val montps.Monitor) bool { + if p, e := val.MarshalText(); e != nil { + err = e return false + } else { + buf.Write(p) + buf.WriteRune('\n') } - } - - return true -} - -func (r Response) IsOkMandatory() bool { - if len(r.Components) < 1 { return true - } - - for _, c := range r.Components { - if !c.Mandatory { - continue - } + }) - if c.Status != DefMessageOK { - return false - } + if err != nil { + return nil, err } - return true + return buf.Bytes(), nil } -func (r *Response) appendNewCpt(cpt CptResponse) { - r.m.Lock() - defer r.m.Unlock() +func (o *pool) MarshalJSON() ([]byte, error) { + var res = make(map[string]montps.MonitorStatus, 0) + + o.MonitorWalk(func(name string, val montps.Monitor) bool { + res[name] = val + return true + }) - r.Components = append(r.Components, cpt) + return json.Marshal(res) } diff --git a/monitor/pool/interface.go b/monitor/pool/interface.go new file mode 100644 index 00000000..60e8f37c --- /dev/null +++ b/monitor/pool/interface.go @@ -0,0 +1,52 @@ +/* + * 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 pool + +import ( + "sync" + + libsrv "github.com/nabbar/golib/server" + + libctx "github.com/nabbar/golib/context" + montps "github.com/nabbar/golib/monitor/types" + libprm "github.com/nabbar/golib/prometheus" +) + +type Pool interface { + montps.Pool + libsrv.Server + + RegisterFctProm(prm libprm.FuncGetPrometheus) +} + +func New(ctx libctx.FuncContext) Pool { + return &pool{ + m: sync.RWMutex{}, + fp: nil, + p: libctx.NewConfig[string](ctx), + } +} diff --git a/monitor/pool/metrics.go b/monitor/pool/metrics.go new file mode 100644 index 00000000..f94fd200 --- /dev/null +++ b/monitor/pool/metrics.go @@ -0,0 +1,221 @@ +/* + * 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 pool + +import ( + "context" + "strings" + + montps "github.com/nabbar/golib/monitor/types" + libprm "github.com/nabbar/golib/prometheus" + libmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" +) + +const ( + metricBaseName = "monitor" + metricLatency = "latency" + metricUptime = "uptime" + metricDowntime = "downtime" + metricRisetime = "risetime" + metricFalltime = "falltime" + metricStatus = "status" + metricBoolTrue = "true" + metricBoolFalse = "false" +) + +func (o *pool) normalizeName(name string) string { + name = strings.ToLower(name) + + name = strings.Replace(name, " ", "_", -1) + name = strings.Replace(name, "-", "_", -1) + name = strings.Replace(name, ".", "", -1) + + for strings.Contains(name, "__") { + name = strings.Replace(name, "__", "_", -1) + } + + return name +} + +func (o *pool) getMetricName(monitor, metric string) string { + part := make([]string, 0) + part = append(part, o.normalizeName(metricBaseName)) + part = append(part, o.normalizeName(metric)) + part = append(part, o.normalizeName(monitor)) + return strings.Join(part, "_") +} + +func (o *pool) getProm() libprm.Prometheus { + o.m.RLock() + defer o.m.RUnlock() + + if o.fp == nil { + return nil + } else if p := o.fp(); p != nil { + return p + } + + return nil +} + +func (o *pool) createMetrics(mon montps.Monitor) error { + var prm libprm.Prometheus + + if prm = o.getProm(); prm == nil { + return nil + } + + var ( + name = mon.Name() + met libmet.Metric + mnm string + ) + + mnm = o.getMetricName(name, metricLatency) + mon.RegisterMetricsAddName(mnm) + met = libmet.NewMetrics(mnm, prmtps.Histogram) + met.SetDesc("the time monitor took to check the health of '" + name + "' component") + met.AddLabel(metricBaseName) + met.AddBuckets(prm.GetDuration()...) + met.SetCollect(func(ctx context.Context, m libmet.Metric) { + _ = m.Observe([]string{name}, mon.CollectLatency().Seconds()) + }) + + if e := prm.AddMetric(false, met); e != nil { + return e + } + + mnm = o.getMetricName(name, metricUptime) + mon.RegisterMetricsAddName(mnm) + met = libmet.NewMetrics(mnm, prmtps.Histogram) + met.SetDesc("the total time during which the '" + name + "' component is up") + met.AddLabel(metricBaseName) + met.AddBuckets(prm.GetDuration()...) + met.SetCollect(func(ctx context.Context, m libmet.Metric) { + _ = m.Observe([]string{name}, mon.CollectUpTime().Seconds()) + }) + + if e := prm.AddMetric(false, met); e != nil { + return e + } + + mnm = o.getMetricName(name, metricDowntime) + mon.RegisterMetricsAddName(mnm) + met = libmet.NewMetrics(mnm, prmtps.Histogram) + met.SetDesc("the total time during which the '" + name + "' component is down") + met.AddLabel(metricBaseName) + met.AddBuckets(prm.GetDuration()...) + met.SetCollect(func(ctx context.Context, m libmet.Metric) { + _ = m.Observe([]string{name}, mon.CollectDownTime().Seconds()) + }) + + if e := prm.AddMetric(false, met); e != nil { + return e + } + + mnm = o.getMetricName(name, metricRisetime) + mon.RegisterMetricsAddName(mnm) + met = libmet.NewMetrics(mnm, prmtps.Histogram) + met.SetDesc("the total time during which the '" + name + "' component is rising") + met.AddLabel(metricBaseName) + met.AddBuckets(prm.GetDuration()...) + met.SetCollect(func(ctx context.Context, m libmet.Metric) { + _ = m.Observe([]string{name}, mon.CollectRiseTime().Seconds()) + }) + + if e := prm.AddMetric(false, met); e != nil { + return e + } + + mnm = o.getMetricName(name, metricFalltime) + mon.RegisterMetricsAddName(mnm) + met = libmet.NewMetrics(mnm, prmtps.Histogram) + met.SetDesc("the total time during which the '" + name + "' component is falling") + met.AddLabel(metricBaseName) + met.AddBuckets(prm.GetDuration()...) + met.SetCollect(func(ctx context.Context, m libmet.Metric) { + _ = m.Observe([]string{name}, mon.CollectFallTime().Seconds()) + }) + + if e := prm.AddMetric(false, met); e != nil { + return e + } + + mnm = o.getMetricName(name, metricStatus) + mon.RegisterMetricsAddName(mnm) + met = libmet.NewMetrics(mnm, prmtps.Counter) + met.SetDesc("the total time during which the '" + name + "' component is falling") + met.AddLabel(metricBaseName, "status", "rise", "fall") + met.SetCollect(func(ctx context.Context, m libmet.Metric) { + var ( + s, r, f = mon.CollectStatus() + rs, fs string + ) + + if r { + rs = metricBoolTrue + } else { + rs = metricBoolFalse + } + + if f { + fs = metricBoolTrue + } else { + fs = metricBoolFalse + } + + _ = m.Inc([]string{name, s, rs, fs}) + }) + + if e := prm.AddMetric(false, met); e != nil { + return e + } + + mon.RegisterCollectMetrics(prm.CollectMetrics) + + return nil +} + +func (o *pool) deleteMetrics(mon montps.Monitor) { + var prm libprm.Prometheus + + if prm = o.getProm(); prm == nil { + return + } + + var ( + name = mon.Name() + ) + + prm.DelMetric(o.getMetricName(name, metricLatency)) + prm.DelMetric(o.getMetricName(name, metricUptime)) + prm.DelMetric(o.getMetricName(name, metricDowntime)) + prm.DelMetric(o.getMetricName(name, metricRisetime)) + prm.DelMetric(o.getMetricName(name, metricFalltime)) + prm.DelMetric(o.getMetricName(name, metricStatus)) +} diff --git a/monitor/pool/model.go b/monitor/pool/model.go new file mode 100644 index 00000000..9bb6f7aa --- /dev/null +++ b/monitor/pool/model.go @@ -0,0 +1,47 @@ +/* + * 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 pool + +import ( + "sync" + + libctx "github.com/nabbar/golib/context" + libprm "github.com/nabbar/golib/prometheus" +) + +type pool struct { + m sync.RWMutex + fp libprm.FuncGetPrometheus + p libctx.Config[string] +} + +func (o *pool) RegisterFctProm(prm libprm.FuncGetPrometheus) { + o.m.Lock() + defer o.m.Unlock() + + o.fp = prm +} diff --git a/monitor/pool/pool.go b/monitor/pool/pool.go new file mode 100644 index 00000000..ab6c27a3 --- /dev/null +++ b/monitor/pool/pool.go @@ -0,0 +1,123 @@ +/* + * 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 pool + +import ( + "fmt" + + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *pool) MonitorAdd(mon montps.Monitor) error { + if mon == nil { + return nil + } else if len(mon.Name()) < 1 { + return fmt.Errorf("monitor name cannot be empty") + } + + if e := o.createMetrics(mon); e != nil { + return e + } + + if o.IsRunning() && !mon.IsRunning() { + if e := mon.Start(o.p.GetContext()); e != nil { + return e + } + } + + o.p.Store(mon.Name(), mon) + return nil +} + +func (o *pool) MonitorGet(name string) montps.Monitor { + if len(name) < 1 { + return nil + } else if i, l := o.p.Load(name); !l { + return nil + } else if v, k := i.(montps.Monitor); !k { + return nil + } else { + return v + } +} + +func (o *pool) MonitorSet(mon montps.Monitor) error { + if mon == nil { + return fmt.Errorf("nil monitor") + } else if len(mon.Name()) < 1 { + return fmt.Errorf("missing monitor name") + } else if _, l := o.p.Load(mon.Name()); !l { + return o.MonitorAdd(mon) + } + + o.p.Store(mon.Name(), mon) + return nil +} + +func (o *pool) MonitorDel(name string) { + if len(name) < 1 { + return + } else if i, l := o.p.LoadAndDelete(name); !l { + return + } else if v, k := i.(montps.Monitor); !k { + return + } else { + o.deleteMetrics(v) + } +} + +func (o *pool) MonitorList() []string { + var res = make([]string, 0) + + o.MonitorWalk(func(name string, val montps.Monitor) bool { + res = append(res, name) + return true + }) + + return res +} + +func (o *pool) MonitorWalk(fct func(name string, val montps.Monitor) bool, validName ...string) { + f := func(key string, val interface{}) bool { + var ( + ok bool + mon montps.Monitor + ) + + if mon, ok = val.(montps.Monitor); !ok { + return true + } else { + return fct(key, mon) + } + } + + if len(validName) > 0 { + o.p.WalkLimit(f, validName...) + } else { + o.p.Walk(f) + } +} diff --git a/monitor/pool/server.go b/monitor/pool/server.go new file mode 100644 index 00000000..49e7e223 --- /dev/null +++ b/monitor/pool/server.go @@ -0,0 +1,100 @@ +/* + * 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 pool + +import ( + "context" + "fmt" + "strings" + + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *pool) Start(ctx context.Context) error { + var err = make([]string, 0) + o.MonitorWalk(func(name string, val montps.Monitor) bool { + if e := val.Start(ctx); e != nil { + err = append(err, fmt.Sprintf("error on starting monitor '%s': %v", val.Name(), e)) + } else if e = o.MonitorSet(val); e != nil { + err = append(err, fmt.Sprintf("error on starting monitor '%s': %v", val.Name(), e)) + } + return true + }) + + if len(err) > 0 { + return fmt.Errorf("%s", strings.Join(err, ", \n")) + } + + return nil +} + +func (o *pool) Stop(ctx context.Context) error { + var err = make([]string, 0) + o.MonitorWalk(func(name string, val montps.Monitor) bool { + if e := val.Stop(ctx); e != nil { + err = append(err, fmt.Sprintf("error on stopping monitor '%s': %v", val.Name(), e)) + } else if e = o.MonitorSet(val); e != nil { + err = append(err, fmt.Sprintf("error on stopping monitor '%s': %v", val.Name(), e)) + } + return true + }) + + if len(err) > 0 { + return fmt.Errorf("%s", strings.Join(err, ", \n")) + } + + return nil +} + +func (o *pool) Restart(ctx context.Context) error { + var err = make([]string, 0) + o.MonitorWalk(func(name string, val montps.Monitor) bool { + if e := val.Restart(ctx); e != nil { + err = append(err, fmt.Sprintf("error on restarting monitor '%s': %v", val.Name(), e)) + } else if e = o.MonitorSet(val); e != nil { + err = append(err, fmt.Sprintf("error on restarting monitor '%s': %v", val.Name(), e)) + } + return true + }) + + if len(err) > 0 { + return fmt.Errorf("%s", strings.Join(err, ", \n")) + } + + return nil +} + +func (o *pool) IsRunning() bool { + var res = false + + o.MonitorWalk(func(name string, val montps.Monitor) bool { + res = val.IsRunning() + return !res + }) + + return res +} diff --git a/monitor/server.go b/monitor/server.go new file mode 100644 index 00000000..330b5b91 --- /dev/null +++ b/monitor/server.go @@ -0,0 +1,191 @@ +/* + * 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 monitor + +import ( + "context" + "time" + + "github.com/nabbar/golib/monitor/types" +) + +func (o *mon) setChan() { + o.m.Lock() + defer o.m.Unlock() + + o.s = make(chan struct{}) +} + +func (o *mon) getChan() <-chan struct{} { + o.m.RLock() + defer o.m.RUnlock() + + return o.s +} + +func (o *mon) sendChan() { + o.m.RLock() + defer o.m.RUnlock() + + o.s <- struct{}{} +} + +func (o *mon) Start(ctx context.Context) error { + if o.IsRunning() { + _ = o.Stop(ctx) + } + + o.setChan() + go o.ticker(ctx) + + if o.IsRunning() { + return nil + } + + t := time.Now() + for { + if time.Since(t) > 15*time.Second { + return ErrorTimeout.Error(nil) + } else if o.IsRunning() { + return nil + } + + time.Sleep(100 * time.Millisecond) + } +} + +func (o *mon) Stop(ctx context.Context) error { + if !o.IsRunning() { + return nil + } + + t := time.Now() + + for { + o.sendChan() + time.Sleep(100 * time.Millisecond) + + if time.Since(t) > 15*time.Second { + return ErrorTimeout.Error(nil) + } else if !o.IsRunning() { + return nil + } + } +} + +func (o *mon) Restart(ctx context.Context) error { + if e := o.Stop(ctx); e != nil { + return e + } else if e = o.Start(ctx); e != nil { + return e + } + + return nil +} + +func (o *mon) IsRunning() bool { + if i, l := o.x.Load(keyRun); !l { + return false + } else if v, k := i.(bool); !k { + return false + } else { + return v + } +} + +func (o *mon) setRunning(state bool) { + if state { + o.x.Store(keyRun, state) + } else { + o.x.Delete(keyRun) + } +} + +func (o *mon) ticker(ctx context.Context) { + var ( + cfg = o.getCfg() + chg = false + tck *time.Ticker + ) + + tck = time.NewTicker(cfg.intervalCheck) + defer tck.Stop() + + o.setRunning(true) + defer o.setRunning(false) + + for { + select { + case <-tck.C: + o.check(ctx, cfg) + + if o.IsRise() { + tck.Reset(cfg.intervalRise) + chg = true + } else if o.IsFall() { + tck.Reset(cfg.intervalFall) + chg = true + } else if chg { + tck.Reset(cfg.intervalCheck) + chg = false + } + + case <-ctx.Done(): + return + + case <-o.getChan(): + return + } + } +} + +func (o *mon) check(ctx context.Context, cfg *runCfg) { + var fct types.HealthCheck + + if fct = o.getFct(); fct == nil { + _ = o.setStatus(ErrorMissingHealthCheck.Error(nil), cfg) + } else if cfg == nil { + _ = o.setStatus(ErrorValidatorError.Error(nil), cfg) + } + + m := newMiddleware(cfg, fct) + m.Add(o.setLatency) + m.Add(o.mdlStatus) + // add here other part to run + + m.Add(o.setUpTime) + m.Add(o.setDownTime) + m.Add(o.setRiseTime) + m.Add(o.setFallTime) + + // no add after this + m.Add(o.setLastCheck) + m.Run(ctx) + + // store metrics to prometheus exporter + o.collectMetrics(ctx) +} diff --git a/monitor/status.go b/monitor/status.go new file mode 100644 index 00000000..36c36662 --- /dev/null +++ b/monitor/status.go @@ -0,0 +1,228 @@ +/* + * 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 monitor + +import ( + "time" + + monsts "github.com/nabbar/golib/monitor/status" + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *mon) Name() string { + return o.getName() +} + +func (o *mon) InfoName() string { + o.m.RLock() + defer o.m.RUnlock() + + return o.i.Name() +} + +func (o *mon) InfoMap() map[string]interface{} { + o.m.RLock() + defer o.m.RUnlock() + + return o.i.Info() +} + +func (o *mon) InfoGet() montps.Info { + o.m.Lock() + defer o.m.Unlock() + + return o.i +} + +func (o *mon) InfoUpd(inf montps.Info) { + o.m.Lock() + defer o.m.Unlock() + + o.i = inf +} + +func (o *mon) Status() monsts.Status { + if i, l := o.x.Load(keyStatus); !l { + return monsts.KO + } else if v, k := i.(monsts.Status); !k { + return monsts.KO + } else { + return v + } +} + +func (o *mon) Message() string { + if i, l := o.x.Load(keyMessage); !l { + return "" + } else if v, k := i.(error); !k { + return "" + } else if v == nil { + return "" + } else { + return v.Error() + } +} + +func (o *mon) IsRise() bool { + if sts := o.Status(); sts == monsts.OK { + return false + } else { + return o.riseGet() > 0 + } +} + +func (o *mon) IsFall() bool { + if sts := o.Status(); sts == monsts.KO { + return false + } else { + return o.fallGet() > 0 + } +} + +func (o *mon) Latency() time.Duration { + if i, l := o.x.Load(keyMetricLatency); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) Uptime() time.Duration { + if i, l := o.x.Load(keyMetricUpTime); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) Downtime() time.Duration { + if i, l := o.x.Load(keyMetricDownTime); !l { + return 0 + } else if v, k := i.(time.Duration); !k { + return 0 + } else { + return v + } +} + +func (o *mon) riseInc() uint8 { + i := o.riseGet() + 1 + o.x.Store(keyRise, i) + return i +} + +func (o *mon) riseReset() { + o.x.Delete(keyRise) +} + +func (o *mon) riseGet() uint8 { + if i, l := o.x.Load(keyRise); !l { + return 0 + } else if v, k := i.(uint8); !k { + return 0 + } else { + return v + } +} + +func (o *mon) fallInc() uint8 { + i := o.fallGet() + 1 + o.x.Store(keyFall, i) + return i +} + +func (o *mon) fallReset() { + o.x.Delete(keyFall) +} + +func (o *mon) fallGet() uint8 { + if i, l := o.x.Load(keyFall); !l { + return 0 + } else if v, k := i.(uint8); !k { + return 0 + } else { + return v + } +} + +func (o *mon) setStatus(err error, cfg *runCfg) error { + if err != nil { + o.x.Store(keyMessage, err) + o.setStatusFall(cfg) + } else { + o.x.Delete(keyMessage) + o.setStatusRise(cfg) + } + + return err +} + +func (o *mon) mdlStatus(m middleWare) error { + return o.setStatus(m.Next(), m.Config()) +} + +func (o *mon) setStatusFall(cfg *runCfg) { + sts := o.Status() + + if sts == monsts.KO || cfg == nil { + return + } + + o.riseReset() + i := o.fallInc() + + if i > cfg.fallCountKO { + o.x.Store(keyStatus, monsts.KO) + } else if i > cfg.fallCountWarn { + o.x.Store(keyStatus, monsts.Warn) + } else { + o.x.Store(keyStatus, monsts.OK) + } +} + +func (o *mon) setStatusRise(cfg *runCfg) { + sts := o.Status() + + if sts == monsts.OK || cfg == nil { + return + } + + o.fallReset() + i := o.riseInc() + + if i >= cfg.riseCountWarn { + o.x.Store(keyStatus, monsts.OK) + } else if i >= cfg.riseCountKO { + o.x.Store(keyStatus, monsts.Warn) + } else { + o.x.Store(keyStatus, monsts.KO) + } +} diff --git a/monitor/status/status.go b/monitor/status/status.go new file mode 100644 index 00000000..704ff6ae --- /dev/null +++ b/monitor/status/status.go @@ -0,0 +1,88 @@ +/* + * 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 status + +import ( + "strconv" + "strings" +) + +func (s Status) String() string { + switch s { + case OK: + return "OK" + case Warn: + return "Warn" + default: + return "KO" + } +} + +func (s Status) Int() int64 { + return int64(s) +} + +func (s Status) MarshalJSON() ([]byte, error) { + b := make([]byte, 0, len(s.String())+2) + b = append(b, '"') + b = append(b, []byte(s.String())...) + b = append(b, '"') + return b, nil +} + +func (s *Status) UnmarshalJSON(data []byte) error { + var ( + e error + i int64 + a Status + str string + ) + + str = string(data) + + if str == "null" { + *s = KO + return nil + } + + if strings.HasPrefix(str, "\"") || strings.HasSuffix(str, "\"") { + if str, e = strconv.Unquote(str); e != nil { + return e + } + } + + if i, e = strconv.ParseInt(str, 10, 8); e != nil { + *s = NewFromString(str) + return nil + } else if a = NewFromInt(i); a != KO { + *s = a + return nil + } else { + *s = NewFromString(str) + return nil + } +} diff --git a/status/component.go b/monitor/status/type.go similarity index 62% rename from status/component.go rename to monitor/status/type.go index 5c079cc0..74116d5d 100644 --- a/status/component.go +++ b/monitor/status/type.go @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2021 Nicolas JUHEL + * 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 @@ -21,46 +21,46 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * + * */ package status import ( - "time" - - "github.com/gin-gonic/gin" + "math" + "strings" ) -type CptResponse struct { - InfoResponse - StatusResponse -} +type Status uint8 -type Component interface { - Get(x *gin.Context) CptResponse - Clean() -} +const ( + KO Status = iota + Warn + OK +) -func NewComponent(key string, mandatory bool, info FctInfo, health FctHealth, msg FctMessage, infoCacheDuration, statusCacheDuration time.Duration) Component { - return &cpt{ - i: NewInfo(info, mandatory, infoCacheDuration), - s: NewStatus(key, health, msg, statusCacheDuration), +func NewFromString(sts string) Status { + switch strings.ToLower(sts) { + case OK.String(): + return OK + case Warn.String(): + return Warn + default: + return KO } } -type cpt struct { - i Info - s Status -} - -func (c *cpt) Get(x *gin.Context) CptResponse { - return CptResponse{ - InfoResponse: c.i.Get(x), - StatusResponse: c.s.Get(x), +func NewFromInt(sts int64) Status { + if sts > math.MaxUint8 { + return KO } -} -func (c *cpt) Clean() { - c.i.Clean() - c.s.Clean() + pSts := Status(uint8(sts)) + + switch pSts { + case OK, Warn: + return pSts + default: + return KO + } } diff --git a/monitor/types/config.go b/monitor/types/config.go new file mode 100644 index 00000000..ffcaad76 --- /dev/null +++ b/monitor/types/config.go @@ -0,0 +1,136 @@ +/* + * 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 types + +import ( + "bytes" + "encoding/json" + "fmt" + "time" + + cptlog "github.com/nabbar/golib/logger/config" + + cfgtps "github.com/nabbar/golib/config/const" + + libval "github.com/go-playground/validator/v10" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" +) + +var _defaultConfig = []byte(`{ + "name": "", + "check-timeout": "", + "interval-check": "", + "interval-fall": "", + "interval-rise": "", + "fall-count-ko": "", + "fall-count-warn": "", + "rise-count-ko": "", + "rise-count-warn": "", + "logger": ` + string(cptlog.DefaultConfig(cfgtps.JSONIndent+cfgtps.JSONIndent)) + ` +}`) + +func SetDefaultConfig(cfg []byte) { + _defaultConfig = cfg +} + +func DefaultConfig(indent string) []byte { + var res = bytes.NewBuffer(make([]byte, 0)) + if err := json.Indent(res, _defaultConfig, indent, cfgtps.JSONIndent); err != nil { + return _defaultConfig + } else { + return res.Bytes() + } +} + +type Config struct { + // Name define the name of the monitor + Name string `json:"name" yaml:"name" toml:"name" mapstructure:"name"` + + // CheckTimeout define the timeout use for healthcheck. Default is 5 second. + CheckTimeout time.Duration `json:"check-timeout" yaml:"check-timeout" toml:"check-timeout" mapstructure:"check-timeout"` + + // IntervalCheck define the time waiting between 2 healthcheck. Default is 5 second. + IntervalCheck time.Duration `json:"interval-check" yaml:"interval-check" toml:"interval-check" mapstructure:"interval-check"` + + // IntervalFall define the time waiting between 2 healthcheck when last check is KO. Default is 5 second. + IntervalFall time.Duration `json:"interval-fall" yaml:"interval-fall" toml:"interval-fall" mapstructure:"interval-down"` + + // IntervalRise define the time waiting between 2 healthcheck when status is KO or Warn but last check is OK. Default is 5 second. + IntervalRise time.Duration `json:"interval-rise" yaml:"interval-rise" toml:"interval-rise" mapstructure:"interval-rise"` + + // FallCountKO define the number of KO before considerate the component as down. + FallCountKO uint8 `json:"fall-count-ko" yaml:"fall-count-ko" toml:"fall-count-ko" mapstructure:"fall-count-ko"` + + // FallCountWarn define the number of KO before considerate the component as warn. + FallCountWarn uint8 `json:"fall-count-warn" yaml:"fall-count-warn" toml:"fall-count-warn" mapstructure:"fall-count-warn"` + + // RiseCountKO define the number of OK when status is KO before considerate the component as up. + RiseCountKO uint8 `json:"rise-count-ko" yaml:"rise-count-ko" toml:"rise-count-ko" mapstructure:"rise-count-ko"` + + // RiseCountWarn define the number of OK when status is Warn before considerate the component as up. + RiseCountWarn uint8 `json:"rise-count-warn" yaml:"rise-count-warn" toml:"rise-count-warn" mapstructure:"rise-count-warn"` + + // Logger define the logger options for current monitor log + Logger liblog.Options `json:"logger" yaml:"logger" toml:"logger" mapstructure:"logger"` +} + +func (o Config) Validate() liberr.Error { + var e = ErrorValidatorError.Error(nil) + + if err := libval.New().Struct(o); err != nil { + if er, ok := err.(*libval.InvalidValidationError); ok { + e.AddParent(er) + } + + for _, er := range err.(libval.ValidationErrors) { + //nolint #goerr113 + e.AddParent(fmt.Errorf("config field '%s' is not validated by constraint '%s'", er.Namespace(), er.ActualTag())) + } + } + + if !e.HasParent() { + e = nil + } + + return e +} + +func (o Config) Clone() Config { + return Config{ + Name: o.Name, + CheckTimeout: o.CheckTimeout, + IntervalCheck: o.IntervalCheck, + IntervalFall: o.IntervalFall, + IntervalRise: o.IntervalRise, + FallCountKO: o.FallCountKO, + FallCountWarn: o.FallCountWarn, + RiseCountKO: o.RiseCountKO, + RiseCountWarn: o.RiseCountWarn, + Logger: o.Logger.Clone(), + } +} diff --git a/monitor/types/error.go b/monitor/types/error.go new file mode 100644 index 00000000..6ecaf580 --- /dev/null +++ b/monitor/types/error.go @@ -0,0 +1,56 @@ +/* + * 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 types + +import ( + "fmt" + + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgMonitorCfg + ErrorValidatorError +) + +func init() { + if liberr.ExistInMapMessage(ErrorParamEmpty) { + panic(fmt.Errorf("error code collision with package golib/logger")) + } + liberr.RegisterIdFctMessage(ErrorParamEmpty, getMessage) +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamEmpty: + return "given parameters is empty" + case ErrorValidatorError: + return "invalid config" + } + + return liberr.NullMessage +} diff --git a/monitor/types/info.go b/monitor/types/info.go new file mode 100644 index 00000000..76448619 --- /dev/null +++ b/monitor/types/info.go @@ -0,0 +1,40 @@ +/* + * 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 types + +import ( + "encoding" + "encoding/json" +) + +type Info interface { + encoding.TextMarshaler + json.Marshaler + + Name() string + Info() map[string]interface{} +} diff --git a/monitor/types/monitor.go b/monitor/types/monitor.go new file mode 100644 index 00000000..407b1f23 --- /dev/null +++ b/monitor/types/monitor.go @@ -0,0 +1,120 @@ +/* + * 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 types + +import ( + "context" + "encoding" + "encoding/json" + "time" + + liblog "github.com/nabbar/golib/logger" + + libctx "github.com/nabbar/golib/context" + + liberr "github.com/nabbar/golib/errors" + monsts "github.com/nabbar/golib/monitor/status" + libprm "github.com/nabbar/golib/prometheus" + libsrv "github.com/nabbar/golib/server" +) + +type HealthCheck func(ctx context.Context) error + +type MonitorStatus interface { + encoding.TextMarshaler + json.Marshaler + + // Name return the name of the monitor. + Name() string + + // Status return the last status (OK / Warn / KO). + Status() monsts.Status + + // Message return the last error, warning, message of the last status + Message() string + + // IsRise return true if rising status from KO or Warn + IsRise() bool + + // IsFall return true if falling status to KO or Warn + IsFall() bool + + // Latency return the last check's latency + Latency() time.Duration + + // Uptime return the total duration of uptime (OK status) + Uptime() time.Duration + + // Downtime return the total duration of downtime (KO status) + Downtime() time.Duration +} + +type MonitorMetrics interface { + RegisterMetricsName(names ...string) + RegisterMetricsAddName(names ...string) + RegisterCollectMetrics(fct libprm.FuncCollectMetrics) + + CollectLatency() time.Duration + CollectUpTime() time.Duration + CollectDownTime() time.Duration + CollectRiseTime() time.Duration + CollectFallTime() time.Duration + CollectStatus() (sts string, rise bool, fall bool) +} + +type MonitorInfo interface { + InfoGet() Info + InfoUpd(inf Info) + InfoName() string + InfoMap() map[string]interface{} +} + +type Monitor interface { + MonitorInfo + MonitorStatus + MonitorMetrics + libsrv.Server + + // SetConfig is used to set or update config of the monitor + SetConfig(ctx libctx.FuncContext, cfg Config) liberr.Error + + // RegisterLoggerDefault is used to define the default logger. + // Default logger can be used to extend options logger from it + RegisterLoggerDefault(fct liblog.FuncLog) + + // GetConfig is used to retrieve config of the monitor + GetConfig() Config + + // SetHealthCheck is used to set or update the healthcheck func + SetHealthCheck(fct HealthCheck) + + // GetHealthCheck is used to retrieve the healthcheck func + GetHealthCheck() HealthCheck + + // Clone is used to clone monitor to another standalone instance + Clone(ctx context.Context) (Monitor, liberr.Error) +} diff --git a/monitor/types/pool.go b/monitor/types/pool.go new file mode 100644 index 00000000..d55b1cb4 --- /dev/null +++ b/monitor/types/pool.go @@ -0,0 +1,50 @@ +/* + * 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 types + +import ( + "encoding" + "encoding/json" +) + +type FuncPool func() Pool + +type PoolStatus interface { + encoding.TextMarshaler + json.Marshaler +} + +type Pool interface { + PoolStatus + + MonitorAdd(mon Monitor) error + MonitorGet(name string) Monitor + MonitorSet(mon Monitor) error + MonitorDel(name string) + MonitorList() []string + MonitorWalk(fct func(name string, val Monitor) bool, validName ...string) +} diff --git a/nats/config.go b/nats/config.go index 97057ca2..c59e2cf8 100644 --- a/nats/config.go +++ b/nats/config.go @@ -36,7 +36,7 @@ import ( "strings" "time" - libsts "github.com/nabbar/golib/status/config" + moncfg "github.com/nabbar/golib/monitor/types" libval "github.com/go-playground/validator/v10" libtls "github.com/nabbar/golib/certificates" @@ -48,16 +48,16 @@ import ( ) type Config struct { - Server ConfigSrv `mapstructure:"server" json:"server" yaml:"server" toml:"server" validate:"dive,required"` - Cluster ConfigCluster `mapstructure:"cluster" json:"cluster" yaml:"cluster" toml:"cluster" validate:"dive,required"` - Gateways ConfigGateway `mapstructure:"gateways" json:"gateways" yaml:"gateways" toml:"gateways" validate:"dive,required"` - Leaf ConfigLeaf `mapstructure:"leaf" json:"leaf" yaml:"leaf" toml:"leaf" validate:"dive,required"` - Websockets ConfigWebsocket `mapstructure:"websockets" json:"websockets" yaml:"websockets" toml:"websockets" validate:"dive,required"` - MQTT ConfigMQTT `mapstructure:"mqtt" json:"mqtt" yaml:"mqtt" toml:"mqtt" validate:"dive,required"` - Limits ConfigLimits `mapstructure:"limits" json:"limits" yaml:"limits" toml:"limits" validate:"dive,required"` - Logs ConfigLogger `mapstructure:"logs" json:"logs" yaml:"logs" toml:"logs" validate:"dive,required"` - Auth ConfigAuth `mapstructure:"auth" json:"auth" yaml:"auth" toml:"auth" validate:"dive,required"` - Status libsts.ConfigStatus `mapstructure:"status" json:"status" yaml:"status" toml:"status" validate:"dive"` + Server ConfigSrv `mapstructure:"server" json:"server" yaml:"server" toml:"server" validate:"dive,required"` + Cluster ConfigCluster `mapstructure:"cluster" json:"cluster" yaml:"cluster" toml:"cluster" validate:"dive,required"` + Gateways ConfigGateway `mapstructure:"gateways" json:"gateways" yaml:"gateways" toml:"gateways" validate:"dive,required"` + Leaf ConfigLeaf `mapstructure:"leaf" json:"leaf" yaml:"leaf" toml:"leaf" validate:"dive,required"` + Websockets ConfigWebsocket `mapstructure:"websockets" json:"websockets" yaml:"websockets" toml:"websockets" validate:"dive,required"` + MQTT ConfigMQTT `mapstructure:"mqtt" json:"mqtt" yaml:"mqtt" toml:"mqtt" validate:"dive,required"` + Limits ConfigLimits `mapstructure:"limits" json:"limits" yaml:"limits" toml:"limits" validate:"dive,required"` + Logs ConfigLogger `mapstructure:"logs" json:"logs" yaml:"logs" toml:"logs" validate:"dive,required"` + Auth ConfigAuth `mapstructure:"auth" json:"auth" yaml:"auth" toml:"auth" validate:"dive,required"` + Monitor moncfg.Config `mapstructure:"monitor" json:"monitor" yaml:"monitor" toml:"monitor" validate:"dive"` //function / interface are not defined in config marshall Customs *ConfigCustom `mapstructure:"-" json:"-" yaml:"-" toml:"-"` diff --git a/nats/configPart.go b/nats/configPart.go index c487c32c..28d9dfb0 100644 --- a/nats/configPart.go +++ b/nats/configPart.go @@ -32,11 +32,9 @@ import ( "os" "time" - natsrv "github.com/nats-io/nats-server/v2/server" - - "github.com/nats-io/jwt/v2" - libtls "github.com/nabbar/golib/certificates" + natjwt "github.com/nats-io/jwt/v2" + natsrv "github.com/nats-io/nats-server/v2/server" ) type ConfigCustom struct { @@ -301,7 +299,7 @@ type ConfigSrv struct { //Tags describing the server. //They will be included in varz and used as a filter criteria for some system requests - Tags jwt.TagList `mapstructure:"tags" json:"tags" yaml:"tags" toml:"tags" validate:"dive"` + Tags natjwt.TagList `mapstructure:"tags" json:"tags" yaml:"tags" toml:"tags" validate:"dive"` //TLS Enable tls for server. TLS bool `mapstructure:"tls" json:"tls" yaml:"tls" toml:"tls"` diff --git a/nats/monitor.go b/nats/monitor.go new file mode 100644 index 00000000..75b43d62 --- /dev/null +++ b/nats/monitor.go @@ -0,0 +1,119 @@ +/* + * 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 nats + +import ( + "context" + "fmt" + "runtime" + "time" + + libctx "github.com/nabbar/golib/context" + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" + natsrv "github.com/nats-io/nats-server/v2/server" +) + +const ( + defaultNameMonitor = "Nats Server" +) + +func (s *server) HealthCheck(ctx context.Context) error { + for i := 0; i < 5; i++ { + if s.IsRunning() { + if s.IsReadyTimeout(context.Background(), time.Second) { + return nil + } + } + + time.Sleep(time.Second) + } + + if e := s._GetError(); e != nil { + return e + } + + return fmt.Errorf("node not ready") +} + +func (s *server) Monitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) { + + var ( + e error + inf moninf.Info + mon montps.Monitor + opt *natsrv.Options + cfg *montps.Config + res = make(map[string]interface{}, 0) + ) + + s.m.Lock() + opt = s.GetOptions() + cfg = s.c + s.m.Unlock() + + if cfg == nil { + return nil, fmt.Errorf("cannot load config") + } else if opt == nil { + return nil, fmt.Errorf("cannot load config") + } + + res["runtime"] = runtime.Version()[2:] + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + res["advertise"] = opt.ClientAdvertise + + if inf, e = moninf.New(defaultNameMonitor); e != nil { + return nil, e + } else { + inf.RegisterName(func() (string, error) { + return fmt.Sprintf("%s [%s]", defaultNameMonitor, opt.Host), nil + }) + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon, e = libmon.New(ctx, inf); e != nil { + return nil, e + } + + mon.SetHealthCheck(s.HealthCheck) + + if e = mon.SetConfig(ctx, *cfg); e != nil { + return nil, e + } + + if e = mon.Start(ctx()); e != nil { + return nil, e + } + + return mon, nil +} diff --git a/nats/server.go b/nats/server.go index 24271942..0988ec7c 100644 --- a/nats/server.go +++ b/nats/server.go @@ -30,17 +30,16 @@ package nats import ( "context" "fmt" - "runtime" "strings" "sync" "sync/atomic" "time" - "github.com/nabbar/golib/status/config" - libtls "github.com/nabbar/golib/certificates" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" natsrv "github.com/nats-io/nats-server/v2/server" natcli "github.com/nats-io/nats.go" ) @@ -67,12 +66,10 @@ type Server interface { ClientCluster(ctx context.Context, tick time.Duration, defTls libtls.TLSConfig, opt Client) (cli *natcli.Conn, err liberr.Error) ClientServer(ctx context.Context, tick time.Duration, defTls libtls.TLSConfig, opt Client) (cli *natcli.Conn, err liberr.Error) - StatusInfo() (name string, release string, hash string) - StatusHealth() error - StatusRouter(sts libsts.RouteStatus, prefix string) + Monitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) } -func NewServer(opt *natsrv.Options, sts config.ConfigStatus) Server { +func NewServer(opt *natsrv.Options, cfg montps.Config) Server { o := new(atomic.Value) if opt != nil { @@ -80,7 +77,7 @@ func NewServer(opt *natsrv.Options, sts config.ConfigStatus) Server { } return &server{ - c: &sts, + c: &cfg, o: o, s: nil, r: new(atomic.Value), @@ -88,7 +85,7 @@ func NewServer(opt *natsrv.Options, sts config.ConfigStatus) Server { } type server struct { - c *config.ConfigStatus + c *montps.Config o *atomic.Value s *atomic.Value r *atomic.Value @@ -261,48 +258,6 @@ func (s *server) ClientServer(ctx context.Context, tick time.Duration, defTls li return opt.NewClient(defTls) } -func (s *server) StatusInfo() (name string, release string, hash string) { - s.m.Lock() - defer s.m.Unlock() - - hash = "" - release = strings.TrimLeft(strings.ToLower(runtime.Version()), "go") - name = fmt.Sprintf("Nats %d (%s)", s.GetOptions().Host, s.GetOptions().ClientAdvertise) - - return name, release, hash -} - -func (s *server) StatusHealth() error { - for i := 0; i < 5; i++ { - if s.IsRunning() { - if s.IsReadyTimeout(context.Background(), time.Second) { - return nil - } - } - - time.Sleep(time.Second) - } - - if e := s._GetError(); e != nil { - return e - } - - return fmt.Errorf("node not ready") -} - -func (s *server) StatusRouter(sts libsts.RouteStatus, prefix string) { - s.m.Lock() - defer s.m.Unlock() - - if prefix != "" { - prefix = fmt.Sprintf("%s Nats %d (%s)", prefix, s.GetOptions().Host, s.GetOptions().ClientAdvertise) - } else { - prefix = fmt.Sprintf("Nats %d (%s)", s.GetOptions().Host, s.GetOptions().ClientAdvertise) - } - - s.c.RegisterStatus(sts, prefix, s.StatusInfo, s.StatusHealth) -} - func (s *server) _GetServer() *natsrv.Server { s.m.Lock() defer s.m.Unlock() diff --git a/nutsdb/config.go b/nutsdb/config.go index 5c8f1437..de7c7917 100644 --- a/nutsdb/config.go +++ b/nutsdb/config.go @@ -33,18 +33,19 @@ package nutsdb import ( "fmt" + moncfg "github.com/nabbar/golib/monitor/types" + libval "github.com/go-playground/validator/v10" libclu "github.com/nabbar/golib/cluster" liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status/config" "github.com/xujiajun/nutsdb" ) type Config struct { - DB NutsDBOptions `mapstructure:"db" json:"db" yaml:"db" toml:"db" validate:"dive"` - Cluster libclu.Config `mapstructure:"cluster" json:"cluster" yaml:"cluster" toml:"cluster" validate:"dive"` - Directory NutsDBFolder `mapstructure:"directories" json:"directories" yaml:"directories" toml:"directories" validate:"dive"` - Status libsts.ConfigStatus `mapstructure:"status" json:"status" yaml:"status" toml:"status" validate:"dive"` + DB NutsDBOptions `mapstructure:"db" json:"db" yaml:"db" toml:"db" validate:"dive"` + Cluster libclu.Config `mapstructure:"cluster" json:"cluster" yaml:"cluster" toml:"cluster" validate:"dive"` + Directory NutsDBFolder `mapstructure:"directories" json:"directories" yaml:"directories" toml:"directories" validate:"dive"` + Monitor moncfg.Config `mapstructure:"monitor" json:"monitor" yaml:"monitor" toml:"monitor" validate:"dive"` } func (c Config) GetConfigFolder() NutsDBFolder { diff --git a/nutsdb/interface.go b/nutsdb/interface.go index a7d621c9..df169501 100644 --- a/nutsdb/interface.go +++ b/nutsdb/interface.go @@ -36,10 +36,12 @@ import ( "time" libclu "github.com/nabbar/golib/cluster" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" liblog "github.com/nabbar/golib/logger" + montps "github.com/nabbar/golib/monitor/types" libsh "github.com/nabbar/golib/shell" - libsts "github.com/nabbar/golib/status" + libver "github.com/nabbar/golib/version" ) const LogLib = "NutsDB" @@ -60,9 +62,7 @@ type NutsDB interface { GetLogger() liblog.Logger SetLogger(l liblog.FuncLog) - StatusInfo() (name string, release string, hash string) - StatusHealth() error - StatusRouter(sts libsts.RouteStatus, prefix string) + Monitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) Cluster() libclu.Cluster Client(ctx context.Context, tickSync time.Duration) Client diff --git a/nutsdb/model.go b/nutsdb/model.go index b8e2cfb1..133960f6 100644 --- a/nutsdb/model.go +++ b/nutsdb/model.go @@ -32,15 +32,10 @@ package nutsdb import ( "context" - "fmt" - "runtime" - "strings" "sync" "sync/atomic" "time" - libsts "github.com/nabbar/golib/status" - dgbstm "github.com/lni/dragonboat/v3/statemachine" libclu "github.com/nabbar/golib/cluster" liberr "github.com/nabbar/golib/errors" @@ -337,49 +332,6 @@ func (n *ndb) ShellCommand(ctx func() context.Context, tickSync time.Duration) [ return res } -func (n *ndb) StatusInfo() (name string, release string, hash string) { - n.m.Lock() - defer n.m.Unlock() - - hash = "" - release = strings.TrimLeft(strings.ToLower(runtime.Version()), "go") - name = fmt.Sprintf("NutsDB %d (%s)", n.c.Cluster.Cluster.NodeID, n.c.Cluster.Node.RaftAddress) - - return name, release, hash -} - -func (n *ndb) StatusHealth() error { - for i := 0; i < 5; i++ { - if n.IsRunning() { - if n.IsReadyTimeout(context.Background(), time.Second) { - return nil - } - } - - time.Sleep(time.Second) - } - - if e := n._GetError(); e != nil { - return e - } - - return fmt.Errorf("node not ready") -} - -func (n *ndb) StatusRouter(sts libsts.RouteStatus, prefix string) { - n.m.Lock() - defer n.m.Unlock() - - if prefix != "" { - prefix = fmt.Sprintf("%s NutsDB %d (%s)", prefix, n.c.Cluster.Cluster.NodeID, n.c.Cluster.Node.RaftAddress) - } else { - prefix = fmt.Sprintf("NutsDB %d (%s)", n.c.Cluster.Cluster.NodeID, n.c.Cluster.Node.RaftAddress) - } - - cfg := n.c.Status - cfg.RegisterStatus(sts, prefix, n.StatusInfo, n.StatusHealth) -} - func (n *ndb) _SetError(e liberr.Error) { n.m.Lock() defer n.m.Unlock() diff --git a/nutsdb/monitor.go b/nutsdb/monitor.go new file mode 100644 index 00000000..d7a16a2e --- /dev/null +++ b/nutsdb/monitor.go @@ -0,0 +1,110 @@ +/* + * 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 nutsdb + +import ( + "context" + "fmt" + "runtime" + "time" + + libctx "github.com/nabbar/golib/context" + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +const ( + defaultNameMonitor = "NutsDB Server" +) + +func (n *ndb) HealthCheck(ctx context.Context) error { + for i := 0; i < 5; i++ { + if n.IsRunning() { + if n.IsReadyTimeout(context.Background(), time.Second) { + return nil + } + } + + time.Sleep(time.Second) + } + + if e := n._GetError(); e != nil { + return e + } + + return fmt.Errorf("node not ready") +} + +func (n *ndb) Monitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) { + + var ( + e error + inf moninf.Info + mon montps.Monitor + cfg Config + res = make(map[string]interface{}, 0) + ) + + n.m.Lock() + cfg = n.c + n.m.Unlock() + + res["runtime"] = runtime.Version()[2:] + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + res["nodId"] = n.c.Cluster.Cluster.NodeID + + if inf, e = moninf.New(defaultNameMonitor); e != nil { + return nil, e + } else { + inf.RegisterName(func() (string, error) { + return fmt.Sprintf("%s [%s]", defaultNameMonitor, cfg.Cluster.Node.RaftAddress), nil + }) + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon, e = libmon.New(ctx, inf); e != nil { + return nil, e + } + + mon.SetHealthCheck(n.HealthCheck) + + if e = mon.SetConfig(ctx, cfg.Monitor); e != nil { + return nil, e + } + + if e = mon.Start(ctx()); e != nil { + return nil, e + } + + return mon, nil +} diff --git a/prometheus/bloom.go b/prometheus/bloom/bloom.go similarity index 99% rename from prometheus/bloom.go rename to prometheus/bloom/bloom.go index eb9b9888..c4a9fc2e 100644 --- a/prometheus/bloom.go +++ b/prometheus/bloom/bloom.go @@ -24,7 +24,7 @@ * */ -package prometheus +package bloom import ( "sync" diff --git a/prometheus/bloom/collection.go b/prometheus/bloom/collection.go new file mode 100644 index 00000000..5bb40435 --- /dev/null +++ b/prometheus/bloom/collection.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 bloom + +import "sync" + +type Collection interface { + Add(metricName, value string) + Contains(metricName, value string) bool +} + +type colBf struct { + m sync.RWMutex + bf map[string]BloomFilter +} + +func New() Collection { + return &colBf{ + m: sync.RWMutex{}, + bf: make(map[string]BloomFilter, 0), + } +} + +func (c *colBf) Add(metricName, value string) { + c.m.Lock() + defer c.m.Unlock() + + if len(c.bf) < 1 { + c.bf = make(map[string]BloomFilter, 0) + } + + var ( + b BloomFilter + ok bool + ) + + if b, ok = c.bf[metricName]; !ok { + b = NewBloomFilter() + } + + b.Add(value) + c.bf[metricName] = b +} + +func (c *colBf) Contains(metricName, value string) bool { + c.m.RLock() + defer c.m.RUnlock() + + if len(c.bf) < 1 { + return false + } + + if b, ok := c.bf[metricName]; ok { + return b.Contains(value) + } + + return false +} diff --git a/prometheus/interface.go b/prometheus/interface.go index bfc8b440..05cff102 100644 --- a/prometheus/interface.go +++ b/prometheus/interface.go @@ -27,27 +27,56 @@ package prometheus import ( - "sync/atomic" - "time" + "context" + "sync" - "github.com/gin-gonic/gin" + ginsdk "github.com/gin-gonic/gin" + + libctx "github.com/nabbar/golib/context" + libmet "github.com/nabbar/golib/prometheus/metrics" + prmpol "github.com/nabbar/golib/prometheus/pool" ) const ( DefaultSlowTime = int32(5) ) -type Prometheus interface { - Expose(c *gin.Context) - MiddleWare(c *gin.Context) - CollectMetrics(c *gin.Context, start time.Time) +type FuncGetPrometheus func() Prometheus +type FuncCollectMetrics func(ctx context.Context, name ...string) - ExcludePath(startWith ...string) +type MetricsCollection interface { + // GetMetric is used to retrieve the metric instance from prometheus instance. + GetMetric(name string) libmet.Metric + + // AddMetric is used to register the metric instance into prometheus instance. + AddMetric(isAPI bool, metric libmet.Metric) error + + // DelMetric is used to unregister the metric instance into prometheus instance. + DelMetric(name string) - GetMetric(name string) *metrics - SetMetric(metric Metrics) error - AddMetric(metric Metrics) error + // ListMetric retrieve a slice of ginMet' name registered for all type API or not. ListMetric() []string +} + +type GinRoute interface { + Expose(ctx context.Context) + ExposeGin(c *ginsdk.Context) + + MiddleWare(ctx context.Context) + MiddleWareGin(c *ginsdk.Context) + + ExcludePath(startWith ...string) +} + +type Collect interface { + libmet.Collect + CollectMetrics(ctx context.Context, name ...string) +} + +type Prometheus interface { + GinRoute + MetricsCollection + Collect SetSlowTime(slowTime int32) GetSlowTime() int32 @@ -57,11 +86,13 @@ type Prometheus interface { } // New will return a new object that implement interface GinPrometheus. -func New() Prometheus { - return &monitor{ +func New(ctx libctx.FuncContext) Prometheus { + return &prom{ + m: sync.RWMutex{}, + exclude: make([]string, 0), slowTime: DefaultSlowTime, reqDuration: []float64{0.1, 0.3, 1.2, 5, 10}, - metrics: make(map[string]*atomic.Value), - exclude: make([]string, 0), + ginMet: prmpol.New(ctx), + othMet: prmpol.New(ctx), } } diff --git a/prometheus/metrics.go b/prometheus/metrics.go deleted file mode 100644 index c0a52096..00000000 --- a/prometheus/metrics.go +++ /dev/null @@ -1,239 +0,0 @@ -/* - * 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 prometheus - -import ( - "sync" - "time" - - "github.com/gin-gonic/gin" - "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" -) - -type Metrics interface { - SetGaugeValue(labelValues []string, value float64) error - Inc(labelValues []string) error - Add(labelValues []string, value float64) error - Observe(labelValues []string, value float64) error - - Collect(c *gin.Context, start time.Time) - - GetName() string - GetType() MetricType - SetDesc(desc string) - - AddLabel(label ...string) - AddBuckets(bucket ...float64) - AddObjective(key, value float64) - SetCollect(fct FuncCollect) -} - -type FuncCollect func(m Metrics, c *gin.Context, start time.Time) - -func NewMetrics(name string, metricType MetricType) Metrics { - return &metrics{ - t: metricType, - n: name, - d: "", - l: make([]string, 0), - b: make([]float64, 0), - o: make(map[float64]float64, 0), - f: nil, - v: nil, - } -} - -// metrics defines a metric object. Users can use it to save -// metric data. Every metric should be globally unique by name. -type metrics struct { - m sync.Mutex - t MetricType - n string - d string - l []string - b []float64 - o map[float64]float64 - f FuncCollect - v prometheus.Collector -} - -// SetGaugeValue set data for Gauge type metrics. -func (m *metrics) SetGaugeValue(labelValues []string, value float64) error { - m.m.Lock() - defer m.m.Unlock() - - if m.t == None { - return errors.Errorf("metric '%s' not existed.", m.n) - } - - if m.t != Gauge { - return errors.Errorf("metric '%s' not Gauge type", m.n) - } - - m.v.(*prometheus.GaugeVec).WithLabelValues(labelValues...).Set(value) - return nil -} - -// Inc increases value for Counter/Gauge type metric, increments -// the counter by 1 -func (m *metrics) Inc(labelValues []string) error { - m.m.Lock() - defer m.m.Unlock() - - if m.t == None { - return errors.Errorf("metric '%s' not existed.", m.n) - } - - if m.t != Gauge && m.t != Counter { - return errors.Errorf("metric '%s' not Gauge or Counter type", m.n) - } - - switch m.t { - case Counter: - m.v.(*prometheus.CounterVec).WithLabelValues(labelValues...).Inc() - break - case Gauge: - m.v.(*prometheus.GaugeVec).WithLabelValues(labelValues...).Inc() - break - } - - return nil -} - -// Add adds the given value to the metrics object. Only -// for Counter/Gauge type metric. -func (m *metrics) Add(labelValues []string, value float64) error { - m.m.Lock() - defer m.m.Unlock() - - if m.t == None { - return errors.Errorf("metric '%s' not existed.", m.n) - } - - if m.t != Gauge && m.t != Counter { - return errors.Errorf("metric '%s' not Gauge or Counter type", m.n) - } - - switch m.t { - case Counter: - m.v.(*prometheus.CounterVec).WithLabelValues(labelValues...).Add(value) - break - case Gauge: - m.v.(*prometheus.GaugeVec).WithLabelValues(labelValues...).Add(value) - break - } - - return nil -} - -// Observe is used by Histogram and Summary type metric to -// add observations. -func (m *metrics) Observe(labelValues []string, value float64) error { - m.m.Lock() - defer m.m.Unlock() - - if m.t == 0 { - return errors.Errorf("metric '%s' not existed.", m.n) - } - - if m.t != Histogram && m.t != Summary { - return errors.Errorf("metric '%s' not Histogram or Summary type", m.n) - } - - switch m.t { - case Histogram: - m.v.(*prometheus.HistogramVec).WithLabelValues(labelValues...).Observe(value) - break - case Summary: - m.v.(*prometheus.SummaryVec).WithLabelValues(labelValues...).Observe(value) - break - } - - return nil -} - -func (m *metrics) Collect(c *gin.Context, start time.Time) { - if m.f != nil { - m.f(m, c, start) - } -} - -func (m *metrics) GetName() string { - return m.n -} - -func (m *metrics) GetType() MetricType { - return m.t -} - -func (m *metrics) SetDesc(desc string) { - m.m.Lock() - defer m.m.Unlock() - - m.d = desc -} - -func (m *metrics) AddLabel(label ...string) { - m.m.Lock() - defer m.m.Unlock() - - if len(m.l) < 1 { - m.l = make([]string, 0) - } - - m.l = append(m.l, label...) -} - -func (m *metrics) AddBuckets(bucket ...float64) { - m.m.Lock() - defer m.m.Unlock() - - if len(m.b) < 1 { - m.b = make([]float64, 0) - } - - m.b = append(m.b, bucket...) -} - -func (m *metrics) AddObjective(key, value float64) { - m.m.Lock() - defer m.m.Unlock() - - if len(m.o) < 1 { - m.o = make(map[float64]float64, 0) - } - - m.o[key] = value -} - -func (m *metrics) SetCollect(fct FuncCollect) { - m.m.Lock() - defer m.m.Unlock() - - m.f = fct -} diff --git a/prometheus/metrics/interface.go b/prometheus/metrics/interface.go new file mode 100644 index 00000000..fcf98c7d --- /dev/null +++ b/prometheus/metrics/interface.go @@ -0,0 +1,81 @@ +/* + * 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 metrics + +import ( + "context" + "sync" + + prmtps "github.com/nabbar/golib/prometheus/types" + prmsdk "github.com/prometheus/client_golang/prometheus" +) + +type FuncGetMetric func() Metric +type FuncGetMetricName func(name string) Metric +type FuncCollectMetrics func() Collect +type FuncCollectMetricsByName func(name string) Collect + +type Collect interface { + Collect(ctx context.Context) +} + +type Metric interface { + prmtps.Metric + Collect + + SetGaugeValue(labelValues []string, value float64) error + Inc(labelValues []string) error + Add(labelValues []string, value float64) error + Observe(labelValues []string, value float64) error + + Register(vec prmsdk.Collector) error + UnRegister() bool + + SetDesc(desc string) + AddLabel(label ...string) + AddBuckets(bucket ...float64) + AddObjective(key, value float64) + + SetCollect(fct FuncCollect) + GetCollect() FuncCollect +} + +type FuncCollect func(ctx context.Context, m Metric) + +func NewMetrics(name string, metricType prmtps.MetricType) Metric { + return &metrics{ + m: sync.RWMutex{}, + t: metricType, + n: name, + d: "", + l: make([]string, 0), + b: make([]float64, 0), + o: make(map[float64]float64, 0), + f: nil, + v: nil, + } +} diff --git a/prometheus/metrics/model.go b/prometheus/metrics/model.go new file mode 100644 index 00000000..a1c5ade8 --- /dev/null +++ b/prometheus/metrics/model.go @@ -0,0 +1,97 @@ +/* + * 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 metrics + +import ( + "context" + "sync" + + prmtps "github.com/nabbar/golib/prometheus/types" + prmsdk "github.com/prometheus/client_golang/prometheus" +) + +// metrics defines a metric object. Users can use it to save +// metric data. Every metric should be globally unique by name. +type metrics struct { + m sync.RWMutex + t prmtps.MetricType + n string + d string + l []string + b []float64 + o map[float64]float64 + f FuncCollect + v prmsdk.Collector +} + +func (m *metrics) SetCollect(fct FuncCollect) { + m.m.Lock() + defer m.m.Unlock() + + m.f = fct +} + +func (m *metrics) GetCollect() FuncCollect { + m.m.RLock() + defer m.m.RUnlock() + + return m.f +} + +func (m *metrics) GetName() string { + m.m.RLock() + defer m.m.RUnlock() + + return m.n +} + +func (m *metrics) GetType() prmtps.MetricType { + m.m.RLock() + defer m.m.RUnlock() + + return m.t +} + +func (m *metrics) Collect(ctx context.Context) { + if m.f != nil { + m.f(ctx, m) + } +} + +func (m *metrics) Register(vec prmsdk.Collector) error { + m.m.Lock() + defer m.m.Unlock() + m.v = vec + prmsdk.Unregister(m.v) + return prmsdk.Register(m.v) +} + +func (m *metrics) UnRegister() bool { + m.m.Lock() + defer m.m.Unlock() + return prmsdk.Unregister(m.v) +} diff --git a/prometheus/metrics/types.go b/prometheus/metrics/types.go new file mode 100644 index 00000000..8a3b7c4d --- /dev/null +++ b/prometheus/metrics/types.go @@ -0,0 +1,95 @@ +/* + * 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 metrics + +func (m *metrics) SetDesc(desc string) { + m.m.Lock() + defer m.m.Unlock() + + m.d = desc +} + +func (m *metrics) GetDesc() string { + m.m.RLock() + defer m.m.RUnlock() + + return m.d +} + +func (m *metrics) AddLabel(label ...string) { + m.m.Lock() + defer m.m.Unlock() + + if len(m.l) < 1 { + m.l = make([]string, 0) + } + + m.l = append(m.l, label...) +} + +func (m *metrics) GetLabel() []string { + m.m.RLock() + defer m.m.RUnlock() + + return m.l +} + +func (m *metrics) AddBuckets(bucket ...float64) { + m.m.Lock() + defer m.m.Unlock() + + if len(m.b) < 1 { + m.b = make([]float64, 0) + } + + m.b = append(m.b, bucket...) +} + +func (m *metrics) GetBuckets() []float64 { + m.m.RLock() + defer m.m.RUnlock() + + return m.b +} + +func (m *metrics) AddObjective(key, value float64) { + m.m.Lock() + defer m.m.Unlock() + + if len(m.o) < 1 { + m.o = make(map[float64]float64, 0) + } + + m.o[key] = value +} + +func (m *metrics) GetObjectives() map[float64]float64 { + m.m.RLock() + defer m.m.RUnlock() + + return m.o +} diff --git a/prometheus/metrics/value.go b/prometheus/metrics/value.go new file mode 100644 index 00000000..7a8ef99a --- /dev/null +++ b/prometheus/metrics/value.go @@ -0,0 +1,129 @@ +/* + * 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 metrics + +import ( + "fmt" + + prmtps "github.com/nabbar/golib/prometheus/types" + prmsdk "github.com/prometheus/client_golang/prometheus" +) + +// SetGaugeValue set data for Gauge type ginMet. +func (m *metrics) SetGaugeValue(labelValues []string, value float64) error { + m.m.Lock() + defer m.m.Unlock() + + if m.t == prmtps.None { + return fmt.Errorf("metric '%s' not existed", m.n) + } + + if m.t != prmtps.Gauge { + return fmt.Errorf("metric '%s' not Gauge type", m.n) + } + + m.v.(*prmsdk.GaugeVec).WithLabelValues(labelValues...).Set(value) + return nil +} + +// Inc increases value for Counter/Gauge type metric, increments +// the counter by 1 +func (m *metrics) Inc(labelValues []string) error { + m.m.Lock() + defer m.m.Unlock() + + if m.t == prmtps.None { + return fmt.Errorf("metric '%s' not existed", m.n) + } + + if m.t != prmtps.Gauge && m.t != prmtps.Counter { + return fmt.Errorf("metric '%s' not Gauge or Counter type", m.n) + } + + switch m.t { + case prmtps.Counter: + m.v.(*prmsdk.CounterVec).WithLabelValues(labelValues...).Inc() + break + case prmtps.Gauge: + m.v.(*prmsdk.GaugeVec).WithLabelValues(labelValues...).Inc() + break + } + + return nil +} + +// Add adds the given value to the ginMet object. Only +// for Counter/Gauge type metric. +func (m *metrics) Add(labelValues []string, value float64) error { + m.m.Lock() + defer m.m.Unlock() + + if m.t == prmtps.None { + return fmt.Errorf("metric '%s' not existed", m.n) + } + + if m.t != prmtps.Gauge && m.t != prmtps.Counter { + return fmt.Errorf("metric '%s' not Gauge or Counter type", m.n) + } + + switch m.t { + case prmtps.Counter: + m.v.(*prmsdk.CounterVec).WithLabelValues(labelValues...).Add(value) + break + case prmtps.Gauge: + m.v.(*prmsdk.GaugeVec).WithLabelValues(labelValues...).Add(value) + break + } + + return nil +} + +// Observe is used by Histogram and Summary type metric to +// add observations. +func (m *metrics) Observe(labelValues []string, value float64) error { + m.m.Lock() + defer m.m.Unlock() + + if m.t == 0 { + return fmt.Errorf("metric '%s' not existed", m.n) + } + + if m.t != prmtps.Histogram && m.t != prmtps.Summary { + return fmt.Errorf("metric '%s' not Histogram or Summary type", m.n) + } + + switch m.t { + case prmtps.Histogram: + m.v.(*prmsdk.HistogramVec).WithLabelValues(labelValues...).Observe(value) + break + case prmtps.Summary: + m.v.(*prmsdk.SummaryVec).WithLabelValues(labelValues...).Observe(value) + break + } + + return nil +} diff --git a/prometheus/model.go b/prometheus/model.go new file mode 100644 index 00000000..cf518328 --- /dev/null +++ b/prometheus/model.go @@ -0,0 +1,116 @@ +/* + * 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 prometheus + +import ( + "net/http" + "strings" + "sync" + + prmpol "github.com/nabbar/golib/prometheus/pool" +) + +// GinPrometheus is an object that uses to set gin server monitor. +type prom struct { + m sync.RWMutex + + exclude []string + slowTime int32 + reqDuration []float64 + + ginMet prmpol.MetricPool + othMet prmpol.MetricPool + handle http.Handler +} + +// SetSlowTime set slowTime property. slowTime is used to determine whether +// the request is slow. For "gin_slow_request_total" metric. +func (m *prom) SetSlowTime(slowTime int32) { + m.m.Lock() + defer m.m.Unlock() + + m.slowTime = slowTime +} + +// GetSlowTime retrieve the slowTime property. slowTime is used to determine whether +// the request is slow. For "gin_slow_request_total" metric. +func (m *prom) GetSlowTime() int32 { + m.m.RLock() + defer m.m.RUnlock() + + return m.slowTime +} + +// SetDuration set duration property. duration is used to ginRequestDuration +// metric buckets. +func (m *prom) SetDuration(duration []float64) { + m.m.Lock() + defer m.m.Unlock() + + m.reqDuration = duration +} + +// GetDuration retrieve the duration property. duration is used to ginRequestDuration +// metric buckets. +func (m *prom) GetDuration() []float64 { + m.m.RLock() + defer m.m.RUnlock() + + return m.reqDuration +} + +func (m *prom) ExcludePath(startWith ...string) { + m.m.Lock() + defer m.m.Unlock() + + for _, p := range startWith { + if p != "" { + m.exclude = append(m.exclude, p) + } + } +} + +func (m *prom) isExclude(path string) bool { + m.m.RLock() + defer m.m.RUnlock() + + if len(m.exclude) < 1 { + return false + } + + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + + for _, p := range m.exclude { + if p != "" && strings.HasPrefix(path, p) { + return true + } + } + + return false +} diff --git a/prometheus/monitor.go b/prometheus/monitor.go deleted file mode 100644 index 08d33736..00000000 --- a/prometheus/monitor.go +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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 prometheus - -import ( - "strings" - "sync" - "sync/atomic" - "time" - - librtr "github.com/nabbar/golib/router" - - "github.com/gin-gonic/gin" - "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// GinPrometheus is an object that uses to set gin server monitor. -type monitor struct { - m sync.Mutex - - exclude []string - slowTime int32 - reqDuration []float64 - - metrics map[string]*atomic.Value -} - -// Expose adds metric path to a given router. -// The router can be different with the one passed to UseWithoutExposingEndpoint. -// This allows to expose metrics on different port. -func (m *monitor) Expose(c *gin.Context) { - promhttp.Handler().ServeHTTP(c.Writer, c.Request) -} - -// MiddleWare as gin monitor middleware. -func (m *monitor) MiddleWare(c *gin.Context) { - startTime := c.GetInt64(librtr.GinContextStartUnixNanoTime) - - if startTime == 0 { - startTime = time.Now().UnixNano() - } - - path := c.GetString(librtr.GinContextRequestPath) - if path == "" { - path = c.Request.URL.Path - if raw := c.Request.URL.RawQuery; len(raw) > 0 { - path += "?" + raw - } - } - - if len(m.exclude) > 0 { - r := c.Request.URL.Path - if !strings.HasPrefix(r, "/") { - r = "/" + r - } - for _, p := range m.exclude { - if p != "" && strings.HasPrefix(r, p) { - return - } - } - } - - // execute normal process. - c.Next() - - // after request - m.CollectMetrics(c, time.Unix(0, startTime)) -} - -func (m *monitor) CollectMetrics(c *gin.Context, start time.Time) { - for _, k := range m.ListMetric() { - metric := m.GetMetric(k) - metric.Collect(c, start) - _ = m.SetMetric(metric) - } -} - -func (m *monitor) ExcludePath(startWith ...string) { - m.m.Lock() - defer m.m.Unlock() - - for _, p := range startWith { - if p != "" { - m.exclude = append(m.exclude, p) - } - } -} - -// SetSlowTime set slowTime property. slowTime is used to determine whether -// the request is slow. For "gin_slow_request_total" metric. -func (m *monitor) SetSlowTime(slowTime int32) { - m.m.Lock() - defer m.m.Unlock() - - m.slowTime = slowTime -} - -// GetSlowTime retrieve the slowTime property. slowTime is used to determine whether -// the request is slow. For "gin_slow_request_total" metric. -func (m *monitor) GetSlowTime() int32 { - m.m.Lock() - defer m.m.Unlock() - - return m.slowTime -} - -// SetDuration set duration property. duration is used to ginRequestDuration -// metric buckets. -func (m *monitor) SetDuration(duration []float64) { - m.m.Lock() - defer m.m.Unlock() - - m.reqDuration = duration -} - -// GetDuration retrieve the duration property. duration is used to ginRequestDuration -// metric buckets. -func (m *monitor) GetDuration() []float64 { - m.m.Lock() - defer m.m.Unlock() - - return m.reqDuration -} - -// GetMetric used to get metric object by metric_name. -func (m *monitor) GetMetric(name string) *metrics { - m.m.Lock() - defer m.m.Unlock() - - if a, ok := m.metrics[name]; !ok || a == nil { - return &metrics{} - } else if i := a.Load(); i == nil { - return &metrics{} - } else if o, ok := i.(*metrics); !ok || o == nil { - return &metrics{} - } else { - return o - } -} - -// SetMetric used to store an atomic value of the metric object by metric_name. -func (m *monitor) SetMetric(metric Metrics) error { - m.m.Lock() - defer m.m.Unlock() - - var ( - ok bool - o *metrics - ) - - if o, ok = metric.(*metrics); !ok { - return errors.Errorf("metric is not a valid metric instance") - } - - if o.n == "" { - return errors.Errorf("metric name cannot be empty.") - } - - if o.f == nil { - return errors.Errorf("metric collect func cannot be empty.") - } - - if _, ok = m.metrics[o.n]; !ok { - if err := o.t.Register(o); err != nil { - return err - } - - prometheus.MustRegister(o.v) - } - - if m.metrics[o.n] == nil { - m.metrics[o.n] = new(atomic.Value) - } - - m.metrics[o.n].Store(metric) - - return nil -} - -// AddMetric add custom monitor metric. -func (m *monitor) AddMetric(metric Metrics) error { - return m.SetMetric(metric) -} - -// ListMetric retrieve a slice of metrics' name registered -func (m *monitor) ListMetric() []string { - var res = make([]string, 0) - - m.m.Lock() - defer m.m.Unlock() - - for k := range m.metrics { - res = append(res, k) - } - - return res -} diff --git a/prometheus/pool.go b/prometheus/pool.go new file mode 100644 index 00000000..324149b9 --- /dev/null +++ b/prometheus/pool.go @@ -0,0 +1,108 @@ +/* + * 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 prometheus + +import ( + "context" + + ginsdk "github.com/gin-gonic/gin" + libmet "github.com/nabbar/golib/prometheus/metrics" + prmpol "github.com/nabbar/golib/prometheus/pool" + libsem "github.com/nabbar/golib/semaphore" +) + +// GetMetric used to retrieve a metric instance by his name. +func (m *prom) GetMetric(name string) libmet.Metric { + if v := m.ginMet.Get(name); v != nil { + return v + } else if v = m.othMet.Get(name); v != nil { + return v + } + + return nil +} + +// AddMetric used to store the metric object to the API Metric Pool or Other Metric Pool. +func (m *prom) AddMetric(isAPI bool, metric libmet.Metric) error { + if isAPI { + return m.ginMet.Add(metric) + } else { + return m.othMet.Add(metric) + } +} + +// DelMetric used to unregister the metric and delete the instance from Pool. +func (m *prom) DelMetric(name string) { + m.ginMet.Del(name) + m.othMet.Del(name) +} + +// ListMetric retrieve a slice of metrics' name registered for all type, API or not. +func (m *prom) ListMetric() []string { + var res = make([]string, 0) + res = append(res, m.ginMet.List()...) + res = append(res, m.othMet.List()...) + return res +} + +func (m *prom) Collect(ctx context.Context) { + m.CollectMetrics(ctx) +} + +func (m *prom) CollectMetrics(ctx context.Context, name ...string) { + var ( + ok bool + s libsem.Sem + ) + + s = libsem.NewSemaphoreWithContext(ctx, 0) + defer s.DeferMain() + + if _, ok = ctx.(*ginsdk.Context); ok { + m.ginMet.Walk(m.runCollect(ctx, s), name...) + } else { + m.othMet.Walk(m.runCollect(ctx, s), name...) + } + + _ = s.WaitAll() +} + +func (m *prom) runCollect(ctx context.Context, sem libsem.Sem) prmpol.FuncWalk { + return func(pool prmpol.MetricPool, key string, val libmet.Metric) bool { + if e := sem.NewWorker(); e != nil { + return false + } + + go func() { + defer sem.DeferWorker() + val.Collect(ctx) + pool.Set(key, val) + }() + + return true + } +} diff --git a/prometheus/pool/interface.go b/prometheus/pool/interface.go new file mode 100644 index 00000000..92b5d201 --- /dev/null +++ b/prometheus/pool/interface.go @@ -0,0 +1,64 @@ +/* + * 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 pool + +import ( + libctx "github.com/nabbar/golib/context" + libmet "github.com/nabbar/golib/prometheus/metrics" +) + +type FuncGet func(name string) libmet.Metric +type FuncAdd func(metric libmet.Metric) error +type FuncSet func(key string, metric libmet.Metric) +type FuncWalk func(pool MetricPool, key string, val libmet.Metric) bool + +type MetricPool interface { + // Get is used to retrieve the metric instance from the pool. + Get(name string) libmet.Metric + + // Add is used to register the metric instance into the pool. + Add(metric libmet.Metric) error + + // Set is used to replace the metric instance into the pool. + Set(key string, metric libmet.Metric) + + // Del is used to remove the metric instance into the pool. + Del(key string) + + // List retrieve a slice of metrics' name registered into the pool. + List() []string + + // Walk run the given function for each stored item into the pool. + // If the function return false, the process is stopped. + Walk(fct FuncWalk, limit ...string) bool +} + +func New(ctx libctx.FuncContext) MetricPool { + return &pool{ + p: libctx.NewConfig[string](ctx), + } +} diff --git a/prometheus/pool/model.go b/prometheus/pool/model.go new file mode 100644 index 00000000..394ebc7c --- /dev/null +++ b/prometheus/pool/model.go @@ -0,0 +1,103 @@ +/* + * 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 pool + +import ( + "fmt" + + libctx "github.com/nabbar/golib/context" + libmet "github.com/nabbar/golib/prometheus/metrics" +) + +type pool struct { + p libctx.Config[string] +} + +func (p *pool) Get(name string) libmet.Metric { + if i, l := p.p.Load(name); !l { + return nil + } else if v, k := i.(libmet.Metric); !k { + return nil + } else { + return v + } +} + +func (p *pool) Add(metric libmet.Metric) error { + if metric.GetName() == "" { + return fmt.Errorf("metric name cannot be empty") + } + + if metric.GetCollect() == nil { + return fmt.Errorf("metric collect func cannot be empty") + } + + if v, e := metric.GetType().Register(metric); e != nil { + return e + } else if e = metric.Register(v); e != nil { + return e + } + + p.p.Store(metric.GetName(), metric) + return nil +} + +func (p *pool) Set(key string, metric libmet.Metric) { + p.p.Store(key, metric) +} + +func (p *pool) Del(key string) { + if i, l := p.p.LoadAndDelete(key); !l { + return + } else if m, k := i.(libmet.Metric); !k { + return + } else { + m.UnRegister() + } +} + +func (p *pool) List() []string { + var res = make([]string, 0) + + p.p.Walk(func(key string, val interface{}) bool { + res = append(res, key) + return true + }) + + return res +} + +func (p *pool) Walk(fct FuncWalk, limit ...string) bool { + f := func(key string, val interface{}) bool { + if v, k := val.(libmet.Metric); k { + return fct(p, key, v) + } + return true + } + + return p.p.WalkLimit(f, limit...) +} diff --git a/prometheus/route.go b/prometheus/route.go new file mode 100644 index 00000000..c36dec10 --- /dev/null +++ b/prometheus/route.go @@ -0,0 +1,118 @@ +/* + * 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 prometheus + +import ( + "context" + "fmt" + "net/http" + "time" + + ginsdk "github.com/gin-gonic/gin" + librtr "github.com/nabbar/golib/router" + sdkpht "github.com/prometheus/client_golang/prometheus/promhttp" +) + +func (m *prom) getHandler() http.Handler { + m.m.RLock() + defer m.m.RUnlock() + + if m.handle != nil { + return m.handle + } + + return nil +} + +func (m *prom) setHandler() { + m.m.Lock() + defer m.m.Unlock() + + m.handle = sdkpht.Handler() +} + +// Expose adds metric path to a given router. +// The router can be different with the one passed to UseWithoutExposingEndpoint. +// This allows to expose ginMet on different port. +func (m *prom) Expose(ctx context.Context) { + if c, ok := ctx.(*ginsdk.Context); ok { + m.ExposeGin(c) + } +} + +// ExposeGin is like Expose but dedicated to gin context struct. +func (m *prom) ExposeGin(c *ginsdk.Context) { + if h := m.getHandler(); h != nil { + h.ServeHTTP(c.Writer, c.Request) + return + } + + m.setHandler() + if h := m.getHandler(); h != nil { + h.ServeHTTP(c.Writer, c.Request) + return + } + + _ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("cannot initiate prometheus handler")) + return +} + +// GinHandlerFunc adds metric path to a given router. +// The router can be different with the one passed to UseWithoutExposingEndpoint. +// This allows to expose ginMet on different port. +func (m *prom) MiddleWareGin(c *ginsdk.Context) { + if c.GetInt64(librtr.GinContextStartUnixNanoTime) == 0 { + c.Set(librtr.GinContextStartUnixNanoTime, time.Now().UnixNano()) + } + + path := c.GetString(librtr.GinContextRequestPath) + if path == "" { + path = c.Request.URL.Path + if raw := c.Request.URL.RawQuery; len(raw) > 0 { + path += "?" + raw + } + } + + if m.isExclude(c.Request.URL.Path) { + return + } + + // execute normal process. + c.Next() + + // after request + m.Collect(c) +} + +// MiddleWare as gin monitor middleware. +func (m *prom) MiddleWare(ctx context.Context) { + if c, ok := ctx.(*ginsdk.Context); !ok { + return + } else { + m.MiddleWareGin(c) + } +} diff --git a/prometheus/types.go b/prometheus/types.go deleted file mode 100644 index eba5dee0..00000000 --- a/prometheus/types.go +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 prometheus - -import ( - "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" -) - -type MetricType int - -const ( - None MetricType = iota - Counter - Gauge - Histogram - Summary -) - -func (m MetricType) Register(metric *metrics) error { - switch metric.t { - case Counter: - return m.counterHandler(metric) - case Gauge: - return m.gaugeHandler(metric) - case Histogram: - return m.histogramHandler(metric) - case Summary: - return m.summaryHandler(metric) - } - - return errors.Errorf("metric type is not compatible.") -} - -func (m MetricType) counterHandler(metric *metrics) error { - metric.v = prometheus.NewCounterVec( - prometheus.CounterOpts{Name: metric.n, Help: metric.d}, - metric.l, - ) - return nil -} - -func (m MetricType) gaugeHandler(metric *metrics) error { - metric.v = prometheus.NewGaugeVec( - prometheus.GaugeOpts{Name: metric.n, Help: metric.d}, - metric.l, - ) - return nil -} - -func (m MetricType) histogramHandler(metric *metrics) error { - if len(metric.b) == 0 { - return errors.Errorf("metric '%s' is histogram type, cannot lose bucket param.", metric.n) - } - metric.v = prometheus.NewHistogramVec( - prometheus.HistogramOpts{Name: metric.n, Help: metric.d, Buckets: metric.b}, - metric.l, - ) - return nil -} - -func (m MetricType) summaryHandler(metric *metrics) error { - if len(metric.o) == 0 { - return errors.Errorf("metric '%s' is summary type, cannot lose objectives param.", metric.n) - } - prometheus.NewSummaryVec( - prometheus.SummaryOpts{Name: metric.n, Help: metric.d, Objectives: metric.o}, - metric.l, - ) - return nil -} diff --git a/prometheus/types/metrics.go b/prometheus/types/metrics.go new file mode 100644 index 00000000..806b1109 --- /dev/null +++ b/prometheus/types/metrics.go @@ -0,0 +1,36 @@ +/* + * 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 types + +type Metric interface { + GetName() string + GetType() MetricType + GetDesc() string + GetLabel() []string + GetBuckets() []float64 + GetObjectives() map[float64]float64 +} diff --git a/prometheus/types/types.go b/prometheus/types/types.go new file mode 100644 index 00000000..105e890f --- /dev/null +++ b/prometheus/types/types.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 types + +import ( + "fmt" + + prmsdk "github.com/prometheus/client_golang/prometheus" +) + +type MetricType int + +const ( + None MetricType = iota + Counter + Gauge + Histogram + Summary +) + +func (m MetricType) Register(met Metric) (prmsdk.Collector, error) { + switch met.GetType() { + case Counter: + return m.counterHandler(met) + case Gauge: + return m.gaugeHandler(met) + case Histogram: + return m.histogramHandler(met) + case Summary: + return m.summaryHandler(met) + } + + return nil, fmt.Errorf("metric type is not compatible") +} + +func (m MetricType) counterHandler(met Metric) (prmsdk.Collector, error) { + return prmsdk.NewCounterVec( + prmsdk.CounterOpts{Name: met.GetName(), Help: met.GetDesc()}, + met.GetLabel(), + ), nil +} + +func (m MetricType) gaugeHandler(met Metric) (prmsdk.Collector, error) { + return prmsdk.NewGaugeVec( + prmsdk.GaugeOpts{Name: met.GetName(), Help: met.GetDesc()}, + met.GetLabel(), + ), nil +} + +func (m MetricType) histogramHandler(met Metric) (prmsdk.Collector, error) { + if len(met.GetBuckets()) == 0 { + return nil, fmt.Errorf("metric '%s' is histogram type, cannot lose bucket param", met.GetName()) + } + + return prmsdk.NewHistogramVec( + prmsdk.HistogramOpts{Name: met.GetName(), Help: met.GetDesc(), Buckets: met.GetBuckets()}, + met.GetLabel(), + ), nil +} + +func (m MetricType) summaryHandler(met Metric) (prmsdk.Collector, error) { + if len(met.GetObjectives()) == 0 { + return nil, fmt.Errorf("metric '%s' is summary type, cannot lose objectives param", met.GetName()) + } + + return prmsdk.NewSummaryVec( + prmsdk.SummaryOpts{Name: met.GetName(), Help: met.GetDesc(), Objectives: met.GetObjectives()}, + met.GetLabel(), + ), nil +} diff --git a/prometheus/webmetrics/requestBody.go b/prometheus/webmetrics/requestBody.go new file mode 100644 index 00000000..3f2e4aec --- /dev/null +++ b/prometheus/webmetrics/requestBody.go @@ -0,0 +1,59 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "context" + + ginsdk "github.com/gin-gonic/gin" + prmmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" +) + +func MetricRequestBody(prefixName string) prmmet.Metric { + var met prmmet.Metric + + met = prmmet.NewMetrics(getDefaultPrefix(prefixName, "request_body_total"), prmtps.Counter) + met.SetDesc("the server received request body size, unit byte") + met.SetCollect(func(ctx context.Context, m prmmet.Metric) { + var ( + c *ginsdk.Context + ok bool + ) + + if c, ok = ctx.(*ginsdk.Context); !ok { + return + } + + if c.Request.ContentLength >= 0 { + _ = m.Add(nil, float64(c.Request.ContentLength)) + } + }) + + return met +} diff --git a/prometheus/webmetrics/requestIPTotal.go b/prometheus/webmetrics/requestIPTotal.go new file mode 100644 index 00000000..843b971e --- /dev/null +++ b/prometheus/webmetrics/requestIPTotal.go @@ -0,0 +1,62 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "context" + + ginsdk "github.com/gin-gonic/gin" + prmmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" +) + +func MetricRequestIPTotal(prefixName string) prmmet.Metric { + var met prmmet.Metric + + met = prmmet.NewMetrics(getDefaultPrefix(prefixName, "request_ip_total"), prmtps.Counter) + met.SetDesc("all the server received ip num.") + met.SetCollect(func(ctx context.Context, m prmmet.Metric) { + var ( + c *ginsdk.Context + ok bool + ) + + if c, ok = ctx.(*ginsdk.Context); !ok { + return + } + + clientIP := c.ClientIP() + + if !DefaultBloom.Contains(m.GetName(), clientIP) { + DefaultBloom.Add(m.GetName(), clientIP) + _ = m.Inc(nil) + } + }) + + return met +} diff --git a/prometheus/webmetrics/requestLatency.go b/prometheus/webmetrics/requestLatency.go new file mode 100644 index 00000000..4269d998 --- /dev/null +++ b/prometheus/webmetrics/requestLatency.go @@ -0,0 +1,79 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "context" + "time" + + ginsdk "github.com/gin-gonic/gin" + libprm "github.com/nabbar/golib/prometheus" + prmmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" + librtr "github.com/nabbar/golib/router" +) + +func MetricRequestLatency(prefixName string, fct libprm.FuncGetPrometheus) prmmet.Metric { + var ( + met prmmet.Metric + prm libprm.Prometheus + ) + + if fct == nil { + return nil + } else if prm = fct(); prm == nil { + return nil + } + + met = prmmet.NewMetrics(getDefaultPrefix(prefixName, "request_duration"), prmtps.Histogram) + met.SetDesc("the time server took to handle the request.") + met.AddLabel("uri") + met.AddBuckets(prm.GetDuration()...) + met.SetCollect(func(ctx context.Context, m prmmet.Metric) { + var ( + c *ginsdk.Context + ok bool + ) + + if c, ok = ctx.(*ginsdk.Context); !ok { + return + } else if start := c.GetInt64(librtr.GinContextStartUnixNanoTime); start == 0 { + return + } else if ts := time.Unix(0, start); ts.IsZero() { + return + } else { + _ = m.Observe([]string{c.FullPath()}, time.Since(ts).Seconds()) + } + }) + + return met +} + +func collectRequestLatency(ctx context.Context, m prmmet.Metric) { + +} diff --git a/prometheus/webmetrics/requestSlow.go b/prometheus/webmetrics/requestSlow.go new file mode 100644 index 00000000..3ae3cb00 --- /dev/null +++ b/prometheus/webmetrics/requestSlow.go @@ -0,0 +1,81 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "context" + "fmt" + "strconv" + "time" + + ginsdk "github.com/gin-gonic/gin" + libprm "github.com/nabbar/golib/prometheus" + prmmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" + librtr "github.com/nabbar/golib/router" +) + +func MetricRequestSlow(prefixName string, fct libprm.FuncGetPrometheus) prmmet.Metric { + var ( + met prmmet.Metric + prm libprm.Prometheus + ) + + if fct == nil { + return nil + } else if prm = fct(); prm == nil { + return nil + } + + met = prmmet.NewMetrics(getDefaultPrefix(prefixName, "request_slow_total"), prmtps.Histogram) + met.SetDesc(fmt.Sprintf("the server handled slow requests counter, t=%d.", prm.GetSlowTime())) + met.AddLabel("uri", "method", "code") + met.AddBuckets(prm.GetDuration()...) + met.SetCollect(func(ctx context.Context, m prmmet.Metric) { + var ( + c *ginsdk.Context + ok bool + ) + + if c, ok = ctx.(*ginsdk.Context); !ok { + return + } else if fct == nil { + return + } else if prm = fct(); prm == nil { + return + } else if start := c.GetInt64(librtr.GinContextStartUnixNanoTime); start == 0 { + return + } else if ts := time.Unix(0, start); ts.IsZero() { + return + } else if int32(time.Since(ts).Seconds()) > prm.GetSlowTime() { + _ = m.Inc([]string{c.FullPath(), c.Request.Method, strconv.Itoa(c.Writer.Status())}) + } + }) + + return met +} diff --git a/prometheus/webmetrics/requestTotal.go b/prometheus/webmetrics/requestTotal.go new file mode 100644 index 00000000..ca8ccaf7 --- /dev/null +++ b/prometheus/webmetrics/requestTotal.go @@ -0,0 +1,47 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "context" + + prmmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" +) + +func MetricRequestTotal(prefixName string) prmmet.Metric { + var met prmmet.Metric + + met = prmmet.NewMetrics(getDefaultPrefix(prefixName, "request_total"), prmtps.Counter) + met.SetDesc("all the server received request num.") + met.SetCollect(func(ctx context.Context, m prmmet.Metric) { + _ = m.Inc(nil) + }) + + return met +} diff --git a/prometheus/webmetrics/requestURITotal.go b/prometheus/webmetrics/requestURITotal.go new file mode 100644 index 00000000..4ac077e7 --- /dev/null +++ b/prometheus/webmetrics/requestURITotal.go @@ -0,0 +1,59 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "context" + "strconv" + + ginsdk "github.com/gin-gonic/gin" + prmmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" +) + +func MetricRequestURITotal(prefixName string) prmmet.Metric { + var met prmmet.Metric + + met = prmmet.NewMetrics(getDefaultPrefix(prefixName, "request_uri_total"), prmtps.Counter) + met.SetDesc("all the server received request num with every uri.") + met.AddLabel("uri", "method", "code") + met.SetCollect(func(ctx context.Context, m prmmet.Metric) { + var ( + c *ginsdk.Context + ok bool + ) + + if c, ok = ctx.(*ginsdk.Context); !ok { + return + } + + _ = m.Inc([]string{c.FullPath(), c.Request.Method, strconv.Itoa(c.Writer.Status())}) + }) + + return met +} diff --git a/prometheus/webmetrics/responseBody.go b/prometheus/webmetrics/responseBody.go new file mode 100644 index 00000000..307af472 --- /dev/null +++ b/prometheus/webmetrics/responseBody.go @@ -0,0 +1,63 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "context" + + ginsdk "github.com/gin-gonic/gin" + prmmet "github.com/nabbar/golib/prometheus/metrics" + prmtps "github.com/nabbar/golib/prometheus/types" +) + +func MetricResponseBody(prefixName string) prmmet.Metric { + var met prmmet.Metric + + met = prmmet.NewMetrics(getDefaultPrefix(prefixName, "response_body_total"), prmtps.Counter) + met.SetDesc("the server send response body size, unit byte") + met.SetCollect(func(ctx context.Context, m prmmet.Metric) { + var ( + c *ginsdk.Context + ok bool + ) + + if c, ok = ctx.(*ginsdk.Context); !ok { + return + } + + if c.Writer.Size() > 0 { + _ = m.Add(nil, float64(c.Writer.Size())) + } + }) + + return met +} + +func collectResponseBody(ctx context.Context, m prmmet.Metric) { + +} diff --git a/prometheus/webmetrics/tools.go b/prometheus/webmetrics/tools.go new file mode 100644 index 00000000..64d1c2f3 --- /dev/null +++ b/prometheus/webmetrics/tools.go @@ -0,0 +1,46 @@ +/*********************************************************************************************************************** + * + * 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 webmetrics + +import ( + "fmt" + + prmblm "github.com/nabbar/golib/prometheus/bloom" +) + +const defaultGinPrefix = "gin" + +var DefaultBloom = prmblm.New() + +func getDefaultPrefix(pfx, name string) string { + if len(pfx) < 1 { + pfx = "gin" + } + + return fmt.Sprintf("%s_%s", pfx, name) +} diff --git a/request/auth.go b/request/auth.go new file mode 100644 index 00000000..480809eb --- /dev/null +++ b/request/auth.go @@ -0,0 +1,37 @@ +/* + * 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 request + +import "encoding/base64" + +func (r *request) AuthBearer(token string) { + r.SetHeader(_Authorization, _AuthorizationBearer+" "+token) +} + +func (r *request) AuthBasic(user, pass string) { + r.SetHeader(_Authorization, _AuthorizationBasic+" "+base64.StdEncoding.EncodeToString([]byte(user+":"+pass))) +} diff --git a/request/body.go b/request/body.go new file mode 100644 index 00000000..819c49f0 --- /dev/null +++ b/request/body.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 request + +import ( + "bytes" + "encoding/json" + "io" +) + +const contentTypeJson = "application/json" + +func (r *request) BodyJson(body interface{}) error { + if p, e := json.Marshal(body); e != nil { + return e + } else { + r._BodyReader(bytes.NewBuffer(p)) + } + + r.ContentType(contentTypeJson) + return nil +} + +func (r *request) BodyReader(body io.Reader, contentType string) { + r._BodyReader(body) + + if contentType != "" { + r.ContentType(contentType) + } +} + +func (r *request) _BodyReader(body io.Reader) { + r.s.Lock() + defer r.s.Unlock() + + r.b = body +} diff --git a/request/header.go b/request/header.go new file mode 100644 index 00000000..0b32d36a --- /dev/null +++ b/request/header.go @@ -0,0 +1,69 @@ +/* + * 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 request + +import "net/url" + +func (r *request) ContentType(mime string) { + r.SetHeader(_ContentType, mime) +} + +func (r *request) CleanHeader() { + r.s.Lock() + defer r.s.Unlock() + + r.h = make(url.Values) +} + +func (r *request) DelHeader(key string) { + r.s.Lock() + defer r.s.Unlock() + + r.h.Del(key) +} + +func (r *request) SetHeader(key, value string) { + r.s.Lock() + defer r.s.Unlock() + + if len(r.h) < 1 { + r.h = make(url.Values) + } + + r.h.Set(key, value) +} + +func (r *request) AddHeader(key, value string) { + r.s.Lock() + defer r.s.Unlock() + + if len(r.h) < 1 { + r.h = make(url.Values) + } + + r.h.Add(key, value) +} diff --git a/request/interface.go b/request/interface.go index 4f4e6801..f44156c6 100644 --- a/request/interface.go +++ b/request/interface.go @@ -28,21 +28,23 @@ package request import ( "bytes" + "context" "io" "net/http" "net/url" "sync" + liblog "github.com/nabbar/golib/logger" + + montps "github.com/nabbar/golib/monitor/types" + libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" + libver "github.com/nabbar/golib/version" ) -type FctHttpClient func(def libtls.TLSConfig, servername string) *http.Client -type FctTLSDefault func() libtls.TLSConfig - -type RequestError interface { +type Error interface { StatusCode() int Status() string Body() *bytes.Buffer @@ -55,15 +57,7 @@ type RequestError interface { ParseBody(i interface{}) bool } -type Request interface { - Clone() (Request, error) - New() (Request, error) - - GetOption() *Options - SetOption(opt *Options) error - SetClient(fct FctHttpClient) - SetContext(fct libcfg.FuncContext) - +type Url interface { SetEndpoint(u string) error GetEndpoint() string @@ -80,30 +74,52 @@ type Request interface { GetFullUrl() *url.URL SetFullUrl(u *url.URL) +} +type Authorization interface { AuthBearer(token string) AuthBasic(user, pass string) - ContentType(content string) +} - CleanHeader() - DelHeader(key string) +type Header interface { SetHeader(key, value string) AddHeader(key, value string) + DelHeader(key string) + + CleanHeader() + ContentType(mime string) +} +type Body interface { BodyJson(body interface{}) error BodyReader(body io.Reader, contentType string) +} + +type Request interface { + Url + Authorization + Header + Body - Error() RequestError + Clone() (Request, error) + New() (Request, error) + + GetOption() *Options + SetOption(opt *Options) error + RegisterHTTPClient(fct libtls.FctHttpClient) + RegisterDefaultLogger(fct liblog.FuncLog) + RegisterContext(fct libctx.FuncContext) + + Error() Error IsError() bool Do() (*http.Response, liberr.Error) DoParse(model interface{}, validStatus ...int) liberr.Error - StatusRegister(sts libsts.RouteStatus, prefix string) - StatusRegisterInfo(fct libsts.FctInfo) + Monitor(ctx context.Context, vrs libver.Version) (montps.Monitor, error) } -func New(ctx libcfg.FuncContext, cli FctHttpClient, opt Options) (Request, error) { +func New(ctx libctx.FuncContext, opt *Options) (Request, error) { r := &request{ s: sync.Mutex{}, o: nil, @@ -117,9 +133,7 @@ func New(ctx libcfg.FuncContext, cli FctHttpClient, opt Options) (Request, error e: nil, } - r.SetClient(cli) - - if e := r.SetOption(&opt); e != nil { + if e := r.SetOption(opt); e != nil { return nil, e } else { return r, nil diff --git a/request/model.go b/request/model.go index c4ca64e5..bcbee62b 100644 --- a/request/model.go +++ b/request/model.go @@ -27,24 +27,17 @@ package request import ( - "bytes" "context" - "encoding/base64" - "encoding/json" - "fmt" "io" "net/http" "net/url" - "path" - "runtime" - "strings" "sync" "sync/atomic" + liblog "github.com/nabbar/golib/logger" + libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" - liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" + libctx "github.com/nabbar/golib/context" ) const ( @@ -57,253 +50,16 @@ const ( type request struct { s sync.Mutex - o *atomic.Value // Options - x libcfg.FuncContext // Context function - f FctHttpClient // Http client func - u *url.URL // endpoint url - h url.Values // header values - p url.Values // parameters values - b io.Reader // body io reader - m string // method - i libsts.FctInfo // Status Info func - e *requestError // Error pointer -} - -func (r *request) _StatusInfo() (name string, release string, build string) { - edp := r.GetFullUrl().Hostname() - - r.s.Lock() - defer r.s.Unlock() - - if r.i != nil { - name, release, build = r.i() - } - - if name == "" { - name = fmt.Sprintf("%s", edp) - } - - if release == "" { - release = runtime.Version()[2:] - } - - name = fmt.Sprintf("[%s] %s", edp, name) - - return name, release, build -} - -func (r *request) _StatusHealth() error { - opts := r.GetOption() - ednp := r.GetFullUrl() - - r.s.Lock() - defer r.s.Unlock() - - head := make(url.Values, 0) - if v := r.h.Get(_Authorization); v != "" { - head.Set(_Authorization, v) - } - - if !opts.Health.Enable { - return nil - } - - if opts.Health.Endpoint != "" { - if u, e := url.Parse(opts.Health.Endpoint); e == nil { - ednp = u - } - } - - if opts.Health.Auth.Basic.Enable { - head.Set(_Authorization, _AuthorizationBasic+" "+base64.StdEncoding.EncodeToString([]byte(opts.Health.Auth.Basic.Username+":"+opts.Health.Auth.Basic.Password))) - } else if opts.Health.Auth.Bearer.Enable { - head.Set(_Authorization, _AuthorizationBearer+" "+opts.Health.Auth.Bearer.Token) - } - - var ( - e error - - err liberr.Error - buf *bytes.Buffer - req *http.Request - rsp *http.Response - ) - - req, err = r._MakeRequest(ednp, http.MethodGet, nil, head, nil) - - if err != nil { - return err - } - - rsp, e = r._GetClient().Do(req) - - if e != nil { - return ErrorSendRequest.ErrorParent(e) - } - - if buf, err = r._CheckResponse(rsp); err != nil { - return err - } - - if len(opts.Health.Result.ValidHTTPCode) > 0 { - if !r._IsValidCode(opts.Health.Result.ValidHTTPCode, rsp.StatusCode) { - return ErrorResponseStatus.ErrorParent(fmt.Errorf("status: %s", rsp.Status)) - } - } else if len(opts.Health.Result.InvalidHTTPCode) > 0 { - if r._IsValidCode(opts.Health.Result.InvalidHTTPCode, rsp.StatusCode) { - return ErrorResponseStatus.ErrorParent(fmt.Errorf("status: %s", rsp.Status)) - } - } - - if len(opts.Health.Result.Contain) > 0 { - if !r._IsValidContents(opts.Health.Result.Contain, buf) { - return ErrorResponseContainsNotFound.Error(nil) - } - } else if len(opts.Health.Result.NotContain) > 0 { - if r._IsValidContents(opts.Health.Result.NotContain, buf) { - return ErrorResponseNotContainsFound.Error(nil) - } - } - - return nil -} - -func (r *request) _GetContext() context.Context { - if r.x != nil { - if x := r.x(); x != nil { - return x - } - } - - return context.Background() -} - -func (r *request) _GetOption() *Options { - if r.o == nil { - return nil - } else if i := r.o.Load(); i == nil { - return nil - } else if o, ok := i.(*Options); !ok { - return nil - } else { - return o - } -} - -func (r *request) _GetDefaultTLS() libtls.TLSConfig { - if cfg := r._GetOption(); cfg != nil { - return cfg._GetDefaultTLS() - } - - return nil -} - -func (r *request) _GetClient() *http.Client { - var h string - - if r.u != nil { - h = r.u.Hostname() - } - - if r.f != nil { - if c := r.f(r._GetDefaultTLS(), h); c != nil { - return c - } - } - - if cfg := r._GetOption(); cfg != nil { - return cfg.GetClientHTTP(h) - } - - return &http.Client{} -} - -func (r *request) _MakeRequest(u *url.URL, mtd string, body io.Reader, head url.Values, params url.Values) (*http.Request, liberr.Error) { - var ( - req *http.Request - err error - ) - - req, err = http.NewRequestWithContext(r._GetContext(), mtd, u.String(), body) - - if err != nil { - return nil, ErrorCreateRequest.ErrorParent(err) - } - - if len(head) > 0 { - for k := range head { - req.Header.Set(k, head.Get(k)) - } - } - - if len(params) > 0 { - q := req.URL.Query() - for k := range params { - q.Add(k, params.Get(k)) - } - req.URL.RawQuery = q.Encode() - } - - return req, nil -} - -func (r *request) _CheckResponse(rsp *http.Response, validStatus ...int) (*bytes.Buffer, liberr.Error) { - var ( - e error - b = bytes.NewBuffer(make([]byte, 0)) - ) - - defer func() { - if rsp != nil && !rsp.Close && rsp.Body != nil { - _ = rsp.Body.Close() - } - }() - - if rsp == nil { - return b, ErrorResponseInvalid.Error(nil) - } - - if rsp.Body != nil { - if _, e = io.Copy(b, rsp.Body); e != nil { - return b, ErrorResponseLoadBody.ErrorParent(e) - } - } - - if !r._IsValidCode(validStatus, rsp.StatusCode) { - return b, ErrorResponseStatus.Error(nil) - } - - return b, nil -} - -func (r *request) _IsValidCode(listValid []int, statusCode int) bool { - if len(listValid) < 1 { - return true - } - - for _, c := range listValid { - if c == statusCode { - return true - } - } - - return false -} - -func (r *request) _IsValidContents(contains []string, buf *bytes.Buffer) bool { - if len(contains) < 1 { - return true - } else if buf.Len() < 1 { - return false - } - - for _, c := range contains { - if strings.Contains(buf.String(), c) { - return true - } - } - - return false + o *atomic.Value // Options + x libctx.FuncContext // Context function + f libtls.FctHttpClient // Http client func + l liblog.FuncLog // Default logger + u *url.URL // endpoint url + h url.Values // header values + p url.Values // parameters values + b io.Reader // body io reader + m string // method + e *requestError // Error pointer } func (r *request) Clone() (Request, error) { @@ -333,14 +89,14 @@ func (r *request) New() (Request, error) { var ( n *request - c = r._GetOption() + c = r.options() ) if c == nil { c = &Options{} } - if i, e := New(r.x, r.f, *c); e != nil { + if i, e := New(r.x, c); e != nil { return nil, e } else { n = i.(*request) @@ -361,280 +117,52 @@ func (r *request) New() (Request, error) { } } - return n, nil -} - -func (r *request) GetOption() *Options { - r.s.Lock() - defer r.s.Unlock() - - return r._GetOption() -} - -func (r *request) SetOption(opt *Options) error { - if e := r.SetEndpoint(opt.Endpoint); e != nil { - return e - } - - if opt.Auth.Basic.Enable { - r.AuthBasic(opt.Auth.Basic.Username, opt.Auth.Basic.Password) - } else if opt.Auth.Bearer.Enable { - r.AuthBearer(opt.Auth.Bearer.Token) - } - - r.s.Lock() - defer r.s.Unlock() - - if r.o == nil { - r.o = new(atomic.Value) + if r.l != nil { + n.l = r.l } - r.o.Store(opt) - return nil -} - -func (r *request) SetClient(fct FctHttpClient) { - r.s.Lock() - defer r.s.Unlock() - - r.f = fct -} - -func (r *request) SetContext(fct libcfg.FuncContext) { - r.s.Lock() - defer r.s.Unlock() - - r.x = fct -} - -func (r *request) SetEndpoint(u string) error { - if uri, err := url.Parse(u); err != nil { - return err - } else { - r.s.Lock() - defer r.s.Unlock() - - r.u = uri - return nil + if r.f != nil { + n.f = r.f } -} - -func (r *request) GetEndpoint() string { - r.s.Lock() - defer r.s.Unlock() - return r.u.String() -} - -func (r *request) SetPath(raw bool, path string) { - r.s.Lock() - defer r.s.Unlock() - - if raw { - r.u.RawPath = path - } else { - r.u.Path = path - } + return n, nil } -func (r *request) AddPath(raw bool, pathPart ...string) { - r.s.Lock() - defer r.s.Unlock() - - if r.u == nil { - return - } - - var str string - if raw { - str = path.Clean(r.u.RawPath) - } else { - str = path.Clean(r.u.Path) - } - - for i := range pathPart { - if strings.HasSuffix(str, "/") && strings.HasPrefix(pathPart[i], "/") { - pathPart[i] = strings.TrimPrefix(pathPart[i], "/") - } - - if strings.HasSuffix(pathPart[i], "/") { - pathPart[i] = strings.TrimSuffix(pathPart[i], "/") +func (r *request) context() context.Context { + if r.x != nil { + if x := r.x(); x != nil { + return x } - - str = path.Join(str, pathPart[i]) - } - - if raw { - r.u.RawPath = path.Clean(str) - } else { - r.u.Path = path.Clean(str) - } -} - -func (r *request) SetMethod(method string) { - r.s.Lock() - defer r.s.Unlock() - - switch strings.ToUpper(method) { - case http.MethodGet: - r.m = http.MethodGet - case http.MethodHead: - r.m = http.MethodHead - case http.MethodPost: - r.m = http.MethodPost - case http.MethodPut: - r.m = http.MethodPut - case http.MethodPatch: - r.m = http.MethodPatch - case http.MethodDelete: - r.m = http.MethodDelete - case http.MethodConnect: - r.m = http.MethodConnect - case http.MethodOptions: - r.m = http.MethodOptions - case http.MethodTrace: - r.m = http.MethodTrace - default: - r.m = strings.ToUpper(method) - } - - if r.m == "" { - r.m = http.MethodGet - } -} - -func (r *request) GetMethod() string { - r.s.Lock() - defer r.s.Unlock() - - return r.m -} - -func (r *request) CleanParams() { - r.s.Lock() - defer r.s.Unlock() - - r.p = make(url.Values) -} - -func (r *request) DelParams(key string) { - r.s.Lock() - defer r.s.Unlock() - - r.p.Del(key) -} - -func (r *request) SetParams(key, val string) { - r.s.Lock() - defer r.s.Unlock() - - if len(r.p) < 1 { - r.p = make(url.Values) - } - - r.p.Set(key, val) -} - -func (r *request) AddParams(key, val string) { - r.s.Lock() - defer r.s.Unlock() - - if len(r.p) < 1 { - r.p = make(url.Values) - } - - r.p.Set(key, val) -} - -func (r *request) GetFullUrl() *url.URL { - r.s.Lock() - defer r.s.Unlock() - - return r.u -} - -func (r *request) SetFullUrl(u *url.URL) { - r.s.Lock() - defer r.s.Unlock() - - r.u = u -} - -func (r *request) AuthBearer(token string) { - r.SetHeader(_Authorization, _AuthorizationBearer+" "+token) -} - -func (r *request) AuthBasic(user, pass string) { - r.SetHeader(_Authorization, _AuthorizationBasic+" "+base64.StdEncoding.EncodeToString([]byte(user+":"+pass))) -} - -func (r *request) ContentType(content string) { - r.SetHeader(_ContentType, content) -} - -func (r *request) CleanHeader() { - r.s.Lock() - defer r.s.Unlock() - - r.h = make(url.Values) -} - -func (r *request) DelHeader(key string) { - r.s.Lock() - defer r.s.Unlock() - - r.h.Del(key) -} - -func (r *request) SetHeader(key, value string) { - r.s.Lock() - defer r.s.Unlock() - - if len(r.h) < 1 { - r.h = make(url.Values) } - r.h.Set(key, value) + return context.Background() } -func (r *request) AddHeader(key, value string) { - r.s.Lock() - defer r.s.Unlock() +func (r *request) client() *http.Client { + var h string - if len(r.h) < 1 { - r.h = make(url.Values) + if r.u != nil { + h = r.u.Hostname() } - r.h.Add(key, value) -} - -func (r *request) BodyJson(body interface{}) error { - if p, e := json.Marshal(body); e != nil { - return e + if r.f == nil { + return r.clientDefault(h) + } else if c := r.f(r.defaultTLS(), h); c != nil { + return c } else { - r._BodyReader(bytes.NewBuffer(p)) + return r.clientDefault(h) } - - r.ContentType("application/json") - return nil } -func (r *request) BodyReader(body io.Reader, contentType string) { - r._BodyReader(body) - - if contentType != "" { - r.ContentType(contentType) +func (r *request) clientDefault(h string) *http.Client { + if cfg := r.options(); cfg != nil { + return cfg.ClientHTTP(h) + } else { + return &http.Client{} } } -func (r *request) _BodyReader(body io.Reader) { - r.s.Lock() - defer r.s.Unlock() - - r.b = body -} - -func (r *request) Error() RequestError { +func (r *request) Error() Error { r.s.Lock() defer r.s.Unlock() @@ -647,113 +175,3 @@ func (r *request) IsError() bool { return r.e != nil && r.e.IsError() } - -func (r *request) Do() (*http.Response, liberr.Error) { - r.s.Lock() - defer r.s.Unlock() - - if r.m == "" || r.u == nil || r.u.String() == "" { - return nil, ErrorParamInvalid.Error(nil) - } - - var ( - e error - req *http.Request - rsp *http.Response - err liberr.Error - ) - - r.e = &requestError{ - c: 0, - s: "", - se: false, - b: bytes.NewBuffer(make([]byte, 0)), - be: false, - e: nil, - } - - req, err = r._MakeRequest(r.u, r.m, r.b, r.h, r.p) - if err != nil { - r.e.e = err - return nil, err - } - - rsp, e = r._GetClient().Do(req) - - if e != nil { - r.e.e = e - return nil, ErrorSendRequest.ErrorParent(e) - } - - return rsp, nil -} - -func (r *request) DoParse(model interface{}, validStatus ...int) liberr.Error { - var ( - e error - b = bytes.NewBuffer(make([]byte, 0)) - - err liberr.Error - rsp *http.Response - ) - - r.e = &requestError{ - c: 0, - s: "", - se: false, - b: bytes.NewBuffer(make([]byte, 0)), - be: false, - e: nil, - } - - if rsp, err = r.Do(); err != nil { - return err - } else if rsp == nil { - return ErrorResponseInvalid.Error(nil) - } else { - r.e.c = rsp.StatusCode - r.e.s = rsp.Status - } - - b, err = r._CheckResponse(rsp, validStatus...) - r.e.b = b - - if err != nil && err.HasCodeError(ErrorResponseStatus) { - r.e.se = true - } else if err != nil { - r.e.e = err - return err - } - - if b.Len() > 0 { - if e = json.Unmarshal(b.Bytes(), model); e != nil { - r.e.be = true - r.e.e = e - return ErrorResponseUnmarshall.ErrorParent(e) - } - } - - return nil -} - -func (r *request) StatusRegister(sts libsts.RouteStatus, prefix string) { - opts := r.GetOption() - - r.s.Lock() - defer r.s.Unlock() - - if len(prefix) > 0 { - prefix = fmt.Sprintf("%s %s", prefix, r.u.Hostname()) - } else { - prefix = fmt.Sprintf("%s %s", "HTTP Request", r.u.Hostname()) - } - - opts.Health.Status.RegisterStatus(sts, prefix, r._StatusInfo, r._StatusHealth) -} - -func (r *request) StatusRegisterInfo(fct libsts.FctInfo) { - r.s.Lock() - defer r.s.Unlock() - - r.i = fct -} diff --git a/request/monitor.go b/request/monitor.go new file mode 100644 index 00000000..ef414d5b --- /dev/null +++ b/request/monitor.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 request + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "runtime" + + liberr "github.com/nabbar/golib/errors" + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +const ( + defaultNameMonitor = "HTTP Request Client" +) + +func (r *request) HealthCheck(ctx context.Context) error { + opts := r.GetOption() + ednp := r.GetFullUrl() + + r.s.Lock() + defer r.s.Unlock() + + head := make(url.Values, 0) + if v := r.h.Get(_Authorization); v != "" { + head.Set(_Authorization, v) + } + + if !opts.Health.Enable { + return nil + } + + if opts.Health.Endpoint != "" { + if u, e := url.Parse(opts.Health.Endpoint); e == nil { + ednp = u + } + } + + if opts.Health.Auth.Basic.Enable { + head.Set(_Authorization, _AuthorizationBasic+" "+base64.StdEncoding.EncodeToString([]byte(opts.Health.Auth.Basic.Username+":"+opts.Health.Auth.Basic.Password))) + } else if opts.Health.Auth.Bearer.Enable { + head.Set(_Authorization, _AuthorizationBearer+" "+opts.Health.Auth.Bearer.Token) + } + + var ( + e error + + err liberr.Error + buf *bytes.Buffer + req *http.Request + rsp *http.Response + ) + + req, err = r._MakeRequest(ctx, ednp, http.MethodGet, nil, head, nil) + + if err != nil { + return err + } + + rsp, e = r.client().Do(req) + if e != nil { + return ErrorSendRequest.ErrorParent(e) + } + + if buf, err = r._CheckResponse(rsp); err != nil { + return err + } + + if len(opts.Health.Result.ValidHTTPCode) > 0 { + if !r._IsValidCode(opts.Health.Result.ValidHTTPCode, rsp.StatusCode) { + return ErrorResponseStatus.ErrorParent(fmt.Errorf("status: %s", rsp.Status)) + } + } else if len(opts.Health.Result.InvalidHTTPCode) > 0 { + if r._IsValidCode(opts.Health.Result.InvalidHTTPCode, rsp.StatusCode) { + return ErrorResponseStatus.ErrorParent(fmt.Errorf("status: %s", rsp.Status)) + } + } + + if len(opts.Health.Result.Contain) > 0 { + if !r._IsValidContents(opts.Health.Result.Contain, buf) { + return ErrorResponseContainsNotFound.Error(nil) + } + } else if len(opts.Health.Result.NotContain) > 0 { + if r._IsValidContents(opts.Health.Result.NotContain, buf) { + return ErrorResponseNotContainsFound.Error(nil) + } + } + + return nil +} + +func (r *request) Monitor(ctx context.Context, vrs libver.Version) (montps.Monitor, error) { + var ( + e error + inf moninf.Info + mon montps.Monitor + opt *Options + res = make(map[string]interface{}, 0) + ) + + if opt = r.GetOption(); opt == nil { + return nil, fmt.Errorf("cannot load options") + } + + res["runtime"] = runtime.Version()[2:] + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + + if inf, e = moninf.New(defaultNameMonitor); e != nil { + return nil, e + } else { + inf.RegisterName(func() (string, error) { + return fmt.Sprintf("%s [%s]", defaultNameMonitor, r.GetEndpoint()), nil + }) + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon, e = libmon.New(r.x, inf); e != nil { + return nil, e + } else if mon == nil { + return nil, nil + } + + mon.RegisterLoggerDefault(r._getDefaultLogger) + + if e = mon.SetConfig(r.x, opt.Health.Monitor); e != nil { + return nil, e + } + + mon.SetHealthCheck(r.HealthCheck) + + if e = mon.Start(ctx); e != nil { + return nil, e + } + + return mon, nil +} diff --git a/request/options.go b/request/options.go index 986cb0b0..777d074c 100644 --- a/request/options.go +++ b/request/options.go @@ -29,12 +29,15 @@ package request import ( "fmt" "net/http" + "sync/atomic" - libsts "github.com/nabbar/golib/status/config" + liblog "github.com/nabbar/golib/logger" + + moncfg "github.com/nabbar/golib/monitor/types" libval "github.com/go-playground/validator/v10" libtls "github.com/nabbar/golib/certificates" - libcfg "github.com/nabbar/golib/config" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" libhtc "github.com/nabbar/golib/httpcli" ) @@ -60,7 +63,7 @@ type OptionsHealth struct { Endpoint string `json:"endpoint" yaml:"endpoint" toml:"endpoint" mapstructure:"endpoint" validate:"required,url"` Auth OptionsAuth `json:"auth" yaml:"auth" toml:"auth" mapstructure:"auth" validate:"required,dive"` Result OptionsHealthResult `json:"result" yaml:"result" toml:"result" mapstructure:"result" validate:"required,dive"` - Status libsts.ConfigStatus `json:"status" yaml:"status" toml:"status" mapstructure:"status" validate:"required,dive"` + Monitor moncfg.Config `json:"monitor" yaml:"monitor" toml:"monitor" mapstructure:"monitor" validate:"required,dive"` } type OptionsHealthResult struct { @@ -76,10 +79,11 @@ type Options struct { Auth OptionsAuth `json:"auth" yaml:"auth" toml:"auth" mapstructure:"auth" validate:"required,dive"` Health OptionsHealth `json:"health" yaml:"health" toml:"health" mapstructure:"health" validate:"required,dive"` - def FctTLSDefault + tls libtls.FctTLSDefault + log liblog.FuncLog } -func (o Options) Validate() liberr.Error { +func (o *Options) Validate() liberr.Error { var e = ErrorValidatorError.Error(nil) if err := libval.New().Struct(o); err != nil { @@ -100,39 +104,45 @@ func (o Options) Validate() liberr.Error { return e } -func (o Options) _GetDefaultTLS() libtls.TLSConfig { - if o.def != nil { - return o.def() +func (o *Options) defaultTLS() libtls.TLSConfig { + if o.tls != nil { + return o.tls() } return nil } -func (o Options) SetDefaultTLS(fct FctTLSDefault) { - o.def = fct +func (o *Options) SetDefaultTLS(fct libtls.FctTLSDefault) { + o.tls = fct +} + +func (o *Options) SetDefaultLog(fct liblog.FuncLog) { + o.log = fct } -func (o Options) GetClientHTTP(servername string) *http.Client { - if c, e := o.HttpClient.GetClient(o._GetDefaultTLS(), servername); e == nil { +func (o *Options) ClientHTTPTLS(tls libtls.TLSConfig, servername string) *http.Client { + if c, e := o.HttpClient.GetClient(tls, servername); e == nil { return c } return &http.Client{} } -func (o Options) New(ctx libcfg.FuncContext, cli FctHttpClient, tls FctTLSDefault) (Request, error) { - if tls != nil { - o.def = tls - } - - return New(ctx, cli, o) +func (o *Options) ClientHTTP(servername string) *http.Client { + return o.ClientHTTPTLS(o.defaultTLS(), servername) } -func (o Options) Update(req Request, ctx libcfg.FuncContext, cli FctHttpClient, tls FctTLSDefault) (Request, error) { - if tls != nil { - o.def = tls +func (o *Options) New(ctx libctx.FuncContext) (Request, error) { + if n, e := New(ctx, o); e != nil { + return nil, e + } else { + n.RegisterDefaultLogger(o.log) + n.RegisterHTTPClient(o.ClientHTTPTLS) + return n, nil } +} +func (o *Options) Update(ctx libctx.FuncContext, req Request) (Request, error) { var ( e error n Request @@ -143,16 +153,92 @@ func (o Options) Update(req Request, ctx libcfg.FuncContext, cli FctHttpClient, } if ctx != nil { - n.SetContext(ctx) - } - - if cli != nil { - n.SetClient(cli) + n.RegisterContext(ctx) } - if e = n.SetOption(&o); e != nil { + if e = n.SetOption(o); e != nil { return nil, e } return n, nil } + +func (r *request) options() *Options { + if r.o == nil { + return nil + } else if i := r.o.Load(); i == nil { + return nil + } else if o, ok := i.(*Options); !ok { + return nil + } else { + return o + } +} + +func (r *request) GetOption() *Options { + r.s.Lock() + defer r.s.Unlock() + return r.options() +} + +func (r *request) SetOption(opt *Options) error { + if e := r.SetEndpoint(opt.Endpoint); e != nil { + return e + } + + if opt.Auth.Basic.Enable { + r.AuthBasic(opt.Auth.Basic.Username, opt.Auth.Basic.Password) + } else if opt.Auth.Bearer.Enable { + r.AuthBearer(opt.Auth.Bearer.Token) + } + + r.s.Lock() + defer r.s.Unlock() + + if r.o == nil { + r.o = new(atomic.Value) + } + + r.o.Store(opt) + return nil +} + +func (r *request) RegisterHTTPClient(fct libtls.FctHttpClient) { + r.s.Lock() + defer r.s.Unlock() + + r.f = fct +} + +func (r *request) RegisterDefaultLogger(fct liblog.FuncLog) { + r.s.Lock() + defer r.s.Unlock() + + r.l = fct +} + +func (r *request) _getDefaultLogger() liblog.Logger { + r.s.Lock() + defer r.s.Unlock() + + if r.l == nil { + return nil + } else { + return r.l() + } +} + +func (r *request) defaultTLS() libtls.TLSConfig { + if cfg := r.options(); cfg != nil { + return cfg.defaultTLS() + } + + return nil +} + +func (r *request) RegisterContext(fct libctx.FuncContext) { + r.s.Lock() + defer r.s.Unlock() + + r.x = fct +} diff --git a/request/params.go b/request/params.go new file mode 100644 index 00000000..0db25bc3 --- /dev/null +++ b/request/params.go @@ -0,0 +1,79 @@ +/* + * 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 request + +import "net/url" + +func (r *request) CleanParams() { + r.s.Lock() + defer r.s.Unlock() + + r.p = make(url.Values) +} + +func (r *request) DelParams(key string) { + r.s.Lock() + defer r.s.Unlock() + + r.p.Del(key) +} + +func (r *request) SetParams(key, val string) { + r.s.Lock() + defer r.s.Unlock() + + if len(r.p) < 1 { + r.p = make(url.Values) + } + + r.p.Set(key, val) +} + +func (r *request) AddParams(key, val string) { + r.s.Lock() + defer r.s.Unlock() + + if len(r.p) < 1 { + r.p = make(url.Values) + } + + r.p.Set(key, val) +} + +func (r *request) GetFullUrl() *url.URL { + r.s.Lock() + defer r.s.Unlock() + + return r.u +} + +func (r *request) SetFullUrl(u *url.URL) { + r.s.Lock() + defer r.s.Unlock() + + r.u = u +} diff --git a/request/request.go b/request/request.go new file mode 100644 index 00000000..eecab552 --- /dev/null +++ b/request/request.go @@ -0,0 +1,215 @@ +/* + * 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 request + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/url" + "strings" + + liberr "github.com/nabbar/golib/errors" +) + +func (r *request) _MakeRequest(ctx context.Context, u *url.URL, mtd string, body io.Reader, head url.Values, params url.Values) (*http.Request, liberr.Error) { + var ( + req *http.Request + err error + ) + + req, err = http.NewRequestWithContext(ctx, mtd, u.String(), body) + + if err != nil { + return nil, ErrorCreateRequest.ErrorParent(err) + } + + if len(head) > 0 { + for k := range head { + req.Header.Set(k, head.Get(k)) + } + } + + if len(params) > 0 { + q := req.URL.Query() + for k := range params { + q.Add(k, params.Get(k)) + } + req.URL.RawQuery = q.Encode() + } + + return req, nil +} + +func (r *request) _CheckResponse(rsp *http.Response, validStatus ...int) (*bytes.Buffer, liberr.Error) { + var ( + e error + b = bytes.NewBuffer(make([]byte, 0)) + ) + + defer func() { + if rsp != nil && !rsp.Close && rsp.Body != nil { + _ = rsp.Body.Close() + } + }() + + if rsp == nil { + return b, ErrorResponseInvalid.Error(nil) + } + + if rsp.Body != nil { + if _, e = io.Copy(b, rsp.Body); e != nil { + return b, ErrorResponseLoadBody.ErrorParent(e) + } + } + + if !r._IsValidCode(validStatus, rsp.StatusCode) { + return b, ErrorResponseStatus.Error(nil) + } + + return b, nil +} + +func (r *request) _IsValidCode(listValid []int, statusCode int) bool { + if len(listValid) < 1 { + return true + } + + for _, c := range listValid { + if c == statusCode { + return true + } + } + + return false +} + +func (r *request) _IsValidContents(contains []string, buf *bytes.Buffer) bool { + if len(contains) < 1 { + return true + } else if buf.Len() < 1 { + return false + } + + for _, c := range contains { + if strings.Contains(buf.String(), c) { + return true + } + } + + return false +} + +func (r *request) Do() (*http.Response, liberr.Error) { + r.s.Lock() + defer r.s.Unlock() + + if r.m == "" || r.u == nil || r.u.String() == "" { + return nil, ErrorParamInvalid.Error(nil) + } + + var ( + e error + req *http.Request + rsp *http.Response + err liberr.Error + ) + + r.e = &requestError{ + c: 0, + s: "", + se: false, + b: bytes.NewBuffer(make([]byte, 0)), + be: false, + e: nil, + } + + req, err = r._MakeRequest(r.context(), r.u, r.m, r.b, r.h, r.p) + if err != nil { + r.e.e = err + return nil, err + } + + rsp, e = r.client().Do(req) + + if e != nil { + r.e.e = e + return nil, ErrorSendRequest.ErrorParent(e) + } + + return rsp, nil +} + +func (r *request) DoParse(model interface{}, validStatus ...int) liberr.Error { + var ( + e error + b = bytes.NewBuffer(make([]byte, 0)) + + err liberr.Error + rsp *http.Response + ) + + r.e = &requestError{ + c: 0, + s: "", + se: false, + b: bytes.NewBuffer(make([]byte, 0)), + be: false, + e: nil, + } + + if rsp, err = r.Do(); err != nil { + return err + } else if rsp == nil { + return ErrorResponseInvalid.Error(nil) + } else { + r.e.c = rsp.StatusCode + r.e.s = rsp.Status + } + + b, err = r._CheckResponse(rsp, validStatus...) + r.e.b = b + + if err != nil && err.HasCodeError(ErrorResponseStatus) { + r.e.se = true + } else if err != nil { + r.e.e = err + return err + } + + if b.Len() > 0 { + if e = json.Unmarshal(b.Bytes(), model); e != nil { + r.e.be = true + r.e.e = e + return ErrorResponseUnmarshall.ErrorParent(e) + } + } + + return nil +} diff --git a/request/url.go b/request/url.go new file mode 100644 index 00000000..cf748b00 --- /dev/null +++ b/request/url.go @@ -0,0 +1,137 @@ +/* + * 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 request + +import ( + "net/http" + "net/url" + "path" + "strings" +) + +func (r *request) SetEndpoint(u string) error { + if uri, err := url.Parse(u); err != nil { + return err + } else { + r.s.Lock() + defer r.s.Unlock() + + r.u = uri + return nil + } +} + +func (r *request) GetEndpoint() string { + r.s.Lock() + defer r.s.Unlock() + + return r.u.String() +} + +func (r *request) SetPath(raw bool, path string) { + r.s.Lock() + defer r.s.Unlock() + + if raw { + r.u.RawPath = path + } else { + r.u.Path = path + } +} + +func (r *request) AddPath(raw bool, pathPart ...string) { + r.s.Lock() + defer r.s.Unlock() + + if r.u == nil { + return + } + + var str string + if raw { + str = path.Clean(r.u.RawPath) + } else { + str = path.Clean(r.u.Path) + } + + for i := range pathPart { + if strings.HasSuffix(str, "/") && strings.HasPrefix(pathPart[i], "/") { + pathPart[i] = strings.TrimPrefix(pathPart[i], "/") + } + + if strings.HasSuffix(pathPart[i], "/") { + pathPart[i] = strings.TrimSuffix(pathPart[i], "/") + } + + str = path.Join(str, pathPart[i]) + } + + if raw { + r.u.RawPath = path.Clean(str) + } else { + r.u.Path = path.Clean(str) + } +} + +func (r *request) SetMethod(method string) { + r.s.Lock() + defer r.s.Unlock() + + switch strings.ToUpper(method) { + case http.MethodGet: + r.m = http.MethodGet + case http.MethodHead: + r.m = http.MethodHead + case http.MethodPost: + r.m = http.MethodPost + case http.MethodPut: + r.m = http.MethodPut + case http.MethodPatch: + r.m = http.MethodPatch + case http.MethodDelete: + r.m = http.MethodDelete + case http.MethodConnect: + r.m = http.MethodConnect + case http.MethodOptions: + r.m = http.MethodOptions + case http.MethodTrace: + r.m = http.MethodTrace + default: + r.m = strings.ToUpper(method) + } + + if r.m == "" { + r.m = http.MethodGet + } +} + +func (r *request) GetMethod() string { + r.s.Lock() + defer r.s.Unlock() + + return r.m +} diff --git a/router/auth.go b/router/auth.go index 84466cb7..51b4c932 100644 --- a/router/auth.go +++ b/router/auth.go @@ -29,9 +29,9 @@ import ( "net/http" "strings" - "github.com/gin-gonic/gin" - "github.com/nabbar/golib/errors" - "github.com/nabbar/golib/logger" + ginsdk "github.com/gin-gonic/gin" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" ) type AuthCode uint8 @@ -48,11 +48,11 @@ const ( HEAD_AUTH_REAL = "Basic realm=LDAP Authorization Required" ) -func AuthRequire(c *gin.Context, err error) { +func AuthRequire(c *ginsdk.Context, err error) { if err != nil { - c.Errors = append(c.Errors, &gin.Error{ + c.Errors = append(c.Errors, &ginsdk.Error{ Err: err, - Type: gin.ErrorTypePrivate, + Type: ginsdk.ErrorTypePrivate, }) } // Credentials doesn't match, we return 401 and abort handlers chain. @@ -60,46 +60,46 @@ func AuthRequire(c *gin.Context, err error) { c.AbortWithStatus(http.StatusUnauthorized) } -func AuthForbidden(c *gin.Context, err error) { +func AuthForbidden(c *ginsdk.Context, err error) { if err != nil { - c.Errors = append(c.Errors, &gin.Error{ + c.Errors = append(c.Errors, &ginsdk.Error{ Err: err, - Type: gin.ErrorTypePrivate, + Type: ginsdk.ErrorTypePrivate, }) } c.AbortWithStatus(http.StatusForbidden) } type authorization struct { - check func(AuthHeader string) (AuthCode, errors.Error) - router []gin.HandlerFunc + check func(AuthHeader string) (AuthCode, liberr.Error) + router []ginsdk.HandlerFunc authType string } type Authorization interface { - Handler(c *gin.Context) - Register(router ...gin.HandlerFunc) gin.HandlerFunc - Append(router ...gin.HandlerFunc) + Handler(c *ginsdk.Context) + Register(router ...ginsdk.HandlerFunc) ginsdk.HandlerFunc + Append(router ...ginsdk.HandlerFunc) } -func NewAuthorization(HeadAuthType string, authCheckFunc func(AuthHeader string) (AuthCode, errors.Error)) Authorization { +func NewAuthorization(HeadAuthType string, authCheckFunc func(AuthHeader string) (AuthCode, liberr.Error)) Authorization { return &authorization{ check: authCheckFunc, authType: HeadAuthType, - router: make([]gin.HandlerFunc, 0), + router: make([]ginsdk.HandlerFunc, 0), } } -func (a *authorization) Register(router ...gin.HandlerFunc) gin.HandlerFunc { +func (a *authorization) Register(router ...ginsdk.HandlerFunc) ginsdk.HandlerFunc { a.router = router return a.Handler } -func (a *authorization) Append(router ...gin.HandlerFunc) { +func (a *authorization) Append(router ...ginsdk.HandlerFunc) { a.router = append(a.router, router...) } -func (a authorization) Handler(c *gin.Context) { +func (a authorization) Handler(c *ginsdk.Context) { // Search user in the slice of allowed credentials auth := c.Request.Header.Get(HEAD_AUTH_SEND) @@ -126,7 +126,7 @@ func (a authorization) Handler(c *gin.Context) { switch code { case AUTH_CODE_SUCCESS: for _, r := range a.router { - logger.DebugLevel.Logf("Calling router '%s=%s'", c.Request.Method, c.Request.URL.RawPath) + liblog.DebugLevel.Logf("Calling router '%s=%s'", c.Request.Method, c.Request.URL.RawPath) r(c) } case AUTH_CODE_REQUIRE: @@ -134,9 +134,9 @@ func (a authorization) Handler(c *gin.Context) { case AUTH_CODE_FORBIDDEN: AuthForbidden(c, ErrorHeaderAuthForbidden.Error(err).GetErrorFull("")) default: - c.Errors = append(c.Errors, &gin.Error{ + c.Errors = append(c.Errors, &ginsdk.Error{ Err: ErrorHeaderAuth.Error(err).GetErrorFull(""), - Type: gin.ErrorTypePrivate, + Type: ginsdk.ErrorTypePrivate, }) c.AbortWithStatus(http.StatusInternalServerError) } diff --git a/router/error.go b/router/error.go index 135087ba..d1a7b85a 100644 --- a/router/error.go +++ b/router/error.go @@ -34,6 +34,7 @@ import ( const ( ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgRouter + ErrorConfigValidator ErrorHeaderAuth ErrorHeaderAuthMissing ErrorHeaderAuthEmpty @@ -52,6 +53,8 @@ func getMessage(code liberr.CodeError) (message string) { switch code { case ErrorParamEmpty: return "given parameters is empty" + case ErrorConfigValidator: + return "invalid config, validation error" case ErrorHeaderAuthMissing: return "missing authorization header" case ErrorHeaderAuthEmpty: diff --git a/router/headers.go b/router/headers.go index 82344c9e..d244a766 100644 --- a/router/headers.go +++ b/router/headers.go @@ -28,7 +28,8 @@ package router import ( "net/http" - "github.com/gin-gonic/gin" + ginsdk "github.com/gin-gonic/gin" + liberr "github.com/nabbar/golib/errors" ) type HeadersConfig map[string]string @@ -43,6 +44,16 @@ func (h HeadersConfig) New() Headers { return res } +func (h HeadersConfig) Validate() liberr.Error { + err := ErrorConfigValidator.Error(nil) + + if !err.HasParent() { + err = nil + } + + return err +} + type headers struct { head http.Header } @@ -54,8 +65,8 @@ type Headers interface { Del(key string) Header() map[string]string - Register(router ...gin.HandlerFunc) []gin.HandlerFunc - Handler(c *gin.Context) + Register(router ...ginsdk.HandlerFunc) []ginsdk.HandlerFunc + Handler(c *ginsdk.Context) Clone() Headers } @@ -72,8 +83,8 @@ func (h headers) Clone() Headers { } } -func (h headers) Register(router ...gin.HandlerFunc) []gin.HandlerFunc { - res := make([]gin.HandlerFunc, 0) +func (h headers) Register(router ...ginsdk.HandlerFunc) []ginsdk.HandlerFunc { + res := make([]ginsdk.HandlerFunc, 0) res = append(res, h.Handler) res = append(res, router...) @@ -94,7 +105,7 @@ func (h headers) Header() map[string]string { return res } -func (h headers) Handler(c *gin.Context) { +func (h headers) Handler(c *ginsdk.Context) { if h.head == nil { return } diff --git a/router/register.go b/router/register.go index 5ddbe7de..c70e2eac 100644 --- a/router/register.go +++ b/router/register.go @@ -34,9 +34,8 @@ import ( "strings" "time" + ginsdk "github.com/gin-gonic/gin" liberr "github.com/nabbar/golib/errors" - - "github.com/gin-gonic/gin" liblog "github.com/nabbar/golib/logger" ) @@ -44,16 +43,17 @@ const ( EmptyHandlerGroup = "" GinContextStartUnixNanoTime = "gin-ctx-start-unix-nano-time" GinContextRequestPath = "gin-ctx-request-path" + GinContextRequestUser = "gin-ctx-request-user" ) var ( defaultRouters = NewRouterList(DefaultGinInit) ) -func GinEngine(trustedPlatform string, trustyProxy ...string) (*gin.Engine, error) { +func GinEngine(trustedPlatform string, trustyProxy ...string) (*ginsdk.Engine, error) { var err error - engine := gin.New() + engine := ginsdk.New() if len(trustyProxy) > 0 { err = engine.SetTrustedProxies(trustyProxy) } @@ -64,12 +64,12 @@ func GinEngine(trustedPlatform string, trustyProxy ...string) (*gin.Engine, erro return engine, err } -func GinAddGlobalMiddleware(eng *gin.Engine, middleware ...gin.HandlerFunc) *gin.Engine { +func GinAddGlobalMiddleware(eng *ginsdk.Engine, middleware ...ginsdk.HandlerFunc) *ginsdk.Engine { eng.Use(middleware...) return eng } -func GinLatencyContext(c *gin.Context) { +func GinLatencyContext(c *ginsdk.Context) { // Start timer c.Set(GinContextStartUnixNanoTime, time.Now().UnixNano()) @@ -77,22 +77,44 @@ func GinLatencyContext(c *gin.Context) { c.Next() } -func GinRequestContext(c *gin.Context) { - // Set Path - path := c.Request.URL.Path +func sanitizeString(s string) string { + s = strings.Replace(s, "\n", "", -1) + s = strings.Replace(s, "\r", "", -1) + s = strings.Replace(s, "\t", "", -1) + return s +} - if raw := c.Request.URL.RawQuery; len(raw) > 0 { - path += "?" + raw +func GinRequestContext(c *ginsdk.Context) { + // Set Path + if c != nil { + if req := c.Request; req != nil { + if u := req.URL; u != nil { + if p := u.Path; len(p) > 0 { + if q := u.Query(); q != nil { + if enc := q.Encode(); len(enc) > 0 { + c.Set(GinContextRequestPath, sanitizeString(p+"?"+enc)) + } else { + c.Set(GinContextRequestPath, sanitizeString(p)) + } + } else { + c.Set(GinContextRequestPath, sanitizeString(p)) + } + } + if r := u.User; r != nil { + if nm := r.Username(); len(nm) > 0 { + c.Set(GinContextRequestUser, sanitizeString(nm)) + } + } + } + } } - c.Set(GinContextRequestPath, path) - // Process request c.Next() } -func GinAccessLog(log liblog.FuncLog) gin.HandlerFunc { - return func(c *gin.Context) { +func GinAccessLog(log liblog.FuncLog) ginsdk.HandlerFunc { + return func(c *ginsdk.Context) { // Process request c.Next() @@ -110,10 +132,11 @@ func GinAccessLog(log liblog.FuncLog) gin.HandlerFunc { sttm := time.Unix(0, c.GetInt64(GinContextStartUnixNanoTime)) path := c.GetString(GinContextRequestPath) + user := c.GetString(GinContextRequestUser) ent := l.Access( c.ClientIP(), - c.Request.URL.User.Username(), + user, time.Now(), time.Now().Sub(sttm), c.Request.Method, @@ -127,8 +150,8 @@ func GinAccessLog(log liblog.FuncLog) gin.HandlerFunc { } } -func GinErrorLog(log liblog.FuncLog) gin.HandlerFunc { - return func(c *gin.Context) { +func GinErrorLog(log liblog.FuncLog) ginsdk.HandlerFunc { + return func(c *ginsdk.Context) { defer func() { var rec liberr.Error @@ -193,16 +216,16 @@ func GinErrorLog(log liblog.FuncLog) gin.HandlerFunc { } } -func DefaultGinInit() *gin.Engine { - engine := gin.New() - engine.Use(gin.Logger(), gin.Recovery()) +func DefaultGinInit() *ginsdk.Engine { + engine := ginsdk.New() + engine.Use(ginsdk.Logger(), ginsdk.Recovery()) return engine } -func DefaultGinWithTrustyProxy(trustyProxy []string) *gin.Engine { - engine := gin.New() - engine.Use(gin.Logger(), gin.Recovery()) +func DefaultGinWithTrustyProxy(trustyProxy []string) *ginsdk.Engine { + engine := ginsdk.New() + engine.Use(ginsdk.Logger(), ginsdk.Recovery()) if len(trustyProxy) > 0 { _ = engine.SetTrustedProxies(trustyProxy) @@ -211,9 +234,9 @@ func DefaultGinWithTrustyProxy(trustyProxy []string) *gin.Engine { return engine } -func DefaultGinWithTrustedPlatform(trustedPlatform string) *gin.Engine { - engine := gin.New() - engine.Use(gin.Logger(), gin.Recovery()) +func DefaultGinWithTrustedPlatform(trustedPlatform string) *ginsdk.Engine { + engine := ginsdk.New() + engine.Use(ginsdk.Logger(), ginsdk.Recovery()) if len(trustedPlatform) > 0 { engine.TrustedPlatform = trustedPlatform @@ -225,44 +248,44 @@ func DefaultGinWithTrustedPlatform(trustedPlatform string) *gin.Engine { type routerItem struct { method string relative string - router []gin.HandlerFunc + router []ginsdk.HandlerFunc } type routerList struct { - init func() *gin.Engine + init func() *ginsdk.Engine list map[string][]routerItem } -type RegisterRouter func(method string, relativePath string, router ...gin.HandlerFunc) -type RegisterRouterInGroup func(group, method string, relativePath string, router ...gin.HandlerFunc) +type RegisterRouter func(method string, relativePath string, router ...ginsdk.HandlerFunc) +type RegisterRouterInGroup func(group, method string, relativePath string, router ...ginsdk.HandlerFunc) type RouterList interface { - Register(method string, relativePath string, router ...gin.HandlerFunc) - RegisterInGroup(group, method string, relativePath string, router ...gin.HandlerFunc) - Handler(engine *gin.Engine) - Engine() *gin.Engine + Register(method string, relativePath string, router ...ginsdk.HandlerFunc) + RegisterInGroup(group, method string, relativePath string, router ...ginsdk.HandlerFunc) + Handler(engine *ginsdk.Engine) + Engine() *ginsdk.Engine } -func RoutersRegister(method string, relativePath string, router ...gin.HandlerFunc) { +func RoutersRegister(method string, relativePath string, router ...ginsdk.HandlerFunc) { defaultRouters.Register(method, relativePath, router...) } -func RoutersRegisterInGroup(group, method string, relativePath string, router ...gin.HandlerFunc) { +func RoutersRegisterInGroup(group, method string, relativePath string, router ...ginsdk.HandlerFunc) { defaultRouters.RegisterInGroup(group, method, relativePath, router...) } -func RoutersHandler(engine *gin.Engine) { +func RoutersHandler(engine *ginsdk.Engine) { defaultRouters.Handler(engine) } -func NewRouterList(initGin func() *gin.Engine) RouterList { +func NewRouterList(initGin func() *ginsdk.Engine) RouterList { return &routerList{ init: initGin, list: make(map[string][]routerItem), } } -func (l routerList) Handler(engine *gin.Engine) { +func (l routerList) Handler(engine *ginsdk.Engine) { for grpRoute, grpList := range l.list { if grpRoute == EmptyHandlerGroup { for _, r := range grpList { @@ -277,7 +300,7 @@ func (l routerList) Handler(engine *gin.Engine) { } } -func (l *routerList) RegisterInGroup(group, method string, relativePath string, router ...gin.HandlerFunc) { +func (l *routerList) RegisterInGroup(group, method string, relativePath string, router ...ginsdk.HandlerFunc) { if group == "" { group = EmptyHandlerGroup } @@ -293,11 +316,11 @@ func (l *routerList) RegisterInGroup(group, method string, relativePath string, }) } -func (l *routerList) Register(method string, relativePath string, router ...gin.HandlerFunc) { +func (l *routerList) Register(method string, relativePath string, router ...ginsdk.HandlerFunc) { l.RegisterInGroup("", method, relativePath, router...) } -func (l routerList) Engine() *gin.Engine { +func (l routerList) Engine() *ginsdk.Engine { if l.init != nil { return l.init() } else { diff --git a/router/router.go b/router/router.go index fdf0fb4e..a961bbef 100644 --- a/router/router.go +++ b/router/router.go @@ -29,17 +29,17 @@ import ( "net/http" "os" - "github.com/gin-gonic/gin" + ginsdk "github.com/gin-gonic/gin" ) func init() { if os.Getenv("GIN_MODE") == "" { - gin.SetMode(gin.ReleaseMode) + ginsdk.SetMode(ginsdk.ReleaseMode) } } // SetGinHandler func that return given func as ginTonic HandlerFunc interface type. -func SetGinHandler(fct func(c *gin.Context)) gin.HandlerFunc { +func SetGinHandler(fct func(c *ginsdk.Context)) ginsdk.HandlerFunc { return fct } diff --git a/server/interface.go b/server/interface.go new file mode 100644 index 00000000..896ad623 --- /dev/null +++ b/server/interface.go @@ -0,0 +1,48 @@ +/* + * 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 server + +import "context" + +type Server interface { + // Start is used to start the server + Start(ctx context.Context) error + + // Stop is used to stop the server + Stop(ctx context.Context) error + + // Restart is used to restart the server + Restart(ctx context.Context) error + + // IsRunning return true if the server is currently running + IsRunning() bool +} + +type WaitNotify interface { + StartWaitNotify(ctx context.Context) + StopWaitNotify() +} diff --git a/size/encode.go b/size/encode.go new file mode 100644 index 00000000..bd68d3e9 --- /dev/null +++ b/size/encode.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 bytes + +import ( + "fmt" + + "gopkg.in/yaml.v3" +) + +func (s Size) MarshalJSON() ([]byte, error) { + t := s.String() + b := make([]byte, 0, len(t)+2) + b = append(b, '"') + b = append(b, []byte(t)...) + b = append(b, '"') + return b, nil +} + +func (s *Size) UnmarshalJSON(bytes []byte) error { + return s.unmarshall(bytes) +} + +func (s Size) MarshalYAML() (interface{}, error) { + return []byte(s.String()), nil +} + +func (s *Size) UnmarshalYAML(value *yaml.Node) error { + return s.unmarshall([]byte(value.Value)) +} + +func (s Size) MarshalTOML() ([]byte, error) { + return []byte(s.String()), nil +} + +func (s *Size) UnmarshalTOML(i interface{}) error { + if p, k := i.([]byte); k { + return s.unmarshall(p) + } + if p, k := i.(string); k { + return s.unmarshall([]byte(p)) + } + return fmt.Errorf("size: value not in valid format") +} + +func (s Size) MarshalText() ([]byte, error) { + return []byte(s.String()), nil +} + +func (s *Size) UnmarshalText(bytes []byte) error { + return s.unmarshall(bytes) +} + +func (s Size) MarshalCBOR() ([]byte, error) { + return []byte(s.String()), nil +} + +func (s *Size) UnmarshalCBOR(bytes []byte) error { + return s.unmarshall(bytes) +} diff --git a/size/format.go b/size/format.go new file mode 100644 index 00000000..9ef0ab08 --- /dev/null +++ b/size/format.go @@ -0,0 +1,139 @@ +/*********************************************************************************************************************** + * + * 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 bytes + +import ( + "fmt" + "math" +) + +const ( + _space = " " + FormatRound0 = "%.0f" + FormatRound1 = "%.1f" + FormatRound2 = "%.2f" + FormatRound3 = "%.3f" +) + +var ( + _maxFloat = uint64(math.Ceil(math.MaxFloat64)) +) + +func (s Size) String() string { + u := s.Unit(0) + + if len(u) > 0 { + return s.Format(FormatRound2) + _space + u + } else { + return s.Format(FormatRound2) + } +} + +func (s Size) Int64() int64 { + if uint64(s) > math.MaxInt64 { + // overflow + return math.MaxInt64 + } + + return int64(s) +} + +func (s Size) Uint64() uint64 { + return uint64(s) +} + +func (s Size) Float64() float64 { + if uint64(s) > _maxFloat { + // overflow + return math.MaxFloat64 + } + + return float64(s) +} + +func (s Size) Format(format string) string { + switch { + case SizeExa.isMax(s): + return fmt.Sprintf(format, s.sizeByUnit(SizeExa)) + case SizePeta.isMax(s): + return fmt.Sprintf(format, s.sizeByUnit(SizePeta)) + case SizeTera.isMax(s): + return fmt.Sprintf(format, s.sizeByUnit(SizeTera)) + case SizeGiga.isMax(s): + return fmt.Sprintf(format, s.sizeByUnit(SizeGiga)) + case SizeMega.isMax(s): + return fmt.Sprintf(format, s.sizeByUnit(SizeMega)) + case SizeKilo.isMax(s): + return fmt.Sprintf(format, s.sizeByUnit(SizeKilo)) + default: + return fmt.Sprintf(format, s.sizeByUnit(SizeUnit)) + } +} + +func (s Size) Unit(unit rune) string { + switch { + case SizeExa.isMax(s): + return SizeExa.Code(unit) + case SizePeta.isMax(s): + return SizePeta.Code(unit) + case SizeTera.isMax(s): + return SizeTera.Code(unit) + case SizeGiga.isMax(s): + return SizeGiga.Code(unit) + case SizeMega.isMax(s): + return SizeMega.Code(unit) + case SizeKilo.isMax(s): + return SizeKilo.Code(unit) + default: + return SizeUnit.Code(unit) + } +} + +func (s Size) KiloBytes() uint64 { + return s.floorByUnit(SizeKilo) +} + +func (s Size) MegaBytes() uint64 { + return s.floorByUnit(SizeMega) +} + +func (s Size) GigaBytes() uint64 { + return s.floorByUnit(SizeGiga) +} + +func (s Size) TeraBytes() uint64 { + return s.floorByUnit(SizeTera) +} + +func (s Size) PetaBytes() uint64 { + return s.floorByUnit(SizePeta) +} + +func (s Size) ExaBytes() uint64 { + return s.floorByUnit(SizeExa) +} diff --git a/size/interface.go b/size/interface.go new file mode 100644 index 00000000..05d19e02 --- /dev/null +++ b/size/interface.go @@ -0,0 +1,78 @@ +/*********************************************************************************************************************** + * + * 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 bytes + +type Size uint64 + +const ( + SizeNul Size = 0 + SizeUnit Size = 1 + SizeKilo Size = 1 << 10 + SizeMega Size = 1 << 20 + SizeGiga Size = 1 << 30 + SizeTera Size = 1 << 40 + SizePeta Size = 1 << 50 + SizeExa Size = 1 << 60 +) + +var defUnit = 'B' + +func SetDefaultUnit(unit rune) { + if unit == 0 { + defUnit = 'B' + } else if s := string(unit); len(s) < 1 { + defUnit = 'B' + } else { + defUnit = unit + } +} + +func GetSize(s string) (sizeBytes Size, success bool) { + if z, e := parseString(s); e != nil { + return SizeNul, false + } else { + return z, true + } +} + +func SizeFromInt64(val int64) Size { + v := uint64(val) + return Size(v) +} + +func Parse(s string) (Size, error) { + return parseString(s) +} + +func ParseSize(s string) (Size, error) { + return parseString(s) +} + +func ParseByteAsSize(p []byte) (Size, error) { + return parseBytes(p) +} diff --git a/size/model.go b/size/model.go new file mode 100644 index 00000000..c8e79972 --- /dev/null +++ b/size/model.go @@ -0,0 +1,123 @@ +/*********************************************************************************************************************** + * + * 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 bytes + +import ( + "fmt" + "math" + "reflect" + + libmap "github.com/mitchellh/mapstructure" +) + +func (s Size) Code(unit rune) string { + var uni string + + if unit == 0 { + uni = "%s" + string(defUnit) + } else { + uni = "%s" + string(unit) + } + + switch s { + case SizeUnit: + return fmt.Sprintf(uni, "") + case SizeKilo: + return fmt.Sprintf(uni, "K") + case SizeMega: + return fmt.Sprintf(uni, "M") + case SizeGiga: + return fmt.Sprintf(uni, "G") + case SizeTera: + return fmt.Sprintf(uni, "T") + case SizePeta: + return fmt.Sprintf(uni, "E") + } + + return fmt.Sprintf(uni, "") +} + +func (s Size) isMax(size Size) bool { + val := math.Abs(size.Float64()) + uni := math.Abs(s.Float64()) + return val >= (10 * uni) +} + +func (s Size) sizeByUnit(unit Size) float64 { + if s > 0 && uint64(s/unit) > _maxFloat { + // overflow + return math.MaxFloat64 + } else if s < 0 && uint64(-s/unit) > _maxFloat { + // overflow + return -math.MaxFloat64 + } else { + return float64(s / unit) + } +} + +func (s Size) floorByUnit(unit Size) uint64 { + return uint64(math.Floor(s.sizeByUnit(unit))) +} + +func (s *Size) unmarshall(val []byte) error { + if tmp, err := parseBytes(val); err != nil { + return err + } else { + *s = tmp + return nil + } +} + +func ViperDecoderHook() libmap.DecodeHookFuncType { + return func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) { + var ( + z = Size(0) + t string + k bool + ) + + // Check if the data type matches the expected one + if from.Kind() != reflect.String { + return data, nil + } else if t, k = data.(string); !k { + return data, nil + } + + // Check if the target type matches the expected one + if to != reflect.TypeOf(z) { + return data, nil + } + + // Format/decode/parse the data and return the new value + if e := z.unmarshall([]byte(t)); e != nil { + return nil, e + } else { + return z, nil + } + } +} diff --git a/size/parse.go b/size/parse.go new file mode 100644 index 00000000..ce74be1d --- /dev/null +++ b/size/parse.go @@ -0,0 +1,257 @@ +/*********************************************************************************************************************** + * + * 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 bytes + +import ( + "errors" + "fmt" + "strings" +) + +// Based on ParseDuration from time package + +var unitMap = map[string]uint64{ + "B": uint64(SizeUnit), + "b": uint64(SizeUnit), + "k": uint64(SizeKilo), + "K": uint64(SizeKilo), + "kb": uint64(SizeKilo), + "Kb": uint64(SizeKilo), + "kB": uint64(SizeKilo), + "KB": uint64(SizeKilo), + "m": uint64(SizeMega), + "M": uint64(SizeMega), + "mb": uint64(SizeMega), + "Mb": uint64(SizeMega), + "mB": uint64(SizeMega), + "MB": uint64(SizeMega), + "g": uint64(SizeGiga), + "G": uint64(SizeGiga), + "gb": uint64(SizeGiga), + "Gb": uint64(SizeGiga), + "gB": uint64(SizeGiga), + "GB": uint64(SizeGiga), + "t": uint64(SizeTera), + "T": uint64(SizeTera), + "tb": uint64(SizeTera), + "Tb": uint64(SizeTera), + "tB": uint64(SizeTera), + "TB": uint64(SizeTera), + "p": uint64(SizePeta), + "P": uint64(SizePeta), + "pb": uint64(SizePeta), + "Pb": uint64(SizePeta), + "pB": uint64(SizePeta), + "PB": uint64(SizePeta), + // no e/E to prevent mismatching with notation 1.23E45/1.23e+45/1.23E-45 + "eb": uint64(SizeExa), + "Eb": uint64(SizeExa), + "eB": uint64(SizeExa), + "EB": uint64(SizeExa), +} + +func parseBytes(p []byte) (Size, error) { + return parseString(string(p)) +} + +func parseString(s string) (Size, error) { + // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ + + var ( + orig = s + neg bool + + d uint64 + errInvalid = fmt.Errorf("size: invalid size '%s'", orig) + errUnit = fmt.Errorf("size: missing unit '%s'", orig) + errUnkUnit = fmt.Errorf("size: unknown unit '%s'", orig) + ) + + s = strings.TrimSpace(s) + s = strings.Trim(s, "\"") + s = strings.Trim(s, "'") + s = strings.TrimSpace(s) + + // Consume [-+]? + if s != "" { + c := s[0] + if c == '-' || c == '+' { + neg = c == '-' + s = s[1:] + } + } + + // Special case: if all that is left is "0", this is zero. + if s == "" { + return 0, errInvalid + } + + for s != "" { + var ( + v, f uint64 // integers before, after decimal point + scale float64 = 1 // value = v + f/scale + ) + + var err error + + // The next character must be [0-9.] + if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { + return 0, errInvalid + } + + // Consume [0-9]* + pl := len(s) + v, s, err = leadingInt(s) + if err != nil { + return 0, errInvalid + } + pre := pl != len(s) // whether we consumed anything before a period + + // Consume (\.[0-9]*)? + post := false + if s != "" && s[0] == '.' { + s = s[1:] + pl := len(s) + f, scale, s = leadingFraction(s) + post = pl != len(s) + } + if !pre && !post { + // no digits (e.g. ".s" or "-.s") + return 0, errInvalid + } + + // Consume unit. + i := 0 + + for ; i < len(s); i++ { + c := s[i] + if c == '.' || '0' <= c && c <= '9' { + break + } + } + + if i == 0 { + return 0, errUnit + } + + u := strings.TrimSpace(s[:i]) + s = s[i:] + unit, ok := unitMap[u] + + if !ok { + return 0, errUnkUnit + } + + if v > 1<<63/unit { + // overflow + return 0, errInvalid + } + + v *= unit + if f > 0 { + // float64 is needed to be nanosecond accurate for fractions of hours. + // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) + v += uint64(float64(f) * (float64(unit) / scale)) + + if v > 1<<63 { + // overflow + return 0, errInvalid + } + } + d += v + + if d > 1<<63 { + return 0, errInvalid + } + } + + if neg { + return -Size(d), nil + } + + if d > 1<<63-1 { + return 0, errInvalid + } + + return Size(d), nil + +} + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x uint64, rem string, err error) { + var errLeadingInt = errors.New("size: bad [0-9]*") // never printed + + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x > 1<<63/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + uint64(c) - '0' + if x > 1<<63 { + // overflow + return 0, "", errLeadingInt + } + } + return x, s[i:], nil +} + +// leadingFraction consumes the leading [0-9]* from s. +// It is used only for fractions, so does not return an error on overflow, +// it just stops accumulating precision. +func leadingFraction(s string) (x uint64, scale float64, rem string) { + i := 0 + scale = 1 + overflow := false + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if overflow { + continue + } + if x > (1<<63-1)/10 { + // It's possible for overflow to give a positive number, so take care. + overflow = true + continue + } + y := x*10 + uint64(c) - '0' + if y > 1<<63 { + overflow = true + continue + } + x = y + scale *= 10 + } + return x, scale, s[i:] +} diff --git a/smtp/client.go b/smtp/client.go new file mode 100644 index 00000000..016d6082 --- /dev/null +++ b/smtp/client.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 smtp + +import ( + "context" + "fmt" + "io" + "net/smtp" + + liberr "github.com/nabbar/golib/errors" + smtpnt "github.com/nabbar/golib/smtp/network" +) + +func (s *smtpClient) _client(ctx context.Context) (*smtp.Client, liberr.Error) { + if s.cli != nil && s.con != nil { + return s.cli, nil + } + + if s.con == nil && s.cli != nil { + if e := s.cli.Quit(); e != nil { + _ = s.cli.Close() + } + } else if s.con != nil && s.cli == nil { + _ = s.con.Close() + } + + s.cli = nil + s.con = nil + + var ( + addr = s.cfg.GetHost() + tlsc = s.tls.Clone() + ) + + if s.cfg.GetTlSServerName() != "" && s.cfg.GetNet() != smtpnt.NetworkUnixSocket { + tlsc.ServerName = s.cfg.GetTlSServerName() + } + + if s.cfg.IsTLSSkipVerify() && s.cfg.GetNet() != smtpnt.NetworkUnixSocket { + tlsc.InsecureSkipVerify = true + } + + if s.cfg.GetPort() > 0 { + addr = fmt.Sprintf("%s:%v", s.cfg.GetHost(), s.cfg.GetPort()) + } + + if cli, con, err := s.tryClient(ctx, addr, tlsc); err != nil { + return nil, err + } else if err = s.auth(cli, addr); err != nil { + return nil, err + } else { + s.con = con + s.cli = cli + } + + return s.cli, nil +} + +// Client Get SMTP Client interface. +func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, liberr.Error) { + s.mut.Lock() + defer s.mut.Unlock() + + return s._client(ctx) +} + +// Check Try to initiate SMTP dial and negotiation and try to close connection. +func (s *smtpClient) Check(ctx context.Context) liberr.Error { + s.mut.Lock() + defer func() { + s._close() + s.mut.Unlock() + }() + + if c, err := s._client(ctx); err != nil { + return err + } else if e := c.Noop(); e != nil { + return ErrorSMTPClientNoop.ErrorParent(e) + } + + return nil +} + +// Send is used to initiate the smtp connection with the client and send a mail before closing the connection. +// This function is based on smtp.SendMail function. +func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io.WriterTo) liberr.Error { + //from smtp.SendMail() + + var ( + e error + c *smtp.Client + w io.WriteCloser + + err liberr.Error + ) + + s.mut.Lock() + + defer func() { + if w != nil { + _ = w.Close() + } + + //mandatory for SMTP protocol + s._close() + + s.mut.Unlock() + }() + + if err = s._ValidateLine(from); err != nil { + return err + } + + for _, recp := range to { + if err = s._ValidateLine(recp); err != nil { + return err + } + } + + if c, err = s._client(ctx); err != nil { + return ErrorSMTPClientInit.Error(err) + } + + if e = c.Noop(); e != nil { + return ErrorSMTPClientNoop.ErrorParent(e) + } + + if e = c.Mail(from); e != nil { + return ErrorSMTPClientMail.ErrorParent(e) + } + + for _, addr := range to { + if e = c.Rcpt(addr); e != nil { + return ErrorSMTPClientRcpt.ErrorParent(e) + } + } + + if w, e = c.Data(); e != nil { + return ErrorSMTPClientData.ErrorParent(e) + } + + if _, e = data.WriteTo(w); e != nil { + return ErrorSMTPClientWrite.ErrorParent(e) + } + + return nil +} diff --git a/smtp/config.go b/smtp/config.go deleted file mode 100644 index 884232c4..00000000 --- a/smtp/config.go +++ /dev/null @@ -1,254 +0,0 @@ -/* -MIT License - -Copyright (c) 2019 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 smtp - -import ( - "bytes" - "fmt" - "net/url" - - libsts "github.com/nabbar/golib/status/config" - - libval "github.com/go-playground/validator/v10" - libtls "github.com/nabbar/golib/certificates" - liberr "github.com/nabbar/golib/errors" -) - -type ConfigModel struct { - DSN string `json:"dsn" yaml:"dsn" toml:"dsn" mapstructure:"dsn"` - TLS libtls.Config `json:"tls,omitempty" yaml:"tls,omitempty" toml:"tls,omitempty" mapstructure:"tls,omitempty"` - Status libsts.ConfigStatus `json:"status,omitempty" yaml:"status,omitempty" toml:"status,omitempty" mapstructure:"status,omitempty"` - - _tls func() libtls.TLSConfig -} - -func (c ConfigModel) Validate() liberr.Error { - err := ErrorConfigValidator.Error(nil) - - if er := libval.New().Struct(c); er != nil { - if e, ok := er.(*libval.InvalidValidationError); ok { - err.AddParent(e) - } - - for _, e := range er.(libval.ValidationErrors) { - //nolint goerr113 - err.AddParent(fmt.Errorf("config field '%s' is not validated by constraint '%s'", e.Namespace(), e.ActualTag())) - } - } - - if c.DSN != "" { - if _, er := NewConfig(c); er != nil { - err.AddParent(er) - } - } else { - err.AddParent(ErrorConfigInvalidDSN.Error(nil)) - } - - if !err.HasParent() { - err = nil - } - - return err -} - -func (c ConfigModel) RegisterDefaultTLS(fct func() libtls.TLSConfig) { - c._tls = fct -} - -func (c ConfigModel) GetSMTP() (SMTP, liberr.Error) { - if c._tls == nil { - return c.SMTP(nil) - } - - return c.SMTP(c._tls()) -} - -func (c ConfigModel) SMTP(tlsDefault libtls.TLSConfig) (SMTP, liberr.Error) { - var ( - err liberr.Error - cfg Config - tls libtls.TLSConfig - ) - - cfg, err = NewConfig(c) - if err != nil { - return nil, err - } - - tls, err = c.TLS.NewFrom(tlsDefault) - if err != nil { - return nil, err - } - - return NewSMTP(cfg, tls.TlsConfig("")) -} - -type smtpConfig struct { - DSN string - Host string - Port int - User string - Pass string - Net NETMode - TLS TLSMode - SkipVerify bool - ServerName string - - TLSCfg libtls.Config - Status libsts.ConfigStatus -} - -func (c *smtpConfig) SetHost(host string) { - c.Host = host -} - -func (c *smtpConfig) GetHost() string { - return c.Host -} - -func (c *smtpConfig) SetPort(port int) { - c.Port = port -} - -func (c *smtpConfig) GetPort() int { - return c.Port -} - -func (c *smtpConfig) SetUser(user string) { - c.User = user -} - -func (c *smtpConfig) GetUser() string { - return c.User -} - -func (c *smtpConfig) SetPass(pass string) { - c.Pass = pass -} - -func (c *smtpConfig) GetPass() string { - return c.Pass -} - -func (c *smtpConfig) SetNet(mode NETMode) { - c.Net = mode -} - -func (c *smtpConfig) GetNet() NETMode { - return c.Net -} - -func (c *smtpConfig) SetTlsMode(mode TLSMode) { - c.TLS = mode -} - -func (c *smtpConfig) GetTlsMode() TLSMode { - return c.TLS -} - -func (c *smtpConfig) SetTls(tls libtls.Config) { - c.TLSCfg = tls -} - -func (c *smtpConfig) GetTls() libtls.Config { - return c.TLSCfg -} - -func (c *smtpConfig) ForceTLSSkipVerify(skip bool) { - c.SkipVerify = skip -} - -func (c *smtpConfig) IsTLSSkipVerify() bool { - return c.SkipVerify -} - -func (c *smtpConfig) SetTLSServerName(serverName string) { - c.ServerName = serverName -} - -func (c *smtpConfig) GetTlSServerName() string { - return c.ServerName -} - -// GetDsn Return a correct SMTP DSN. -func (c *smtpConfig) GetDsn() string { - var buf bytes.Buffer - - // [username[:password]@] - if len(c.User) > 0 { - _, _ = buf.WriteString(c.User) - if len(c.Pass) > 0 { - _ = buf.WriteByte(':') - _, _ = buf.WriteString(c.Pass) - } - _ = buf.WriteByte('@') - } - - // [protocol[(address)]] - _, _ = buf.WriteString(c.Net.string()) - - // [username[:password]@] - if len(c.Host) > 0 { - _ = buf.WriteByte('(') - _, _ = buf.WriteString(c.Host) - if c.Port > 0 { - _ = buf.WriteByte(':') - _, _ = buf.WriteString(fmt.Sprintf("%d", c.Port)) - } - _ = buf.WriteByte(')') - } - - // /tlsmode - _ = buf.WriteByte('/') - _, _ = buf.WriteString(c.TLS.string()) - - // [?param1=value1&...¶mN=valueN] - var val = &url.Values{} - - if c.ServerName != "" { - val.Add("ServerName", c.ServerName) - } - - if c.SkipVerify { - val.Add("SkipVerify", "true") - } - - params := val.Encode() - - // nolint: gomnd - if len(params) > 2 { - _, _ = buf.WriteString("?" + params) - } - - return buf.String() -} - -func (c *smtpConfig) SetStatusConfig(sts libsts.ConfigStatus) { - c.Status = sts -} - -func (c *smtpConfig) GetStatusConfig() libsts.ConfigStatus { - return c.Status -} diff --git a/smtp/config/config.go b/smtp/config/config.go new file mode 100644 index 00000000..992be551 --- /dev/null +++ b/smtp/config/config.go @@ -0,0 +1,74 @@ +/* +MIT License + +Copyright (c) 2019 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 ( + "fmt" + + libmon "github.com/nabbar/golib/monitor/types" + + libval "github.com/go-playground/validator/v10" + libtls "github.com/nabbar/golib/certificates" + liberr "github.com/nabbar/golib/errors" +) + +type ConfigModel struct { + DSN string `json:"dsn" yaml:"dsn" toml:"dsn" mapstructure:"dsn"` + TLS libtls.Config `json:"tls,omitempty" yaml:"tls,omitempty" toml:"tls,omitempty" mapstructure:"tls,omitempty"` + Monitor libmon.Config `json:"monitor,omitempty" yaml:"monitor,omitempty" toml:"monitor,omitempty" mapstructure:"monitor,omitempty"` +} + +func (c ConfigModel) Validate() liberr.Error { + err := ErrorConfigValidator.Error(nil) + + if er := libval.New().Struct(c); er != nil { + if e, ok := er.(*libval.InvalidValidationError); ok { + err.AddParent(e) + } + + for _, e := range er.(libval.ValidationErrors) { + //nolint goerr113 + err.AddParent(fmt.Errorf("config field '%s' is not validated by constraint '%s'", e.Namespace(), e.ActualTag())) + } + } + + if c.DSN != "" { + if _, er := New(c); er != nil { + err.AddParent(er) + } + } else { + err.AddParent(ErrorConfigInvalidDSN.Error(nil)) + } + + if !err.HasParent() { + err = nil + } + + return err +} + +func (c ConfigModel) Config() (Config, liberr.Error) { + return New(c) +} diff --git a/smtp/config/error.go b/smtp/config/error.go new file mode 100644 index 00000000..b2bfe072 --- /dev/null +++ b/smtp/config/error.go @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2020 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 ( + "fmt" + + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgSMTPConfig + ErrorConfigValidator + ErrorConfigInvalidDSN + ErrorConfigInvalidNetwork + ErrorConfigInvalidParams + ErrorConfigInvalidHost +) + +func init() { + if liberr.ExistInMapMessage(ErrorParamEmpty) { + panic(fmt.Errorf("error code collision with package golib/smtp")) + } + liberr.RegisterIdFctMessage(ErrorParamEmpty, getMessage) +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamEmpty: + return "given parameters is empty" + case ErrorConfigValidator: + return "invalid config, validation error" + case ErrorConfigInvalidDSN: + return "invalid DSN: did you forget to escape a param value" + case ErrorConfigInvalidNetwork: + return "invalid DSN: network address not terminated (missing closing brace)" + case ErrorConfigInvalidParams: + return "invalid DSN: parsing uri parameters occurs an error" + case ErrorConfigInvalidHost: + return "invalid DSN: missing the slash ending the host" + } + + return liberr.NullMessage +} diff --git a/smtp/config/interface.go b/smtp/config/interface.go new file mode 100644 index 00000000..d14bbc97 --- /dev/null +++ b/smtp/config/interface.go @@ -0,0 +1,191 @@ +/* + * 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 ( + "net" + "net/url" + "strconv" + "strings" + + "github.com/nabbar/golib/smtp/network" + smtptp "github.com/nabbar/golib/smtp/tlsmode" + + libtls "github.com/nabbar/golib/certificates" + liberr "github.com/nabbar/golib/errors" +) + +type SMTP interface { + GetHost() string + GetPort() int + GetUser() string + GetPass() string + GetNet() network.NetworkMode + + GetTls() libtls.Config + GetTlSServerName() string + GetTlsMode() smtptp.TLSMode + SetTlsMode(mode smtptp.TLSMode) + + IsTLSSkipVerify() bool + GetDsn() string +} + +type Config interface { + SMTP + + SetHost(host string) + SetPort(port int) + SetUser(user string) + SetPass(pass string) + SetNet(mode network.NetworkMode) + SetTls(tls libtls.Config) + + ForceTLSSkipVerify(skip bool) + SetTLSServerName(serverName string) +} + +// New parses the DSN string to a Config. +// nolint: gocognit +func New(cfg ConfigModel) (Config, liberr.Error) { + var ( + dsn = cfg.DSN + smtpcnf = &smtpConfig{ + DSN: dsn, + TLSCfg: cfg.TLS, + } + ) + + // [user[:password]@][net[(addr)]]/tlsmode[?param1=value1¶mN=valueN] + // Find the last '/' (since the password or the net addr might contain a '/') + if !strings.ContainsRune(dsn, '?') && !strings.ContainsRune(dsn, '/') { + dsn += "/" + } else if strings.ContainsRune(dsn, '?') && !strings.ContainsRune(dsn, '/') { + v := strings.Split(dsn, "?") + v[len(v)-2] += "/" + dsn = strings.Join(v, "?") + } + + foundSlash := false + for i := len(dsn) - 1; i >= 0; i-- { + if dsn[i] == '/' { + foundSlash = true + var j, k int + + // left part is empty if i <= 0 + if i > 0 { + // [username[:password]@][protocol[(address)]] + // Find the last '@' in dsn[:i] + for j = i; j >= 0; j-- { + if dsn[j] == '@' { + // username[:password] + // Find the first ':' in dsn[:j] + for k = 0; k < j; k++ { + if dsn[k] == ':' { + smtpcnf.Pass = dsn[k+1 : j] + break + } + } + smtpcnf.User = dsn[:k] + + break + } + } + + // [protocol[(address)]] + // Find the first '(' in dsn[j+1:i] + for k = j + 1; k < i; k++ { + if dsn[k] == '(' { + // dsn[i-1] must be == ')' if an address is specified + if dsn[i-1] != ')' { + if strings.ContainsRune(dsn[k+1:i], ')') { + return nil, ErrorConfigInvalidDSN.Error(nil) + } + return nil, ErrorConfigInvalidNetwork.Error(nil) + } + + if strings.ContainsRune(dsn[k+1:i-1], ':') { + h, p, e := net.SplitHostPort(dsn[k+1 : i-1]) + if e == nil && p != "" { + pint, er := strconv.ParseInt(p, 10, 64) + if er == nil { + if pint <= 65535 { + smtpcnf.Port = int(pint) + } + } + smtpcnf.Host = h + } + } + + if smtpcnf.Host == "" || smtpcnf.Port == 0 { + smtpcnf.Host = dsn[k+1 : i-1] + } + + break + } + } + + smtpcnf.Net = network.NetworkModeFromString(dsn[j+1 : k]) + + } + + // [?param1=value1&...¶mN=valueN] + // Find the first '?' in dsn[i+1:] + for j = i + 1; j < len(dsn); j++ { + if dsn[j] == '?' { + + if val, err := url.ParseQuery(dsn[j+1:]); err != nil { + return nil, ErrorConfigInvalidParams.ErrorParent(err) + } else { + + if val.Get("ServerName") != "" { + smtpcnf.ServerName = val.Get("ServerName") + } + + if val.Get("SkipVerify") != "" { + vi, e := strconv.ParseBool(val.Get("SkipVerify")) + if e == nil { + smtpcnf.SkipVerify = vi + } + } + } + + break + } + } + + smtpcnf.TLS = smtptp.TLSModeFromString(dsn[i+1 : j]) + break + } + } + + if !foundSlash && len(dsn) > 0 { + return nil, ErrorConfigInvalidHost.Error(nil) + } + + return smtpcnf, nil +} diff --git a/smtp/config/model.go b/smtp/config/model.go new file mode 100644 index 00000000..37cebeef --- /dev/null +++ b/smtp/config/model.go @@ -0,0 +1,177 @@ +/* + * 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" + "fmt" + "net/url" + + "github.com/nabbar/golib/smtp/network" + smtptp "github.com/nabbar/golib/smtp/tlsmode" + + libtls "github.com/nabbar/golib/certificates" +) + +type smtpConfig struct { + DSN string + Host string + Port int + User string + Pass string + Net network.NetworkMode + TLS smtptp.TLSMode + SkipVerify bool + ServerName string + + TLSCfg libtls.Config +} + +func (c *smtpConfig) SetHost(host string) { + c.Host = host +} + +func (c *smtpConfig) GetHost() string { + return c.Host +} + +func (c *smtpConfig) SetPort(port int) { + c.Port = port +} + +func (c *smtpConfig) GetPort() int { + return c.Port +} + +func (c *smtpConfig) SetUser(user string) { + c.User = user +} + +func (c *smtpConfig) GetUser() string { + return c.User +} + +func (c *smtpConfig) SetPass(pass string) { + c.Pass = pass +} + +func (c *smtpConfig) GetPass() string { + return c.Pass +} + +func (c *smtpConfig) SetNet(mode network.NetworkMode) { + c.Net = mode +} + +func (c *smtpConfig) GetNet() network.NetworkMode { + return c.Net +} + +func (c *smtpConfig) SetTlsMode(mode smtptp.TLSMode) { + c.TLS = mode +} + +func (c *smtpConfig) GetTlsMode() smtptp.TLSMode { + return c.TLS +} + +func (c *smtpConfig) SetTls(tls libtls.Config) { + c.TLSCfg = tls +} + +func (c *smtpConfig) GetTls() libtls.Config { + return c.TLSCfg +} + +func (c *smtpConfig) ForceTLSSkipVerify(skip bool) { + c.SkipVerify = skip +} + +func (c *smtpConfig) IsTLSSkipVerify() bool { + return c.SkipVerify +} + +func (c *smtpConfig) SetTLSServerName(serverName string) { + c.ServerName = serverName +} + +func (c *smtpConfig) GetTlSServerName() string { + return c.ServerName +} + +// GetDsn Return a correct SMTP DSN. +func (c *smtpConfig) GetDsn() string { + var buf bytes.Buffer + + // [username[:password]@] + if len(c.User) > 0 { + _, _ = buf.WriteString(c.User) + if len(c.Pass) > 0 { + _ = buf.WriteByte(':') + _, _ = buf.WriteString(c.Pass) + } + _ = buf.WriteByte('@') + } + + // [protocol[(address)]] + _, _ = buf.WriteString(c.Net.String()) + + // [username[:password]@] + if len(c.Host) > 0 { + _ = buf.WriteByte('(') + _, _ = buf.WriteString(c.Host) + if c.Port > 0 { + _ = buf.WriteByte(':') + _, _ = buf.WriteString(fmt.Sprintf("%d", c.Port)) + } + _ = buf.WriteByte(')') + } + + // /tlsmode + _ = buf.WriteByte('/') + _, _ = buf.WriteString(c.TLS.String()) + + // [?param1=value1&...¶mN=valueN] + var val = &url.Values{} + + if c.ServerName != "" { + val.Add("ServerName", c.ServerName) + } + + if c.SkipVerify { + val.Add("SkipVerify", "true") + } + + params := val.Encode() + + // nolint: gomnd + if len(params) > 2 { + _, _ = buf.WriteString("?" + params) + } + + return buf.String() +} diff --git a/smtp/dial.go b/smtp/dial.go index 1c7297b8..563a5347 100644 --- a/smtp/dial.go +++ b/smtp/dial.go @@ -32,6 +32,7 @@ import ( "net/smtp" liberr "github.com/nabbar/golib/errors" + smtptl "github.com/nabbar/golib/smtp/tlsmode" ) func (s *smtpClient) dialTLS(ctx context.Context, addr string, tlsConfig *tls.Config) (con net.Conn, err liberr.Error) { @@ -63,7 +64,7 @@ func (s *smtpClient) dial(ctx context.Context, addr string) (con net.Conn, err l d := net.Dialer{} - if con, e = d.DialContext(ctx, s.cfg.GetNet().string(), addr); e != nil { + if con, e = d.DialContext(ctx, s.cfg.GetNet().String(), addr); e != nil { return con, ErrorSMTPDial.ErrorParent(e) } @@ -84,15 +85,15 @@ func (s *smtpClient) client(ctx context.Context, addr string, tlsConfig *tls.Con } }() - if s.cfg.GetTlsMode() == TLS_STARTTLS && tlsConfig == nil { + if s.cfg.GetTlsMode() == smtptl.TLSStartTLS && tlsConfig == nil { err = ErrorParamEmpty.Error(nil) return - } else if s.cfg.GetTlsMode() == TLS_TLS && tlsConfig == nil { + } else if s.cfg.GetTlsMode() == smtptl.TLSStrictTLS && tlsConfig == nil { err = ErrorParamEmpty.Error(nil) return } - if s.cfg.GetTlsMode() == TLS_TLS && tlsConfig != nil { + if s.cfg.GetTlsMode() == smtptl.TLSStrictTLS && tlsConfig != nil { if con, err = s.dialTLS(ctx, addr, tlsConfig); err != nil { return } else if cli, e = smtp.NewClient(con, addr); e != nil { @@ -109,12 +110,12 @@ func (s *smtpClient) client(ctx context.Context, addr string, tlsConfig *tls.Con try := s.checkExtension(cli, "STARTTLS") - if s.cfg.GetTlsMode() == TLS_STARTTLS || try { + if s.cfg.GetTlsMode() == smtptl.TLSStartTLS || try { if e = cli.StartTLS(tlsConfig); e != nil && !try { err = ErrorSMTPClientStartTLS.ErrorParent(e) return } else if e == nil && try { - s.cfg.SetTlsMode(TLS_STARTTLS) + s.cfg.SetTlsMode(smtptl.TLSStartTLS) } } } @@ -130,10 +131,10 @@ func (s *smtpClient) tryClient(ctx context.Context, addr string, tlsConfig *tls. } switch s.cfg.GetTlsMode() { - case TLS_TLS: - s.cfg.SetTlsMode(TLS_STARTTLS) + case smtptl.TLSStrictTLS: + s.cfg.SetTlsMode(smtptl.TLSStartTLS) return s.tryClient(ctx, addr, tlsConfig) - case TLS_STARTTLS, TLS_NONE: + case smtptl.TLSStartTLS, smtptl.TLSNone: return } diff --git a/smtp/error.go b/smtp/error.go index fe3d39ee..1716f8f2 100644 --- a/smtp/error.go +++ b/smtp/error.go @@ -34,11 +34,6 @@ import ( const ( ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgSMTP - ErrorConfigValidator - ErrorConfigInvalidDSN - ErrorConfigInvalidNetwork - ErrorConfigInvalidParams - ErrorConfigInvalidHost ErrorSMTPDial ErrorSMTPClientInit ErrorSMTPClientStartTLS @@ -62,16 +57,6 @@ func getMessage(code liberr.CodeError) (message string) { switch code { case ErrorParamEmpty: return "given parameters is empty" - case ErrorConfigValidator: - return "invalid config, validation error" - case ErrorConfigInvalidDSN: - return "invalid DSN: did you forget to escape a param value" - case ErrorConfigInvalidNetwork: - return "invalid DSN: network address not terminated (missing closing brace)" - case ErrorConfigInvalidParams: - return "invalid DSN: parsing uri parameters occurs an error" - case ErrorConfigInvalidHost: - return "invalid DSN: missing the slash ending the host" case ErrorSMTPDial: return "error while trying to dial with SMTP server" case ErrorSMTPClientInit: diff --git a/smtp/interface.go b/smtp/interface.go index 5d33830a..654df5f4 100644 --- a/smtp/interface.go +++ b/smtp/interface.go @@ -29,74 +29,35 @@ import ( "context" "crypto/tls" "io" - "net" "net/smtp" - "net/url" - "strconv" - "strings" "sync" - "github.com/nabbar/golib/status/config" - - libtls "github.com/nabbar/golib/certificates" - + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" - libsts "github.com/nabbar/golib/status" + montps "github.com/nabbar/golib/monitor/types" + smtpcf "github.com/nabbar/golib/smtp/config" + libver "github.com/nabbar/golib/version" ) -type Config interface { - SetHost(host string) - GetHost() string - - SetPort(port int) - GetPort() int - - SetUser(user string) - GetUser() string - - SetPass(pass string) - GetPass() string - - SetNet(mode NETMode) - GetNet() NETMode - - SetTlsMode(mode TLSMode) - GetTlsMode() TLSMode - - SetTls(tls libtls.Config) - GetTls() libtls.Config - - ForceTLSSkipVerify(skip bool) - IsTLSSkipVerify() bool - - SetTLSServerName(serverName string) - GetTlSServerName() string - - SetStatusConfig(sts config.ConfigStatus) - GetStatusConfig() config.ConfigStatus - - GetDsn() string -} - type SMTP interface { Clone() SMTP Close() + UpdConfig(cfg smtpcf.SMTP, tslConfig *tls.Config) + Client(ctx context.Context) (*smtp.Client, liberr.Error) Check(ctx context.Context) liberr.Error Send(ctx context.Context, from string, to []string, data io.WriterTo) liberr.Error - StatusInfo() (name string, release string, hash string) - StatusHealth() error - StatusRouter(sts libsts.RouteStatus, prefix string) + Monitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) } -// NewSMTP return a SMTP interface to operation negotiation with a SMTP server. +// New return a SMTP interface to operation negotiation with a SMTP server. // the dsn parameter must be string like this '[user[:password]@][net[(addr)]]/tlsmode[?param1=value1¶mN=valueN]". // - params available are : ServerName (string), SkipVerify (boolean). // - tls mode acceptable are : starttls, tls, . -// - net aceeptable are : tcp4, tcp6, unix. -func NewSMTP(cfg Config, tlsConfig *tls.Config) (SMTP, liberr.Error) { +// - net acceptable are : tcp4, tcp6, unix. +func New(cfg smtpcf.SMTP, tlsConfig *tls.Config) (SMTP, liberr.Error) { if tlsConfig == nil { /* #nosec */ //nolint #nosec @@ -113,125 +74,3 @@ func NewSMTP(cfg Config, tlsConfig *tls.Config) (SMTP, liberr.Error) { }, nil } } - -// NewConfig parses the DSN string to a Config. -// nolint: gocognit -func NewConfig(cfg ConfigModel) (Config, liberr.Error) { - var ( - dsn = cfg.DSN - smtpcnf = &smtpConfig{ - DSN: dsn, - TLSCfg: cfg.TLS, - Status: cfg.Status, - } - ) - - // [user[:password]@][net[(addr)]]/tlsmode[?param1=value1¶mN=valueN] - // Find the last '/' (since the password or the net addr might contain a '/') - if !strings.ContainsRune(dsn, '?') && !strings.ContainsRune(dsn, '/') { - dsn += "/" - } else if strings.ContainsRune(dsn, '?') && !strings.ContainsRune(dsn, '/') { - v := strings.Split(dsn, "?") - v[len(v)-2] += "/" - dsn = strings.Join(v, "?") - } - - foundSlash := false - for i := len(dsn) - 1; i >= 0; i-- { - if dsn[i] == '/' { - foundSlash = true - var j, k int - - // left part is empty if i <= 0 - if i > 0 { - // [username[:password]@][protocol[(address)]] - // Find the last '@' in dsn[:i] - for j = i; j >= 0; j-- { - if dsn[j] == '@' { - // username[:password] - // Find the first ':' in dsn[:j] - for k = 0; k < j; k++ { - if dsn[k] == ':' { - smtpcnf.Pass = dsn[k+1 : j] - break - } - } - smtpcnf.User = dsn[:k] - - break - } - } - - // [protocol[(address)]] - // Find the first '(' in dsn[j+1:i] - for k = j + 1; k < i; k++ { - if dsn[k] == '(' { - // dsn[i-1] must be == ')' if an address is specified - if dsn[i-1] != ')' { - if strings.ContainsRune(dsn[k+1:i], ')') { - return nil, ErrorConfigInvalidDSN.Error(nil) - } - return nil, ErrorConfigInvalidNetwork.Error(nil) - } - - if strings.ContainsRune(dsn[k+1:i-1], ':') { - h, p, e := net.SplitHostPort(dsn[k+1 : i-1]) - if e == nil && p != "" { - pint, er := strconv.ParseInt(p, 10, 64) - if er == nil { - if pint <= 65535 { - smtpcnf.Port = int(pint) - } - } - smtpcnf.Host = h - } - } - - if smtpcnf.Host == "" || smtpcnf.Port == 0 { - smtpcnf.Host = dsn[k+1 : i-1] - } - - break - } - } - - smtpcnf.Net = parseNETMode(dsn[j+1 : k]) - - } - - // [?param1=value1&...¶mN=valueN] - // Find the first '?' in dsn[i+1:] - for j = i + 1; j < len(dsn); j++ { - if dsn[j] == '?' { - - if val, err := url.ParseQuery(dsn[j+1:]); err != nil { - return nil, ErrorConfigInvalidParams.ErrorParent(err) - } else { - - if val.Get("ServerName") != "" { - smtpcnf.ServerName = val.Get("ServerName") - } - - if val.Get("SkipVerify") != "" { - vi, e := strconv.ParseBool(val.Get("SkipVerify")) - if e == nil { - smtpcnf.SkipVerify = vi - } - } - } - - break - } - } - - smtpcnf.TLS = parseTLSMode(dsn[i+1 : j]) - break - } - } - - if !foundSlash && len(dsn) > 0 { - return nil, ErrorConfigInvalidHost.Error(nil) - } - - return smtpcnf, nil -} diff --git a/smtp/model.go b/smtp/model.go index 44913e25..10343cf2 100644 --- a/smtp/model.go +++ b/smtp/model.go @@ -26,20 +26,14 @@ package smtp import ( - "context" "crypto/tls" - "fmt" - "io" "net" "net/smtp" - "runtime" "strings" "sync" - "time" - libsts "github.com/nabbar/golib/status" - - "github.com/nabbar/golib/errors" + liberr "github.com/nabbar/golib/errors" + smtpcf "github.com/nabbar/golib/smtp/config" ) type smtpClient struct { @@ -47,7 +41,29 @@ type smtpClient struct { con net.Conn cli *smtp.Client tls *tls.Config - cfg Config + cfg smtpcf.SMTP +} + +func (s *smtpClient) _ValidateLine(line string) liberr.Error { + if strings.ContainsAny(line, "\n\r") { + return ErrorSMTPLineCRLF.Error(nil) + } + + return nil +} + +func (s *smtpClient) _close() { + if s.cli != nil { + if e := s.cli.Quit(); e != nil { + _ = s.cli.Close() + } + s.cli = nil + } + + if s.con != nil { + _ = s.con.Close() + s.con = nil + } } // Clone is used to clone current smtp pointer to a new one with same config. @@ -68,191 +84,13 @@ func (s *smtpClient) Close() { s._close() } -// Client Get SMTP Client interface. -func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, errors.Error) { - s.mut.Lock() - defer s.mut.Unlock() - - return s._client(ctx) -} - -// Check Try to initiate SMTP dial and negotiation and try to close connection. -func (s *smtpClient) Check(ctx context.Context) errors.Error { - s.mut.Lock() - defer func() { - s._close() - s.mut.Unlock() - }() - - if c, err := s._client(ctx); err != nil { - return err - } else if e := c.Noop(); e != nil { - return ErrorSMTPClientNoop.ErrorParent(e) - } - - return nil -} - -// Send is used to initiate the smtp connection with the client and send a mail before closing the connection. -// This function is based on smtp.SendMail function. -func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io.WriterTo) errors.Error { - //from smtp.SendMail() - - var ( - e error - c *smtp.Client - w io.WriteCloser - - err errors.Error - ) - - s.mut.Lock() - - defer func() { - if w != nil { - _ = w.Close() - } - - //mandatory for SMTP protocol - s._close() - - s.mut.Unlock() - }() - - if err = s._ValidateLine(from); err != nil { - return err - } - - for _, recp := range to { - if err = s._ValidateLine(recp); err != nil { - return err - } - } - - if c, err = s._client(ctx); err != nil { - return ErrorSMTPClientInit.Error(err) - } - - if e = c.Noop(); e != nil { - return ErrorSMTPClientNoop.ErrorParent(e) - } - - if e = c.Mail(from); e != nil { - return ErrorSMTPClientMail.ErrorParent(e) - } - - for _, addr := range to { - if e = c.Rcpt(addr); e != nil { - return ErrorSMTPClientRcpt.ErrorParent(e) - } - } - - if w, e = c.Data(); e != nil { - return ErrorSMTPClientData.ErrorParent(e) - } - - if _, e = data.WriteTo(w); e != nil { - return ErrorSMTPClientWrite.ErrorParent(e) - } - - return nil -} +// UpdConfig is used to update the config & TLS Config for an instance. +func (s *smtpClient) UpdConfig(cfg smtpcf.SMTP, tslConfig *tls.Config) { + s._close() -// StatusInfo is used to return the information part for the router status process -func (s *smtpClient) StatusInfo() (name string, release string, hash string) { s.mut.Lock() defer s.mut.Unlock() - hash = "" - release = strings.TrimLeft(strings.ToLower(runtime.Version()), "go") - name = fmt.Sprintf("SMTP %s:%d", s.cfg.GetHost(), s.cfg.GetPort()) - - return name, release, hash -} - -// StatusHealth is used to return the status of the SMTP connection to the server (with a timeout of 5 sec). -func (s *smtpClient) StatusHealth() error { - ctx, cnl := context.WithTimeout(context.Background(), 5*time.Second) - defer cnl() - - return s.Check(ctx) -} - -// StatusRouter is used to initiate a router status component and register it to the router status interface -func (s *smtpClient) StatusRouter(sts libsts.RouteStatus, prefix string) { - if prefix != "" { - prefix = fmt.Sprintf("%s SMTP %s:%d", prefix, s.cfg.GetHost(), s.cfg.GetPort()) - } else { - prefix = fmt.Sprintf("SMTP %s:%d", s.cfg.GetHost(), s.cfg.GetPort()) - } - - cfg := s.cfg.GetStatusConfig() - cfg.RegisterStatus(sts, prefix, s.StatusInfo, s.StatusHealth) -} - -func (s *smtpClient) _ValidateLine(line string) errors.Error { - if strings.ContainsAny(line, "\n\r") { - return ErrorSMTPLineCRLF.Error(nil) - } - - return nil -} - -func (s *smtpClient) _client(ctx context.Context) (*smtp.Client, errors.Error) { - if s.cli != nil && s.con != nil { - return s.cli, nil - } - - if s.con == nil && s.cli != nil { - if e := s.cli.Quit(); e != nil { - _ = s.cli.Close() - } - } else if s.con != nil && s.cli == nil { - _ = s.con.Close() - } - - s.cli = nil - s.con = nil - - var ( - addr = s.cfg.GetHost() - tlsc = s.tls.Clone() - ) - - if s.cfg.GetTlSServerName() != "" && s.cfg.GetNet() != NET_UNIX { - tlsc.ServerName = s.cfg.GetTlSServerName() - } - - if s.cfg.IsTLSSkipVerify() && s.cfg.GetNet() != NET_UNIX { - tlsc.InsecureSkipVerify = true - } - - if s.cfg.GetPort() > 0 { - addr = fmt.Sprintf("%s:%v", s.cfg.GetHost(), s.cfg.GetPort()) - } - - if cli, con, err := s.tryClient(ctx, addr, tlsc); err != nil { - return nil, err - } else if err = s.auth(cli, addr); err != nil { - return nil, err - } else { - s.con = con - s.cli = cli - } - - return s.cli, nil -} - -func (s *smtpClient) _close() { - if s.cli != nil { - if e := s.cli.Quit(); e != nil { - _ = s.cli.Close() - } - s.cli = nil - } - - if s.con != nil { - _ = s.con.Close() - s.con = nil - } + s.cfg = cfg + s.tls = tslConfig } diff --git a/smtp/monitor.go b/smtp/monitor.go new file mode 100644 index 00000000..2e227d5e --- /dev/null +++ b/smtp/monitor.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 smtp + +import ( + "context" + "fmt" + "runtime" + + libctx "github.com/nabbar/golib/context" + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +const ( + defaultNameMonitor = "SMTP Client" +) + +// HealthCheck is used to return the status of the SMTP connection to the server (with a timeout of 5 sec). +func (s *smtpClient) HealthCheck(ctx context.Context) error { + return s.Check(ctx) +} + +// Monitor is used to return the monitor of the SMTP to check the connection to the server. +func (s *smtpClient) Monitor(ctx libctx.FuncContext, vrs libver.Version) (montps.Monitor, error) { + var ( + e error + inf moninf.Info + mon montps.Monitor + res = make(map[string]interface{}, 0) + ) + + res["runtime"] = runtime.Version()[2:] + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + + if inf, e = moninf.New(defaultNameMonitor); e != nil { + return nil, e + } else { + inf.RegisterName(func() (string, error) { + return fmt.Sprintf("%s [%s:%d]", defaultNameMonitor, s.cfg.GetHost(), s.cfg.GetPort()), nil + }) + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon, e = libmon.New(ctx, inf); e != nil { + return nil, e + } + + mon.SetHealthCheck(s.HealthCheck) + if e = mon.Start(ctx()); e != nil { + return nil, e + } + + return mon, nil +} diff --git a/smtp/network.go b/smtp/network.go deleted file mode 100644 index 4e3c70ed..00000000 --- a/smtp/network.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 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 smtp - -import "strings" - -type NETMode uint8 - -const ( - NET_TCP NETMode = iota - NET_TCP_4 - NET_TCP_6 - NET_UNIX -) - -func parseNETMode(str string) NETMode { - switch strings.ToLower(str) { - case NET_TCP_4.string(): - return NET_TCP_4 - case NET_TCP_6.string(): - return NET_TCP_6 - case NET_UNIX.string(): - return NET_UNIX - } - - return NET_TCP -} - -func (n NETMode) string() string { - switch n { - case NET_TCP_4: - return "tcp4" - case NET_TCP_6: - return "tcp6" - case NET_UNIX: - return "unix" - case NET_TCP: - return "tcp" - } - - return NET_TCP.string() -} diff --git a/smtp/network/network.go b/smtp/network/network.go new file mode 100644 index 00000000..398c4040 --- /dev/null +++ b/smtp/network/network.go @@ -0,0 +1,131 @@ +/* + * MIT License + * + * Copyright (c) 2020 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 network + +import ( + "math" + "strconv" + "strings" +) + +type NetworkMode uint8 + +const ( + NetworkTCP NetworkMode = iota + NetworkTCPIPv4 + NetworkTCPIPv6 + NetworkUnixSocket +) + +func NetworkModeFromString(str string) NetworkMode { + switch strings.ToLower(str) { + case NetworkTCPIPv4.String(): + return NetworkTCPIPv4 + case NetworkTCPIPv6.String(): + return NetworkTCPIPv6 + case NetworkUnixSocket.String(): + return NetworkUnixSocket + } + + return NetworkTCP +} + +func NetworkModeFromInt(i int64) NetworkMode { + if i > math.MaxUint8 { + return NetworkTCP + } + + switch NetworkMode(i) { + case NetworkTCPIPv4: + return NetworkTCPIPv4 + case NetworkTCPIPv6: + return NetworkTCPIPv6 + case NetworkUnixSocket: + return NetworkUnixSocket + } + + return NetworkTCP +} + +func (n NetworkMode) String() string { + switch n { + case NetworkTCPIPv4: + return "tcp4" + case NetworkTCPIPv6: + return "tcp6" + case NetworkUnixSocket: + return "unix" + case NetworkTCP: + return "tcp" + } + + return NetworkTCP.String() +} + +func (n NetworkMode) Int() int64 { + return int64(n) +} + +func (n NetworkMode) MarshalJSON() ([]byte, error) { + b := make([]byte, 0, len(n.String())+2) + b = append(b, '"') + b = append(b, []byte(n.String())...) + b = append(b, '"') + return b, nil +} + +func (n *NetworkMode) UnmarshalJSON(data []byte) error { + var ( + e error + i int64 + a NetworkMode + str string + ) + + str = string(data) + + if str == "null" { + *n = NetworkTCP + return nil + } + + if strings.HasPrefix(str, "\"") || strings.HasSuffix(str, "\"") { + if str, e = strconv.Unquote(str); e != nil { + return e + } + } + + if i, e = strconv.ParseInt(str, 10, 8); e != nil { + *n = NetworkModeFromString(str) + return nil + } else if a = NetworkModeFromInt(i); a != NetworkTCP { + *n = a + return nil + } else { + *n = NetworkModeFromString(str) + return nil + } +} diff --git a/smtp/tls.go b/smtp/tls.go deleted file mode 100644 index 5b9007ae..00000000 --- a/smtp/tls.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 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 smtp - -import "strings" - -type TLSMode uint8 - -const ( - TLS_NONE TLSMode = iota - TLS_STARTTLS - TLS_TLS -) - -func parseTLSMode(str string) TLSMode { - switch strings.ToLower(str) { - case TLS_TLS.string(): - return TLS_TLS - case TLS_STARTTLS.string(): - return TLS_STARTTLS - } - - return TLS_NONE -} - -func (tlm TLSMode) string() string { - switch tlm { - case TLS_TLS: - return "tls" - case TLS_STARTTLS: - return "starttls" - case TLS_NONE: - return "none" - } - - return TLS_NONE.string() -} diff --git a/smtp/tlsmode/tls.go b/smtp/tlsmode/tls.go new file mode 100644 index 00000000..04bd8689 --- /dev/null +++ b/smtp/tlsmode/tls.go @@ -0,0 +1,124 @@ +/* + * MIT License + * + * Copyright (c) 2020 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 tlsmode + +import ( + "math" + "strconv" + "strings" +) + +type TLSMode uint8 + +const ( + TLSNone TLSMode = iota + TLSStartTLS + TLSStrictTLS +) + +func TLSModeFromString(str string) TLSMode { + switch strings.ToLower(str) { + case TLSStrictTLS.String(): + return TLSStrictTLS + case TLSStartTLS.String(): + return TLSStartTLS + } + + return TLSNone +} + +func TLSModeFromInt(i int64) TLSMode { + if i > math.MaxUint8 { + return TLSNone + } + + switch TLSMode(i) { + case TLSStrictTLS: + return TLSStrictTLS + case TLSStartTLS: + return TLSStartTLS + } + + return TLSNone +} + +func (tlm TLSMode) String() string { + switch tlm { + case TLSStrictTLS: + return "tls" + case TLSStartTLS: + return "starttls" + case TLSNone: + return "none" + } + + return TLSNone.String() +} + +func (tlm TLSMode) Int() int64 { + return int64(tlm) +} + +func (tlm TLSMode) MarshalJSON() ([]byte, error) { + b := make([]byte, 0, len(tlm.String())+2) + b = append(b, '"') + b = append(b, []byte(tlm.String())...) + b = append(b, '"') + return b, nil +} + +func (tlm *TLSMode) UnmarshalJSON(data []byte) error { + var ( + e error + i int64 + a TLSMode + str string + ) + + str = string(data) + + if str == "null" { + *tlm = TLSNone + return nil + } + + if strings.HasPrefix(str, "\"") || strings.HasSuffix(str, "\"") { + if str, e = strconv.Unquote(str); e != nil { + return e + } + } + + if i, e = strconv.ParseInt(str, 10, 8); e != nil { + *tlm = TLSModeFromString(str) + return nil + } else if a = TLSModeFromInt(i); a != TLSNone { + *tlm = a + return nil + } else { + *tlm = TLSModeFromString(str) + return nil + } +} diff --git a/smtp/types/interface.go b/smtp/types/interface.go new file mode 100644 index 00000000..3ce89d3a --- /dev/null +++ b/smtp/types/interface.go @@ -0,0 +1,49 @@ +/* + * 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 types + +import ( + "github.com/nabbar/golib/smtp/network" + "github.com/nabbar/golib/smtp/tlsmode" + + libtls "github.com/nabbar/golib/certificates" +) + +type Config interface { + GetHost() string + GetPort() int + GetUser() string + GetPass() string + GetNet() network.NetworkMode + GetTlsMode() tlsmode.TLSMode + GetTls() libtls.Config + GetTlSServerName() string + GetDsn() string +} + +type SMTP interface { +} diff --git a/static/download.go b/static/download.go new file mode 100644 index 00000000..92353721 --- /dev/null +++ b/static/download.go @@ -0,0 +1,43 @@ +/* + * 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 static + +func (s *staticHandler) SetDownload(pathFile string, flag bool) { + if pathFile != "" && s.Has(pathFile) { + s.d.Store(pathFile, flag) + } +} + +func (s *staticHandler) IsDownload(pathFile string) bool { + if i, l := s.d.Load(pathFile); !l { + return false + } else if v, k := i.(bool); !k { + return false + } else { + return v + } +} diff --git a/static/follow.go b/static/follow.go new file mode 100644 index 00000000..c1ef54bc --- /dev/null +++ b/static/follow.go @@ -0,0 +1,56 @@ +/* + * 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 static + +func (s *staticHandler) SetRedirect(srcGroup, srcRoute, dstGroup, dstRoute string) { + srcRoute = s._makeRoute(srcGroup, srcRoute) + dstRoute = s._makeRoute(dstGroup, dstRoute) + + s.f.Store(srcRoute, dstRoute) +} + +func (s *staticHandler) GetRedirect(srcGroup, srcRoute string) string { + srcRoute = s._makeRoute(srcGroup, srcRoute) + if i, l := s.f.Load(srcRoute); !l { + return "" + } else if v, k := i.(string); !k { + return "" + } else { + return v + } +} + +func (s *staticHandler) IsRedirect(group, route string) bool { + route = s._makeRoute(group, route) + + if i, l := s.f.Load(route); !l { + return false + } else { + _, ok := i.(string) + return ok + } +} diff --git a/static/index.go b/static/index.go new file mode 100644 index 00000000..dcae187a --- /dev/null +++ b/static/index.go @@ -0,0 +1,84 @@ +/* + * 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 static + +import ( + "golang.org/x/exp/slices" +) + +func (s *staticHandler) SetIndex(group, route, pathFile string) { + if pathFile != "" && s.Has(pathFile) { + var lst []string + + if i, l := s.i.Load(pathFile); !l { + lst = make([]string, 0) + } else if v, k := i.([]string); !k { + lst = make([]string, 0) + } else { + lst = v + } + + s.i.Store(pathFile, append(lst, s._makeRoute(group, route))) + } +} + +func (s *staticHandler) GetIndex(group, route string) string { + route = s._makeRoute(group, route) + var found string + + s.i.Walk(func(key string, val interface{}) bool { + if v, k := val.([]string); !k { + return true + } else if !slices.Contains(v, route) { + return true + } + + found = key + return false + }) + + return found +} + +func (s *staticHandler) IsIndex(pathFile string) bool { + if i, l := s.i.Load(pathFile); !l { + return false + } else { + _, ok := i.([]string) + return ok + } +} + +func (s *staticHandler) IsIndexForRoute(pathFile, group, route string) bool { + if i, l := s.i.Load(pathFile); !l { + return false + } else if v, k := i.([]string); !k { + return false + } else { + return slices.Contains(v, s._makeRoute(group, route)) + } +} diff --git a/static/interface.go b/static/interface.go index af414639..db873007 100644 --- a/static/interface.go +++ b/static/interface.go @@ -31,20 +31,21 @@ import ( "io" "os" "sync" - "time" - - "github.com/gin-gonic/gin" + "sync/atomic" + ginsdk "github.com/gin-gonic/gin" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" libiot "github.com/nabbar/golib/ioutils" liblog "github.com/nabbar/golib/logger" + montps "github.com/nabbar/golib/monitor/types" librtr "github.com/nabbar/golib/router" - libsts "github.com/nabbar/golib/status" + libver "github.com/nabbar/golib/version" ) type Static interface { - RegisterRouter(route string, register librtr.RegisterRouter, router ...gin.HandlerFunc) - RegisterRouterInGroup(route, group string, register librtr.RegisterRouterInGroup, router ...gin.HandlerFunc) + RegisterRouter(route string, register librtr.RegisterRouter, router ...ginsdk.HandlerFunc) + RegisterRouterInGroup(route, group string, register librtr.RegisterRouterInGroup, router ...ginsdk.HandlerFunc) RegisterLogger(log func() liblog.Logger) @@ -53,8 +54,8 @@ type Static interface { GetIndex(group, route string) string SetRedirect(srcGroup, srcRoute, dstGroup, dstRoute string) GetRedirect(srcGroup, srcRoute string) string - SetSpecific(group, route string, router gin.HandlerFunc) - GetSpecific(group, route string) gin.HandlerFunc + SetSpecific(group, route string, router ginsdk.HandlerFunc) + GetSpecific(group, route string) ginsdk.HandlerFunc IsDownload(pathFile string) bool IsIndex(pathFile string) bool @@ -70,24 +71,22 @@ type Static interface { Map(func(pathFile string, inf os.FileInfo) error) liberr.Error UseTempForFileSize(size int64) - StatusInfo() (name string, release string, hash string) - StatusHealth() error - StatusComponent(mandatory bool, message libsts.FctMessage, infoCacheTimeout, healthCacheTimeout time.Duration, sts libsts.RouteStatus) + Monitor(ctx libctx.FuncContext, cfg montps.Config, vrs libver.Version) (montps.Monitor, error) - Get(c *gin.Context) - SendFile(c *gin.Context, filename string, size int64, isDownload bool, buf io.ReadCloser) + Get(c *ginsdk.Context) + SendFile(c *ginsdk.Context, filename string, size int64, isDownload bool, buf io.ReadCloser) } -func New(content embed.FS, embedRootDir ...string) Static { +func New(ctx libctx.FuncContext, content embed.FS, embedRootDir ...string) Static { return &staticHandler{ - m: sync.Mutex{}, + m: sync.RWMutex{}, l: nil, c: content, b: embedRootDir, z: 0, - i: nil, - d: nil, - f: nil, - r: nil, + i: libctx.NewConfig[string](ctx), + d: libctx.NewConfig[string](ctx), + f: libctx.NewConfig[string](ctx), + r: new(atomic.Value), } } diff --git a/static/model.go b/static/model.go index 6fc82997..a8cbcc0a 100644 --- a/static/model.go +++ b/static/model.go @@ -27,87 +27,32 @@ package static import ( - "bytes" "embed" - "errors" - "fmt" - "io" - "io/fs" - "mime" - "net/http" - "os" - "path" - "runtime" - "strings" "sync" "sync/atomic" - "time" - "golang.org/x/exp/slices" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/render" - - liberr "github.com/nabbar/golib/errors" - libiot "github.com/nabbar/golib/ioutils" + libctx "github.com/nabbar/golib/context" liblog "github.com/nabbar/golib/logger" - librtr "github.com/nabbar/golib/router" - libsts "github.com/nabbar/golib/status" ) const ( urlPathSeparator = "/" - textEmbed = "Embed FS" ) type staticHandler struct { - m sync.Mutex + m sync.RWMutex l func() liblog.Logger c embed.FS - b []string - z int64 - - i *atomic.Value - d *atomic.Value - f *atomic.Value - s *atomic.Value - r *atomic.Value -} - -func (s *staticHandler) _makeRoute(group, route string) string { - if group == "" { - group = urlPathSeparator - } - return path.Join(group, route) -} - -func (s *staticHandler) _getSize() int64 { - s.m.Lock() - defer s.m.Unlock() - - return s.z -} - -func (s *staticHandler) _setSize(size int64) { - s.m.Lock() - defer s.m.Unlock() - - s.z = size -} - -func (s *staticHandler) _getBase() []string { - s.m.Lock() - defer s.m.Unlock() - - return s.b -} - -func (s *staticHandler) _setBase(base ...string) { - s.m.Lock() - defer s.m.Unlock() + b []string // base + z int64 // size - s.b = base + i libctx.Config[string] // index + d libctx.Config[string] // download + f libctx.Config[string] // follow + s libctx.Config[string] // specific + r *atomic.Value // router + h *atomic.Value // monitor } func (s *staticHandler) _setLogger(fct func() liblog.Logger) { @@ -118,8 +63,8 @@ func (s *staticHandler) _setLogger(fct func() liblog.Logger) { } func (s *staticHandler) _getLogger() liblog.Logger { - s.m.Lock() - defer s.m.Unlock() + s.m.RLock() + defer s.m.RUnlock() if s.l == nil { return liblog.GetDefault() @@ -130,649 +75,6 @@ func (s *staticHandler) _getLogger() liblog.Logger { } } -func (s *staticHandler) _getIndex() map[string][]string { - s.m.Lock() - defer s.m.Unlock() - - var def = make(map[string][]string, 0) - if s.i == nil { - return def - } - if i := s.i.Load(); i == nil { - return def - } else if o, ok := i.(map[string][]string); !ok { - return def - } else { - return o - } -} - -func (s *staticHandler) _setIndex(val map[string][]string) { - s.m.Lock() - defer s.m.Unlock() - - if val == nil { - val = make(map[string][]string, 0) - } - - if s.i == nil { - s.i = new(atomic.Value) - } - - s.i.Store(val) -} - -func (s *staticHandler) _getDownload() map[string]bool { - s.m.Lock() - defer s.m.Unlock() - - var def = make(map[string]bool, 0) - if s.d == nil { - return def - } - if i := s.d.Load(); i == nil { - return def - } else if o, ok := i.(map[string]bool); !ok { - return def - } else { - return o - } -} - -func (s *staticHandler) _setDownload(val map[string]bool) { - s.m.Lock() - defer s.m.Unlock() - - if val == nil { - val = make(map[string]bool, 0) - } - - if s.d == nil { - s.d = new(atomic.Value) - } - - s.d.Store(val) -} - -func (s *staticHandler) _getFollow() map[string]string { - s.m.Lock() - defer s.m.Unlock() - - var def = make(map[string]string, 0) - if s.f == nil { - return def - } - if i := s.f.Load(); i == nil { - return def - } else if o, ok := i.(map[string]string); !ok { - return def - } else { - return o - } -} - -func (s *staticHandler) _setFollow(val map[string]string) { - s.m.Lock() - defer s.m.Unlock() - - if val == nil { - val = make(map[string]string, 0) - } - - if s.f == nil { - s.f = new(atomic.Value) - } - - s.f.Store(val) -} - -func (s *staticHandler) _getSpecific() map[string]gin.HandlerFunc { - s.m.Lock() - defer s.m.Unlock() - - var def = make(map[string]gin.HandlerFunc, 0) - if s.s == nil { - return def - } - if i := s.s.Load(); i == nil { - return def - } else if o, ok := i.(map[string]gin.HandlerFunc); !ok { - return def - } else { - return o - } -} - -func (s *staticHandler) _setSpecific(val map[string]gin.HandlerFunc) { - s.m.Lock() - defer s.m.Unlock() - - if val == nil { - val = make(map[string]gin.HandlerFunc, 0) - } - - if s.s == nil { - s.s = new(atomic.Value) - } - - s.s.Store(val) -} - -func (s *staticHandler) _getRouter() []string { - s.m.Lock() - defer s.m.Unlock() - - var def = make([]string, 0) - if s.r == nil { - return def - } - if i := s.r.Load(); i == nil { - return def - } else if o, ok := i.([]string); !ok { - return def - } else { - return o - } -} - -func (s *staticHandler) _setRouter(val []string) { - s.m.Lock() - defer s.m.Unlock() - - if val == nil { - val = make([]string, 0) - } - - if s.r == nil { - s.r = new(atomic.Value) - } - - s.r.Store(val) -} - -func (s *staticHandler) _listEmbed(root string) ([]fs.DirEntry, liberr.Error) { - if root == "" { - return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) - } - - s.m.Lock() - defer s.m.Unlock() - - val, err := s.c.ReadDir(root) - - if err != nil && errors.Is(err, fs.ErrNotExist) { - return nil, ErrorFileNotFound.ErrorParent(err) - } else if err != nil { - return nil, ErrorFileOpen.ErrorParent(err) - } else { - return val, nil - } -} - -func (s *staticHandler) _fileGet(pathFile string) (fs.FileInfo, io.ReadCloser, liberr.Error) { - if pathFile == "" { - return nil, nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) - } - - if inf, err := s._fileInfo(pathFile); err != nil { - return nil, nil, err - } else if inf.Size() >= s._getSize() { - r, e := s._fileTemp(pathFile) - return inf, r, e - } else { - r, e := s._fileBuff(pathFile) - return inf, r, e - } -} - -func (s *staticHandler) _fileInfo(pathFile string) (fs.FileInfo, liberr.Error) { - if pathFile == "" { - return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) - } - - s.m.Lock() - defer s.m.Unlock() - - var inf fs.FileInfo - obj, err := s.c.Open(pathFile) - - if err != nil && errors.Is(err, fs.ErrNotExist) { - return nil, ErrorFileNotFound.ErrorParent(err) - } else if err != nil { - return nil, ErrorFileOpen.ErrorParent(err) - } - - defer func() { - _ = obj.Close() - }() - - inf, err = obj.Stat() - - if err != nil && errors.Is(err, fs.ErrNotExist) { - return nil, ErrorFileNotFound.ErrorParent(err) - } else if err != nil { - return nil, ErrorFileOpen.ErrorParent(err) - } - - return inf, nil -} - -func (s *staticHandler) _fileBuff(pathFile string) (io.ReadCloser, liberr.Error) { - if pathFile == "" { - return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) - } - - s.m.Lock() - defer s.m.Unlock() - - obj, err := s.c.ReadFile(pathFile) - - if err != nil && errors.Is(err, fs.ErrNotExist) { - return nil, ErrorFileNotFound.ErrorParent(err) - } else if err != nil { - return nil, ErrorFileOpen.ErrorParent(err) - } else { - return libiot.NewBufferReadCloser(bytes.NewBuffer(obj)), nil - } -} - -func (s *staticHandler) _fileTemp(pathFile string) (libiot.FileProgress, liberr.Error) { - if pathFile == "" { - return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) - } - - s.m.Lock() - defer s.m.Unlock() - - var tmp libiot.FileProgress - obj, err := s.c.Open(pathFile) - - if err != nil && errors.Is(err, fs.ErrNotExist) { - return nil, ErrorFileNotFound.ErrorParent(err) - } else if err != nil { - return nil, ErrorFileOpen.ErrorParent(err) - } - - defer func() { - _ = obj.Close() - }() - - tmp, err = libiot.NewFileProgressTemp() - if err != nil { - return nil, ErrorFiletemp.ErrorParent(err) - } - - _, e := io.Copy(tmp, obj) - if e != nil { - return nil, ErrorFiletemp.ErrorParent(e) - } - - return tmp, nil -} - -func (s *staticHandler) RegisterRouter(route string, register librtr.RegisterRouter, router ...gin.HandlerFunc) { - s._setRouter(append(s._getRouter(), s._makeRoute(urlPathSeparator, route))) - - router = append(router, s.Get) - register(http.MethodGet, path.Join(route, urlPathSeparator+"*file"), router...) -} - -func (s *staticHandler) RegisterRouterInGroup(route, group string, register librtr.RegisterRouterInGroup, router ...gin.HandlerFunc) { - s._setRouter(append(s._getRouter(), s._makeRoute(group, route))) - - router = append(router, s.Get) - register(group, http.MethodGet, path.Join(route, urlPathSeparator+"*file"), router...) -} - func (s *staticHandler) RegisterLogger(log func() liblog.Logger) { s._setLogger(log) } - -func (s *staticHandler) SetDownload(pathFile string, flag bool) { - if pathFile != "" && s.Has(pathFile) { - obj := s._getDownload() - obj[pathFile] = flag - s._setDownload(obj) - } -} - -func (s *staticHandler) SetIndex(group, route, pathFile string) { - if pathFile != "" && s.Has(pathFile) { - obj := s._getIndex() - - if obj[pathFile] == nil { - obj[pathFile] = make([]string, 0) - } - - obj[pathFile] = append(obj[pathFile], s._makeRoute(group, route)) - s._setIndex(obj) - } -} - -func (s *staticHandler) GetIndex(group, route string) string { - route = s._makeRoute(group, route) - - for f, r := range s._getIndex() { - if r == nil { - continue - } - - if slices.Contains(r, route) { - return f - } - } - - return "" -} - -func (s *staticHandler) SetRedirect(srcGroup, srcRoute, dstGroup, dstRoute string) { - srcRoute = s._makeRoute(srcGroup, srcRoute) - dstRoute = s._makeRoute(dstGroup, dstRoute) - - obj := s._getFollow() - obj[srcRoute] = dstRoute - s._setFollow(obj) -} - -func (s *staticHandler) GetRedirect(srcGroup, srcRoute string) string { - srcRoute = s._makeRoute(srcGroup, srcRoute) - - for src, dst := range s._getFollow() { - if src == srcRoute { - return dst - } - } - - return "" -} - -func (s *staticHandler) SetSpecific(group, route string, router gin.HandlerFunc) { - route = s._makeRoute(group, route) - - obj := s._getSpecific() - obj[route] = router - s._setSpecific(obj) -} - -func (s *staticHandler) GetSpecific(group, route string) gin.HandlerFunc { - route = s._makeRoute(group, route) - - for src, dst := range s._getSpecific() { - if src == route { - return dst - } - } - - return nil -} - -func (s *staticHandler) IsDownload(pathFile string) bool { - val, ok := s._getDownload()[pathFile] - - if !ok { - return false - } - - return val -} - -func (s *staticHandler) IsIndex(pathFile string) bool { - _, ok := s._getIndex()[pathFile] - return ok -} - -func (s *staticHandler) IsIndexForRoute(pathFile, group, route string) bool { - val, ok := s._getIndex()[pathFile] - - if !ok { - return false - } - - return slices.Contains(val, s._makeRoute(group, route)) -} - -func (s *staticHandler) IsRedirect(group, route string) bool { - _, ok := s._getFollow()[s._makeRoute(group, route)] - return ok -} - -func (s *staticHandler) Has(pathFile string) bool { - if _, e := s._fileInfo(pathFile); e != nil { - return false - } else { - return true - } -} - -func (s *staticHandler) List(rootPath string) ([]string, liberr.Error) { - var ( - err error - res = make([]string, 0) - lst []string - ent []fs.DirEntry - inf fs.FileInfo - ) - - if rootPath == "" { - for _, p := range s._getBase() { - inf, err = s._fileInfo(p) - if err != nil { - return nil, err.(liberr.Error) - } - - if !inf.IsDir() { - res = append(res, p) - continue - } - - lst, err = s.List(p) - - if err != nil { - return nil, err.(liberr.Error) - } - - res = append(res, lst...) - } - } else if ent, err = s._listEmbed(rootPath); err != nil { - return nil, err.(liberr.Error) - } else { - for _, f := range ent { - - if !f.IsDir() { - res = append(res, path.Join(rootPath, f.Name())) - continue - } - - lst, err = s.List(path.Join(rootPath, f.Name())) - - if err != nil { - return nil, err.(liberr.Error) - } - - res = append(res, lst...) - } - } - - return res, nil -} - -func (s *staticHandler) Find(pathFile string) (io.ReadCloser, liberr.Error) { - _, r, e := s._fileGet(pathFile) - return r, e -} - -func (s *staticHandler) Info(pathFile string) (os.FileInfo, liberr.Error) { - return s._fileInfo(pathFile) -} - -func (s *staticHandler) Temp(pathFile string) (libiot.FileProgress, liberr.Error) { - return s._fileTemp(pathFile) -} - -func (s *staticHandler) Map(fct func(pathFile string, inf os.FileInfo) error) liberr.Error { - var ( - err error - lst []string - inf fs.FileInfo - ) - - if lst, err = s.List(""); err != nil { - return err.(liberr.Error) - } else { - for _, f := range lst { - if inf, err = s._fileInfo(f); err != nil { - return err.(liberr.Error) - } else if err = fct(f, inf); err != nil { - return err.(liberr.Error) - } - } - } - - return nil -} - -func (s *staticHandler) UseTempForFileSize(size int64) { - s._setSize(size) -} - -func (s *staticHandler) StatusInfo() (name string, release string, hash string) { - return s._statusInfoPath("") -} - -func (s *staticHandler) _statusInfoPath(pathFile string) (name string, release string, hash string) { - vers := strings.TrimLeft(runtime.Version(), "go") - vers = strings.TrimLeft(vers, "Go") - vers = strings.TrimLeft(vers, "GO") - - if inf, err := s._fileInfo(pathFile); err != nil { - return textEmbed, vers, "" - } else { - return fmt.Sprintf("%s [%s]", textEmbed, inf.Name()), vers, "" - } -} - -func (s *staticHandler) StatusHealth() error { - for _, p := range s._getBase() { - if _, err := s._fileInfo(p); err != nil { - return err - } - } - - return nil -} - -func (s *staticHandler) _statusHealthPath(pathFile string) error { - if _, err := s._fileInfo(pathFile); err != nil { - return err - } - - return nil -} - -func (s *staticHandler) _statusComponentPath(key string, pathFile string, mandatory bool, message libsts.FctMessage, infoCacheTimeout, healthCacheTimeout time.Duration) libsts.Component { - fctSts := func() (name string, release string, hash string) { - return s._statusInfoPath(pathFile) - } - - fctHlt := func() error { - return s._statusHealthPath(pathFile) - } - - return libsts.NewComponent(key, mandatory, fctSts, fctHlt, message, infoCacheTimeout, healthCacheTimeout) -} - -func (s *staticHandler) StatusComponent(mandatory bool, message libsts.FctMessage, infoCacheTimeout, healthCacheTimeout time.Duration, sts libsts.RouteStatus) { - for _, p := range s._getBase() { - name := fmt.Sprintf("%s-%s", strings.ReplaceAll(textEmbed, " ", "."), p) - sts.ComponentNew(name, s._statusComponentPath(name, p, mandatory, message, infoCacheTimeout, healthCacheTimeout)) - } -} - -func (s *staticHandler) Get(c *gin.Context) { - calledFile := c.Request.URL.Path - - if dest := s.GetRedirect("", calledFile); dest != "" { - url := c.Request.URL - url.Path = dest - - c.Redirect(http.StatusPermanentRedirect, url.String()) - return - } - - if router := s.GetSpecific("", calledFile); router != nil { - router(c) - return - } - - if idx := s.GetIndex("", calledFile); idx != "" { - calledFile = idx - } else { - for _, p := range s._getRouter() { - if p == urlPathSeparator { - continue - } - calledFile = strings.TrimLeft(calledFile, p) - } - } - - calledFile = strings.Trim(calledFile, urlPathSeparator) - - if !s.Has(calledFile) { - for _, p := range s._getBase() { - - f := path.Join(p, calledFile) - - if s.Has(f) { - calledFile = f - break - } - } - } - - if !s.Has(calledFile) { - c.AbortWithStatus(http.StatusNotFound) - return - } - - var ( - err liberr.Error - buf io.ReadCloser - inf fs.FileInfo - ) - - if inf, buf, err = s._fileGet(calledFile); err != nil { - c.AbortWithStatus(http.StatusInternalServerError) - ent := s._getLogger().Entry(liblog.ErrorLevel, "get file info") - ent.FieldAdd("filePath", calledFile) - ent.FieldAdd("requestPath", c.Request.URL.Path) - ent.ErrorAdd(true, err) - ent.Log() - if buf != nil { - _ = buf.Close() - } - return - } - - defer func() { - if buf != nil { - _ = buf.Close() - } - }() - - s.SendFile(c, calledFile, inf.Size(), s.IsDownload(calledFile), buf) -} - -func (s *staticHandler) SendFile(c *gin.Context, filename string, size int64, isDownload bool, buf io.ReadCloser) { - head := librtr.NewHeaders() - - if isDownload { - head.Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(filename))) - } - - c.Render(http.StatusOK, render.Reader{ - ContentLength: size, - ContentType: mime.TypeByExtension(path.Ext(filename)), - Headers: head.Header(), - Reader: buf, - }) -} diff --git a/static/monitor.go b/static/monitor.go new file mode 100644 index 00000000..b4f16ce2 --- /dev/null +++ b/static/monitor.go @@ -0,0 +1,103 @@ +/* + * 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 static + +import ( + "context" + "io/fs" + "runtime" + + libctx "github.com/nabbar/golib/context" + + liberr "github.com/nabbar/golib/errors" + libmon "github.com/nabbar/golib/monitor" + moninf "github.com/nabbar/golib/monitor/info" + montps "github.com/nabbar/golib/monitor/types" + libver "github.com/nabbar/golib/version" +) + +const ( + textEmbed = "Embed FS" +) + +func (s *staticHandler) Monitor(ctx libctx.FuncContext, cfg montps.Config, vrs libver.Version) (montps.Monitor, error) { + res := make(map[string]interface{}, 0) + res["runtime"] = runtime.Version()[2:] + res["release"] = vrs.GetRelease() + res["build"] = vrs.GetBuild() + res["date"] = vrs.GetDate() + + var ( + e error + i fs.FileInfo + inf moninf.Info + mon montps.Monitor + err liberr.Error + ) + + if inf, e = moninf.New(textEmbed); e != nil { + return nil, e + } else { + inf.RegisterName(func() (string, error) { + return textEmbed, nil + }) + } + + if i, err = s._fileInfo(""); err != nil { + inf.RegisterInfo(func() (map[string]interface{}, error) { + return nil, err + }) + } else { + res["path"] = i.Name() + inf.RegisterInfo(func() (map[string]interface{}, error) { + return res, nil + }) + } + + if mon, e = libmon.New(s.s.GetContext, inf); e != nil { + return nil, e + } else if e = mon.SetConfig(ctx, cfg); e != nil { + return nil, e + } else { + mon.SetHealthCheck(s.HealthCheck) + if e = mon.Start(ctx()); e != nil { + return nil, e + } + } + + return mon, nil +} + +func (s *staticHandler) HealthCheck(ctx context.Context) error { + for _, p := range s._getBase() { + if _, err := s._fileInfo(p); err != nil { + return err + } + } + + return nil +} diff --git a/static/pathfile.go b/static/pathfile.go new file mode 100644 index 00000000..9fdcc700 --- /dev/null +++ b/static/pathfile.go @@ -0,0 +1,287 @@ +/* + * 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 static + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" + + liberr "github.com/nabbar/golib/errors" + libiot "github.com/nabbar/golib/ioutils" +) + +func (s *staticHandler) _getSize() int64 { + s.m.RLock() + defer s.m.RUnlock() + + return s.z +} + +func (s *staticHandler) _setSize(size int64) { + s.m.Lock() + defer s.m.Unlock() + + s.z = size +} + +func (s *staticHandler) _getBase() []string { + s.m.RLock() + defer s.m.RUnlock() + + return s.b +} + +func (s *staticHandler) _setBase(base ...string) { + s.m.Lock() + defer s.m.Unlock() + + s.b = base +} + +func (s *staticHandler) _listEmbed(root string) ([]fs.DirEntry, liberr.Error) { + if root == "" { + return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) + } + + s.m.RLock() + defer s.m.RUnlock() + + val, err := s.c.ReadDir(root) + + if err != nil && errors.Is(err, fs.ErrNotExist) { + return nil, ErrorFileNotFound.ErrorParent(err) + } else if err != nil { + return nil, ErrorFileOpen.ErrorParent(err) + } else { + return val, nil + } +} + +func (s *staticHandler) _fileGet(pathFile string) (fs.FileInfo, io.ReadCloser, liberr.Error) { + if pathFile == "" { + return nil, nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) + } + + if inf, err := s._fileInfo(pathFile); err != nil { + return nil, nil, err + } else if inf.Size() >= s._getSize() { + r, e := s._fileTemp(pathFile) + return inf, r, e + } else { + r, e := s._fileBuff(pathFile) + return inf, r, e + } +} + +func (s *staticHandler) _fileInfo(pathFile string) (fs.FileInfo, liberr.Error) { + if pathFile == "" { + return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) + } + + s.m.RLock() + defer s.m.RUnlock() + + var inf fs.FileInfo + obj, err := s.c.Open(pathFile) + + if err != nil && errors.Is(err, fs.ErrNotExist) { + return nil, ErrorFileNotFound.ErrorParent(err) + } else if err != nil { + return nil, ErrorFileOpen.ErrorParent(err) + } + + defer func() { + _ = obj.Close() + }() + + inf, err = obj.Stat() + + if err != nil && errors.Is(err, fs.ErrNotExist) { + return nil, ErrorFileNotFound.ErrorParent(err) + } else if err != nil { + return nil, ErrorFileOpen.ErrorParent(err) + } + + return inf, nil +} + +func (s *staticHandler) _fileBuff(pathFile string) (io.ReadCloser, liberr.Error) { + if pathFile == "" { + return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) + } + + s.m.RLock() + defer s.m.RUnlock() + + obj, err := s.c.ReadFile(pathFile) + + if err != nil && errors.Is(err, fs.ErrNotExist) { + return nil, ErrorFileNotFound.ErrorParent(err) + } else if err != nil { + return nil, ErrorFileOpen.ErrorParent(err) + } else { + return libiot.NewBufferReadCloser(bytes.NewBuffer(obj)), nil + } +} + +func (s *staticHandler) _fileTemp(pathFile string) (libiot.FileProgress, liberr.Error) { + if pathFile == "" { + return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("pathfile is empty")) + } + + s.m.RLock() + defer s.m.RUnlock() + + var tmp libiot.FileProgress + obj, err := s.c.Open(pathFile) + + if err != nil && errors.Is(err, fs.ErrNotExist) { + return nil, ErrorFileNotFound.ErrorParent(err) + } else if err != nil { + return nil, ErrorFileOpen.ErrorParent(err) + } + + defer func() { + _ = obj.Close() + }() + + tmp, err = libiot.NewFileProgressTemp() + if err != nil { + return nil, ErrorFiletemp.ErrorParent(err) + } + + _, e := io.Copy(tmp, obj) + if e != nil { + return nil, ErrorFiletemp.ErrorParent(e) + } + + return tmp, nil +} + +func (s *staticHandler) Has(pathFile string) bool { + if _, e := s._fileInfo(pathFile); e != nil { + return false + } else { + return true + } +} + +func (s *staticHandler) List(rootPath string) ([]string, liberr.Error) { + var ( + err error + res = make([]string, 0) + lst []string + ent []fs.DirEntry + inf fs.FileInfo + ) + + if rootPath == "" { + for _, p := range s._getBase() { + inf, err = s._fileInfo(p) + if err != nil { + return nil, err.(liberr.Error) + } + + if !inf.IsDir() { + res = append(res, p) + continue + } + + lst, err = s.List(p) + + if err != nil { + return nil, err.(liberr.Error) + } + + res = append(res, lst...) + } + } else if ent, err = s._listEmbed(rootPath); err != nil { + return nil, err.(liberr.Error) + } else { + for _, f := range ent { + + if !f.IsDir() { + res = append(res, path.Join(rootPath, f.Name())) + continue + } + + lst, err = s.List(path.Join(rootPath, f.Name())) + + if err != nil { + return nil, err.(liberr.Error) + } + + res = append(res, lst...) + } + } + + return res, nil +} + +func (s *staticHandler) Find(pathFile string) (io.ReadCloser, liberr.Error) { + _, r, e := s._fileGet(pathFile) + return r, e +} + +func (s *staticHandler) Info(pathFile string) (os.FileInfo, liberr.Error) { + return s._fileInfo(pathFile) +} + +func (s *staticHandler) Temp(pathFile string) (libiot.FileProgress, liberr.Error) { + return s._fileTemp(pathFile) +} + +func (s *staticHandler) Map(fct func(pathFile string, inf os.FileInfo) error) liberr.Error { + var ( + err error + lst []string + inf fs.FileInfo + ) + + if lst, err = s.List(""); err != nil { + return err.(liberr.Error) + } else { + for _, f := range lst { + if inf, err = s._fileInfo(f); err != nil { + return err.(liberr.Error) + } else if err = fct(f, inf); err != nil { + return err.(liberr.Error) + } + } + } + + return nil +} + +func (s *staticHandler) UseTempForFileSize(size int64) { + s._setSize(size) +} diff --git a/static/route.go b/static/route.go new file mode 100644 index 00000000..1f031dec --- /dev/null +++ b/static/route.go @@ -0,0 +1,153 @@ +/* + * 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 static + +import ( + "fmt" + "io" + "io/fs" + "mime" + "net/http" + "path" + "strings" + + ginsdk "github.com/gin-gonic/gin" + ginrdr "github.com/gin-gonic/gin/render" + liberr "github.com/nabbar/golib/errors" + liblog "github.com/nabbar/golib/logger" + librtr "github.com/nabbar/golib/router" +) + +func (s *staticHandler) _makeRoute(group, route string) string { + if group == "" { + group = urlPathSeparator + } + return path.Join(group, route) +} + +func (s *staticHandler) RegisterRouter(route string, register librtr.RegisterRouter, router ...ginsdk.HandlerFunc) { + s._setRouter(append(s._getRouter(), s._makeRoute(urlPathSeparator, route))) + + router = append(router, s.Get) + register(http.MethodGet, path.Join(route, urlPathSeparator+"*file"), router...) +} + +func (s *staticHandler) RegisterRouterInGroup(route, group string, register librtr.RegisterRouterInGroup, router ...ginsdk.HandlerFunc) { + s._setRouter(append(s._getRouter(), s._makeRoute(group, route))) + + router = append(router, s.Get) + register(group, http.MethodGet, path.Join(route, urlPathSeparator+"*file"), router...) +} + +func (s *staticHandler) Get(c *ginsdk.Context) { + calledFile := c.Request.URL.Path + + if dest := s.GetRedirect("", calledFile); dest != "" { + url := c.Request.URL + url.Path = dest + + c.Redirect(http.StatusPermanentRedirect, url.String()) + return + } + + if router := s.GetSpecific("", calledFile); router != nil { + router(c) + return + } + + if idx := s.GetIndex("", calledFile); idx != "" { + calledFile = idx + } else { + for _, p := range s._getRouter() { + if p == urlPathSeparator { + continue + } + calledFile = strings.TrimLeft(calledFile, p) + } + } + + calledFile = strings.Trim(calledFile, urlPathSeparator) + + if !s.Has(calledFile) { + for _, p := range s._getBase() { + + f := path.Join(p, calledFile) + + if s.Has(f) { + calledFile = f + break + } + } + } + + if !s.Has(calledFile) { + c.AbortWithStatus(http.StatusNotFound) + return + } + + var ( + err liberr.Error + buf io.ReadCloser + inf fs.FileInfo + ) + + if inf, buf, err = s._fileGet(calledFile); err != nil { + c.AbortWithStatus(http.StatusInternalServerError) + ent := s._getLogger().Entry(liblog.ErrorLevel, "get file info") + ent.FieldAdd("filePath", calledFile) + ent.FieldAdd("requestPath", c.Request.URL.Path) + ent.ErrorAdd(true, err) + ent.Log() + if buf != nil { + _ = buf.Close() + } + return + } + + defer func() { + if buf != nil { + _ = buf.Close() + } + }() + + s.SendFile(c, calledFile, inf.Size(), s.IsDownload(calledFile), buf) +} + +func (s *staticHandler) SendFile(c *ginsdk.Context, filename string, size int64, isDownload bool, buf io.ReadCloser) { + head := librtr.NewHeaders() + + if isDownload { + head.Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(filename))) + } + + c.Render(http.StatusOK, ginrdr.Reader{ + ContentLength: size, + ContentType: mime.TypeByExtension(path.Ext(filename)), + Headers: head.Header(), + Reader: buf, + }) +} diff --git a/static/router.go b/static/router.go new file mode 100644 index 00000000..a4fd19e7 --- /dev/null +++ b/static/router.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 static + +import "sync/atomic" + +func (s *staticHandler) _getRouter() []string { + s.m.Lock() + defer s.m.Unlock() + + var def = make([]string, 0) + if s.r == nil { + return def + } + if i := s.r.Load(); i == nil { + return def + } else if o, ok := i.([]string); !ok { + return def + } else { + return o + } +} + +func (s *staticHandler) _setRouter(val []string) { + s.m.Lock() + defer s.m.Unlock() + + if val == nil { + val = make([]string, 0) + } + + if s.r == nil { + s.r = new(atomic.Value) + } + + s.r.Store(val) +} diff --git a/static/specific.go b/static/specific.go new file mode 100644 index 00000000..7974c1af --- /dev/null +++ b/static/specific.go @@ -0,0 +1,48 @@ +/* + * 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 static + +import ( + ginsdk "github.com/gin-gonic/gin" +) + +func (s *staticHandler) SetSpecific(group, route string, router ginsdk.HandlerFunc) { + route = s._makeRoute(group, route) + s.s.Store(route, router) +} + +func (s *staticHandler) GetSpecific(group, route string) ginsdk.HandlerFunc { + route = s._makeRoute(group, route) + + if i, l := s.s.Load(route); !l { + return nil + } else if v, k := i.(ginsdk.HandlerFunc); !k { + return nil + } else { + return v + } +} diff --git a/status/config.go b/status/config.go new file mode 100644 index 00000000..c861125b --- /dev/null +++ b/status/config.go @@ -0,0 +1,119 @@ +/* + * 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 status + +import ( + "fmt" + "net/http" + + libval "github.com/go-playground/validator/v10" + monsts "github.com/nabbar/golib/monitor/status" + "golang.org/x/exp/slices" +) + +const ( + keyConfigReturnCode = "cfgReturnCode" + keyConfigMandatory = "cfgMandatory" +) + +type Config struct { + ReturnCode map[monsts.Status]int + MandatoryComponent []string +} + +func (o Config) Validate() error { + var e = ErrorValidatorError.Error(nil) + + if err := libval.New().Struct(o); err != nil { + if er, ok := err.(*libval.InvalidValidationError); ok { + e.AddParent(er) + } + + for _, er := range err.(libval.ValidationErrors) { + //nolint #goerr113 + e.AddParent(fmt.Errorf("config field '%s' is not validated by constraint '%s'", er.Namespace(), er.ActualTag())) + } + } + + if !e.HasParent() { + e = nil + } + + return e +} + +func (o *sts) SetConfig(cfg Config) { + if len(cfg.ReturnCode) < 1 { + + var def = make(map[monsts.Status]int, 0) + def[monsts.KO] = http.StatusInternalServerError + def[monsts.Warn] = http.StatusMultiStatus + def[monsts.OK] = http.StatusOK + + o.x.Store(keyConfigReturnCode, def) + } else { + o.x.Store(keyConfigReturnCode, cfg.ReturnCode) + } + + if len(cfg.MandatoryComponent) < 1 { + o.x.Store(keyConfigMandatory, make([]string, 0)) + } else { + o.x.Store(keyConfigMandatory, cfg.ReturnCode) + } +} + +func (o *sts) cfgGetReturnCode(s monsts.Status) int { + if i, l := o.x.Load(keyConfigReturnCode); !l { + return http.StatusInternalServerError + } else if v, k := i.(map[monsts.Status]int); !k { + return http.StatusInternalServerError + } else if r, f := v[s]; !f { + return http.StatusInternalServerError + } else { + return r + } +} + +func (o *sts) cfgIsMandatory(name string) bool { + if i, l := o.x.Load(keyConfigMandatory); !l { + return false + } else if v, k := i.([]string); !k { + return false + } else { + return slices.Contains(v, name) + } +} + +func (o *sts) cfgGetMandatory() []string { + if i, l := o.x.Load(keyConfigMandatory); !l { + return nil + } else if v, k := i.([]string); !k { + return nil + } else { + return v + } +} diff --git a/status/config/config.go b/status/config/config.go deleted file mode 100644 index 9b6de981..00000000 --- a/status/config/config.go +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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" - "time" - - "github.com/nabbar/golib/status" - - libcfg "github.com/nabbar/golib/config" - libsts "github.com/nabbar/golib/status" - spfcbr "github.com/spf13/cobra" - spfvpr "github.com/spf13/viper" -) - -type ConfigStatus struct { - // Mandatory define if the component must be available for the api. - // If yes, api status will be KO if this component is down. - // If no, api status can be OK if this component is down. - Mandatory bool `json:"mandatory" yaml:"mandatory" toml:"mandatory" mapstructure:"mandatory"` - - // MessageOK define the message if the status is OK. Default is "OK" - MessageOK string `json:"message_ok" yaml:"message_ok" toml:"message_ok" mapstructure:"message_ok" validate:"printascii"` - - // MessageKO define the message if the status is KO. Default is "KO" - MessageKO string `json:"message_ko" yaml:"message_ko" toml:"message_ko" mapstructure:"message_ko" validate:"printascii"` - - // CacheTimeoutInfo define the time between checking the component information (name, release, ...), to prevent asking it too many. Default is 1 hour. - CacheTimeoutInfo time.Duration `json:"cache_timeout_info" yaml:"cache_timeout_info" toml:"cache_timeout_info" mapstructure:"cache_timeout_info"` - - // CacheTimeoutHealth define the time between checking the component health to prevent asking it too many. Default is 5 second. - CacheTimeoutHealth time.Duration `json:"cache_timeout_health" yaml:"cache_timeout_health" toml:"cache_timeout_health" mapstructure:"cache_timeout_health"` -} - -var _defaultConfig = []byte(`{ - "mandatory": false, - "message_ok": "OK", - "message_ko": "KO", - "cache_timeout_info": "30s", - "cache_timeout_health": "5s" -} -`) - -func DefaultConfig(indent string) []byte { - var res = bytes.NewBuffer(make([]byte, 0)) - if err := json.Indent(res, _defaultConfig, indent, libcfg.JSONIndent); err != nil { - return _defaultConfig - } else { - return res.Bytes() - } -} - -func RegisterFlag(prefix string, Command *spfcbr.Command, Viper *spfvpr.Viper) error { - _ = Command.PersistentFlags().Bool(prefix+".mandatory", true, "define if the component must be available for the api. If yes, api status will be KO if this component is down. If no, api status can be OK if this component is down.") - _ = Command.PersistentFlags().String(prefix+".message_ok", status.DefMessageOK, "define the message if the status is OK.") - _ = Command.PersistentFlags().String(prefix+".message_ko", status.DefMessageKO, "define the message if the status is KO.") - _ = Command.PersistentFlags().Duration(prefix+".cache_timeout_info", time.Hour, "define the time between checking the component information (name, release, ...), to prevent asking it too many.") - _ = Command.PersistentFlags().Duration(prefix+".cache_timeout_health", 5*time.Second, "define the time between checking the component health to prevent asking it too many.") - - if err := Viper.BindPFlag(prefix+".mandatory", Command.PersistentFlags().Lookup(prefix+".mandatory")); err != nil { - return err - } else if err = Viper.BindPFlag(prefix+".message_ok", Command.PersistentFlags().Lookup(prefix+".message_ok")); err != nil { - return err - } else if err = Viper.BindPFlag(prefix+".message_ko", Command.PersistentFlags().Lookup(prefix+".message_ko")); err != nil { - return err - } else if err = Viper.BindPFlag(prefix+".cache_timeout_info", Command.PersistentFlags().Lookup(prefix+".cache_timeout_info")); err != nil { - return err - } else if err = Viper.BindPFlag(prefix+".cache_timeout_health", Command.PersistentFlags().Lookup(prefix+".cache_timeout_health")); err != nil { - return err - } - - return nil -} - -func (c *ConfigStatus) fctMessage() (msgOk string, msgKO string) { - if c.MessageOK == "" { - c.MessageOK = status.DefMessageOK - } - - if c.MessageKO == "" { - c.MessageKO = status.DefMessageKO - } - - return c.MessageOK, c.MessageKO -} - -func (c *ConfigStatus) RegisterStatus(sts libsts.RouteStatus, key string, fctInfo libsts.FctInfo, fctHealth libsts.FctHealth) { - sts.ComponentNew(key, status.NewComponent(key, c.Mandatory, fctInfo, fctHealth, c.fctMessage, c.CacheTimeoutInfo, c.CacheTimeoutHealth)) -} - -func (c *ConfigStatus) Component(key string, fctInfo libsts.FctInfo, fctHealth libsts.FctHealth) libsts.Component { - return status.NewComponent(key, c.Mandatory, fctInfo, fctHealth, c.fctMessage, c.CacheTimeoutInfo, c.CacheTimeoutHealth) -} diff --git a/status/encode.go b/status/encode.go new file mode 100644 index 00000000..0174a626 --- /dev/null +++ b/status/encode.go @@ -0,0 +1,200 @@ +/* + * 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 status + +import ( + "bytes" + "context" + "encoding" + "encoding/json" + "fmt" + "strings" + "time" + + monpol "github.com/nabbar/golib/monitor/pool" + + ginsdk "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/render" + liberr "github.com/nabbar/golib/errors" + + monsts "github.com/nabbar/golib/monitor/status" + montps "github.com/nabbar/golib/monitor/types" +) + +const ( + encTextSepStatus = ": " + encTextSepPart = " | " + encTextSepTime = " / " +) + +type Encode interface { + encoding.TextMarshaler + + String() string + Bytes() []byte + + GinRender(c *ginsdk.Context, isText bool, isShort bool) + GinCode() int +} + +type encodeModel struct { + Name string + Release string + Hash string + DateBuild time.Time + Status monsts.Status + Message string + Component montps.Pool + code int +} + +func (e *encodeModel) MarshalText() (text []byte, err error) { + return e.Bytes(), nil +} + +func (e *encodeModel) GinCode() int { + return e.code +} + +func (e *encodeModel) GinRender(c *ginsdk.Context, isText bool, isShort bool) { + if isShort { + e.Component = monpol.New(func() context.Context { + return c + }) + } + + if isText { + c.Render(e.code, render.Data{ + ContentType: ginsdk.MIMEPlain, + Data: e.Bytes(), + }) + } else { + c.JSON(e.code, *e) + } +} + +func (e *encodeModel) cleanString(str string) string { + str = strings.Replace(str, "\n", " ", -1) + str = strings.Replace(str, "\r", "", -1) + return str +} + +func (e *encodeModel) stringName() string { + var inf []string + + if len(e.Release) > 0 { + inf = append(inf, e.Release) + } + + if len(e.Hash) > 0 { + inf = append(inf, e.Hash) + } + + if !e.DateBuild.IsZero() { + inf = append(inf, e.DateBuild.Format(time.RFC3339)) + } + + if len(inf) > 0 { + return fmt.Sprintf("%s (%s)", e.Name, strings.Join(inf, " ")) + } else { + return e.Name + } +} + +func (e *encodeModel) stringPart() string { + item := make([]string, 0) + item = append(item, e.stringName()) + + if len(e.Message) > 0 { + item = append(item, e.Message) + } + + return strings.Join(item, encTextSepPart) +} + +func (e *encodeModel) String() string { + var buf = bytes.NewBuffer(make([]byte, 0)) + + buf.WriteString(e.Status.String() + encTextSepStatus + e.stringPart()) + buf.WriteRune('\n') + + if p, err := e.Component.MarshalText(); err == nil { + buf.Write(p) + } + + return e.cleanString(buf.String()) +} + +func (e *encodeModel) Bytes() []byte { + return []byte(e.String()) +} + +func (o *sts) getEncodeModel() Encode { + o.m.RLock() + defer o.m.RUnlock() + + var ( + m string + s monsts.Status + ) + + s, m = o.getStatus() + + return &encodeModel{ + Name: o.fn(), + Release: o.fr(), + Hash: o.fh(), + DateBuild: o.fd(), + Status: s, + Message: m, + Component: o._getPool(), + code: o.cfgGetReturnCode(s), + } +} + +func (o *sts) getMarshal() (Encode, liberr.Error) { + if !o.checkFunc() { + return nil, ErrorParamEmpty.ErrorParent(fmt.Errorf("missing status info for API")) + } + return o.getEncodeModel(), nil +} + +func (o *sts) MarshalText() (text []byte, err error) { + if enc, e := o.getMarshal(); e != nil { + return nil, e + } else { + return enc.MarshalText() + } +} + +func (o *sts) MarshalJSON() ([]byte, error) { + if enc, e := o.getMarshal(); e != nil { + return nil, e + } else { + return json.Marshal(enc) + } +} diff --git a/status/error.go b/status/error.go new file mode 100644 index 00000000..96ce0cb6 --- /dev/null +++ b/status/error.go @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2020 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 status + +import ( + "fmt" + + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgStatus + ErrorValidatorError +) + +func init() { + if liberr.ExistInMapMessage(ErrorParamEmpty) { + panic(fmt.Errorf("error code collision with package golib/logger")) + } + liberr.RegisterIdFctMessage(ErrorParamEmpty, getMessage) +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamEmpty: + return "given parameters is empty" + case ErrorValidatorError: + return "invalid config" + } + + return liberr.NullMessage +} diff --git a/status/health.go b/status/health.go deleted file mode 100644 index e199c105..00000000 --- a/status/health.go +++ /dev/null @@ -1,177 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 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 status - -import ( - "fmt" - "sync" - "time" - - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" - - "github.com/gin-gonic/gin" -) - -type FctHealth func() error -type FctMessage func() (msgOk string, msgKO string) - -type StatusResponse struct { - Status string `json:"status"` - Message string `json:"message"` -} - -func (s *StatusResponse) Clone() StatusResponse { - return StatusResponse{ - Status: s.Status, - Message: s.Message, - } -} - -type Status interface { - Get(x *gin.Context) StatusResponse - Clean() - IsValid() bool -} - -func NewStatus(key string, health FctHealth, msg FctMessage, cacheDuration time.Duration) Status { - return &status{ - m: sync.Mutex{}, - fh: health, - fm: msg, - k: key, - c: nil, - t: time.Time{}, - d: cacheDuration, - } -} - -type status struct { - m sync.Mutex - fh FctHealth - fm FctMessage - - k string - c *StatusResponse - t time.Time - d time.Duration -} - -func (s *status) getInfo() (string, string) { - s.m.Lock() - defer s.m.Unlock() - - if s.fm != nil { - return s.fm() - } - - return "", "" -} - -func (s *status) getHealth() error { - s.m.Lock() - defer s.m.Unlock() - - if s.fh != nil { - return s.fh() - } - - return nil -} - -func (s *status) setCache(obj *StatusResponse) { - s.m.Lock() - defer s.m.Unlock() - - s.c = obj - s.t = time.Now() -} - -func (s *status) getCache() StatusResponse { - s.m.Lock() - defer s.m.Unlock() - - return s.c.Clone() -} - -func (s *status) getKey() string { - s.m.Lock() - defer s.m.Unlock() - - return s.k -} - -func (s *status) Get(x *gin.Context) StatusResponse { - if !s.IsValid() { - var err error - - err = s.getHealth() - c := &StatusResponse{} - - if err != nil { - c.Status = DefMessageKO - - if e, ok := err.(liberr.Error); ok { - c.Message = fmt.Sprintf("%v", e.GetError()) - liblog.ErrorLevel.LogErrorCtxf(liblog.DebugLevel, "[%s] get health status", e.GetErrorFull(", "), s.getKey()) - } else { - c.Message = fmt.Sprintf("%v", err) - liblog.ErrorLevel.LogErrorCtxf(liblog.DebugLevel, "[%s] get health status", err, s.getKey()) - } - - } else { - c.Status = DefMessageOK - c.Message = "" - } - - s.setCache(c) - } - - return s.getCache() -} - -func (s *status) Clean() { - s.m.Lock() - defer s.m.Unlock() - - s.c = nil - s.t = time.Now() -} - -func (s *status) IsValid() bool { - s.m.Lock() - defer s.m.Unlock() - - if s.c == nil { - return false - } else if s.t.IsZero() { - return false - } else if time.Since(s.t) > s.d { - return false - } - - return true -} diff --git a/status/info.go b/status/info.go index 51e7360b..84fdd5ff 100644 --- a/status/info.go +++ b/status/info.go @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2021 Nicolas JUHEL + * 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 @@ -21,123 +21,44 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * + * */ package status import ( - "sync" "time" - "github.com/gin-gonic/gin" + libver "github.com/nabbar/golib/version" ) -type FctInfo func() (name string, release string, build string) - -type InfoResponse struct { - Name string `json:"name"` - Release string `json:"release"` - HashBuild string `json:"hash_build"` - Mandatory bool `json:"mandatory"` -} - -func (i *InfoResponse) Clone() InfoResponse { - return InfoResponse{ - Name: i.Name, - Release: i.Release, - HashBuild: i.HashBuild, - Mandatory: i.Mandatory, - } -} - -type Info interface { - Get(x *gin.Context) InfoResponse - Clean() - IsValid() bool -} - -func NewInfo(fct FctInfo, mandatory bool, cacheDuration time.Duration) Info { - return &info{ - f: fct, - o: mandatory, - c: nil, - t: time.Time{}, - d: cacheDuration, - } -} - -type info struct { - m sync.Mutex - f FctInfo - o bool - c *InfoResponse - t time.Time - d time.Duration -} +func (o *sts) SetInfo(name, release, hash string) { + o.m.Lock() + defer o.m.Unlock() -func (i *info) Get(x *gin.Context) InfoResponse { - if !i.IsValid() { - i.regenCache() + o.fn = func() string { + return name } - return i.clone() -} - -func (i *info) Clean() { - i.m.Lock() - defer i.m.Unlock() - - i.c = nil - i.t = time.Now() -} - -func (i *info) IsValid() bool { - i.m.Lock() - defer i.m.Unlock() - - if i.c == nil { - return false - } else if i.t.IsZero() { - return false - } else if time.Since(i.t) > i.d { - return false + o.fr = func() string { + return release } - return true -} -func (i *info) regenCache() { - var ( - name string - vers string - hash string - ) - - i.m.Lock() - defer i.m.Unlock() - - if i.f != nil { - name, vers, hash = i.f() + o.fh = func() string { + return hash } - i.c = &InfoResponse{ - Name: name, - Release: vers, - HashBuild: hash, - Mandatory: i.o, + o.fd = func() time.Time { + return time.Time{} } - - i.t = time.Now() } -func (i *info) clone() InfoResponse { - i.m.Lock() - defer i.m.Unlock() +func (o *sts) SetVersion(v libver.Version) { + o.m.Lock() + defer o.m.Unlock() - if i.c != nil { - return i.c.Clone() - } - - return InfoResponse{ - Mandatory: i.o, - } + o.fn = v.GetPackage + o.fr = v.GetRelease + o.fh = v.GetBuild + o.fd = v.GetTime } diff --git a/status/interface.go b/status/interface.go index 32cd7756..fcef5892 100644 --- a/status/interface.go +++ b/status/interface.go @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2021 Nicolas JUHEL + * 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 @@ -21,51 +21,59 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * + * */ package status import ( - "net/http" + "context" "sync" - "sync/atomic" - librtr "github.com/nabbar/golib/router" + liberr "github.com/nabbar/golib/errors" + + montps "github.com/nabbar/golib/monitor/types" + + libctx "github.com/nabbar/golib/context" - "github.com/gin-gonic/gin" libver "github.com/nabbar/golib/version" + + ginsdk "github.com/gin-gonic/gin" ) -type RouteStatus interface { - MiddlewareAdd(mdw ...gin.HandlerFunc) - HttpStatusCode(codeOk, codeKO, codeWarning int) +type Route interface { + Expose(ctx context.Context) + MiddleWare(c *ginsdk.Context) + SetErrorReturn(f func() liberr.ReturnGin) +} - Get(c *gin.Context) - Register(prefix string, register librtr.RegisterRouter) - RegisterGroup(group, prefix string, register librtr.RegisterRouterInGroup) +type Info interface { + SetInfo(name, release, hash string) + SetVersion(vers libver.Version) +} - ComponentNew(key string, cpt Component) - ComponentDel(key string) - ComponentDelAll(containKey string) +type Pool interface { + montps.Pool + RegisterPool(fct montps.FuncPool) } -func New(Name string, Release string, Hash string, msgOk string, msgKo string, msgWarm string) RouteStatus { - return &rtrStatus{ - m: sync.Mutex{}, - f: make([]gin.HandlerFunc, 0), - n: Name, - v: Release, - h: Hash, - mOK: msgOk, - cOk: http.StatusOK, - mKO: msgKo, - cKO: http.StatusServiceUnavailable, - mWM: msgWarm, - cWM: http.StatusOK, - c: make(map[string]*atomic.Value), - } +type Status interface { + Route + Info + Pool + + SetConfig(cfg Config) + IsHealthy(name ...string) bool } -func NewVersion(version libver.Version, msgOk string, msgKO string, msgWarm string) RouteStatus { - return New(version.GetPackage(), version.GetRelease(), version.GetBuild(), msgOk, msgKO, msgWarm) +func New(ctx libctx.FuncContext) Status { + return &sts{ + m: sync.RWMutex{}, + p: nil, + x: libctx.NewConfig[string](ctx), + fn: nil, + fr: nil, + fh: nil, + fd: nil, + } } diff --git a/status/model.go b/status/model.go index 381e3509..5a658672 100644 --- a/status/model.go +++ b/status/model.go @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2021 Nicolas JUHEL + * 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 @@ -21,286 +21,76 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * + * */ package status import ( - "bytes" - "fmt" - "net/http" - "path" - "strings" "sync" - "sync/atomic" - - "github.com/gin-gonic/gin/render" + "time" - "github.com/gin-gonic/gin" liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" - librtr "github.com/nabbar/golib/router" - libsem "github.com/nabbar/golib/semaphore" -) - -type rtrStatus struct { - m sync.Mutex - f []gin.HandlerFunc - - n string - v string - h string - mOK string - cOk int - mKO string - cKO int - mWM string - cWM int + montps "github.com/nabbar/golib/monitor/types" - c map[string]*atomic.Value -} - -const ( - urlPathSeparator = "/" - keyShortOutput = "short" - keyOneLineOutput = "oneline" + libctx "github.com/nabbar/golib/context" + monsts "github.com/nabbar/golib/monitor/status" + "golang.org/x/exp/slices" ) -func (r *rtrStatus) HttpStatusCode(codeOk, codeKO, codeWarning int) { - r.cOk = codeOk - r.cKO = codeKO - r.cWM = codeWarning -} - -func (r *rtrStatus) MiddlewareAdd(mdw ...gin.HandlerFunc) { - if len(r.f) < 1 { - r.f = make([]gin.HandlerFunc, 0) - } - - r.f = append(r.f, mdw...) -} - -func (r *rtrStatus) cleanPrefix(prefix string) string { - return path.Clean(strings.TrimRight(path.Join(urlPathSeparator, prefix), urlPathSeparator)) -} - -func (r *rtrStatus) Register(prefix string, register librtr.RegisterRouter) { - prefix = r.cleanPrefix(prefix) - - var m = r.f - m = append(m, r.Get) - register(http.MethodGet, prefix, m...) - - if prefix != urlPathSeparator { - register(http.MethodGet, prefix+urlPathSeparator, m...) - } -} - -func (r *rtrStatus) RegisterGroup(group, prefix string, register librtr.RegisterRouterInGroup) { - prefix = r.cleanPrefix(prefix) - - var m = r.f - m = append(m, r.Get) - register(group, http.MethodGet, prefix, m...) - - if prefix != urlPathSeparator { - register(group, http.MethodGet, prefix+urlPathSeparator, m...) - } -} - -func (r *rtrStatus) getInfo() (name string, release string, hash string) { - r.m.Lock() - defer r.m.Unlock() - - return r.n, r.v, r.h -} +type fctGetName func() string +type fctGetRelease func() string +type fctGetHash func() string +type fctGetDateBuild func() time.Time -func (r *rtrStatus) getMsgOk() string { - r.m.Lock() - defer r.m.Unlock() +type sts struct { + m sync.RWMutex + p montps.FuncPool + r func() liberr.ReturnGin + x libctx.Config[string] - return r.mOK + fn fctGetName + fr fctGetRelease + fh fctGetHash + fd fctGetDateBuild } -func (r *rtrStatus) getMsgKo() string { - r.m.Lock() - defer r.m.Unlock() +func (o *sts) checkFunc() bool { + o.m.RLock() + defer o.m.RUnlock() - return r.mKO + return o.fn != nil && o.fr != nil && o.fh != nil && o.fd != nil } -func (r *rtrStatus) getMsgWarn() string { - r.m.Lock() - defer r.m.Unlock() - - return r.mWM +func (o *sts) IsHealthy(name ...string) bool { + s, _ := o.getStatus(name...) + return s == monsts.OK } -func (r *rtrStatus) Get(x *gin.Context) { - var ( - key string - err liberr.Error - rsp *Response - s libsem.Sem - ) - - defer func() { - if s != nil { - s.DeferMain() - } - }() - - inf := InfoResponse{ - Mandatory: true, - } - inf.Name, inf.Release, inf.HashBuild = r.getInfo() - - sts := StatusResponse{ - Status: DefMessageOK, - } - sts.Message = r.getMsgOk() - - rsp = &Response{ - InfoResponse: inf, - StatusResponse: sts, - Components: make([]CptResponse, 0), - } - - s = libsem.NewSemaphoreWithContext(x, 0) - - for _, key = range r.ComponentKeys() { - var c Component - - if c = r.ComponentGet(key); c == nil { - continue - } - - err = s.NewWorker() - if liblog.ErrorLevel.LogGinErrorCtxf(liblog.DebugLevel, "init new thread to collect data for component '%s'", err, x, key) { - continue - } - - go func(ctx *gin.Context, sem libsem.Sem, cpt Component, resp *Response) { - defer sem.DeferWorker() - resp.appendNewCpt(cpt.Get(ctx)) - }(x, s, c, rsp) - } - - err = s.WaitAll() - - var ( - code int - ) - - if liblog.ErrorLevel.LogGinErrorCtx(liblog.DebugLevel, "waiting all thread to collect data component ", err, x) { - rsp.Message = r.getMsgKo() - rsp.Status = DefMessageKO - code = r.cKO - } else if !rsp.IsOkMandatory() { - rsp.Message = r.getMsgKo() - rsp.Status = DefMessageKO - code = r.cKO - } else if !rsp.IsOk() { - rsp.Message = r.getMsgWarn() - rsp.Status = DefMessageOK - code = r.cWM - } else { - rsp.Message = r.getMsgOk() - rsp.Status = DefMessageOK - code = r.cOk - } - - if x.Request.URL.Query().Has(keyShortOutput) { - rsp.Components = make([]CptResponse, 0) - } - - x.Header("Connection", "Close") - - if code == r.cKO { - x.Abort() - } - - if x.Request.URL.Query().Has(keyOneLineOutput) { - var buf = bytes.NewBuffer(make([]byte, 0)) - buf.WriteString(fmt.Sprintf("%s: %s (%s - %s) : %s\n", rsp.Status, rsp.Name, rsp.Release, rsp.HashBuild, rsp.Message)) - - for _, c := range rsp.Components { - buf.WriteString(fmt.Sprintf("%s: %s (%s - %s) : %s\n", c.Status, c.Name, c.Release, c.HashBuild, c.Message)) - } - - x.Render(code, render.Data{ - ContentType: gin.MIMEPlain, - Data: buf.Bytes(), - }) - } else { - x.JSON(code, rsp) +func (o *sts) getStatus(keys ...string) (monsts.Status, string) { + if len(keys) < 1 { + keys = o.cfgGetMandatory() } -} -func (r *rtrStatus) ComponentKeys() []string { - var l = make([]string, 0) + o.m.RLock() + defer o.m.RUnlock() - r.m.Lock() - defer r.m.Unlock() + s := monsts.OK + m := "" - for k := range r.c { - if len(k) > 0 { - l = append(l, k) + o.MonitorWalk(func(name string, val montps.Monitor) bool { + if !slices.Contains(keys, name) { + return true } - } - - return l -} - -func (r *rtrStatus) ComponentGet(key string) Component { - var ( - v *atomic.Value - i interface{} - o Component - ok bool - ) - - if v, ok = r.c[key]; !ok || 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 (r *rtrStatus) ComponentNew(key string, cpt Component) { - if len(r.c) < 1 { - r.c = make(map[string]*atomic.Value) - } - if _, ok := r.c[key]; !ok { - r.c[key] = &atomic.Value{} - } - - r.c[key].Store(cpt) -} - -func (r *rtrStatus) ComponentDel(key string) { - for k := range r.c { - if k == key { - r.c[k].Store(nil) + if v := val.Status(); s > v { + s = v + m = val.Message() } - } -} -func (r *rtrStatus) ComponentDelAll(containKey string) { - if containKey == "" { - r.c = make(map[string]*atomic.Value) - return - } + return true + }) - for k := range r.c { - if strings.Contains(k, containKey) { - r.c[k].Store(nil) - } - } + return s, m } diff --git a/status/pool.go b/status/pool.go new file mode 100644 index 00000000..0e2dae0b --- /dev/null +++ b/status/pool.go @@ -0,0 +1,98 @@ +/* + * 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 status + +import ( + "fmt" + + montps "github.com/nabbar/golib/monitor/types" +) + +func (o *sts) _getPool() montps.Pool { + o.m.RLock() + defer o.m.RUnlock() + + if o.p == nil { + return nil + } else if p := o.p(); p == nil { + return nil + } else { + return p + } +} + +func (o *sts) MonitorAdd(mon montps.Monitor) error { + if p := o._getPool(); p == nil { + return fmt.Errorf("monitor pool not defined") + } else { + return p.MonitorAdd(mon) + } +} + +func (o *sts) MonitorGet(name string) montps.Monitor { + if p := o._getPool(); p == nil { + return nil + } else { + return p.MonitorGet(name) + } +} + +func (o *sts) MonitorSet(mon montps.Monitor) error { + if p := o._getPool(); p == nil { + return fmt.Errorf("monitor pool not defined") + } else { + return p.MonitorSet(mon) + } +} + +func (o *sts) MonitorDel(name string) { + if p := o._getPool(); p == nil { + return + } else { + p.MonitorDel(name) + } +} + +func (o *sts) MonitorList() []string { + if p := o._getPool(); p == nil { + return nil + } else { + return p.MonitorList() + } +} + +func (o *sts) MonitorWalk(fct func(name string, val montps.Monitor) bool, validName ...string) { + if p := o._getPool(); p != nil { + p.MonitorWalk(fct, validName...) + } +} + +func (o *sts) RegisterPool(fct montps.FuncPool) { + o.m.Lock() + defer o.m.Unlock() + o.p = fct +} diff --git a/status/route.go b/status/route.go new file mode 100644 index 00000000..ae72e90a --- /dev/null +++ b/status/route.go @@ -0,0 +1,148 @@ +/* + * 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 status + +import ( + "context" + "strconv" + "strings" + + liberr "github.com/nabbar/golib/errors" + + ginsdk "github.com/gin-gonic/gin" +) + +const ( + headVerbose = "X-Verbose" + headFormat = "Accept" + keyVerbose = "short" + keyFormat = "format" +) + +// Expose adds metric path to a given router. +// The router can be different with the one passed to UseWithoutExposingEndpoint. +// This allows to expose ginMet on different port. +func (o *sts) Expose(ctx context.Context) { + if c, ok := ctx.(*ginsdk.Context); ok { + o.MiddleWare(c) + } +} + +// MiddleWare as gin monitor middleware HandleFunc. +func (o *sts) MiddleWare(c *ginsdk.Context) { + var ( + err liberr.Error + enc Encode + shr = o.isShort(c.Request.URL.Query().Get(keyVerbose), c.Request.Header.Get(headVerbose)) + txt = o.isText(c.Request.URL.Query().Get(keyFormat), strings.Join(c.Request.Header.Values(headFormat), ",")) + ) + + if enc, err = o.getMarshal(); err != nil { + ret := o.getErrorReturn() + err.Return(ret) + ret.GinTonicErrorAbort(c, 0) // 0 = internal server error + return + } + + if shr { + c.Header("X-Verbose", "False") + } else { + c.Header("X-Verbose", "True") + } + + c.Header("Connection", "Close") + enc.GinRender(c, txt, shr) +} + +func (o *sts) isText(query, header string) bool { + var txt = false + + if len(query) > 0 { + if strings.EqualFold(query, "text") { + txt = true + } + } + + if len(header) > 0 { + val := strings.Split(header, ",") + + for _, m := range val { + m = strings.TrimSpace(m) + if m == ginsdk.MIMEJSON { + txt = false + break + } else if m == ginsdk.MIMEPlain { + txt = true + break + } + } + } + + return txt +} + +func (o *sts) isShort(query, header string) bool { + var shr = false + + if len(query) > 0 { + if strings.EqualFold(query, "true") { + shr = true + } else if strings.EqualFold(query, "1") { + shr = true + } + } + + if len(header) > 0 { + if b, e := strconv.ParseBool(header); e == nil { + shr = !b + } + } + + return shr +} + +// MiddleWare as gin monitor middleware HandleFunc. +func (o *sts) getErrorReturn() liberr.ReturnGin { + o.m.RLock() + defer o.m.RUnlock() + + if o.r == nil { + return liberr.NewDefaultReturn() + } else if r := o.r(); r == nil { + return liberr.NewDefaultReturn() + } else { + return r + } +} + +// SetErrorReturn allow to register a return model use to export error to output. +func (o *sts) SetErrorReturn(f func() liberr.ReturnGin) { + o.m.Lock() + defer o.m.Unlock() + + o.r = f +} diff --git a/version/version.go b/version/version.go index 13b22410..5cf4dc2b 100644 --- a/version/version.go +++ b/version/version.go @@ -34,13 +34,13 @@ import ( "time" govers "github.com/hashicorp/go-version" - . "github.com/nabbar/golib/errors" + liberr "github.com/nabbar/golib/errors" ) type versionModel struct { versionRelease string versionBuild string - versionDate string + versionTime time.Time versionPackage string versionDescription string versionAuthor string @@ -50,12 +50,13 @@ type versionModel struct { } type Version interface { - CheckGo(RequireGoVersion, RequireGoContraint string) Error + CheckGo(RequireGoVersion, RequireGoContraint string) liberr.Error GetAppId() string GetAuthor() string GetBuild() string GetDate() string + GetTime() time.Time GetDescription() string GetHeader() string GetInfo() string @@ -87,10 +88,17 @@ func NewVersion(License license, Package, Description, Date, Build, Release, Aut Package = filepath.Base(Source) } + var timeBuild time.Time + if ts, err := time.Parse(time.RFC3339, Date); err != nil { + timeBuild = time.Now() + } else { + timeBuild = ts + } + return &versionModel{ versionRelease: Release, versionBuild: Build, - versionDate: Date, + versionTime: timeBuild, versionPackage: Package, versionDescription: Description, versionAuthor: Author, @@ -100,7 +108,7 @@ func NewVersion(License license, Package, Description, Date, Build, Release, Aut } } -func (vers versionModel) CheckGo(RequireGoVersion, RequireGoContraint string) Error { +func (v versionModel) CheckGo(RequireGoVersion, RequireGoContraint string) liberr.Error { constraint, err := govers.NewConstraint(RequireGoContraint + RequireGoVersion) if err != nil { return ErrorGoVersionInit.ErrorParent(err) @@ -119,88 +127,77 @@ func (vers versionModel) CheckGo(RequireGoVersion, RequireGoContraint string) Er return nil } -func (vers versionModel) getYearOfDate() string { - dt, err := time.Parse(time.RFC3339, vers.versionDate) - - if err != nil { - dt = time.Now() - } - - return fmt.Sprintf("%d", dt.Year()) +func (v versionModel) getYearOfDate() string { + return fmt.Sprintf("%d", v.versionTime.Year()) } // Info print all information about current build and version. -func (vers versionModel) PrintInfo() { - println(fmt.Sprintf("Running %s", vers.GetHeader())) +func (v versionModel) PrintInfo() { + println(fmt.Sprintf("Running %s", v.GetHeader())) } // GetInfo return string about current build and version. -func (vers versionModel) GetInfo() string { - return fmt.Sprintf("Release: %s, Build: %s, Date: %s", vers.versionRelease, vers.versionBuild, vers.versionDate) +func (v versionModel) GetInfo() string { + return fmt.Sprintf("Release: %s, Build: %s, Date: %s", v.versionRelease, v.versionBuild, v.GetDate()) } // GetAppId return string about package name, release and runtime info. -func (vers versionModel) GetAppId() string { - return fmt.Sprintf("%s (OS: %s; Arch: %s; Runtime: %s)", vers.versionRelease, runtime.GOOS, runtime.GOARCH, runtime.Version()[2:]) +func (v versionModel) GetAppId() string { + return fmt.Sprintf("%s (OS: %s; Arch: %s; Runtime: %s)", v.versionRelease, runtime.GOOS, runtime.GOARCH, runtime.Version()[2:]) } // GetAuthor return string about author name and repository info. -func (vers versionModel) GetAuthor() string { - return fmt.Sprintf("by %s (source : %s)", vers.versionAuthor, vers.versionSource) +func (v versionModel) GetAuthor() string { + return fmt.Sprintf("by %s (source : %s)", v.versionAuthor, v.versionSource) } -func (vers versionModel) GetDescription() string { - return vers.versionDescription +func (v versionModel) GetDescription() string { + return v.versionDescription } // GetAuthor return string about author name and repository info. -func (vers versionModel) GetHeader() string { - return fmt.Sprintf("%s (%s)", vers.versionPackage, vers.GetInfo()) +func (v versionModel) GetHeader() string { + return fmt.Sprintf("%s (%s)", v.versionPackage, v.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{} - } +func (v versionModel) GetDate() string { + return v.versionTime.Format(time.RFC1123) +} - return ts.Format(time.RFC1123) +func (v versionModel) GetTime() time.Time { + return v.versionTime } -func (vers versionModel) GetBuild() string { - return vers.versionBuild +func (v versionModel) GetBuild() string { + return v.versionBuild } -func (vers versionModel) GetPackage() string { - return vers.versionPackage +func (v versionModel) GetPackage() string { + return v.versionPackage } -func (vers versionModel) GetRootPackagePath() string { - return vers.versionSource +func (v versionModel) GetRootPackagePath() string { + return v.versionSource } -func (vers versionModel) GetPrefix() string { - return strings.ToUpper(vers.versionPrefix) +func (v versionModel) GetPrefix() string { + return strings.ToUpper(v.versionPrefix) } -func (vers versionModel) GetRelease() string { - return vers.versionRelease +func (v versionModel) GetRelease() string { + return v.versionRelease } -func (vers versionModel) GetLicenseName() string { - return vers.licenceType.GetLicenseName() +func (v versionModel) GetLicenseName() string { + return v.licenceType.GetLicenseName() } -func (vers versionModel) GetLicenseLegal(addMoreLicence ...license) string { +func (v versionModel) GetLicenseLegal(addMoreLicence ...license) string { if len(addMoreLicence) == 0 { - return vers.licenceType.GetLicense() + return v.licenceType.GetLicense() } - buff := bytes.NewBufferString(vers.licenceType.GetLicense()) + buff := bytes.NewBufferString(v.licenceType.GetLicense()) for _, l := range addMoreLicence { //nolint #nosec @@ -223,8 +220,8 @@ func (vers versionModel) GetLicenseLegal(addMoreLicence ...license) string { return buff.String() } -func (vers versionModel) GetLicenseFull(addMoreLicence ...license) string { - buff := bytes.NewBufferString(vers.GetLicenseBoiler(addMoreLicence...)) +func (v versionModel) GetLicenseFull(addMoreLicence ...license) string { + buff := bytes.NewBufferString(v.GetLicenseBoiler(addMoreLicence...)) //nolint #nosec /* #nosec */ @@ -240,18 +237,18 @@ func (vers versionModel) GetLicenseFull(addMoreLicence ...license) string { _, _ = buff.WriteString("\n\n") //nolint #nosec /* #nosec */ - _, _ = buff.WriteString(vers.GetLicenseLegal(addMoreLicence...)) + _, _ = buff.WriteString(v.GetLicenseLegal(addMoreLicence...)) return buff.String() } -func (vers versionModel) GetLicenseBoiler(addMoreLicence ...license) string { +func (v versionModel) GetLicenseBoiler(addMoreLicence ...license) string { if len(addMoreLicence) == 0 { - return vers.licenceType.GetBoilerPlate(vers.versionPackage, vers.versionDescription, vers.getYearOfDate(), vers.versionAuthor) + return v.licenceType.GetBoilerPlate(v.versionPackage, v.versionDescription, v.getYearOfDate(), v.versionAuthor) } - year := vers.getYearOfDate() - buff := bytes.NewBufferString(vers.licenceType.GetBoilerPlate(vers.versionPackage, vers.versionDescription, year, vers.versionAuthor)) + year := v.getYearOfDate() + buff := bytes.NewBufferString(v.licenceType.GetBoilerPlate(v.versionPackage, v.versionDescription, year, v.versionAuthor)) for _, l := range addMoreLicence { //nolint #nosec @@ -259,12 +256,12 @@ func (vers versionModel) GetLicenseBoiler(addMoreLicence ...license) string { _, _ = buff.WriteString("\n\n") //nolint #nosec /* #nosec */ - _, _ = buff.WriteString(l.GetBoilerPlate(vers.versionPackage, vers.versionDescription, year, vers.versionAuthor)) + _, _ = buff.WriteString(l.GetBoilerPlate(v.versionPackage, v.versionDescription, year, v.versionAuthor)) } return buff.String() } -func (vers versionModel) PrintLicense(addMoreLicence ...license) { - println(vers.GetLicenseBoiler(addMoreLicence...)) +func (v versionModel) PrintLicense(addMoreLicence ...license) { + println(v.GetLicenseBoiler(addMoreLicence...)) } diff --git a/viper/hook.go b/viper/hook.go new file mode 100644 index 00000000..13b09303 --- /dev/null +++ b/viper/hook.go @@ -0,0 +1,95 @@ +/*********************************************************************************************************************** + * + * 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 ( + "math" + "reflect" + + libmap "github.com/mitchellh/mapstructure" + spfvpr "github.com/spf13/viper" +) + +func (v *viper) hookIdx() uint8 { + i := v.i.Load() + if i < math.MaxUint8 { + return uint8(i) + } else { + return math.MaxUint8 + } +} + +func (v *viper) hookIdxInc() uint8 { + v.i.Add(1) + return v.hookIdx() +} + +func (v *viper) HookRegister(hook libmap.DecodeHookFunc) { + v.h.Store(v.hookIdxInc(), hook) +} + +func (v *viper) HookReset() { + v.h.Clean() +} + +func (v *viper) hookFuncViperDecoder() spfvpr.DecoderConfigOption { + return func(c *libmap.DecoderConfig) { + h := c.DecodeHook + c.DecodeHook = libmap.ComposeDecodeHookFunc( + v.hookFuncMapDecoder(), + h, + ) + } +} + +func (v *viper) hookFuncMapDecoder() libmap.DecodeHookFunc { + return func(f reflect.Value, t reflect.Value) (interface{}, error) { + var err error + data := f.Interface() + newFrom := f + + v.h.Walk(func(key uint8, val interface{}) bool { + if val == nil { + return true + } + + if data, err = libmap.DecodeHookExec(val, newFrom, t); err != nil { + return false + } + + newFrom = reflect.ValueOf(data) + return true + }) + + if err != nil { + return nil, err + } + + return data, nil + } +} diff --git a/viper/interface.go b/viper/interface.go index 7c758d31..b0bd18da 100644 --- a/viper/interface.go +++ b/viper/interface.go @@ -28,13 +28,19 @@ package viper import ( "io" + "sync/atomic" - spfvpr "github.com/spf13/viper" - + libmap "github.com/mitchellh/mapstructure" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" liblog "github.com/nabbar/golib/logger" + spfvpr "github.com/spf13/viper" ) +type FuncViper func() Viper +type FuncSPFViper func() *spfvpr.Viper +type FuncConfigGet func(key string, model interface{}) liberr.Error + type Viper interface { SetRemoteProvider(provider string) SetRemoteEndpoint(endpoint string) @@ -52,10 +58,23 @@ type Viper interface { Viper() *spfvpr.Viper WatchFS(logLevelFSInfo liblog.Level) Unset(key ...string) error + + HookRegister(hook libmap.DecodeHookFunc) + HookReset() + + UnmarshalKey(key string, rawVal interface{}) error + Unmarshal(rawVal interface{}) error + UnmarshalExact(rawVal interface{}) error } -func New() Viper { - return &viper{ +func New(ctx libctx.FuncContext) Viper { + v := &viper{ v: spfvpr.New(), + i: new(atomic.Uint32), + h: libctx.NewConfig[uint8](ctx), } + + v.i.Store(0) + + return v } diff --git a/viper/model.go b/viper/model.go index 7092b547..b504085c 100644 --- a/viper/model.go +++ b/viper/model.go @@ -29,8 +29,10 @@ package viper import ( "fmt" "io" + "sync/atomic" libhom "github.com/mitchellh/go-homedir" + libctx "github.com/nabbar/golib/context" liberr "github.com/nabbar/golib/errors" spfvpr "github.com/spf13/viper" ) @@ -50,6 +52,8 @@ type viperRemote struct { type viper struct { v *spfvpr.Viper + i *atomic.Uint32 + h libctx.Config[uint8] base string prfx string diff --git a/viper/unmarshall.go b/viper/unmarshall.go new file mode 100644 index 00000000..f559269a --- /dev/null +++ b/viper/unmarshall.go @@ -0,0 +1,40 @@ +/*********************************************************************************************************************** + * + * 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 + +func (v *viper) UnmarshalKey(key string, rawVal interface{}) error { + return v.v.UnmarshalKey(key, rawVal, v.hookFuncViperDecoder()) +} + +func (v *viper) Unmarshal(rawVal interface{}) error { + return v.v.Unmarshal(rawVal, v.hookFuncViperDecoder()) +} + +func (v *viper) UnmarshalExact(rawVal interface{}) error { + return v.v.UnmarshalExact(rawVal, v.hookFuncViperDecoder()) +}