1 Commits

Author SHA1 Message Date
1416ab1b3a update golangci
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build was killed
2023-06-04 19:26:25 +03:00
177 changed files with 1604 additions and 8931 deletions

29
.drone.yml Normal file
View File

@@ -0,0 +1,29 @@
kind: pipeline
name: default
services:
- name: vault
image: vault:1.7.1
environment:
VAULT_DEV_ROOT_TOKEN_ID: dev
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
- name: etcd
image: bitnami/etcd:3
environment:
ALLOW_NONE_AUTHENTICATION: yes
environment:
VAULT_DEV_LISTEN_ADDRESS: http://vault:8200
VAULT_DEV_ROOT_TOKEN_ID: dev
FDEVS_CONFIG_ETCD_HOST: etcd:2379
steps:
- name: test
image: golang
commands:
- go test -parallel 10 -race ./...
- name: golangci-lint
image: golangci/golangci-lint:v1.53
commands:
- golangci-lint run

View File

@@ -1,38 +0,0 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/dasel/**'
- '.gitea/workflows/dasel.yml'
pull_request:
paths:
- 'provider/dasel/**'
- '.gitea/workflows/dasel.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/dasel
- name: Run go test
run: go test ./...
working-directory: ./provider/dasel

View File

@@ -1,50 +0,0 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/etcd/**'
- '.gitea/workflows/etcd.yml'
pull_request:
paths:
- 'provider/etcd/**'
- '.gitea/workflows/etcd.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
services:
# The label "etcd-server" becomes the hostname for the service.
etcd-server:
# Use the official etcd Docker image
image: quay.io/coreos/etcd:v3.6.7
env:
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
ETCD_ADVERTISE_CLIENT_URLS: http://etcd-server:2379
env:
FDEVS_CONFIG_ETCD_HOST: 'etcd-server:2379'
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/etcd
- name: Run go test
run: go test ./...
working-directory: ./provider/etcd

View File

@@ -1,47 +0,0 @@
name: Go Action
on:
push:
branches:
- master
pull_request:
paths-ignore:
- 'provider/etcd/**'
- '.gitea/workflows/etcd.yml'
- 'provider/ini/**'
- '.gitea/workflows/ini.yml'
- 'provider/json/**'
- '.gitea/workflows/json.yml'
- 'provider/toml/**'
- '.gitea/workflows/toml.yml'
- 'provider/vault/**'
- '.gitea/workflows/vault.yml'
- 'provider/yaml/**'
- '.gitea/workflows/yaml.yml'
- 'provider/dasel/**'
- '.gitea/workflows/dasel.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
- name: Run go test
run: go test ./...

View File

@@ -1,38 +0,0 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/ini/**'
- '.gitea/workflows/ini.yml'
pull_request:
paths:
- 'provider/ini/**'
- '.gitea/workflows/ini.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/ini
- name: Run go test
run: go test ./...
working-directory: ./provider/ini

View File

@@ -1,38 +0,0 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/json/**'
- '.gitea/workflows/json.yml'
pull_request:
paths:
- 'provider/json/**'
- '.gitea/workflows/json.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/json
- name: Run go test
run: go test ./...
working-directory: ./provider/json

View File

@@ -1,38 +0,0 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/toml/**'
- '.gitea/workflows/toml.yml'
pull_request:
paths:
- 'provider/toml/**'
- '.gitea/workflows/toml.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/toml
- name: Run go test
run: go test ./...
working-directory: ./provider/toml

View File

@@ -1,46 +0,0 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/vault/**'
- '.gitea/workflows/vault.yml'
pull_request:
paths:
- 'provider/vault/**'
- '.gitea/workflows/vault.yml'
jobs:
goaction:
services:
vault-server:
image: vault:1.13.3
env:
VAULT_DEV_ROOT_TOKEN_ID: "dev"
env:
VAULT_DEV_LISTEN_ADDRESS: 'http://vault-server:8200'
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/vault
- name: Run go test
run: go test ./...
working-directory: ./provider/vault

View File

@@ -1,38 +0,0 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/yaml/**'
- '.gitea/workflows/yaml.yml'
pull_request:
paths:
- 'provider/yaml/**'
- '.gitea/workflows/yaml.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/yaml
- name: Run go test
run: go test ./...
working-directory: ./provider/yaml

View File

@@ -1,20 +1,7 @@
version: "2"
linters:
default: all
disable:
- noinlineerr
- depguard
- ireturn
- gochecknoglobals
# deprecated
- wsl
settings:
recvcheck:
disable-builtin: true
exclusions:
- "*.String"
funcorder:
constructor: false
run:
timeout: 5m
linters-settings:
dupl:
threshold: 100
funlen:
@@ -23,60 +10,54 @@ linters:
goconst:
min-len: 2
min-occurrences: 2
cyclop:
max-complexity: 15
gocyclo:
min-complexity: 15
golint:
min-confidence: 0
govet:
check-shadowing: true
lll:
line-length: 140
maligned:
suggest-new: true
misspell:
locale: US
locale: US
varnamelen:
min-name-length: 2
ignore-decls:
- w io.Writer
- t testing.T
- e error
- i int
- b bytes.Buffer
- h Handle
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- exhaustivestruct
- exhaustruct
- funlen
- mnd
- tenv
- varnamelen
- wrapcheck
path: _test\.go
- linters:
- exhaustivestruct
- exhaustruct
- mnd
- varnamelen
- wrapcheck
path: test/*
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
linters:
enable-all: true
disable:
- exhaustivestruct
- maligned
- interfacer
- scopelint
- exhaustruct
- depguard
#deprecated
- structcheck
- varcheck
- golint
- deadcode
- ifshort
- nosnakecase
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- path: _test\.go
linters:
- gomnd
- exhaustivestruct
- wrapcheck
- exhaustruct
- varnamelen
- tenv
- funlen
- path: test/*
linters:
- gomnd
- exhaustivestruct
- wrapcheck
- exhaustruct
- varnamelen

View File

@@ -1,5 +1,5 @@
# config
![Build Status](https://gitoa.ru/go-4devs/config/actions/workflows/goaction.yml/badge.svg)
[![Build Status](https://drone.gitoa.ru/api/badges/go-4devs/cache/status.svg)](https://drone.gitoa.ru/go-4devs/config)
[![Go Report Card](https://goreportcard.com/badge/gitoa.ru/go-4devs/config)](https://goreportcard.com/report/gitoa.ru/go-4devs/config)
[![GoDoc](https://godoc.org/gitoa.ru/go-4devs/config?status.svg)](http://godoc.org/gitoa.ru/go-4devs/config)

175
client.go
View File

@@ -4,12 +4,12 @@ import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
)
var _ Providers = (*Client)(nil)
func Must(providers ...any) *Client {
client, err := New(providers...)
func Must(namespace, appName string, providers ...interface{}) *Client {
client, err := New(namespace, appName, providers...)
if err != nil {
panic(err)
}
@@ -17,77 +17,144 @@ func Must(providers ...any) *Client {
return client
}
func New(providers ...any) (*Client, error) {
func New(namespace, appName string, providers ...interface{}) (*Client, error) {
client := &Client{
namespace: namespace,
appName: appName,
providers: make([]Provider, len(providers)),
name: make(map[string]int),
chain: make([]Providers, 0, len(providers)),
}
for idx, prov := range providers {
var name string
switch current := prov.(type) {
case Provider:
client.providers[idx] = current
name = current.Name()
case Factory:
client.providers[idx] = WrapFactory(current, client)
name = current.Name()
client.providers[idx] = &provider{
factory: func(ctx context.Context) (Provider, error) {
return current(ctx, client)
},
}
default:
return nil, fmt.Errorf("provier[%d]: %w %T", idx, ErrUnknowType, prov)
}
client.name[name] = idx
if current, ok := prov.(Providers); ok {
client.chain = append(client.chain, current)
}
}
return client, nil
}
type Client struct {
providers []Provider
name map[string]int
chain []Providers
type provider struct {
mu sync.Mutex
done uint32
provider Provider
factory func(ctx context.Context) (Provider, error)
}
func (c *Client) Name() string {
return "client"
func (p *provider) init(ctx context.Context) error {
if atomic.LoadUint32(&p.done) == 0 {
if !p.mu.TryLock() {
return fmt.Errorf("%w", ErrInitFactory)
}
defer atomic.StoreUint32(&p.done, 1)
defer p.mu.Unlock()
var err error
if p.provider, err = p.factory(ctx); err != nil {
return fmt.Errorf("init provider factory:%w", err)
}
}
return nil
}
func (p *provider) Watch(ctx context.Context, key Key, callback WatchCallback) error {
if err := p.init(ctx); err != nil {
return fmt.Errorf("init read:%w", err)
}
watch, ok := p.provider.(WatchProvider)
if !ok {
return nil
}
if err := watch.Watch(ctx, key, callback); err != nil {
return fmt.Errorf("factory provider: %w", err)
}
return nil
}
func (p *provider) Read(ctx context.Context, key Key) (Variable, error) {
if err := p.init(ctx); err != nil {
return Variable{}, fmt.Errorf("init read:%w", err)
}
variable, err := p.provider.Read(ctx, key)
if err != nil {
return Variable{}, fmt.Errorf("factory provider: %w", err)
}
return variable, nil
}
type Client struct {
providers []Provider
appName string
namespace string
}
func (c *Client) key(name string) Key {
return Key{
Name: name,
AppName: c.appName,
Namespace: c.namespace,
}
}
// Value get value by name.
func (c *Client) Value(ctx context.Context, path ...string) (Value, error) {
// nolint: ireturn
func (c *Client) Value(ctx context.Context, name string) (Value, error) {
variable, err := c.Variable(ctx, name)
if err != nil {
return nil, fmt.Errorf("variable:%w", err)
}
return variable.Value, nil
}
func (c *Client) Variable(ctx context.Context, name string) (Variable, error) {
var (
value Value
err error
variable Variable
err error
)
key := c.key(name)
for _, provider := range c.providers {
value, err = provider.Value(ctx, path...)
if err == nil || (!errors.Is(err, ErrNotFound) && !errors.Is(err, ErrInitFactory)) {
variable, err = provider.Read(ctx, key)
if err == nil || !(errors.Is(err, ErrVariableNotFound) || errors.Is(err, ErrInitFactory)) {
break
}
}
if err != nil {
return value, fmt.Errorf("client failed get value: %w", err)
return variable, fmt.Errorf("client failed get variable: %w", err)
}
return value, nil
return variable, nil
}
func (c *Client) Watch(ctx context.Context, callback WatchCallback, path ...string) error {
func (c *Client) Watch(ctx context.Context, name string, callback WatchCallback) error {
key := c.key(name)
for idx, prov := range c.providers {
provider, ok := prov.(WatchProvider)
if !ok {
continue
}
err := provider.Watch(ctx, callback, path...)
err := provider.Watch(ctx, key, callback)
if err != nil {
if errors.Is(err, ErrNotFound) || errors.Is(err, ErrInitFactory) {
if errors.Is(err, ErrVariableNotFound) || errors.Is(err, ErrInitFactory) {
continue
}
@@ -97,45 +164,3 @@ func (c *Client) Watch(ctx context.Context, callback WatchCallback, path ...stri
return nil
}
func (c *Client) Bind(ctx context.Context, data Variables) error {
for idx, prov := range c.providers {
provider, ok := prov.(BindProvider)
if !ok {
continue
}
if err := provider.Bind(ctx, data); err != nil {
return fmt.Errorf("bind[%d] %v:%w", idx, provider.Name(), err)
}
}
return nil
}
func (c *Client) Provider(name string) (Provider, error) {
if idx, ok := c.name[name]; ok {
return c.providers[idx], nil
}
for _, prov := range c.chain {
if cprov, err := prov.Provider(name); err == nil {
return cprov, nil
}
}
return nil, fmt.Errorf("provider[%v]:%w", c.Name(), ErrNotFound)
}
func (c *Client) Names() []string {
names := make([]string, 0, len(c.providers))
for name := range c.name {
names = append(names, name)
}
for _, prov := range c.chain {
names = append(names, prov.Names()...)
}
return names
}

View File

@@ -11,8 +11,11 @@ import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/provider/env"
"gitoa.ru/go-4devs/config/provider/factory"
"gitoa.ru/go-4devs/config/provider/etcd"
"gitoa.ru/go-4devs/config/provider/json"
"gitoa.ru/go-4devs/config/provider/vault"
"gitoa.ru/go-4devs/config/provider/watcher"
"gitoa.ru/go-4devs/config/provider/yaml"
"gitoa.ru/go-4devs/config/test"
)
@@ -29,11 +32,218 @@ func ExampleClient_Value() {
os.Args = []string{"main.go", "--host=gitoa.ru"}
// read json config
// configure etcd client
etcdClient, err := test.NewEtcd(ctx)
if err != nil {
log.Print(err)
config, err := config.New(
return
}
// configure vault client
vaultClient, err := test.NewVault()
if err != nil {
log.Print(err)
return
}
// read json config
jsonConfig := test.ReadFile("config.json")
config, err := config.New(test.Namespace, test.AppName,
arg.New(),
env.New(test.Namespace, test.AppName),
env.New(),
etcd.NewProvider(etcdClient),
vault.NewSecretKV2(vaultClient),
json.New(jsonConfig),
)
if err != nil {
log.Print(err)
return
}
dsn, err := config.Value(ctx, "example:dsn")
if err != nil {
log.Print(err)
return
}
port, err := config.Value(ctx, "listen")
if err != nil {
log.Print(err)
return
}
enabled, err := config.Value(ctx, "maintain")
if err != nil {
log.Print(err)
return
}
title, err := config.Value(ctx, "app.name.title")
if err != nil {
log.Print(err)
return
}
cfgValue, err := config.Value(ctx, "cfg")
if err != nil {
log.Print(err)
return
}
hostValue, err := config.Value(ctx, "host")
if err != nil {
log.Print(err)
return
}
cfg := test.Config{}
_ = cfgValue.Unmarshal(&cfg)
fmt.Printf("dsn from vault: %s\n", dsn.String())
fmt.Printf("listen from env: %d\n", port.Int())
fmt.Printf("maintain from etcd: %v\n", enabled.Bool())
fmt.Printf("title from json: %v\n", title.String())
fmt.Printf("struct from json: %+v\n", cfg)
fmt.Printf("replace env host by args: %v\n", hostValue.String())
// Output:
// dsn from vault: pgsql://user@pass:127.0.0.1:5432
// listen from env: 8080
// maintain from etcd: true
// title from json: config title
// struct from json: {Duration:21m0s Enabled:true}
// replace env host by args: gitoa.ru
}
func ExampleClient_Watch() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// configure etcd client
etcdClient, err := test.NewEtcd(ctx)
if err != nil {
log.Print(err)
return
}
_ = os.Setenv("FDEVS_CONFIG_EXAMPLE_ENABLE", "true")
_, err = etcdClient.KV.Put(ctx, "fdevs/config/example_db_dsn", "pgsql://user@pass:127.0.0.1:5432")
if err != nil {
log.Print(err)
return
}
defer func() {
cancel()
if _, err = etcdClient.KV.Delete(context.Background(), "fdevs/config/example_db_dsn"); err != nil {
log.Print(err)
return
}
}()
watcher, err := config.New(test.Namespace, test.AppName,
watcher.New(time.Microsecond, env.New()),
watcher.New(time.Microsecond, yaml.NewWatch("test/fixture/config.yaml")),
etcd.NewProvider(etcdClient),
)
if err != nil {
log.Print(err)
return
}
wg := sync.WaitGroup{}
wg.Add(2)
err = watcher.Watch(ctx, "example_enable", func(ctx context.Context, oldVar, newVar config.Variable) {
fmt.Println("update ", oldVar.Provider, " variable:", oldVar.Name, ", old: ", oldVar.Value.Bool(), " new:", newVar.Value.Bool())
wg.Done()
})
if err != nil {
log.Print(err)
return
}
_ = os.Setenv("FDEVS_CONFIG_EXAMPLE_ENABLE", "false")
err = watcher.Watch(ctx, "example_db_dsn", func(ctx context.Context, oldVar, newVar config.Variable) {
fmt.Println("update ", oldVar.Provider, " variable:", oldVar.Name, ", old: ", oldVar.Value.String(), " new:", newVar.Value.String())
wg.Done()
})
if err != nil {
log.Print(err)
return
}
time.AfterFunc(time.Second, func() {
if _, err := etcdClient.KV.Put(ctx, "fdevs/config/example_db_dsn", "mysql://localhost:5432"); err != nil {
log.Print(err)
return
}
})
wg.Wait()
// Output:
// update env variable: FDEVS_CONFIG_EXAMPLE_ENABLE , old: true new: false
// update etcd variable: fdevs/config/example_db_dsn , old: pgsql://user@pass:127.0.0.1:5432 new: mysql://localhost:5432
}
func ExampleClient_Value_factory() {
ctx := context.Background()
_ = os.Setenv("FDEVS_CONFIG_LISTEN", "8080")
_ = os.Setenv("FDEVS_CONFIG_HOST", "localhost")
args := os.Args
defer func() {
os.Args = args
}()
os.Args = []string{"main.go", "--config-json=config.json", "--config-yaml=test/fixture/config.yaml"}
config, err := config.New(test.Namespace, test.AppName,
arg.New(),
env.New(),
config.Factory(func(ctx context.Context, cfg config.ReadConfig) (config.Provider, error) {
val, err := cfg.Value(ctx, "config-json")
if err != nil {
return nil, fmt.Errorf("failed read config file:%w", err)
}
jsonConfig := test.ReadFile(val.String())
return json.New(jsonConfig), nil
}),
config.Factory(func(ctx context.Context, cfg config.ReadConfig) (config.Provider, error) {
val, err := cfg.Value(ctx, "config-yaml")
if err != nil {
return nil, fmt.Errorf("failed read config file:%w", err)
}
provader, err := yaml.NewFile(val.String())
if err != nil {
return nil, fmt.Errorf("failed init by file %v:%w", val.String(), err)
}
return provader, nil
}),
)
if err != nil {
log.Print(err)
@@ -43,133 +253,42 @@ func ExampleClient_Value() {
port, err := config.Value(ctx, "listen")
if err != nil {
log.Print("listen: ", err)
log.Print(err)
return
}
hostValue, err := config.Value(ctx, "host")
title, err := config.Value(ctx, "app.name.title")
if err != nil {
log.Print("host:", err)
log.Print(err)
return
}
yamlTitle, err := config.Value(ctx, "app/title")
if err != nil {
log.Print(err)
return
}
cfgValue, err := config.Value(ctx, "cfg")
if err != nil {
log.Print(err)
return
}
cfg := test.Config{}
_ = cfgValue.Unmarshal(&cfg)
fmt.Printf("listen from env: %d\n", port.Int())
fmt.Printf("replace env host by args: %v\n", hostValue.String())
fmt.Printf("title from json: %v\n", title.String())
fmt.Printf("yaml title: %v\n", yamlTitle.String())
fmt.Printf("struct from json: %+v\n", cfg)
// Output:
// listen from env: 8080
// replace env host by args: gitoa.ru
}
func ExampleClient_Watch() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_ = os.Setenv("FDEVS_CONFIG_EXAMPLE_ENABLE", "true")
watcher, err := config.New(
watcher.New(time.Microsecond, env.New(test.Namespace, test.AppName)),
)
if err != nil {
log.Print(err)
return
}
wg := sync.WaitGroup{}
wg.Add(1)
err = watcher.Watch(ctx, func(_ context.Context, oldVar, newVar config.Value) error {
fmt.Println("update example_enable old: ", oldVar.Bool(), " new:", newVar.Bool())
wg.Done()
return nil
}, "example_enable")
if err != nil {
log.Print(err)
return
}
_ = os.Setenv("FDEVS_CONFIG_EXAMPLE_ENABLE", "false")
err = watcher.Watch(ctx, func(_ context.Context, oldVar, newVar config.Value) error {
fmt.Println("update example_db_dsn old: ", oldVar.String(), " new:", newVar.String())
wg.Done()
return nil
}, "example_db_dsn")
if err != nil {
log.Print(err)
return
}
wg.Wait()
// Output:
// update example_enable old: true new: false
}
func ExampleClient_Value_factory() {
ctx := context.Background()
_ = os.Setenv("FDEVS_CONFIG_LISTEN", "8080")
_ = os.Setenv("FDEVS_CONFIG_HOST", "localhost")
_ = os.Setenv("FDEVS_GOLANG_HOST", "go.dev")
args := os.Args
defer func() {
os.Args = args
}()
os.Args = []string{"main.go", "--env=golang"}
config, err := config.New(
arg.New(),
factory.New("factory:env", func(ctx context.Context, cfg config.Provider) (config.Provider, error) {
val, err := cfg.Value(ctx, "env")
if err != nil {
return nil, fmt.Errorf("failed read config file:%w", err)
}
return env.New(test.Namespace, val.String()), nil
}),
env.New(test.Namespace, test.AppName),
)
if err != nil {
log.Print(err)
return
}
envName, err := config.Value(ctx, "env")
if err != nil {
log.Print("env ", err)
return
}
host, err := config.Value(ctx, "host")
if err != nil {
log.Print("host ", err)
return
}
listen, err := config.Value(ctx, "listen")
if err != nil {
log.Print("listen", err)
return
}
fmt.Printf("envName from env: %s\n", envName.String())
fmt.Printf("host from env with app name golang: %s\n", host.String())
fmt.Printf("listen from env with default app name: %s\n", listen.String())
// Output:
// envName from env: golang
// host from env with app name golang: go.dev
// listen from env with default app name: 8080
// title from json: config title
// yaml title: yaml title
// struct from json: {Duration:21m0s Enabled:true}
}

View File

@@ -1,32 +0,0 @@
package main
import (
"context"
"os"
"os/signal"
"syscall"
"gitoa.ru/go-4devs/config/definition/generate/command"
"gitoa.ru/go-4devs/console"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan os.Signal, 1)
defer close(ch)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-ch
cancel()
}()
console.
New().
Add(
command.Command(),
).
Execute(ctx)
}

View File

@@ -1,40 +0,0 @@
package definition
import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
)
var _ config.Options = (*Definition)(nil)
func New(opts ...config.Option) *Definition {
return &Definition{
options: opts,
Params: param.New(),
}
}
type Definition struct {
param.Params
options []config.Option
}
func (d *Definition) Add(opts ...config.Option) {
d.options = append(d.options, opts...)
}
func (d *Definition) Options() []config.Option {
return d.options
}
func (d *Definition) With(params param.Params) *Definition {
def := &Definition{
options: make([]config.Option, len(d.options)),
Params: param.Chain(params, d.Params),
}
copy(def.options, d.options)
return def
}

View File

@@ -1,94 +0,0 @@
package bootstrap
import (
"context"
"embed"
"fmt"
"os"
"path/filepath"
"text/template"
"gitoa.ru/go-4devs/config/definition/generate/pkg"
)
//go:embed *.tpl
var tpls embed.FS
type Boot struct {
Config
*pkg.Packages
Configure []string
}
func (b Boot) Pkg() string {
return pkg.Pkg(b.FullPkg())
}
type Config interface {
File() string
Methods() []string
SkipContext() bool
Prefix() string
Suffix() string
FullPkg() string
}
func Bootstrap(ctx context.Context, cfg Config) (string, error) {
fInfo, err := os.Stat(cfg.File())
if err != nil {
return "", fmt.Errorf("stat[%v]:%w", cfg.File(), err)
}
pkgPath, err := pkg.ByPath(ctx, cfg.File(), fInfo.IsDir())
if err != nil {
return "", fmt.Errorf("pkg by path:%w", err)
}
tmpFile, err := os.CreateTemp(filepath.Dir(fInfo.Name()), "config-bootstrap")
if err != nil {
return "", fmt.Errorf("create tmp file:%w", err)
}
tpl, err := template.ParseFS(tpls, "bootstrap.go.tpl")
if err != nil {
return "", fmt.Errorf("parse template:%w", err)
}
imports := pkg.NewImports("main").
Adds(
"context",
"gitoa.ru/go-4devs/config/definition",
"gitoa.ru/go-4devs/config",
"gitoa.ru/go-4devs/config/definition/generate/view",
"gitoa.ru/go-4devs/config/param",
"gitoa.ru/go-4devs/config/definition/generate",
"os",
"io",
"fmt",
pkgPath,
)
data := Boot{
Packages: imports,
Configure: cfg.Methods(),
Config: cfg,
}
if err := tpl.Execute(tmpFile, data); err != nil {
return "", fmt.Errorf("execute:%w", err)
}
src := tmpFile.Name()
if err := tmpFile.Close(); err != nil {
return src, fmt.Errorf("close file:%w", err)
}
dest := src + ".go"
if err := os.Rename(src, dest); err != nil {
return dest, fmt.Errorf("rename idt:%w", err)
}
return dest, nil
}

View File

@@ -1,43 +0,0 @@
//go:build ignore
// +build ignore
package main
import (
{{range .Imports}}
{{- .Alias }}"{{ .Package }}"
{{end}}
)
func main() {
if err := run(os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(f io.Writer) error {
ctx := context.Background()
defs:=make([]config.Options,0)
{{ range .Configure }}
params{{.}} := param.New(
{{- if $.SkipContext }}view.WithSkipContext,{{ end }}
view.WithStructName("{{$.Prefix}}_{{.}}_{{$.Suffix}}"),
view.WithStructPrefix("{{$.Prefix}}"),
view.WithStructSuffix("{{$.Suffix}}"),
)
def{{.}} := definition.New().With(params{{.}})
if err := {{$.Pkg}}.{{.}}(ctx, def{{.}}); err != nil {
return err
}
defs = append(defs,def{{.}})
{{ end }}
if gerr := generate.Run(ctx,"{{.FullPkg}}",f, defs...);gerr != nil {
return gerr
}
return nil
}

View File

@@ -1,64 +0,0 @@
package command
import (
"context"
"fmt"
"os"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/generate"
"gitoa.ru/go-4devs/config/provider/chain"
"gitoa.ru/go-4devs/console/command"
"gitoa.ru/go-4devs/console/output"
)
const Name = "config:generate"
func Handle(ctx context.Context, in config.Provider, out output.Output, next command.ExecuteFn) error {
var name string
value, err := in.Value(ctx, generate.OptionFile)
if err == nil {
name = value.String()
}
if name == "" {
name = os.Getenv("GOFILE")
}
parser, err := generate.Parse(ctx, name)
if err != nil {
return fmt.Errorf("parse:%w", err)
}
mem, merr := generate.NewMemoryProvider(name,
generate.WithOutName(parser.OutName()),
generate.WithFullPkg(parser.FullPkg()),
generate.WithMethods(parser.Methods()...),
)
if merr != nil {
return fmt.Errorf("mem provider:%w", merr)
}
return next(ctx, chain.New(in, mem), out)
}
func Execute(ctx context.Context, in config.Provider, _ output.Output) error {
cfg := generate.NewConfigure(ctx, in)
if err := generate.Generate(ctx, cfg); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
func Command() command.Command {
return command.New(
Name,
"generate helper for configure command",
Execute,
command.Configure(generate.Configure),
command.Handle(Handle),
)
}

View File

@@ -1,134 +0,0 @@
package generate
import (
"context"
"fmt"
"strconv"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/provider/memory"
)
const (
OptionFile = "file"
optionPrefix = "prefix"
optionSuffix = "suffix"
optionSkipContext = "skip-context"
optionBuildTags = "build-tags"
optionOutName = "out-name"
optionMethod = "method"
optionFullPkg = "full-pkg"
)
func WithPrefix(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionPrefix)
if err != nil {
return fmt.Errorf("append %v:%w", optionPrefix, err)
}
return nil
}
}
func WithSuffix(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionSuffix)
if err != nil {
return fmt.Errorf("append %v:%w", optionSuffix, err)
}
return nil
}
}
func WithSkipContext(in bool) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(strconv.FormatBool(in), optionSkipContext)
if err != nil {
return fmt.Errorf("append %v:%w", optionSkipContext, err)
}
return nil
}
}
func WithBuildTags(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionBuildTags)
if err != nil {
return fmt.Errorf("append %v:%w", optionBuildTags, err)
}
return nil
}
}
func WithOutName(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionOutName)
if err != nil {
return fmt.Errorf("append %v:%w", optionOutName, err)
}
return nil
}
}
func WithMethods(methods ...string) func(*memory.Map) error {
return func(m *memory.Map) error {
for _, method := range methods {
err := m.AppendOption(method, optionMethod)
if err != nil {
return fmt.Errorf("append %v:%w", optionMethod, err)
}
}
return nil
}
}
func WithFullPkg(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionFullPkg)
if err != nil {
return fmt.Errorf("append %v:%w", optionFullPkg, err)
}
return nil
}
}
func NewMemoryProvider(file string, opts ...func(*memory.Map) error) (*memory.Map, error) {
cfg := new(memory.Map)
err := cfg.AppendOption(file, OptionFile)
if err != nil {
return nil, fmt.Errorf("append %v:%w", OptionFile, err)
}
for idx, opt := range opts {
err = opt(cfg)
if err != nil {
return nil, fmt.Errorf("opt[%d]:%w", idx, err)
}
}
return cfg, nil
}
func Configure(_ context.Context, def config.Definition) error {
def.Add(
option.String(OptionFile, "set file", option.Required),
option.String(optionPrefix, "struct prefix"),
option.String(optionSuffix, "struct suffix", option.Default("Config")),
option.Bool(optionSkipContext, "skip contect to method"),
option.String(optionBuildTags, "add build tags"),
option.String(optionOutName, "set out name"),
option.String(optionMethod, "set method", option.Slice),
option.String(optionFullPkg, "set full pkg"),
)
return nil
}

View File

@@ -1,231 +0,0 @@
package generate
import (
"context"
"errors"
"fmt"
"log"
"gitoa.ru/go-4devs/config"
)
func NewConfigure(ctx context.Context, prov config.Provider) ConfigureConfig {
return ConfigureConfig{
Provider: prov,
ctx: ctx,
handle: func(err error) {
if !errors.Is(err, config.ErrNotFound) {
log.Print(err)
}
},
}
}
type ConfigureConfig struct {
config.Provider
ctx context.Context //nolint:containedctx
handle func(err error)
}
func (i ConfigureConfig) ReadBuildTags() (string, error) {
val, verr := i.Value(i.ctx, optionBuildTags)
if verr != nil {
return "", fmt.Errorf("get %v:%w", optionBuildTags, verr)
}
data, err := val.ParseString()
if err != nil {
return "", fmt.Errorf("parse %v:%w", optionBuildTags, err)
}
return data, nil
}
func (i ConfigureConfig) BuildTags() string {
data, err := i.ReadBuildTags()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadOutName() (string, error) {
val, err := i.Value(i.ctx, optionOutName)
if err != nil {
return "", fmt.Errorf("get %v:%w", optionOutName, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionOutName, derr)
}
return data, nil
}
func (i ConfigureConfig) OutName() string {
data, err := i.ReadOutName()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadFile() (string, error) {
val, err := i.Value(i.ctx, OptionFile)
if err != nil {
return "", fmt.Errorf("get %v:%w", OptionFile, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", OptionFile, derr)
}
return data, nil
}
func (i ConfigureConfig) File() string {
data, err := i.ReadFile()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadMethods() ([]string, error) {
val, err := i.Value(i.ctx, optionMethod)
if err != nil {
return nil, fmt.Errorf("get %v:%w", optionMethod, err)
}
var data []string
perr := val.Unmarshal(&data)
if perr != nil {
return nil, fmt.Errorf("unmarshal %v:%w", optionMethod, perr)
}
return data, nil
}
func (i ConfigureConfig) Methods() []string {
data, err := i.ReadMethods()
if err != nil {
i.handle(err)
return nil
}
return data
}
func (i ConfigureConfig) ReadSkipContext() (bool, error) {
val, err := i.Value(i.ctx, optionSkipContext)
if err != nil {
return false, fmt.Errorf("get %v:%w", optionSkipContext, err)
}
data, derr := val.ParseBool()
if derr != nil {
return false, fmt.Errorf("parse %v:%w", optionSkipContext, derr)
}
return data, nil
}
func (i ConfigureConfig) SkipContext() bool {
data, err := i.ReadSkipContext()
if err != nil {
i.handle(err)
return false
}
return data
}
func (i ConfigureConfig) ReadPrefix() (string, error) {
val, err := i.Value(i.ctx, optionPrefix)
if err != nil {
return "", fmt.Errorf("get %v: %w", optionPrefix, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionPrefix, derr)
}
return data, nil
}
func (i ConfigureConfig) Prefix() string {
val, err := i.ReadPrefix()
if err != nil {
i.handle(err)
return ""
}
return val
}
func (i ConfigureConfig) ReadSuffix() (string, error) {
val, err := i.Value(i.ctx, optionSuffix)
if err != nil {
return "", fmt.Errorf("get %v:%w", optionSuffix, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionSuffix, derr)
}
return data, nil
}
func (i ConfigureConfig) Suffix() string {
data, err := i.ReadSuffix()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadFullPkg() (string, error) {
val, err := i.Value(i.ctx, optionFullPkg)
if err != nil {
return "", fmt.Errorf("get %v:%w", optionFullPkg, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionFullPkg, derr)
}
return data, nil
}
func (i ConfigureConfig) FullPkg() string {
data, err := i.ReadFullPkg()
if err != nil {
i.handle(err)
return ""
}
return data
}

View File

@@ -1,39 +0,0 @@
package example
import (
"context"
configs "gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/generate/view"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
)
//go:generate go run ../../../cmd/config/main.go config:generate
type Level string
func (l *Level) UnmarshalText(in []byte) error {
data := string(in)
*l = Level(data)
return nil
}
func Example(_ context.Context, def configs.Definition) error {
def.Add(
option.String("test", "test string", view.WithSkipContext),
group.New("user", "configure user",
option.String("name", "name", option.Default("4devs")),
option.String("pass", "password"),
).With(view.WithContext),
group.New("log", "configure logger",
option.New("level", "log level", Level("")),
proto.New("service", "servise logger", option.New("level", "log level", Level(""))),
),
)
return nil
}

View File

@@ -1,153 +0,0 @@
// Code generated gitoa.ru/go-4devs/config DO NOT EDIT.
package example
import (
"context"
"fmt"
"gitoa.ru/go-4devs/config"
)
func WithExampleConfigHandle(fn func(context.Context, error)) func(*ExampleConfig) {
return func(ci *ExampleConfig) {
ci.handle = fn
}
}
func NewExampleConfig(ctx context.Context, prov config.Provider, opts ...func(*ExampleConfig)) ExampleConfig {
i := ExampleConfig{
Provider: prov,
handle: func(_ context.Context, err error) {
fmt.Printf("ExampleConfig:%v", err)
},
ctx: ctx,
}
for _, opt := range opts {
opt(&i)
}
return i
}
type ExampleConfig struct {
config.Provider
handle func(context.Context, error)
ctx context.Context
}
// readTest test string.
func (i ExampleConfig) readTest(ctx context.Context) (v string, e error) {
val, err := i.Value(ctx, "test")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"test"}, err)
}
return val.ParseString()
}
// ReadTest test string.
func (i ExampleConfig) ReadTest() (string, error) {
return i.readTest(i.ctx)
}
// Test test string.
func (i ExampleConfig) Test() string {
val, err := i.readTest(i.ctx)
if err != nil {
i.handle(i.ctx, err)
}
return val
}
type UserConfig struct {
ExampleConfig
}
// User configure user.
func (i ExampleConfig) User() UserConfig {
return UserConfig{i}
}
type LogConfig struct {
ExampleConfig
}
// Log configure logger.
func (i ExampleConfig) Log() LogConfig {
return LogConfig{i}
}
// readLevel log level.
func (i LogConfig) readLevel(ctx context.Context) (v Level, e error) {
val, err := i.Value(ctx, "log", "level")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", "level"}, err)
}
pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("parse [%v]:%w", []string{"log", "level"}, perr)
}
return v, v.UnmarshalText([]byte(pval))
}
// ReadLevel log level.
func (i LogConfig) ReadLevel(ctx context.Context) (Level, error) {
return i.readLevel(ctx)
}
// Level log level.
func (i LogConfig) Level(ctx context.Context) Level {
val, err := i.readLevel(ctx)
if err != nil {
i.handle(ctx, err)
}
return val
}
type LogServiceConfig struct {
LogConfig
service string
}
// Service servise logger.
func (i LogConfig) Service(key string) LogServiceConfig {
return LogServiceConfig{i, key}
}
// readLevel log level.
func (i LogServiceConfig) readLevel(ctx context.Context) (v Level, e error) {
val, err := i.Value(ctx, "log", i.service, "level")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", i.service, "level"}, err)
}
pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("parse [%v]:%w", []string{"log", i.service, "level"}, perr)
}
return v, v.UnmarshalText([]byte(pval))
}
// ReadLevel log level.
func (i LogServiceConfig) ReadLevel(ctx context.Context) (Level, error) {
return i.readLevel(ctx)
}
// Level log level.
func (i LogServiceConfig) Level(ctx context.Context) Level {
val, err := i.readLevel(ctx)
if err != nil {
i.handle(ctx, err)
}
return val
}

View File

@@ -1,70 +0,0 @@
package generate
import (
"context"
"fmt"
"go/format"
"os"
"os/exec"
"path/filepath"
"gitoa.ru/go-4devs/config/definition/generate/bootstrap"
)
type GConfig interface {
BuildTags() string
OutName() string
bootstrap.Config
}
func Generate(ctx context.Context, cfg GConfig) error {
path, err := bootstrap.Bootstrap(ctx, cfg)
defer os.Remove(path)
if err != nil {
return fmt.Errorf("build bootstrap:%w", err)
}
tmpFile, err := os.Create(cfg.File() + ".tmp")
if err != nil {
return fmt.Errorf("create tmp file:%w", err)
}
defer os.Remove(tmpFile.Name()) // will not remove after rename
execArgs := []string{"run"}
if len(cfg.BuildTags()) > 0 {
execArgs = append(execArgs, "-tags", cfg.BuildTags())
}
execArgs = append(execArgs, filepath.Base(path))
cmd := exec.CommandContext(ctx, "go", execArgs...)
cmd.Stdout = tmpFile
cmd.Stderr = os.Stderr
cmd.Dir = filepath.Dir(path)
if err = cmd.Run(); err != nil {
return fmt.Errorf("start cmd:%w", err)
}
tmpFile.Close()
// format file and write to out path
in, err := os.ReadFile(tmpFile.Name())
if err != nil {
return fmt.Errorf("read file: %w", err)
}
out, err := format.Source(in)
if err != nil {
return fmt.Errorf("format source:%w", err)
}
err = os.WriteFile(cfg.OutName(), out, 0o644) //nolint:gosec,mnd
if err != nil {
return fmt.Errorf("write file:%w", err)
}
return nil
}

View File

@@ -1,84 +0,0 @@
package generate
import (
"bytes"
"context"
"embed"
"fmt"
"io"
"strings"
"text/template"
"unicode"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/generate/pkg"
"gitoa.ru/go-4devs/config/definition/generate/render"
"gitoa.ru/go-4devs/config/definition/generate/view"
)
//go:embed tpl/*
var tpls embed.FS
var initTpl = template.Must(template.New("tpls").ParseFS(tpls, "tpl/*.tpl")).Lookup("init.go.tpl")
func Run(_ context.Context, fullPkg string, w io.Writer, defs ...config.Options) error {
data := Data{
Packages: pkg.NewImports(fullPkg).
Adds("fmt", "context", "gitoa.ru/go-4devs/config"),
}
var buff bytes.Buffer
for _, in := range defs {
vi := view.NewViews(in)
if err := render.Render(&buff, vi, data); err != nil {
return fmt.Errorf("render:%w", err)
}
}
if err := initTpl.Execute(w, data); err != nil {
return fmt.Errorf("render base:%w", err)
}
if _, err := io.Copy(w, &buff); err != nil {
return fmt.Errorf("copy:%w", err)
}
return nil
}
type Data struct {
*pkg.Packages
}
func (f Data) StructName(name string) string {
return FuncName(name)
}
func (f Data) FuncName(in string) string {
return FuncName(in)
}
func FuncName(name string) string {
data := strings.Builder{}
toUp := true
for _, char := range name {
isLeter := unicode.IsLetter(char)
isAllowed := isLeter || unicode.IsDigit(char)
switch {
case isAllowed && !toUp:
data.WriteRune(char)
case !isAllowed:
toUp = true
case toUp:
data.WriteString(strings.ToUpper(string(char)))
toUp = false
}
}
return data.String()
}

View File

@@ -1,88 +0,0 @@
package generate_test
import (
"context"
"os"
"testing"
"gitoa.ru/go-4devs/config/definition"
"gitoa.ru/go-4devs/config/definition/generate"
"gitoa.ru/go-4devs/config/definition/generate/bootstrap"
"gitoa.ru/go-4devs/config/definition/generate/view"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
"gitoa.ru/go-4devs/config/test/require"
)
type LogLevel string
func (l *LogLevel) UnmarshalText(in []byte) error {
data := string(in)
*l = LogLevel(data)
return nil
}
func Configure(_ context.Context, def *definition.Definition) error {
def.Add(
option.String("test", "test string", view.WithSkipContext),
group.New("user", "configure user",
option.String("name", "name", option.Default("4devs")),
option.String("pass", "password"),
),
group.New("log", "configure logger",
option.New("level", "log level", LogLevel("")),
proto.New("service", "servise logger", option.New("level", "log level", LogLevel(""))),
),
)
return nil
}
func TestGenerate_Bootstrap(t *testing.T) {
t.SkipNow()
t.Parallel()
ctx := context.Background()
options := definition.New()
err := Configure(ctx, options)
require.NoError(t, err)
cfg, _ := generate.NewMemoryProvider("generate_test.go",
generate.WithMethods("Config"),
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate_test"),
)
path, err := bootstrap.Bootstrap(ctx, generate.NewConfigure(ctx, cfg))
if err != nil {
t.Error(err)
t.FailNow()
}
os.Remove(path)
t.Log(path)
t.FailNow()
}
func TestGenerate_Genereate(t *testing.T) {
t.SkipNow()
t.Parallel()
ctx := context.Background()
options := definition.New()
err := Configure(ctx, options)
require.NoError(t, err)
cfg, _ := generate.NewMemoryProvider("generate_test.go",
generate.WithMethods("Config"),
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate_test"),
)
err = generate.Generate(ctx, generate.NewConfigure(ctx, cfg))
require.NoError(t, err)
t.FailNow()
}

View File

@@ -1,152 +0,0 @@
package generate
import (
"context"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"reflect"
"slices"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/generate/pkg"
)
const NameSuffix = "_config.go"
func Parse(ctx context.Context, name string) (Parser, error) {
var parse Parser
parse.file = name
stats, err := os.Stat(name)
if err != nil {
return parse, fmt.Errorf("stats:%w", err)
}
parse.fullPkg, err = pkg.ByPath(ctx, name, stats.IsDir())
if err != nil {
return parse, fmt.Errorf("get pkg:%w", err)
}
parse.methods, err = NewParseMethods(
name,
[]reflect.Type{
reflect.TypeFor[context.Context](),
reflect.TypeFor[config.Definition](),
},
[]reflect.Type{reflect.TypeFor[error]()},
)
if err != nil {
return parse, fmt.Errorf("parse methods:%w", err)
}
return parse, nil
}
func NewParseMethods(file string, params []reflect.Type, results []reflect.Type) ([]string, error) {
pfile, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("parse:%w", err)
}
resultAlias := importAlias(pfile, results)
paramsAlias := importAlias(pfile, params)
var methods []string
ast.Inspect(pfile, func(anode ast.Node) bool {
if fn, ok := anode.(*ast.FuncDecl); ok &&
fn.Recv == nil &&
fn.Type != nil &&
(fn.Type.Params != nil && len(params) == len(fn.Type.Params.List) || len(params) == 0 && fn.Type.Params == nil) &&
(fn.Type.Results != nil && len(results) == len(fn.Type.Results.List) || len(results) == 0 && fn.Type.Results == nil) {
if hasFields(fn.Type.Params, params, paramsAlias) && hasFields(fn.Type.Results, results, resultAlias) {
methods = append(methods, fn.Name.String())
}
}
return true
})
return methods, nil
}
func importAlias(file *ast.File, params []reflect.Type) map[int][]string {
paramsAlias := make(map[int][]string, len(params))
for idx := range params {
name := params[idx].Name()
if pkgPath := params[idx].PkgPath(); pkgPath != "" {
name = pkg.Pkg(pkgPath)
}
paramsAlias[idx] = append(paramsAlias[idx], name)
}
ast.Inspect(file, func(anode ast.Node) bool {
if exp, ok := anode.(*ast.ImportSpec); ok {
pathName := strings.Trim(exp.Path.Value, "\"")
pname := pkg.Pkg(pathName)
if exp.Name != nil {
pname = exp.Name.String()
}
for idx, param := range params {
if pathName == param.PkgPath() {
paramsAlias[idx] = append(paramsAlias[idx], pname)
}
}
}
return true
})
return paramsAlias
}
func hasFields(fields *ast.FieldList, params []reflect.Type, alias map[int][]string) bool {
for idx, one := range fields.List {
iparam := params[idx]
if ident, iok := one.Type.(*ast.Ident); iok && iparam.String() == ident.String() {
return true
}
selector, sok := one.Type.(*ast.SelectorExpr)
if !sok {
return false
}
if iparam.Name() != selector.Sel.String() {
return false
}
salias, saok := selector.X.(*ast.Ident)
if iparam.PkgPath() != "" && saok && !slices.Contains(alias[idx], salias.String()) {
return false
}
}
return true
}
type Parser struct {
file string
fullPkg string
methods []string
}
func (p Parser) OutName() string {
return strings.ReplaceAll(p.file, ".go", NameSuffix)
}
func (p Parser) FullPkg() string {
return p.fullPkg
}
func (p Parser) Methods() []string {
return p.methods
}

View File

@@ -1,29 +0,0 @@
package pkg
import (
"strings"
"unicode"
)
func AliasName(name string) string {
data := strings.Builder{}
toUp := false
for _, char := range name {
isLeter := unicode.IsLetter(char)
isAllowed := isLeter || unicode.IsDigit(char)
switch {
case isAllowed && !toUp:
data.WriteRune(char)
case !isAllowed && data.Len() > 0:
toUp = true
case toUp:
data.WriteString(strings.ToUpper(string(char)))
toUp = false
}
}
return data.String()
}

View File

@@ -1,8 +0,0 @@
package pkg
import "errors"
var (
ErrWrongFormat = errors.New("wrong format")
ErrNotFound = errors.New("not found")
)

View File

@@ -1,130 +0,0 @@
package pkg
import (
"fmt"
"strconv"
"strings"
)
func NewImports(pkg string) *Packages {
imp := Packages{
data: make(map[string]string),
pkg: pkg,
}
return &imp
}
type Packages struct {
data map[string]string
pkg string
}
func (i *Packages) Imports() []Import {
imports := make([]Import, 0, len(i.data))
for name, alias := range i.data {
imports = append(imports, Import{
Package: name,
Alias: alias,
})
}
return imports
}
func (i *Packages) Short(fullType string) (string, error) {
idx := strings.LastIndexByte(fullType, '.')
if idx == -1 {
return "", fmt.Errorf("%w: expect package.Type", ErrWrongFormat)
}
if alias, ok := i.data[fullType[:idx]]; ok {
return alias + fullType[idx:], nil
}
return "", fmt.Errorf("%w alias for pkg %v", ErrNotFound, fullType[:idx])
}
func (i *Packages) AddType(fullType string) (string, error) {
idx := strings.LastIndexByte(fullType, '.')
if idx == -1 {
return "", fmt.Errorf("%w: expect pckage.Type got %v", ErrWrongFormat, fullType)
}
imp := i.Add(fullType[:idx])
if imp.Alias == "" {
return fullType[idx+1:], nil
}
return imp.Alias + fullType[idx:], nil
}
func (i *Packages) Adds(pkgs ...string) *Packages {
for _, pkg := range pkgs {
i.Add(pkg)
}
return i
}
func (i *Packages) Add(pkg string) Import {
if pkg == i.pkg {
return Import{
Alias: "",
Package: pkg,
}
}
alias := pkg
if idx := strings.LastIndexByte(pkg, '/'); idx != -1 {
alias = AliasName(pkg[idx+1:])
}
if al, ok := i.data[pkg]; ok {
return Import{Package: pkg, Alias: al}
}
for _, al := range i.data {
if al == alias {
alias += strconv.Itoa(len(i.data))
}
}
i.data[pkg] = alias
return Import{
Alias: alias,
Package: pkg,
}
}
func (i *Packages) Pkg() string {
return Pkg(i.pkg)
}
type Import struct {
Alias string
Package string
}
func (i Import) Pkg() string {
return Pkg(i.Package)
}
func (i Import) String() string {
if i.Alias == i.Pkg() {
return fmt.Sprintf("%q", i.Package)
}
return fmt.Sprintf("%s %q", i.Alias, i.Package)
}
func Pkg(fullPkg string) string {
if idx := strings.LastIndex(fullPkg, "/"); idx != -1 {
return fullPkg[idx+1:]
}
return fullPkg
}

View File

@@ -1,187 +0,0 @@
package pkg
import (
"bytes"
"context"
"fmt"
"go/build"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
)
var cache = sync.Map{}
func ByPath(ctx context.Context, fname string, isDir bool) (string, error) {
if !filepath.IsAbs(fname) {
pwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("%w", err)
}
fname = filepath.Join(pwd, fname)
}
goModPath, _ := goModPath(ctx, fname, isDir)
if strings.Contains(goModPath, "go.mod") {
pkgPath, err := getPkgPathFromGoMod(fname, isDir, goModPath)
if err != nil {
return "", err
}
return pkgPath, nil
}
return getPkgPathFromGOPATH(fname, isDir)
}
// empty if no go.mod, GO111MODULE=off or go without go modules support.
func goModPath(ctx context.Context, fname string, isDir bool) (string, error) {
root := fname
if !isDir {
root = filepath.Dir(fname)
}
var modPath string
loadModPath, ok := cache.Load(root)
if ok {
modPath, _ = loadModPath.(string)
return modPath, nil
}
defer func() {
cache.Store(root, modPath)
}()
cmd := exec.CommandContext(ctx, "go", "env", "GOMOD")
cmd.Dir = root
stdout, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("%w", err)
}
modPath = string(bytes.TrimSpace(stdout))
return modPath, nil
}
func getPkgPathFromGoMod(fname string, isDir bool, goModPath string) (string, error) {
modulePath := getModulePath(goModPath)
if modulePath == "" {
return "", fmt.Errorf("c%w module path from %s", ErrNotFound, goModPath)
}
rel := path.Join(modulePath, filePathToPackagePath(strings.TrimPrefix(fname, filepath.Dir(goModPath))))
if !isDir {
return path.Dir(rel), nil
}
return path.Clean(rel), nil
}
func getModulePath(goModPath string) string {
var pkgPath string
cacheOkgPath, ok := cache.Load(goModPath)
if ok {
pkgPath, _ = cacheOkgPath.(string)
return pkgPath
}
defer func() {
cache.Store(goModPath, pkgPath)
}()
data, err := os.ReadFile(goModPath)
if err != nil {
return ""
}
pkgPath = modulePath(data)
return pkgPath
}
func getPkgPathFromGOPATH(fname string, isDir bool) (string, error) {
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = build.Default.GOPATH
}
for p := range strings.SplitSeq(gopath, string(filepath.ListSeparator)) {
prefix := filepath.Join(p, "src") + string(filepath.Separator)
rel, err := filepath.Rel(prefix, fname)
if err == nil && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
if !isDir {
return path.Dir(filePathToPackagePath(rel)), nil
}
return path.Clean(filePathToPackagePath(rel)), nil
}
}
return "", fmt.Errorf("%w: file '%v' is not in GOPATH '%v'", ErrNotFound, fname, gopath)
}
func filePathToPackagePath(path string) string {
return filepath.ToSlash(path)
}
var (
slashSlash = []byte("//")
moduleStr = []byte("module")
)
// modulePath returns the module path from the gomod file text.
// If it cannot find a module path, it returns an empty string.
// It is tolerant of unrelated problems in the go.mod file.
func modulePath(mod []byte) string {
for len(mod) > 0 {
line := mod
mod = nil
if i := bytes.IndexByte(line, '\n'); i >= 0 {
line, mod = line[:i], line[i+1:]
}
if i := bytes.Index(line, slashSlash); i >= 0 {
line = line[:i]
}
line = bytes.TrimSpace(line)
if !bytes.HasPrefix(line, moduleStr) {
continue
}
line = line[len(moduleStr):]
n := len(line)
line = bytes.TrimSpace(line)
if len(line) == n || len(line) == 0 {
continue
}
if line[0] == '"' || line[0] == '`' {
p, err := strconv.Unquote(string(line))
if err != nil {
return "" // malformed quoted string or multiline module path
}
return p
}
return string(line)
}
return "" // missing module path
}

View File

@@ -1,61 +0,0 @@
package render
import (
"gitoa.ru/go-4devs/config/definition/generate/pkg"
"gitoa.ru/go-4devs/config/definition/generate/view"
)
func NewViewData(render Rendering, view view.View) ViewData {
return ViewData{
Rendering: render,
View: view,
}
}
type ViewData struct {
Rendering
view.View
}
func (d ViewData) StructName() string {
return d.Rendering.StructName(d.View.StructName())
}
func (d ViewData) FuncName() string {
return d.Rendering.FuncName(d.View.FuncName())
}
func (d ViewData) ParentStruct() string {
name := d.View.ParentStruct()
if name == "" {
name = d.Name()
}
return d.Rendering.StructName(name)
}
func (d ViewData) Name() string {
return pkg.AliasName(d.View.Name())
}
func (d ViewData) Type() string {
return Type(d)
}
func (d ViewData) Keys(parent string) string {
return Keys(d.View.Keys(), parent)
}
func (d ViewData) Value(name, val string) string {
return Value(name, val, d)
}
func (d ViewData) Default(name string) string {
return Data(d.View.Default(), name, d)
}
type Rendering interface {
StructName(name string) string
FuncName(name string) string
AddType(pkg string) (string, error)
}

View File

@@ -1,5 +0,0 @@
package render
import "errors"
var ErrNotFound = errors.New("not found")

View File

@@ -1,34 +0,0 @@
package render
import (
"strings"
"gitoa.ru/go-4devs/config/definition/generate/pkg"
"gitoa.ru/go-4devs/config/key"
)
func Keys(keys []string, val string) string {
if len(keys) == 0 {
return ""
}
var out strings.Builder
for idx, one := range keys {
if key.IsWild(one) {
out.WriteString(val)
out.WriteString(".")
out.WriteString(pkg.AliasName(one))
} else {
out.WriteString("\"")
out.WriteString(one)
out.WriteString("\"")
}
if len(keys)-1 != idx {
out.WriteString(", ")
}
}
return out.String()
}

View File

@@ -1,49 +0,0 @@
package render
import (
"fmt"
"io"
"reflect"
"gitoa.ru/go-4devs/config/definition"
"gitoa.ru/go-4devs/config/definition/generate/view"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
)
type Execute func(w io.Writer, vi view.View, rnd Rendering) error
var randders = map[reflect.Type]Execute{
reflect.TypeFor[*definition.Definition](): Template(defTpl),
reflect.TypeFor[*group.Group](): Template(groupTpl),
reflect.TypeFor[option.Option](): Template(optTpl),
reflect.TypeFor[proto.Proto](): Template(protoTpl),
}
func Renders() map[reflect.Type]Execute {
return randders
}
func Add(rt reflect.Type, fn Execute) {
randders[rt] = fn
}
func Render(w io.Writer, view view.View, data Rendering) error {
rnd, ok := randders[view.Kind()]
if !ok {
return fmt.Errorf("%w:%v", ErrNotFound, view.Kind())
}
if err := rnd(w, view, data); err != nil {
return fmt.Errorf("render:%v, err:%w", view.Kind(), err)
}
for _, ch := range view.Views() {
if err := Render(w, ch, data); err != nil {
return fmt.Errorf("render[%v]:%w", ch.Name(), err)
}
}
return nil
}

View File

@@ -1,41 +0,0 @@
package render
import (
"embed"
"fmt"
"io"
"strings"
"text/template"
"gitoa.ru/go-4devs/config/definition/generate/view"
)
//go:embed tpl/*
var tplFS embed.FS
var (
tpls = template.Must(
template.New("tpls").
Funcs(template.FuncMap{
"trim": strings.Trim,
}).
ParseFS(tplFS, "tpl/*.go.tpl"),
)
defTpl = tpls.Lookup("definition.go.tpl")
groupTpl = tpls.Lookup("group.go.tpl")
protoTpl = tpls.Lookup("proto.go.tpl")
optTpl = template.Must(
template.New("opt").ParseFS(tplFS, "tpl/option/option.go.tpl"),
).Lookup("option.go.tpl")
parceTpls = template.Must(template.New("data").ParseFS(tplFS, "tpl/data/*.go.tpl"))
)
func Template(tpl *template.Template) Execute {
return func(w io.Writer, v view.View, rnd Rendering) error {
if err := tpl.Execute(w, NewViewData(rnd, v)); err != nil {
return fmt.Errorf("template[%v]:%w", tpl.Name(), err)
}
return nil
}
}

View File

@@ -1,3 +0,0 @@
{{block "Any" . }}
return {{.ValName}}.Any(), nil
{{end}}

View File

@@ -1,8 +0,0 @@
{{ block "FlagValue" . -}}
pval, perr := {{.ValName}}.ParseString()
if perr != nil {
return {{.Value}}, fmt.Errorf("parse [%v]:%w",[]string{ {{- .Keys "i" -}} }, perr)
}
return {{.Value}}, {{.Value}}.Set(pval)
{{- end -}}

View File

@@ -1,3 +0,0 @@
{{block "Parse" .}}
return {{.ValName}}.Parse{{ .FuncType}}()
{{end}}

View File

@@ -1,3 +0,0 @@
{{ block "ScanValue" . -}}
return {{.Value}}, {{.Value}}.Scan({{.ValName}}.Any())
{{- end }}

View File

@@ -1,8 +0,0 @@
{{block "UnmarshalJSON" . -}}
pval, perr := {{.ValName}}.ParseString()
if perr != nil {
return {{.Value}}, fmt.Errorf("parse [%v]:%w", []string{ {{- .Keys "i" -}} }, perr)
}
return {{.Value}}, {{.Value}}.UnmarshalJSON([]byte(pval))
{{- end -}}

View File

@@ -1,8 +0,0 @@
{{ block "UnmarshalText" . -}}
pval, perr := {{.ValName}}.ParseString()
if perr != nil {
return {{.Value}}, fmt.Errorf("parse [%v]:%w", []string{ {{- .Keys "i" -}} }, perr)
}
return {{.Value}}, {{.Value}}.UnmarshalText([]byte(pval))
{{- end -}}

View File

@@ -1,27 +0,0 @@
func With{{.StructName}}Handle(fn func(context.Context, error)) func(*{{.StructName}}) {
return func(ci *{{.StructName}}) {
ci.handle = fn
}
}
func New{{.StructName}}({{if or .SkipContext .ClildSkipContext }} ctx context.Context,{{end}}prov config.Provider, opts ...func(*{{.StructName}})) {{.StructName}} {
i := {{.StructName}}{
Provider: prov,
handle: func(_ context.Context, err error) {
fmt.Printf("{{.StructName}}:%v",err)
},
{{if or .SkipContext .ClildSkipContext }} ctx: ctx, {{end}}
}
for _, opt := range opts {
opt(&i)
}
return i
}
type {{.StructName}} struct {
config.Provider
handle func(context.Context, error)
{{if or .SkipContext .ClildSkipContext}}ctx context.Context {{end}}
}

View File

@@ -1,8 +0,0 @@
type {{.StructName}} struct {
{{.ParentStruct}}
}
// {{.FuncName}} {{.Description}}.
func (i {{.ParentStruct}}) {{.FuncName}}() {{.StructName}} {
return {{.StructName}}{i}
}

View File

@@ -1,30 +0,0 @@
// read{{.FuncName}} {{.Description}}.
func (i {{.ParentStruct}}) read{{.FuncName}}(ctx context.Context) (v {{.Type}},e error) {
val, err := i.Value(ctx, {{ .Keys "i" }})
if err != nil {
{{- if .HasDefault }}
i.handle(ctx, err)
{{ .Default "val" -}}
{{ else }}
return v, fmt.Errorf("read [%v]:%w",[]string{ {{- .Keys "i" -}} }, err)
{{ end }}
}
{{ .Value "val" "v" }}
}
// Read{{.FuncName}} {{.Description}}.
func (i {{.ParentStruct}}) Read{{.FuncName}}({{if not .SkipContext}} ctx context.Context {{end}}) ({{.Type}}, error) {
return i.read{{.FuncName}}({{if .SkipContext}}i.ctx{{else}}ctx{{end}})
}
// {{.FuncName}} {{.Description}}.
func (i {{.ParentStruct}}) {{.FuncName}}({{if not .SkipContext}} ctx context.Context {{end}}) {{.Type}} {
val, err := i.read{{.FuncName}}({{ if .SkipContext }}i.ctx{{else}}ctx{{ end }})
if err != nil {
i.handle({{ if .SkipContext }}i.ctx{{else}}ctx{{ end }}, err)
}
return val
}

View File

@@ -1,9 +0,0 @@
type {{.StructName}} struct {
{{.ParentStruct}}
{{ .Name }} string
}
// {{.FuncName}} {{.Description}}.
func (i {{.ParentStruct}}) {{.FuncName}}(key string) {{.StructName}} {
return {{.StructName}}{i,key}
}

View File

@@ -1,266 +0,0 @@
package render
import (
"bytes"
"database/sql"
"encoding"
"encoding/json"
"flag"
"fmt"
"reflect"
"time"
)
func Value(name, val string, data ViewData) string {
rnd := renderType(data)
res, err := rnd(ValueData{ValName: name, Value: val, ViewData: data})
if err != nil {
return fmt.Sprintf("render value:%v", err)
}
return res
}
func Type(data ViewData) string {
dt := data.View.Type()
rtype := reflect.TypeOf(dt)
slice := ""
if rtype.Kind() == reflect.Slice {
slice = "[]"
rtype = rtype.Elem()
}
short := rtype.Name()
if rtype.PkgPath() != "" {
var err error
short, err = data.AddType(rtype.PkgPath() + "." + rtype.Name())
if err != nil {
return err.Error()
}
}
return slice + short
}
func Data(val any, name string, view ViewData) string {
fn := renderData(view)
data, err := fn(val, ValueData{ValName: name, Value: "", ViewData: view})
if err != nil {
return fmt.Sprintf("render dara:%v", err)
}
return data
}
func renderDataTime(val any, _ ValueData) (string, error) {
data, _ := val.(time.Time)
return fmt.Sprintf("time.Parse(%q,time.RFC3339Nano)", data.Format(time.RFC3339Nano)), nil
}
func renderDataDuration(val any, _ ValueData) (string, error) {
data, _ := val.(time.Duration)
return fmt.Sprintf("time.ParseDuration(%q)", data), nil
}
func renderDataUnmarhal(val any, view ValueData) (string, error) {
res, err := json.Marshal(val)
if err != nil {
return "", fmt.Errorf("render data unmarshal:%w", err)
}
return fmt.Sprintf("return {{.%[1]s}}, {{.%[1]s}}.UnmarshalJSON(%q)", view.ValName, res), nil
}
func renderDataUnmarhalText(val any, view ValueData) (string, error) {
res, err := json.Marshal(val)
if err != nil {
return "", fmt.Errorf("render data unmarshal:%w", err)
}
return fmt.Sprintf("return {{.%[1]s}}, {{.%[1]s}}.UnmarshalText(%s)", view.ValName, res), nil
}
func renderDataFlag(val any, view ValueData) (string, error) {
return fmt.Sprintf("return {{.%[1]s}}, {{.%[1]s}}.Set(%[2]q)", view.ValName, val), nil
}
func renderType(view ViewData) func(data ValueData) (string, error) {
return dataRender(view).Type
}
func renderData(view ViewData) func(in any, data ValueData) (string, error) {
return dataRender(view).Value
}
func dataRender(view ViewData) DataRender {
data := view.View.Type()
vtype := reflect.TypeOf(data)
if vtype.Kind() == reflect.Slice {
return render[reflect.TypeFor[json.Unmarshaler]()]
}
if h, ok := render[vtype]; ok {
return h
}
if vtype.Kind() != reflect.Interface && vtype.Kind() != reflect.Pointer {
vtype = reflect.PointerTo(vtype)
}
for extypes := range render {
if extypes == nil || extypes.Kind() != reflect.Interface {
continue
}
if vtype.Implements(extypes) {
return render[extypes]
}
}
return render[reflect.TypeOf((any)(nil))]
}
//nolint:gochecknoglobals
var render = map[reflect.Type]DataRender{
reflect.TypeFor[encoding.TextUnmarshaler](): NewDataRender(unmarshalTextType, renderDataUnmarhalText),
reflect.TypeFor[json.Unmarshaler](): NewDataRender(unmarshalType, renderDataUnmarhal),
reflect.TypeFor[flag.Value](): NewDataRender(flagType, renderDataFlag),
reflect.TypeFor[sql.Scanner](): NewDataRender(scanType, nil),
reflect.TypeFor[int](): NewDataRender(internalType, nil),
reflect.TypeFor[int64](): NewDataRender(internalType, anyValue),
reflect.TypeFor[bool](): NewDataRender(internalType, anyValue),
reflect.TypeFor[string](): NewDataRender(internalType, anyValue),
reflect.TypeFor[float64](): NewDataRender(internalType, anyValue),
reflect.TypeFor[uint](): NewDataRender(internalType, anyValue),
reflect.TypeFor[int64](): NewDataRender(internalType, anyValue),
reflect.TypeFor[time.Duration](): NewDataRender(durationType, renderDataDuration),
reflect.TypeFor[time.Time](): NewDataRender(timeType, renderDataTime),
reflect.TypeOf((any)(nil)): NewDataRender(anyType, anyValue),
}
func timeType(data ValueData) (string, error) {
return fmt.Sprintf("return %s.ParseTime()", data.ValName), nil
}
func durationType(data ValueData) (string, error) {
return fmt.Sprintf("return %s.ParseDuration()", data.ValName), nil
}
func scanType(data ValueData) (string, error) {
var b bytes.Buffer
err := parceTpls.Lookup("scan_value.go.tpl").Execute(&b, data)
if err != nil {
return "", fmt.Errorf("execute scan value:%w", err)
}
return b.String(), nil
}
func flagType(data ValueData) (string, error) {
var b bytes.Buffer
err := parceTpls.Lookup("flag_value.go.tpl").Execute(&b, data)
if err != nil {
return "", fmt.Errorf("execute flag value:%w", err)
}
return b.String(), nil
}
func anyType(data ValueData) (string, error) {
var b bytes.Buffer
err := parceTpls.ExecuteTemplate(&b, "any.go.tpl", data)
if err != nil {
return "", fmt.Errorf("unmarshal execute any.go.tpl:%w", err)
}
return b.String(), nil
}
func anyValue(data any, _ ValueData) (string, error) {
return fmt.Sprintf("return %#v, nil", data), nil
}
func unmarshalType(data ValueData) (string, error) {
var b bytes.Buffer
err := parceTpls.ExecuteTemplate(&b, "unmarshal_json.go.tpl", data)
if err != nil {
return "", fmt.Errorf("unmarshal execute unmarshal_json.go.tpl:%w", err)
}
return b.String(), nil
}
func unmarshalTextType(data ValueData) (string, error) {
var b bytes.Buffer
err := parceTpls.Lookup("unmarshal_text.go.tpl").Execute(&b, data)
if err != nil {
return "", fmt.Errorf("execute unmarshal text:%w", err)
}
return b.String(), nil
}
func internalType(data ValueData) (string, error) {
var b bytes.Buffer
err := parceTpls.Lookup("parse.go.tpl").Execute(&b, data)
if err != nil {
return "", fmt.Errorf("internal execute parce.go.tpl:%w", err)
}
return b.String(), nil
}
type ValueData struct {
ViewData
ValName string
Value string
}
func (v ValueData) FuncType() string {
name := reflect.TypeOf(v.ViewData.Type()).Name()
return v.Rendering.FuncName(name)
}
type DataRender struct {
renderType func(data ValueData) (string, error)
renderValue func(data any, view ValueData) (string, error)
}
func (d DataRender) Type(data ValueData) (string, error) {
return d.renderType(data)
}
func (d DataRender) Value(data any, view ValueData) (string, error) {
return d.renderValue(data, view)
}
func NewDataRender(rendeType func(data ValueData) (string, error), renderValue func(data any, view ValueData) (string, error)) DataRender {
if rendeType == nil {
rendeType = anyType
}
if renderValue == nil {
renderValue = anyValue
}
return DataRender{
renderType: rendeType,
renderValue: renderValue,
}
}

View File

@@ -1,129 +0,0 @@
package render_test
import (
"fmt"
"strconv"
"testing"
"gitoa.ru/go-4devs/config/definition/generate/render"
"gitoa.ru/go-4devs/config/definition/generate/view"
"gitoa.ru/go-4devs/config/definition/option"
)
type flagValue int
func (f flagValue) String() string {
return strconv.Itoa(int(f))
}
func (f *flagValue) Set(in string) error {
data, err := strconv.Atoi(in)
if err != nil {
return fmt.Errorf("%w", err)
}
*f = flagValue(data)
return nil
}
func TestValue_FlagType(t *testing.T) {
t.Parallel()
const ex = `pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("parse [%v]:%w",[]string{"flag_value"}, perr)
}
return v, v.Set(pval)`
viewData := render.NewViewData(nil, view.NewView(option.New("flag_value", "flag desc", flagValue(0))))
result := render.Value("val", "v", viewData)
if result != ex {
t.Errorf("failed render flag type ex:%s, res:%s", ex, result)
}
}
func TestData_Flag(t *testing.T) {
t.Parallel()
const ex = `return {{.val}}, {{.val}}.Set("42")`
viewData := render.NewViewData(nil, view.NewView(option.New("flag_value", "flag desc", flagValue(0))))
result := render.Data(flagValue(42), "val", viewData)
if result != ex {
t.Errorf("failed render flag value ex:%s, res:%s", ex, result)
}
}
type scanValue int
func (s *scanValue) Scan(src any) error {
res, _ := src.(string)
data, err := strconv.Atoi(res)
if err != nil {
return fmt.Errorf("%w", err)
}
*s = scanValue(data)
return nil
}
func TestValue_Scan(t *testing.T) {
t.Parallel()
const ex = `return v, v.Scan(val.Any())`
viewData := render.NewViewData(nil, view.NewView(option.New("scan_value", "scan desc", scanValue(42))))
result := render.Value("val", "v", viewData)
if result != ex {
t.Errorf("failed render flag value ex:%s, res:%s", ex, result)
}
}
type textData string
func (j *textData) UnmarshalText(in []byte) error {
val := string(in)
*j = textData(val)
return nil
}
func TestData_UnmarshalText(t *testing.T) {
t.Parallel()
const ex = `return {{.val}}, {{.val}}.UnmarshalText("4devs")`
data := textData("4devs")
viewData := render.NewViewData(nil, view.NewView(option.New("tvalue", "unmarshal text desc", textData(""))))
result := render.Data(data, "val", viewData)
if result != ex {
t.Errorf("failed render flag value ex:%s, res:%s", ex, result)
}
}
func TestValue_UnmarshalText(t *testing.T) {
t.Parallel()
const ex = `pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("parse [%v]:%w", []string{"tvalue"}, perr)
}
return v, v.UnmarshalText([]byte(pval))`
viewData := render.NewViewData(nil, view.NewView(option.New("tvalue", "unmarshal text desc", textData(""))))
result := render.Value("val", "v", viewData)
if result != ex {
t.Errorf("failed render flag value ex:%s, res:%s", ex, result)
}
}

View File

@@ -1,8 +0,0 @@
// Code generated gitoa.ru/go-4devs/config DO NOT EDIT.
package {{.Pkg}}
import (
{{range .Imports}}
{{- . }}
{{end}}
)

View File

@@ -1,250 +0,0 @@
package view
import (
"fmt"
"reflect"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/param"
)
type key int
const (
viewParamFunctName key = iota + 1
viewParamSkipContext
viewParamStructName
viewParamStructPrefix
viewParamStructSuffix
)
func WithStructName(name string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, viewParamStructName, name)
}
}
func WithStructPrefix(prefix string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, viewParamStructPrefix, prefix)
}
}
func WithStructSuffix(suffix string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, viewParamStructSuffix, suffix)
}
}
func WithSkipContext(p param.Params) param.Params {
return param.With(p, viewParamSkipContext, true)
}
func WithContext(p param.Params) param.Params {
return param.With(p, viewParamSkipContext, false)
}
func IsSkipContext(p param.Params) bool {
data, has := p.Param(viewParamSkipContext)
if has {
skip, ok := data.(bool)
return ok && skip
}
return false
}
type Option func(*View)
func WithParent(in *View) Option {
return func(v *View) {
v.parent = in
}
}
func NewViews(option config.Options, opts ...Option) View {
view := newView(option, opts...)
for _, op := range option.Options() {
view.children = append(view.children, NewView(op, WithParent(&view)))
}
return view
}
func newView(params param.Params, opts ...Option) View {
vi := View{
Params: params,
parent: nil,
children: nil,
}
for _, opt := range opts {
opt(&vi)
}
return vi
}
func NewView(opt config.Option, opts ...Option) View {
vi := newView(opt, opts...)
if data, ok := opt.(config.Group); ok {
for _, chi := range data.Options() {
vi.children = append(vi.children, NewView(chi, WithParent(&vi)))
}
}
return vi
}
type View struct {
param.Params
children []View
parent *View
}
func (v View) Types() []any {
types := make([]any, 0)
if v.Type() != "" {
types = append(types, v.Type())
}
for _, child := range v.children {
types = append(types, child.Types()...)
}
return types
}
func (v View) Kind() reflect.Type {
return reflect.TypeOf(v.Params)
}
func (v View) Views() []View {
return v.children
}
func (v View) Param(key any) string {
data, has := v.Params.Param(key)
if has {
return fmt.Sprintf("%v", data)
}
if v.parent != nil {
return v.parent.Param(key)
}
return ""
}
func (v View) ClildSkipContext() bool {
for _, child := range v.children {
if child.SkipContext() {
return true
}
}
return false
}
func (v View) SkipContext() bool {
if IsSkipContext(v.Params) {
return true
}
if v.parent != nil {
return v.parent.SkipContext()
}
return false
}
func (v View) Name() string {
if data, ok := v.Params.(interface{ Name() string }); ok {
return data.Name()
}
return ""
}
func (v View) Keys() []string {
keys := make([]string, 0, 1)
if v.parent != nil {
keys = append(keys, v.parent.Keys()...)
}
if name := v.Name(); name != "" {
keys = append(keys, name)
}
return keys
}
func (v View) Type() any {
return param.Type(v.Params)
}
func (v View) FuncName() string {
data, ok := v.Params.Param(viewParamFunctName)
name, valid := data.(string)
if !ok || !valid {
return v.Name()
}
return name
}
func (v View) StructName() string {
name, ok := param.String(v.Params, viewParamStructName)
if ok {
return name
}
keys := make([]string, 0, len(v.Keys())+2) //nolint:mnd
prefix := v.Param(viewParamStructPrefix)
if prefix != "" {
keys = append(keys, prefix)
}
keys = append(keys, v.Keys()...)
suffix := v.Param(viewParamStructSuffix)
if suffix != "" {
keys = append(keys, suffix)
}
return strings.Join(keys, "_")
}
func (v View) ParentStruct() string {
if v.parent == nil {
return ""
}
return v.parent.StructName()
}
func (v View) Description() string {
return param.Description(v.Params)
}
func (v View) Default() any {
data, ok := param.Default(v.Params)
if !ok {
return nil
}
return data
}
func (v View) HasDefault() bool {
return option.HasDefaut(v.Params)
}

View File

@@ -1,44 +0,0 @@
package group
import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
)
var _ config.Group = New("", "")
func New(name, desc string, opts ...config.Option) *Group {
group := Group{
name: name,
opts: opts,
Params: param.New(param.WithDescription(desc)),
}
return &group
}
type Group struct {
param.Params
name string
opts []config.Option
}
func (g *Group) Name() string {
return g.name
}
func (g *Group) Options() []config.Option {
return g.opts
}
func (g *Group) Add(opts ...config.Option) {
g.opts = append(g.opts, opts...)
}
func (g *Group) With(opts ...param.Option) *Group {
group := New(g.Name(), "")
group.Params = param.Chain(g.Params, param.New(opts...))
return group
}

View File

@@ -1,29 +0,0 @@
package group_test
import (
"testing"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/test/require"
)
func TestGroupAdd(t *testing.T) {
t.Parallel()
var gr group.Group
gr.Add(group.New("test", "test"))
require.Truef(t, len(gr.Options()) == 1, "len(%v) != 1", len(gr.Options()))
}
func TestGroupWith(t *testing.T) {
t.Parallel()
const descrition = "group description"
gr := group.New("test", "test desc")
gr = gr.With(param.WithDescription(descrition))
require.Equal(t, descrition, param.Description(gr))
}

View File

@@ -1,30 +0,0 @@
package option
import (
"errors"
"fmt"
)
type Error struct {
Key []string
Err error
}
func (o Error) Error() string {
return fmt.Sprintf("%s: %s", o.Key, o.Err)
}
func (o Error) Is(err error) bool {
return errors.Is(err, o.Err)
}
func (o Error) Unwrap() error {
return o.Err
}
func Err(err error, key []string) Error {
return Error{
Key: key,
Err: err,
}
}

View File

@@ -1,66 +0,0 @@
package option
import (
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
)
var _ config.Option = New("", "", nil)
func New(name, desc string, vtype any, opts ...param.Option) Option {
opts = append(opts, param.WithDescription(desc), WithType(vtype))
res := Option{
name: name,
Params: param.New(opts...),
}
return res
}
type Option struct {
param.Params
name string
}
func (o Option) Name() string {
return o.name
}
func String(name, description string, opts ...param.Option) Option {
return New(name, description, "", opts...)
}
func Bool(name, description string, opts ...param.Option) Option {
return New(name, description, false, opts...)
}
func Duration(name, description string, opts ...param.Option) Option {
return New(name, description, time.Duration(0), opts...)
}
func Float64(name, description string, opts ...param.Option) Option {
return New(name, description, float64(0), opts...)
}
func Int(name, description string, opts ...param.Option) Option {
return New(name, description, int(0), opts...)
}
func Int64(name, description string, opts ...param.Option) Option {
return New(name, description, int64(0), opts...)
}
func Time(name, description string, opts ...param.Option) Option {
return New(name, description, time.Time{}, opts...)
}
func Uint(name, description string, opts ...param.Option) Option {
return New(name, description, uint(0), opts...)
}
func Uint64(name, descriontion string, opts ...param.Option) Option {
return New(name, descriontion, uint64(0), opts...)
}

View File

@@ -1,118 +0,0 @@
package option
import (
"gitoa.ru/go-4devs/config/param"
)
type key int
const (
paramHidden key = iota + 1
paramRequired
paramSlice
paramBool
paramShort
)
func Short(in rune) param.Option {
return func(v param.Params) param.Params {
return param.With(v, paramShort, string(in))
}
}
func ParamShort(fn param.Params) (string, bool) {
data, ok := param.String(fn, paramShort)
return data, ok
}
func HasShort(short string) param.Has {
return func(fn param.Params) bool {
data, ok := param.String(fn, paramShort)
return ok && data == short
}
}
func WithType(in any) param.Option {
return func(v param.Params) param.Params {
out := param.WithType(in)(v)
if _, ok := in.(bool); ok {
return param.With(out, paramBool, ok)
}
return out
}
}
func Position(pos uint64) param.Option {
return param.WithPostition(pos)
}
func Hidden(v param.Params) param.Params {
return param.With(v, paramHidden, true)
}
func Required(v param.Params) param.Params {
return param.With(v, paramRequired, true)
}
func Slice(v param.Params) param.Params {
return param.With(v, paramSlice, true)
}
func Default(in any) param.Option {
return param.WithDefault(in)
}
// Deprecated: use param.WithDescription.
func Description(in string) param.Option {
return param.WithDescription(in)
}
func HasDefaut(fn param.Params) bool {
_, ok := param.Default(fn)
return ok
}
// Deprecated: use param.Position.
func DataPosition(fn param.Params) (uint64, bool) {
pos := param.Position(fn)
return pos, pos != 0
}
// Deprecated: use param.Default.
func DataDefaut(fn param.Params) (any, bool) {
return param.Default(fn)
}
func IsSlice(fn param.Params) bool {
data, ok := param.Bool(paramSlice, fn)
return ok && data
}
func IsBool(fn param.Params) bool {
data, ok := param.Bool(paramBool, fn)
return ok && data
}
func IsHidden(fn param.Params) bool {
data, ok := param.Bool(paramHidden, fn)
return ok && data
}
func IsRequired(fn param.Params) bool {
data, ok := param.Bool(paramRequired, fn)
return ok && data
}
// Deprecated: use param.Description.
func DataDescription(fn param.Params) string {
return param.Description(fn)
}

View File

@@ -1,32 +0,0 @@
package proto
import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/param"
)
var _ config.Group = New("", "")
func New(name string, desc string, opts ...config.Option) Proto {
return Proto{
name: key.Wild(name),
opts: opts,
Params: param.New(param.WithDescription(desc)),
}
}
type Proto struct {
param.Params
opts []config.Option
name string
}
func (p Proto) Options() []config.Option {
return p.opts
}
func (p Proto) Name() string {
return p.name
}

View File

@@ -1,7 +1,8 @@
version: '3'
services:
vault:
container_name: vault
image: vault:1.13.3
image: vault:latest
cap_add:
- IPC_LOCK
ports:
@@ -9,11 +10,8 @@ services:
environment:
VAULT_DEV_ROOT_TOKEN_ID: "dev"
etcd:
container_name: etcd
image: quay.io/coreos/etcd:v3.6.7
image: bitnami/etcd
environment:
ALLOW_NONE_AUTHENTICATION: "yes"
ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
ETCD_ADVERTISE_CLIENT_URLS: "http://etcd:2379"
ports:
- "2379:2379"

View File

@@ -1,20 +1,10 @@
package config
import (
"errors"
"fmt"
)
import "errors"
var (
ErrInvalidValue = errors.New("invalid value")
ErrUnknowType = errors.New("unknow type")
ErrInitFactory = errors.New("init factory")
ErrStopWatch = errors.New("stop watch")
ErrNotFound = errors.New("not found")
ErrValueNotFound = fmt.Errorf("value %w", ErrNotFound)
ErrToManyArgs = errors.New("to many args")
ErrWrongType = errors.New("wrong type")
ErrInvalidName = errors.New("ivalid name")
ErrUnexpectedType = errors.New("unexpected type")
ErrRequired = errors.New("required")
ErrVariableNotFound = errors.New("variable not found")
ErrInvalidValue = errors.New("invalid value")
ErrUnknowType = errors.New("unknow type")
ErrInitFactory = errors.New("init factory")
)

View File

@@ -1,96 +0,0 @@
package config
import (
"context"
"fmt"
"sync"
"sync/atomic"
)
func WrapFactory(fn Factory, prov Provider) *WrapProvider {
return &WrapProvider{
factory: func(ctx context.Context) (Provider, error) {
return fn.Create(ctx, prov)
},
mu: sync.Mutex{},
done: 0,
provider: nil,
name: fn.Name(),
}
}
type WrapProvider struct {
mu sync.Mutex
done uint32
provider Provider
factory func(ctx context.Context) (Provider, error)
name string
}
func (p *WrapProvider) Watch(ctx context.Context, callback WatchCallback, path ...string) error {
if err := p.init(ctx); err != nil {
return fmt.Errorf("init read:%w", err)
}
watch, ok := p.provider.(WatchProvider)
if !ok {
return nil
}
if err := watch.Watch(ctx, callback, path...); err != nil {
return fmt.Errorf("factory provider: %w", err)
}
return nil
}
func (p *WrapProvider) Value(ctx context.Context, path ...string) (Value, error) {
if err := p.init(ctx); err != nil {
return nil, fmt.Errorf("init read:%w", err)
}
variable, err := p.provider.Value(ctx, path...)
if err != nil {
return nil, fmt.Errorf("factory provider: %w", err)
}
return variable, nil
}
func (p *WrapProvider) Name() string {
return p.name
}
func (p *WrapProvider) Bind(ctx context.Context, data Variables) error {
if err := p.init(ctx); err != nil {
return fmt.Errorf("init bind: %w", err)
}
prov, ok := p.provider.(BindProvider)
if !ok {
return nil
}
if perr := prov.Bind(ctx, data); perr != nil {
return fmt.Errorf("init bind provider: %w", perr)
}
return nil
}
func (p *WrapProvider) init(ctx context.Context) error {
if atomic.LoadUint32(&p.done) == 0 {
if !p.mu.TryLock() {
return fmt.Errorf("%w", ErrInitFactory)
}
defer atomic.StoreUint32(&p.done, 1)
defer p.mu.Unlock()
var err error
if p.provider, err = p.factory(ctx); err != nil {
return fmt.Errorf("init provider factory:%w", err)
}
}
return nil
}

14
go.mod
View File

@@ -1,5 +1,15 @@
module gitoa.ru/go-4devs/config
go 1.24.0
go 1.16
require gitoa.ru/go-4devs/console v0.3.0
require (
github.com/hashicorp/vault/api v1.1.0
github.com/pelletier/go-toml v1.9.0
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.7.5
go.etcd.io/etcd/api/v3 v3.5.0-alpha.0
go.etcd.io/etcd/client/v3 v3.5.0-alpha.0
gopkg.in/ini.v1 v1.62.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

319
go.sum
View File

@@ -1,2 +1,317 @@
gitoa.ru/go-4devs/console v0.3.0 h1:8A8UZXrDAlBDWGWUsWckyEeYE3lowreZANCSRYjzBdM=
gitoa.ru/go-4devs/console v0.3.0/go.mod h1:PG/Zyj1dLh7eFlj9bgnV58+Ys6I/MTrS0q9W7oD7z4U=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.1.0 h1:QcxC7FuqEl0sZaIjcXB/kNEeBa0DH5z57qbWBvZwLC4=
github.com/hashicorp/vault/api v1.1.0/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zGrWJW91rzRroyl5nRNqraaBe4d5hiKcVZuHM=
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/gjson v1.7.5 h1:zmAN/xmX7OtpAkv4Ovfso60r/BiCi5IErCDYGNJu+uc=
github.com/tidwall/gjson v1.7.5/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
go.etcd.io/etcd/api/v3 v3.5.0-alpha.0 h1:+e5nrluATIy3GP53znpkHMFzPTHGYyzvJGFCbuI6ZLc=
go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw=
go.etcd.io/etcd/client/v3 v3.5.0-alpha.0 h1:dr1EOILak2pu4Nf5XbRIOCNIBjcz6UmkQd7hHRXwxaM=
go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8=
go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 h1:3yLUEC0nFCxw/RArImOyRUI4OAFbg4PFpBbAhSNzKNY=
go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 h1:fiNLklpBwWK1mth30Hlwk+fcdBmIALlgF5iy77O37Ig=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

15
key.go Normal file
View File

@@ -0,0 +1,15 @@
package config
import "context"
type Key struct {
Name string
AppName string
Namespace string
}
type KeyFactory func(ctx context.Context, key Key) string
func (k Key) String() string {
return k.Namespace + "_" + k.AppName + "_" + k.Name
}

32
key/helpers.go Normal file
View File

@@ -0,0 +1,32 @@
package key
import (
"context"
"strings"
"gitoa.ru/go-4devs/config"
)
func LastIndex(sep string, factory config.KeyFactory) func(ctx context.Context, key config.Key) (string, string) {
return func(ctx context.Context, key config.Key) (string, string) {
name := factory(ctx, key)
idx := strings.LastIndex(name, sep)
if idx == -1 {
return name, ""
}
return name[0:idx], name[idx+len(sep):]
}
}
func LastIndexField(sep, def string, factory config.KeyFactory) func(ctx context.Context, key config.Key) (string, string) {
return func(ctx context.Context, key config.Key) (string, string) {
p, k := LastIndex(sep, factory)(ctx, key)
if k == "" {
return p, def
}
return p, k
}
}

64
key/helpers_test.go Normal file
View File

@@ -0,0 +1,64 @@
package key_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/key"
)
func TestLastIndex(t *testing.T) {
t.Parallel()
ctx := context.Background()
cases := map[string]struct {
sep string
path string
field string
}{
"/secret/with/field/name": {
sep: "/",
path: "/secret/with/field",
field: "name",
},
"/secret/database:username": {
sep: ":",
path: "/secret/database",
field: "username",
},
"database:username": {
sep: ":",
path: "database",
field: "username",
},
"/secret/database-dsn": {
sep: ":",
path: "/secret/database-dsn",
field: "",
},
"/secret/database--dsn": {
sep: "--",
path: "/secret/database",
field: "dsn",
},
"/secret/database:dsn": {
sep: "--",
path: "/secret/database:dsn",
field: "",
},
}
for path, data := range cases {
k := config.Key{
Name: path,
}
fn := key.LastIndex(data.sep, key.Name)
ns, field := fn(ctx, k)
assert.Equal(t, data.field, field, k)
assert.Equal(t, data.path, ns, k)
}
}

46
key/key.go Normal file
View File

@@ -0,0 +1,46 @@
package key
import (
"context"
"strings"
"gitoa.ru/go-4devs/config"
)
func NsAppName(sep string) config.KeyFactory {
return func(_ context.Context, key config.Key) string {
return strings.Join([]string{key.Namespace, key.AppName, key.Name}, sep)
}
}
func AppName(sep string) config.KeyFactory {
return func(_ context.Context, key config.Key) string {
return strings.Join([]string{key.AppName, key.Name}, sep)
}
}
func PrefixName(prefix string, factory config.KeyFactory) config.KeyFactory {
return func(ctx context.Context, key config.Key) string {
return prefix + factory(ctx, key)
}
}
func Name(_ context.Context, key config.Key) string {
return key.Name
}
func AliasName(name string, alias string, def config.KeyFactory) config.KeyFactory {
return func(ctx context.Context, key config.Key) string {
if name == key.Name {
return alias
}
return def(ctx, key)
}
}
func ReplaceAll(oldVal, newVal string, parent config.KeyFactory) config.KeyFactory {
return func(ctx context.Context, key config.Key) string {
return strings.ReplaceAll(parent(ctx, key), oldVal, newVal)
}
}

View File

@@ -1,125 +0,0 @@
package key
import (
"strings"
)
const (
prefixByPath = "byPath"
wrongIDx = -1
)
func newMap() *Map {
return &Map{
idx: wrongIDx,
wild: nil,
children: nil,
}
}
type Map struct {
idx int
wild *Map
children map[string]*Map
}
func ByPath(name, sep string) []string {
return []string{prefixByPath, name, sep}
}
func (m *Map) Index(path []string) (int, bool) {
if data, ok := m.find(path); ok {
return data.idx, true
}
if len(path) == 3 && path[0] == prefixByPath {
data, ok := m.byPath(path[1], path[2])
return data.idx, ok
}
return 0, false
}
func (m *Map) Add(idx int, path []string) {
m.add(path).idx = idx
}
func (m *Map) add(path []string) *Map {
name, path := path[0], path[1:]
if IsWild(name) {
m.wild = newMap()
return m.wild.add(path)
}
if m.children == nil {
m.children = map[string]*Map{}
}
if _, ok := m.children[name]; !ok {
m.children[name] = newMap()
}
if len(path) > 0 {
return m.children[name].add(path)
}
return m.children[name]
}
func (m *Map) byPath(path, sep string) (*Map, bool) {
if len(path) == 0 {
return m, m.isValid()
}
for name := range m.children {
if after, ok := strings.CutPrefix(path, name); ok {
data := m.children[name]
if len(after) == 0 || len(after) == len(sep) {
return data, data.isValid()
}
return data.byPath(after[len(sep):], sep)
}
}
if m.wild == nil {
return m, m.isValid()
}
if idx := strings.Index(path, sep); idx != -1 {
return m.wild.byPath(path[idx+1:], sep)
}
return m, m.isValid()
}
func (m *Map) find(path []string) (*Map, bool) {
name := path[0]
last := len(path) == 1
if !last {
path = path[1:]
}
data, ok := m.children[name]
if !ok && m.wild != nil {
return m.wild.find(path)
}
if !ok {
return data, false
}
if last {
return data, data.isValid()
}
return data.find(path)
}
func (m *Map) isValid() bool {
return m.idx != wrongIDx
}

View File

@@ -1,101 +0,0 @@
package key_test
import (
"testing"
"gitoa.ru/go-4devs/config/key"
)
func TestMap_ByPath(t *testing.T) {
t.Parallel()
const (
expID int = 1
othID int = 0
newID int = 42
grpID int = 27
)
data := key.Map{}
data.Add(expID, []string{"test", "data", "three"})
data.Add(othID, []string{"test", "other"})
data.Add(newID, []string{"new", "{data}", "test"})
data.Add(grpID, []string{"new", "group"})
idx, ok := data.Index(key.ByPath("test-other", "-"))
if !ok {
t.Error("key not found")
}
if idx != othID {
t.Errorf("idx exp:%v got:%v", othID, idx)
}
if nidx, nok := data.Index(key.ByPath("new-service-test", "-")); !nok && nidx != newID {
t.Errorf("idx exp:%v got:%v", newID, nidx)
}
if gidx, gok := data.Index(key.ByPath("new-group", "-")); !gok && gidx != grpID {
t.Errorf("idx %v exp:%v got:%v", gok, grpID, gidx)
}
}
func TestMap_Add(t *testing.T) {
t.Parallel()
const (
expID int = 1
newID int = 42
)
data := key.Map{}
data.Add(expID, []string{"test", "data"})
data.Add(expID, []string{"test", "other"})
data.Add(newID, []string{"new"})
idx, ok := data.Index([]string{"test", "data"})
if !ok {
t.Error("key not found")
}
if idx != expID {
t.Errorf("idx exp:%v got:%v", expID, idx)
}
if nidx, nok := data.Index([]string{"new"}); !nok && nidx != newID {
t.Errorf("idx exp:%v got:%v", newID, nidx)
}
}
func TestMap_Wild(t *testing.T) {
t.Parallel()
const (
expID int = 1
grpID int = 27
newID int = 42
)
data := key.Map{}
data.Add(expID, []string{"test", "{data}", "id"})
data.Add(grpID, []string{"test", "group"})
data.Add(newID, []string{"new", "data"})
idx, ok := data.Index([]string{"test", "data", "id"})
if !ok {
t.Error("key not found")
}
if idx != expID {
t.Errorf("idx exp:%v got:%v", expID, idx)
}
gidx, gok := data.Index([]string{"test", "group"})
if !gok {
t.Error("key not found")
}
if gidx != grpID {
t.Errorf("idx exp:%v got:%v", grpID, idx)
}
}

View File

@@ -1,21 +0,0 @@
package key
import "slices"
const minWildCount = 3
func IsWild(keys ...string) bool {
return slices.ContainsFunc(keys, isWild)
}
func Wild(name string) string {
return "{" + name + "}"
}
func isWild(name string) bool {
if len(name) < minWildCount {
return false
}
return name[0] == '{' && name[len(name)-1] == '}'
}

View File

@@ -1,16 +0,0 @@
package key_test
import (
"testing"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/test/require"
)
func TestWild(t *testing.T) {
t.Parallel()
require.True(t, key.IsWild(key.Wild("test")))
require.True(t, !key.IsWild("test"))
require.True(t, key.IsWild("test", key.Wild("test"), "key"))
}

View File

@@ -1,45 +0,0 @@
package param
func String(fn Params, key any) (string, bool) {
val, ok := fn.Param(key)
if !ok {
return "", false
}
data, ok := val.(string)
return data, ok
}
func Rune(fn Params, key any) (rune, bool) {
val, ok := fn.Param(key)
if !ok {
return '0', false
}
data, dok := val.(rune)
return data, dok
}
func Bool(key any, fn Params) (bool, bool) {
val, ok := fn.Param(key)
if !ok {
return false, false
}
data, ok := val.(bool)
return data, ok
}
func Uint64(key any, fn Params) (uint64, bool) {
data, ok := fn.Param(key)
if !ok {
return 0, false
}
res, ok := data.(uint64)
return res, ok
}

View File

@@ -1,69 +0,0 @@
package param
type key int
const (
paramTimeFormat key = iota + 1
paramType
paramDescription
paramDefault
paramPosition
)
func WithTimeFormat(format string) Option {
return func(p Params) Params {
return With(p, paramTimeFormat, format)
}
}
func TimeFormat(fn Params) (string, bool) {
return String(fn, paramTimeFormat)
}
func WithType(in any) Option {
return func(v Params) Params {
return With(v, paramType, in)
}
}
func Type(fn Params) any {
param, _ := fn.Param(paramType)
return param
}
func WithDescription(in string) Option {
return func(p Params) Params {
return With(p, paramDescription, in)
}
}
func Description(fn Params) string {
data, _ := String(fn, paramDescription)
return data
}
func WithDefault(in any) Option {
return func(p Params) Params {
return With(p, paramDefault, in)
}
}
func Default(p Params) (any, bool) {
data, ok := p.Param(paramDefault)
return data, ok
}
func WithPostition(in uint64) Option {
return func(p Params) Params {
return With(p, paramPosition, in)
}
}
func Position(in Params) uint64 {
pos, _ := Uint64(paramPosition, in)
return pos
}

View File

@@ -1,74 +0,0 @@
package param
import (
"slices"
)
var emptyParam = empty{}
type (
Option func(p Params) Params
Has func(Params) bool
Params interface {
Param(key any) (any, bool)
}
)
func Chain(vals ...Params) Params {
slices.Reverse(vals)
return chain(vals)
}
func With(parent Params, key, val any) Params {
return value{
Params: parent,
key: key,
val: val,
}
}
func New(opts ...Option) Params {
var parms Params
parms = emptyParam
for _, opt := range opts {
parms = opt(parms)
}
return parms
}
type empty struct{}
func (v empty) Param(_ any) (any, bool) {
return nil, false
}
type value struct {
Params
key, val any
}
func (v value) Param(key any) (any, bool) {
if v.key == key {
return v.val, true
}
return v.Params.Param(key)
}
type chain []Params
func (c chain) Param(key any) (any, bool) {
for _, p := range c {
val, ok := p.Param(key)
if ok {
return val, ok
}
}
return nil, false
}

View File

@@ -1,49 +0,0 @@
package param_test
import (
"testing"
"gitoa.ru/go-4devs/config/param"
)
func TestChainReplace(t *testing.T) {
t.Parallel()
const (
replaceParam = "param1"
replaceValue = "replace"
)
params1 := param.With(param.New(), replaceParam, "param1")
params2 := param.With(param.New(), replaceParam, replaceValue)
data, ok := param.String(param.Chain(params1, params2), replaceParam)
if !ok {
t.Errorf("param %v: not found", replaceParam)
}
if data != replaceValue {
t.Errorf("got:%v, expect:%v", data, replaceValue)
}
}
func TestChainExtend(t *testing.T) {
t.Parallel()
const (
extendParam = "param1"
extendValue = "replace"
)
params1 := param.With(param.New(), extendParam, extendValue)
params2 := param.With(param.New(), "new_value", "param2")
data1, ok := param.String(param.Chain(params1, params2), extendParam)
if !ok {
t.Errorf("param %v: not found", extendParam)
}
if data1 != extendValue {
t.Errorf("got:%v, expect:%v", data1, extendParam)
}
}

View File

@@ -1,123 +0,0 @@
package csv
import (
"bytes"
"context"
"encoding/csv"
"fmt"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
type pkey int
const (
paramDelimiter pkey = iota + 1
paramParse
)
const defaultDelimiter = ','
func WithDelimiter(in rune) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramDelimiter, in)
}
}
func WithInt(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.Atoi)
})
}
func WithInt64(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseInt64)
})
}
func WithFloat(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseFloat)
})
}
func WithBool(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseBool)
})
}
func WithUint(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseUint)
})
}
func WithUint64(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseUint64)
})
}
func WithDuration(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseDuration)
})
}
func WithTime(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseTime)
})
}
func WithParse(fn func(data []string) config.Value) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramParse, fn)
}
}
func Csv(_ context.Context, in config.Value, opts ...param.Option) (config.Value, error) {
sval, serr := in.ParseString()
if serr != nil {
return in, nil //nolint:nilerr
}
params := param.New(opts...)
reader := csv.NewReader(bytes.NewBufferString(sval))
reader.Comma = getDelimiter(params)
data, rerr := reader.Read()
if rerr != nil {
return nil, fmt.Errorf("read csv:%w", rerr)
}
return csvValue(params, data)
}
func csvValue(params param.Params, data []string) (config.Value, error) {
fn, ok := params.Param(paramParse)
if !ok {
return stringsValue(data)
}
parse, _ := fn.(func([]string) (config.Value, error))
return parse(data)
}
func stringsValue(data []string) (config.Value, error) {
return value.New(data), nil
}
func getDelimiter(params param.Params) rune {
if name, ok := param.Rune(params, paramDelimiter); ok {
return name
}
return defaultDelimiter
}

View File

@@ -1,54 +0,0 @@
package csv_test
import (
"context"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/processor/csv"
"gitoa.ru/go-4devs/config/test/require"
"gitoa.ru/go-4devs/config/value"
)
func TestCsv(t *testing.T) {
t.Parallel()
ctx := context.Background()
val := value.String("test2,test3,other")
data, derr := csv.Csv(ctx, val)
require.NoError(t, derr)
var resString []string
require.NoError(t, data.Unmarshal(&resString))
require.Equal(t, []string{"test2", "test3", "other"}, resString)
}
func TestCsv_int(t *testing.T) {
t.Parallel()
ctx := context.Background()
val := value.String("42,0,1")
data, derr := csv.Csv(ctx, val, csv.WithInt)
require.NoError(t, derr)
var resInt []int
require.NoError(t, data.Unmarshal(&resInt))
require.Equal(t, []int{42, 0, 1}, resInt)
}
func TestCsv_invalidValue(t *testing.T) {
t.Parallel()
ctx := context.Background()
val := value.String("42,0.1,1")
data, derr := csv.Csv(ctx, val, csv.WithInt)
require.ErrorIs(t, derr, config.ErrInvalidValue)
require.Equal(t, nil, data)
}

View File

@@ -1,27 +0,0 @@
package env
import (
"context"
"fmt"
"os"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
var _ config.ProcessFunc = Env
func Env(_ context.Context, in config.Value, _ ...param.Option) (config.Value, error) {
key, err := in.ParseString()
if err != nil {
return in, fmt.Errorf("process[env]:%w", err)
}
res, ok := os.LookupEnv(key)
if !ok {
return nil, fmt.Errorf("%w", config.ErrNotFound)
}
return value.String(res), nil
}

View File

@@ -1,19 +0,0 @@
package json //nolint:revive
import (
"context"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
//nolint:revive
func Json(_ context.Context, in config.Value, _ ...param.Option) (config.Value, error) {
data, err := in.ParseString()
if err != nil {
return in, nil //nolint:nilerr
}
return value.JString(data), nil
}

View File

@@ -1,48 +0,0 @@
package json_test
import (
"context"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/processor/json"
"gitoa.ru/go-4devs/config/test/require"
"gitoa.ru/go-4devs/config/value"
)
func TestJson(t *testing.T) {
t.Parallel()
ctx := context.Background()
val, err := json.Json(ctx, value.String("42"))
require.NoError(t, err)
var res int
require.NoError(t, val.Unmarshal(&res))
require.Equal(t, 42, res)
sval, serr := json.Json(ctx, value.String("\"test data\""))
require.NoError(t, serr)
var sres string
require.NoError(t, sval.Unmarshal(&sres))
require.Equal(t, "test data", sres)
slval, slerr := json.Json(ctx, value.String("[\"test\",\"test2 data\",\"test3\"]"))
require.NoError(t, slerr)
var slres []string
require.NoError(t, slval.Unmarshal(&slres))
require.Equal(t, []string{"test", "test2 data", "test3"}, slres)
}
func TestJson_invalidValue(t *testing.T) {
t.Parallel()
ctx := context.Background()
val, err := json.Json(ctx, value.New("42"))
require.NoError(t, err)
var data string
require.ErrorIs(t, val.Unmarshal(&data), config.ErrInvalidValue)
}

View File

@@ -1,49 +0,0 @@
package key
import (
"context"
"fmt"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
type pkey int
const paramKey pkey = iota
func WithKey(in string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramKey, in)
}
}
func Key(_ context.Context, in config.Value, opts ...param.Option) (config.Value, error) {
data := make(map[string]any, 0)
if err := in.Unmarshal(&data); err != nil {
return nil, fmt.Errorf("unmarshal:%w", err)
}
key, ok := getKey(opts...)
if !ok {
return nil, fmt.Errorf("key is %w", config.ErrRequired)
}
val, vok := data[key]
if !vok {
return nil, fmt.Errorf("value by key[%v]: %w", key, config.ErrNotFound)
}
return value.New(val), nil
}
func getKey(opts ...param.Option) (string, bool) {
params := param.New(opts...)
if name, ok := param.String(params, paramKey); ok {
return name, ok
}
return "", false
}

View File

@@ -1,58 +0,0 @@
package key_test
import (
"context"
"encoding/json"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/processor/key"
"gitoa.ru/go-4devs/config/test/require"
"gitoa.ru/go-4devs/config/value"
)
func TestKey(t *testing.T) {
t.Parallel()
ctx := context.Background()
res, rerr := key.Key(ctx, keyData(t), key.WithKey("key"))
require.NoError(t, rerr)
require.Equal(t, "value", res.String())
}
func TestKey_required(t *testing.T) {
t.Parallel()
ctx := context.Background()
res, rerr := key.Key(ctx, keyData(t))
require.ErrorIs(t, rerr, config.ErrRequired)
require.Equal(t, res, nil)
}
func TestKey_notFound(t *testing.T) {
t.Parallel()
ctx := context.Background()
res, rerr := key.Key(ctx, keyData(t), key.WithKey("wrong"))
require.ErrorIs(t, rerr, config.ErrNotFound)
require.Equal(t, res, nil)
}
func keyData(t *testing.T) config.Value {
t.Helper()
data := map[string]string{
"key": "value",
}
jdata, err := json.Marshal(data)
require.NoError(t, err)
return value.JBytes(jdata)
}

View File

@@ -1,78 +1,19 @@
package config
import (
"context"
"io"
"gitoa.ru/go-4devs/config/param"
)
import "context"
type Provider interface {
Value(ctx context.Context, path ...string) (Value, error)
Name() string
Read(ctx context.Context, key Key) (Variable, error)
}
type WatchCallback func(ctx context.Context, oldVar, newVar Value) error
type WatchCallback func(ctx context.Context, oldVar, newVar Variable)
type WatchProvider interface {
Watch(ctx context.Context, callback WatchCallback, path ...string) error
Watch(ctx context.Context, key Key, callback WatchCallback) error
}
type Factory interface {
Name() string
Create(ctx context.Context, prov Provider) (Provider, error)
type ReadConfig interface {
Value(ctx context.Context, name string) (Value, error)
}
type Option interface {
Name() string
Param(key any) (any, bool)
}
type Group interface {
Option
Options
}
type Options interface {
Options() []Option
param.Params
}
type BindProvider interface {
Provider
Bind(ctx context.Context, data Variables) error
}
type DunpProvider interface {
Provider
DumpRefernce(ctx context.Context, w io.Writer, opts Options) error
}
type Providers interface {
Provider
Provider(name string) (Provider, error)
Names() []string
}
type Variables interface {
ByName(name ...string) (Variable, error)
ByParam(filter param.Has) (Variable, error)
Variables() []Variable
}
type Definition interface {
Add(opts ...Option)
}
type ProcessFunc func(ctx context.Context, in Value, opts ...param.Option) (Value, error)
func (o ProcessFunc) Process(ctx context.Context, in Value, opts ...param.Option) (Value, error) {
return o(ctx, in, opts...)
}
type Processor interface {
Process(ctx context.Context, in Value, opts ...param.Option) (Value, error)
}
type Factory func(ctx context.Context, cfg ReadConfig) (Provider, error)

View File

@@ -1,419 +0,0 @@
package arg
import (
"bytes"
"encoding"
"encoding/json"
"errors"
"fmt"
"io"
"sort"
"strings"
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/param"
)
const (
defaultSpace = 2
)
func ResolveStyle(params param.Params) ViewStyle {
var vs ViewStyle
data, ok := params.Param(paramDumpReferenceView)
if ok {
vs, _ = data.(ViewStyle)
}
return vs
}
func WithStyle(comment, info Style) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramDumpReferenceView, ViewStyle{
Comment: comment,
Info: info,
MLen: 0,
})
}
}
type ViewStyle struct {
Comment Style
Info Style
MLen int
}
func (v ViewStyle) ILen() int {
return v.Info.Len() + v.MLen
}
type Style struct {
Start string
End string
}
func (s Style) Len() int {
return len(s.End) + len(s.Start)
}
func NewDump() Dump {
return Dump{
sep: dash,
space: defaultSpace,
}
}
type Dump struct {
sep string
space int
}
func (d Dump) Reference(w io.Writer, opt config.Options) error {
views := NewViews(opt, nil)
style := ResolveStyle(opt)
style.MLen = d.keyMaxLen(views...)
if args := views.Arguments(); len(args) > 0 {
if aerr := d.writeArguments(w, style, args...); aerr != nil {
return fmt.Errorf("write arguments:%w", aerr)
}
}
if opts := views.Options(); len(opts) > 0 {
if oerr := d.writeOptions(w, style, opts...); oerr != nil {
return fmt.Errorf("write option:%w", oerr)
}
}
return nil
}
//nolint:mnd
func (d Dump) keyMaxLen(views ...View) int {
var maxLen int
for _, vi := range views {
vlen := len(vi.Name(d.sep)) + d.space
if !vi.IsArgument() {
if !vi.IsBool() {
vlen = vlen*2 + 1
}
if def := vi.Default(); def != "" {
vlen += d.space
}
vlen += 4 + d.space
}
if vlen > maxLen {
maxLen = vlen
}
}
return maxLen
}
func (d Dump) writeArguments(w io.Writer, style ViewStyle, args ...View) error {
_, err := fmt.Fprintf(w, "\n%sArguments:%s\n",
style.Comment.Start,
style.Comment.End,
)
for _, arg := range args {
alen, ierr := fmt.Fprintf(w, "%s%s%s%s",
strings.Repeat(" ", d.space),
style.Info.Start,
arg.Name(d.sep),
style.Info.End,
)
if ierr != nil {
err = errors.Join(err, ierr)
}
_, ierr = fmt.Fprint(w, strings.Repeat(" ", style.ILen()+d.space-alen))
if ierr != nil {
err = errors.Join(err, ierr)
}
_, ierr = fmt.Fprint(w, arg.Description())
if ierr != nil {
err = errors.Join(err, ierr)
}
if def := arg.Default(); def != "" {
ierr := d.writeDefault(w, style, def)
if ierr != nil {
err = errors.Join(err, ierr)
}
}
_, ierr = fmt.Fprint(w, "\n")
if ierr != nil {
err = errors.Join(err, ierr)
}
}
if err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
//nolint:gocognit,gocyclo,cyclop
func (d Dump) writeOptions(w io.Writer, style ViewStyle, opts ...View) error {
_, err := fmt.Fprintf(w, "\n%sOptions:%s\n",
style.Comment.Start,
style.Comment.End,
)
for _, opt := range opts {
if opt.IsHidden() {
continue
}
var op bytes.Buffer
_, oerr := fmt.Fprintf(&op, "%s%s", strings.Repeat(" ", d.space), style.Info.Start)
if oerr != nil {
err = errors.Join(err, oerr)
}
if short := opt.Short(); short != "" {
op.WriteString("-")
op.WriteString(short)
op.WriteString(", ")
} else {
op.WriteString(" ")
}
op.WriteString("--")
op.WriteString(opt.Name(d.sep))
if !opt.IsBool() {
if !opt.IsRequired() {
op.WriteString("[")
}
op.WriteString("=")
op.WriteString(strings.ToUpper(opt.Name(d.sep)))
if !opt.IsRequired() {
op.WriteString("]")
}
}
_, oerr = fmt.Fprintf(&op, "%s", style.Info.End)
if oerr != nil {
err = errors.Join(err, oerr)
}
olen, oerr := w.Write(op.Bytes())
if oerr != nil {
err = errors.Join(err, oerr)
}
_, oerr = fmt.Fprintf(w, "%s%s",
strings.Repeat(" ", style.ILen()+d.space-olen),
opt.Description(),
)
if oerr != nil {
err = errors.Join(err, oerr)
}
if def := opt.Default(); def != "" {
oerr = d.writeDefault(w, style, def)
if oerr != nil {
err = errors.Join(err, oerr)
}
}
if opt.IsSlice() {
_, oerr = fmt.Fprintf(w, "%s (multiple values allowed)%s", style.Comment.Start, style.Comment.End)
if oerr != nil {
err = errors.Join(err, oerr)
}
}
_, oerr = fmt.Fprint(w, "\n")
if oerr != nil {
err = errors.Join(err, oerr)
}
}
if err != nil {
return fmt.Errorf("write options:%w", err)
}
return nil
}
func (d Dump) writeDefault(w io.Writer, style ViewStyle, data string) error {
_, err := fmt.Fprintf(w, " %s[default:%s]%s",
style.Comment.Start,
data,
style.Comment.End,
)
if err != nil {
return fmt.Errorf("default:%w", err)
}
return nil
}
func NewViews(opts config.Options, parent *View) Views {
views := make(Views, 0, len(opts.Options()))
for _, opt := range opts.Options() {
views = append(views, newViews(opt, parent)...)
}
return views
}
func newViews(opt config.Option, parent *View) []View {
view := NewView(opt, parent)
switch one := opt.(type) {
case config.Group:
return NewViews(one, &view)
default:
return []View{view}
}
}
type Views []View
func (v Views) Arguments() []View {
args := make([]View, 0, len(v))
for _, view := range v {
if view.IsArgument() {
args = append(args, view)
}
}
sort.Slice(args, func(i, j int) bool {
return args[i].Pos() < args[j].Pos()
})
return args
}
func (v Views) Options() []View {
opts := make([]View, 0, len(v))
for _, view := range v {
if !view.IsArgument() {
opts = append(opts, view)
}
}
sort.Slice(opts, func(i, j int) bool {
return opts[i].Pos() < opts[j].Pos()
})
return opts
}
func NewView(params config.Option, parent *View) View {
pos, ok := ParamArgument(params)
keys := make([]string, 0)
if parent != nil {
keys = append(keys, parent.Keys()...)
}
if !ok {
pos = param.Position(params)
}
if name := params.Name(); name != "" {
keys = append(keys, name)
}
return View{
pos: pos,
isArgument: ok,
keys: keys,
parent: parent,
Params: params,
}
}
type View struct {
param.Params
keys []string
pos uint64
isArgument bool
parent *View
}
func (v View) Name(delimiter string) string {
return strings.Join(v.keys, delimiter)
}
func (v View) IsArgument() bool {
return v.isArgument
}
func (v View) Keys() []string {
return v.keys
}
func (v View) Pos() uint64 {
return v.pos
}
func (v View) Default() string {
data, ok := param.Default(v.Params)
if !ok {
return ""
}
switch dt := data.(type) {
case time.Time:
return dt.Format(time.RFC3339)
case encoding.TextMarshaler:
if res, err := dt.MarshalText(); err == nil {
return string(res)
}
case json.Marshaler:
if res, err := dt.MarshalJSON(); err == nil {
return string(res)
}
}
return fmt.Sprintf("%v", data)
}
func (v View) Description() string {
return param.Description(v.Params)
}
func (v View) IsHidden() bool {
return option.IsHidden(v.Params)
}
func (v View) Short() string {
short, _ := option.ParamShort(v.Params)
return short
}
func (v View) IsRequired() bool {
return option.IsHidden(v.Params)
}
func (v View) IsBool() bool {
return option.IsBool(v.Params)
}
func (v View) IsSlice() bool {
return option.IsSlice(v.Params)
}

View File

@@ -1,35 +0,0 @@
package arg_test
import (
"bytes"
"testing"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/test/require"
)
func TestDumpReference(t *testing.T) {
t.Parallel()
//nolint:dupword
const expect = `
Arguments:
config config [default:config.hcl]
user-name username
Options:
-l, --listen[=LISTEN] listen [default:8080]
-p, --user-password[=USER-PASSWORD] user pass
-u, --url[=URL] url (multiple values allowed)
-t, --timeout[=TIMEOUT] timeout (multiple values allowed)
--start-at[=START-AT] start at [default:2010-01-02T15:04:05Z]
--end-after[=END-AFTER] after (multiple values allowed)
--end-{service}-after[=END-{SERVICE}-AFTER] after
`
dump := arg.NewDump()
var buff bytes.Buffer
require.NoError(t, dump.Reference(&buff, testOptions(t)))
require.Equal(t, expect, buff.String())
}

View File

@@ -1,38 +0,0 @@
package arg_test
import (
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/test"
)
func testOptions(t *testing.T) config.Options {
t.Helper()
def := definition.New()
def.Add(
option.Int("listen", "listen", option.Short('l'), option.Default(8080)),
option.String("config", "config", arg.Argument, option.Default("config.hcl")),
group.New("user", "configure user",
option.String("name", "username", arg.Argument),
option.String("password", "user pass", option.Short('p')),
),
option.String("url", "url", option.Short('u'), option.Slice),
option.Duration("timeout", "timeout", option.Short('t'), option.Slice),
option.Time("start-at", "start at", option.Default(test.Time("2010-01-02T15:04:05Z"))),
group.New("end", "end at",
option.Time("after", "after", option.Slice),
proto.New("service", "service after",
option.Time("after", "after"),
),
),
)
return def
}

View File

@@ -1,59 +0,0 @@
package arg
import (
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
func Default(in any) param.Option {
return option.Default(value.New(in))
}
func Required(v param.Params) {
option.Required(v)
}
func Slice(v param.Params) {
option.Slice(v)
}
func String(name, description string, opts ...param.Option) option.Option {
return option.String(name, description, append(opts, Argument)...)
}
func Bool(name, description string, opts ...param.Option) option.Option {
return option.Bool(name, description, append(opts, Argument)...)
}
func Duration(name, description string, opts ...param.Option) option.Option {
return option.Duration(name, description, append(opts, Argument)...)
}
func Float64(name, description string, opts ...param.Option) option.Option {
return option.Float64(name, description, append(opts, Argument)...)
}
func Int(name, description string, opts ...param.Option) option.Option {
return option.Int(name, description, append(opts, Argument)...)
}
func Int64(name, description string, opts ...param.Option) option.Option {
return option.Int64(name, description, append(opts, Argument)...)
}
func Time(name, description string, opts ...param.Option) option.Option {
return option.Time(name, description, append(opts, Argument)...)
}
func Uint(name, description string, opts ...param.Option) option.Option {
return option.Uint(name, description, append(opts, Argument)...)
}
func Uint64(name, descriontion string, opts ...param.Option) option.Option {
return option.Uint64(name, descriontion, append(opts, Argument)...)
}
func Err(err error, key ...string) option.Error {
return option.Err(err, key)
}

View File

@@ -1,39 +0,0 @@
package arg
import (
"sync/atomic"
"gitoa.ru/go-4devs/config/param"
)
type keyParam int
const (
paramArgument keyParam = iota + 1
paramDumpReferenceView
)
//nolint:gochecknoglobals
var argNum uint64
func Argument(v param.Params) param.Params {
return param.With(v, paramArgument, atomic.AddUint64(&argNum, 1)-1)
}
func ParamArgument(fn param.Params) (uint64, bool) {
return param.Uint64(paramArgument, fn)
}
func PosArgument(in uint64) param.Has {
return func(p param.Params) bool {
idx, ok := ParamArgument(p)
return ok && idx == in
}
}
func HasArgument(fn param.Params) bool {
_, ok := ParamArgument(fn)
return ok
}

View File

@@ -3,237 +3,44 @@ package arg
import (
"context"
"fmt"
"io"
"os"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/provider/memory"
"gitoa.ru/go-4devs/config/value"
"gopkg.in/yaml.v3"
)
const (
doubleDash = `--`
defaultLenLognOption = 2
dash = `-`
)
var _ config.Provider = (*Provider)(nil)
// Deprecated: use WithArgs.
func WithSkip(skip int) func(*Argv) {
return func(ar *Argv) {
res := 2
type Option func(*Provider)
switch {
case skip > 0 && len(os.Args) > skip:
res = skip
case skip > 0:
res = len(os.Args)
case len(os.Args) == 1:
res = 1
case len(os.Args) > 1 && os.Args[1][0] == '-':
res = 1
}
ar.args = os.Args[res:]
}
func WithKeyFactory(factory config.KeyFactory) Option {
return func(p *Provider) { p.key = factory }
}
func WithArgs(args []string) func(*Argv) {
return func(a *Argv) {
a.args = args
}
}
func New(opts ...func(*Argv)) *Argv {
arg := &Argv{
args: os.Args[1:],
pos: 0,
Map: memory.Map{},
func New(opts ...Option) *Provider {
prov := Provider{
key: key.Name,
args: make(map[string][]string, len(os.Args[1:])),
}
for _, opt := range opts {
opt(arg)
opt(&prov)
}
return arg
return &prov
}
type Argv struct {
memory.Map
args []string
pos uint64
type Provider struct {
args map[string][]string
key config.KeyFactory
}
func (i *Argv) Value(ctx context.Context, key ...string) (config.Value, error) {
if err := i.parse(); err != nil {
return nil, fmt.Errorf("parse:%w", err)
}
data, err := i.Map.Value(ctx, key...)
if err != nil {
return nil, fmt.Errorf("map: %w", err)
}
return data, nil
}
func (i *Argv) Bind(ctx context.Context, def config.Variables) error {
options := true
for len(i.args) > 0 {
var err error
arg := i.args[0]
i.args = i.args[1:]
switch {
case options && arg == doubleDash:
options = false
case options && len(arg) > 2 && arg[0:2] == doubleDash:
err = i.parseLongOption(arg[2:], def)
case options && arg[0:1] == "-":
if len(arg) == 1 {
return fmt.Errorf("%w: option name required given '-'", config.ErrInvalidName)
}
err = i.parseShortOption(arg[1:], def)
default:
err = i.parseArgument(arg, def)
}
if err != nil {
return fmt.Errorf("arg bind:%w", err)
}
}
if err := i.Map.Bind(ctx, def); err != nil {
return fmt.Errorf("arg map:%w", err)
}
return nil
}
func (i *Argv) DumpRefernce(_ context.Context, w io.Writer, opt config.Options) error {
return NewDump().Reference(w, opt)
}
func (i *Argv) parseLongOption(arg string, def config.Variables) error {
var value *string
name := arg
if strings.Contains(arg, "=") {
vals := strings.SplitN(arg, "=", defaultLenLognOption)
name = vals[0]
value = &vals[1]
}
opt, err := def.ByName(key.ByPath(name, dash)...)
if err != nil {
return Err(err, name)
}
return i.appendOption(value, opt)
}
func (i *Argv) appendOption(data *string, opt config.Variable) error {
if i.HasOption(opt.Key()...) && !option.IsSlice(opt) {
return fmt.Errorf("%w: got: array, expect: %T", config.ErrUnexpectedType, param.Type(opt))
}
var val string
switch {
case data != nil:
val = *data
case option.IsBool(opt):
val = "true"
case len(i.args) > 0 && len(i.args[0]) > 0 && i.args[0][0:1] != "-":
val = i.args[0]
i.args = i.args[1:]
default:
return Err(config.ErrRequired, opt.Key()...)
}
err := i.AppendOption(val, opt.Key()...)
if err != nil {
return Err(err, opt.Key()...)
}
return nil
}
func (i *Argv) parseShortOption(arg string, def config.Variables) error {
name := arg
var value string
if len(name) > 1 {
name, value = arg[0:1], strings.TrimSpace(arg[1:])
}
opt, err := def.ByParam(option.HasShort(name))
if err != nil {
return fmt.Errorf("%w", err)
}
if option.IsBool(opt) && value != "" {
err := i.parseShortOption(value, def)
if err != nil {
return err
}
value = ""
}
if value == "" {
return i.appendOption(nil, opt)
}
return i.appendOption(&value, opt)
}
func (i *Argv) parseArgument(arg string, def config.Variables) error {
opt, err := def.ByParam(PosArgument(i.pos))
if err != nil {
return fmt.Errorf("%w", err)
}
i.pos++
if err := i.AppendOption(arg, opt.Key()...); err != nil {
return Err(err, opt.Key()...)
}
return nil
}
func (i *Argv) parse() error {
if i.Len() > 0 {
return nil
}
for _, arg := range i.args {
name, value, err := i.parseOne(arg)
if err != nil {
return err
}
if name != "" {
if err := i.AppendOption(value, name); err != nil {
return fmt.Errorf("append %v: %w", name, err)
}
}
}
return nil
}
// parseOne return name, value, error.
func (i *Argv) parseOne(arg string) (string, string, error) {
// nolint: cyclop
// return name, value, error.
func (p *Provider) parseOne(arg string) (string, string, error) {
if arg[0] != '-' {
return "", "", nil
}
@@ -270,3 +77,66 @@ func (i *Argv) parseOne(arg string) (string, string, error) {
return name, val, nil
}
func (p *Provider) parse() error {
if len(p.args) > 0 {
return nil
}
for _, arg := range os.Args[1:] {
name, value, err := p.parseOne(arg)
if err != nil {
return err
}
if name != "" {
p.args[name] = append(p.args[name], value)
}
}
return nil
}
func (p *Provider) Name() string {
return "arg"
}
func (p *Provider) IsSupport(ctx context.Context, key config.Key) bool {
return p.key(ctx, key) != ""
}
func (p *Provider) Read(ctx context.Context, key config.Key) (config.Variable, error) {
if err := p.parse(); err != nil {
return config.Variable{
Name: "",
Value: nil,
Provider: p.Name(),
}, err
}
name := p.key(ctx, key)
if val, ok := p.args[name]; ok {
switch {
case len(val) == 1:
return config.Variable{
Name: name,
Provider: p.Name(),
Value: value.JString(val[0]),
}, nil
default:
var yNode yaml.Node
if err := yaml.Unmarshal([]byte("["+strings.Join(val, ",")+"]"), &yNode); err != nil {
return config.Variable{}, fmt.Errorf("arg: failed unmarshal yaml:%w", err)
}
return config.Variable{
Name: name,
Provider: p.Name(),
Value: value.Decode(yNode.Decode),
}, nil
}
}
return config.Variable{}, fmt.Errorf("%w: %s", config.ErrVariableNotFound, name)
}

View File

@@ -1,22 +1,25 @@
package arg_test
import (
"context"
"fmt"
"strings"
"os"
"testing"
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
func TestProvider(t *testing.T) {
t.Parallel()
args := []string{
args := os.Args
defer func() {
os.Args = args
}()
os.Args = []string{
"main.go",
"--listen=8080",
"--config=config.hcl",
"--url=http://4devs.io",
@@ -28,76 +31,18 @@ func TestProvider(t *testing.T) {
"--end-after=2008-01-02T15:04:05+03:00",
}
read := []test.Read{
test.NewRead(8080, "listen"),
test.NewRead("config.hcl", "config"),
test.NewRead(test.Time("2010-01-02T15:04:05Z"), "start-at"),
test.NewReadUnmarshal(&[]string{"http://4devs.io", "https://4devs.io"}, &[]string{}, "url"),
test.NewReadUnmarshal(&[]Duration{{time.Minute}, {time.Hour}}, &[]Duration{}, "timeout"),
test.NewReadUnmarshal(&[]time.Time{
test.NewRead("listen", 8080),
test.NewRead("config", "config.hcl"),
test.NewRead("start-at", test.Time("2010-01-02T15:04:05Z")),
test.NewReadUnmarshal("url", &[]string{"http://4devs.io", "https://4devs.io"}, &[]string{}),
test.NewReadUnmarshal("timeout", &[]time.Duration{time.Minute, time.Hour}, &[]time.Duration{}),
test.NewReadUnmarshal("end-after", &[]time.Time{
test.Time("2009-01-02T15:04:05Z"),
test.Time("2008-01-02T15:04:05+03:00"),
}, &[]time.Time{}, "end-after"),
}, &[]time.Time{}),
}
prov := arg.New(arg.WithArgs(args))
prov := arg.New()
test.Run(t, prov, read)
}
func TestProviderBind(t *testing.T) {
t.Parallel()
args := []string{
"-l 8080",
"--config=config.hcl",
"-u http://4devs.io",
"--url=https://4devs.io",
"-t 1m",
"--timeout=1h",
"--start-at=2010-01-02T15:04:05Z",
"--end-after=2009-01-02T15:04:05Z",
"--end-after=2008-01-02T15:04:05+03:00",
}
read := []test.Read{
test.NewRead(8080, "listen"),
test.NewRead(test.Time("2010-01-02T15:04:05Z"), "start-at"),
test.NewReadUnmarshal(&[]string{"http://4devs.io", "https://4devs.io"}, &[]string{}, "url"),
test.NewReadUnmarshal(&[]Duration{{time.Minute}, {time.Hour}}, &[]Duration{}, "timeout"),
test.NewReadUnmarshal(&[]time.Time{
test.Time("2009-01-02T15:04:05Z"),
test.Time("2008-01-02T15:04:05+03:00"),
}, &[]time.Time{}, "end", "after"),
}
ctx := context.Background()
prov := arg.New(arg.WithArgs(args))
require.NoError(t, prov.Bind(ctx, testVariables(t)))
test.Run(t, prov, read)
}
func testVariables(t *testing.T) config.Variables {
t.Helper()
return config.NewVars(testOptions(t).Options()...)
}
type Duration struct {
time.Duration
}
func (d *Duration) UnmarshalJSON(in []byte) error {
o, err := time.ParseDuration(strings.Trim(string(in), `"`))
if err != nil {
return fmt.Errorf("parse:%w", err)
}
d.Duration = o
return nil
}
func (d *Duration) MarshalJSON() ([]byte, error) {
return fmt.Appendf(nil, "%q", d), nil
}

View File

@@ -1,83 +0,0 @@
package chain
import (
"context"
"fmt"
"gitoa.ru/go-4devs/config"
)
const Name = "chain"
type Providers interface {
config.BindProvider
config.Providers
}
func New(c ...config.Provider) Providers {
return chain(c)
}
type chain []config.Provider
func (c chain) Value(ctx context.Context, name ...string) (config.Value, error) {
for _, in := range c {
if val, err := in.Value(ctx, name...); err == nil {
return val, nil
}
}
return nil, fmt.Errorf("%w", config.ErrNotFound)
}
func (c chain) Bind(ctx context.Context, def config.Variables) error {
for _, input := range c {
if prov, ok := input.(config.BindProvider); ok {
if err := prov.Bind(ctx, def); err != nil {
return fmt.Errorf("%T:%w", input, err)
}
}
}
return nil
}
func (c chain) Name() string {
return Name
}
func (c chain) Provider(name string) (config.Provider, error) {
if c.Name() == name {
return c, nil
}
for _, prov := range c {
if prov.Name() == name {
return prov, nil
}
cprov, ok := prov.(config.Providers)
if !ok {
continue
}
if in, err := cprov.Provider(name); err == nil {
return in, nil
}
}
return nil, fmt.Errorf("prov[%v]:%w", c.Name(), config.ErrNotFound)
}
func (c chain) Names() []string {
names := make([]string, 0, len(c))
for _, prov := range c {
names = append(names, prov.Name())
if cprov, ok := prov.(config.Providers); ok {
names = append(names, cprov.Names()...)
}
}
return names
}

View File

@@ -1,21 +0,0 @@
module gitoa.ru/go-4devs/config/provider/dasel
go 1.25.5
require (
github.com/tomwright/dasel/v3 v3.2.0
gitoa.ru/go-4devs/config v0.0.6
)
require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/tools v0.35.0 // indirect
)

View File

@@ -1,30 +0,0 @@
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/tomwright/dasel/v3 v3.2.0 h1:wyF2A4jVADx10E0kjbzPTiaF26D0OkK2OPuQRPJRDCo=
github.com/tomwright/dasel/v3 v3.2.0/go.mod h1:XyAl6LidZuWOISIeUmKlCqJDz4IWEDp83epNngZgOQA=
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
gitoa.ru/go-4devs/config v0.0.6 h1:BbOH2KHBRMWtSwcFyHKzMXzxYbJpSwZpA8LGnnzBwk8=
gitoa.ru/go-4devs/config v0.0.6/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=

View File

@@ -1,18 +0,0 @@
app {
name {
var = [
"name"
]
title = "config title"
timeout = "1m"
success = true
}
}
cfg {
duration = 1260000000000
enabled = true
type = "json"
}

View File

@@ -1,13 +0,0 @@
package hcl
import (
"github.com/tomwright/dasel/v3/parsing/hcl"
"gitoa.ru/go-4devs/config/provider/dasel"
)
const Name = "dasel:hcl"
//nolint:wrapcheck
func New(data []byte) (dasel.Provider, error) {
return dasel.New(data, hcl.HCL, dasel.WithName(Name))
}

View File

@@ -1,35 +0,0 @@
package hcl_test
import (
"embed"
"testing"
"time"
dhcl "gitoa.ru/go-4devs/config/provider/dasel/hcl"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
//go:embed fixture/*
var fixture embed.FS
func TestProvider(t *testing.T) {
t.Parallel()
js, err := fixture.ReadFile("fixture/config.hcl")
require.NoError(t, err)
prov, derr := dhcl.New(js)
require.NoError(t, derr)
sl := []string{}
read := []test.Read{
test.NewRead("config title", "app", "name", "title"),
test.NewRead(time.Minute, "app.name.timeout"),
test.NewReadUnmarshal(&[]string{"name"}, &sl, "app.name.var"),
test.NewReadConfig("cfg"),
test.NewRead(true, "app", "name", "success"),
}
test.Run(t, prov, read)
}

View File

@@ -1,13 +0,0 @@
package json //nolint:revive
import (
"github.com/tomwright/dasel/v3/parsing/json"
"gitoa.ru/go-4devs/config/provider/dasel"
)
const Name = "dasel:json"
//nolint:wrapcheck
func New(data []byte) (dasel.Provider, error) {
return dasel.New(data, json.JSON, dasel.WithName(Name))
}

View File

@@ -1,35 +0,0 @@
package json_test
import (
"embed"
"testing"
"time"
djson "gitoa.ru/go-4devs/config/provider/dasel/json"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
//go:embed fixture/*
var fixture embed.FS
func TestProvider(t *testing.T) {
t.Parallel()
js, err := fixture.ReadFile("fixture/config.json")
require.NoError(t, err)
prov, derr := djson.New(js)
require.NoError(t, derr)
sl := []string{}
read := []test.Read{
test.NewRead("config title", "app.name.title"),
test.NewRead(time.Minute, "app.name.timeout"),
test.NewReadUnmarshal(&[]string{"name"}, &sl, "app.name.var"),
test.NewReadConfig("cfg"),
test.NewRead(true, "app", "name", "success"),
}
test.Run(t, prov, read)
}

View File

@@ -1,91 +0,0 @@
package dasel
import (
"context"
"encoding/json"
"fmt"
"strings"
dasel "github.com/tomwright/dasel/v3"
"github.com/tomwright/dasel/v3/model"
"github.com/tomwright/dasel/v3/parsing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/value"
)
var _ config.Provider = Provider{} //nolint:exhaustruct
const Name = "dasel"
type Option func(*Provider)
func WithName(in string) Option {
return func(p *Provider) {
p.name = in
}
}
func New(in []byte, format parsing.Format, opts ...Option) (Provider, error) {
reader, err := format.NewReader(parsing.DefaultReaderOptions())
if err != nil {
return Provider{}, fmt.Errorf("%w:%w", config.ErrInitFactory, err)
}
data, verr := reader.Read(in)
if verr != nil {
return Provider{}, fmt.Errorf("%w:%w", config.ErrInitFactory, verr)
}
prov := Provider{
data: data,
key: func(path ...string) string {
return strings.Join(path, ".")
},
name: Name,
}
for _, opt := range opts {
opt(&prov)
}
return prov, nil
}
type Provider struct {
data *model.Value
key func(path ...string) string
name string
}
func (p Provider) Value(ctx context.Context, path ...string) (config.Value, error) {
selector := p.key(path...)
data, cnt, err := dasel.Query(ctx, p.data, selector)
if err != nil {
return nil, fmt.Errorf("query: %w:%w", config.ErrInvalidValue, err)
}
if cnt > 1 {
return nil, fmt.Errorf("count: %v:%w", cnt, config.ErrToManyArgs)
}
if cnt == 0 {
return value.EmptyValue(), nil
}
val, verr := data[0].GoValue()
if verr != nil {
return nil, fmt.Errorf("go value: %w:%w", config.ErrInvalidValue, verr)
}
res, merr := json.Marshal(val)
if merr != nil {
return nil, fmt.Errorf("marshal: %w:%w", config.ErrInvalidValue, merr)
}
return value.JBytes(res), nil
}
func (p Provider) Name() string {
return p.name
}

View File

@@ -2,34 +2,27 @@ package env
import (
"context"
"fmt"
"io"
"os"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
const Name = "env"
var _ config.Provider = (*Provider)(nil)
type Option func(*Provider)
func WithKeyFactory(factory func(...string) string) Option {
func WithKeyFactory(factory config.KeyFactory) Option {
return func(p *Provider) { p.key = factory }
}
func New(namespace, appName string, opts ...Option) *Provider {
func New(opts ...Option) *Provider {
provider := Provider{
key: func(path ...string) string {
return strings.ToUpper(strings.Join(path, "_"))
key: func(ctx context.Context, k config.Key) string {
return strings.ToUpper(key.NsAppName("_")(ctx, k))
},
prefix: strings.ToUpper(namespace + "_" + appName + "_"),
name: "",
}
for _, opt := range opts {
@@ -40,73 +33,26 @@ func New(namespace, appName string, opts ...Option) *Provider {
}
type Provider struct {
key func(...string) string
name string
prefix string
}
func (p *Provider) Key(path ...string) string {
return p.prefix + p.key(path...)
key config.KeyFactory
}
func (p *Provider) Name() string {
return p.name
return "env"
}
func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error) {
if val, ok := os.LookupEnv(p.Key(path...)); ok {
return value.JString(val), nil
}
return nil, fmt.Errorf("%v:%w", p.Name(), config.ErrNotFound)
func (p *Provider) IsSupport(ctx context.Context, key config.Key) bool {
return p.key(ctx, key) != ""
}
func (p *Provider) DumpReference(_ context.Context, w io.Writer, opt config.Options) error {
return p.writeOptions(w, opt)
}
func (p *Provider) writeOptions(w io.Writer, opt config.Options, key ...string) error {
for idx, option := range opt.Options() {
if err := p.writeOption(w, option, key...); err != nil {
return fmt.Errorf("option[%d]:%w", idx, err)
}
}
return nil
}
func (p *Provider) writeOption(w io.Writer, opt config.Option, keys ...string) error {
if desc := param.Description(opt); desc != "" {
if _, derr := fmt.Fprintf(w, "# %v.\n", desc); derr != nil {
return fmt.Errorf("write description:%w", derr)
}
}
var err error
switch one := opt.(type) {
case config.Group:
err = p.writeOptions(w, one, append(keys, one.Name())...)
case config.Options:
err = p.writeOptions(w, one, keys...)
default:
def, dok := param.Default(opt)
prefix := ""
if !dok || key.IsWild(keys...) {
prefix = "#"
}
if !dok {
def = ""
}
_, err = fmt.Fprintf(w, "%s%s=%v\n", prefix, p.Key(append(keys, one.Name())...), def)
}
if err != nil {
return fmt.Errorf("%w", err)
}
return nil
func (p *Provider) Read(ctx context.Context, key config.Key) (config.Variable, error) {
name := p.key(ctx, key)
if val, ok := os.LookupEnv(name); ok {
return config.Variable{
Name: name,
Provider: p.Name(),
Value: value.JString(val),
}, nil
}
return config.Variable{}, config.ErrVariableNotFound
}

View File

@@ -1,60 +1,24 @@
package env_test
import (
"bytes"
"context"
"os"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
"gitoa.ru/go-4devs/config/provider/env"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
func TestProvider(t *testing.T) {
t.Setenv("FDEVS_CONFIG_DSN", test.DSN)
t.Setenv("FDEVS_CONFIG_PORT", "8080")
t.Parallel()
provider := env.New("fdevs", "config")
os.Setenv("FDEVS_CONFIG_DSN", test.DSN)
os.Setenv("FDEVS_CONFIG_PORT", "8080")
provider := env.New()
read := []test.Read{
test.NewRead(test.DSN, "dsn"),
test.NewRead(8080, "port"),
test.NewRead("dsn", test.DSN),
test.NewRead("port", 8080),
}
test.Run(t, provider, read)
}
func TestProvider_DumpReference(t *testing.T) {
t.Parallel()
const expect = `# configure log.
# level.
FDEVS_CONFIG_LOG_LEVEL=info
# configure log service.
# level.
#FDEVS_CONFIG_LOG_{SERVICE}_LEVEL=
`
ctx := context.Background()
prov := env.New("fdevs", "config")
buf := bytes.NewBuffer(nil)
require.NoError(t, prov.DumpReference(ctx, buf, testOptions(t)))
require.Equal(t, buf.String(), expect)
}
func testOptions(t *testing.T) config.Options {
t.Helper()
return group.New("test", "test",
group.New("log", "configure log",
option.String("level", "level", option.Default("info")),
proto.New("service", "configure log service",
option.String("level", "level"),
),
),
)
}

View File

@@ -1,32 +0,0 @@
module gitoa.ru/go-4devs/config/provider/etcd
go 1.23
require (
github.com/stretchr/testify v1.8.4
gitoa.ru/go-4devs/config v0.0.3
go.etcd.io/etcd/api/v3 v3.5.11
go.etcd.io/etcd/client/v3 v3.5.11
)
require (
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,99 +0,0 @@
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
gitoa.ru/go-4devs/config v0.0.1 h1:9KrOO09YbIMO8qL8aVn/G74DurGdOIW5y3O02bays4I=
gitoa.ru/go-4devs/config v0.0.1/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss=
gitoa.ru/go-4devs/config v0.0.2 h1:bkTxW57kDDMf4cj/8W7fxPSN7JCPWEqlhCmL6LP3Vzg=
gitoa.ru/go-4devs/config v0.0.2/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss=
gitoa.ru/go-4devs/config v0.0.3 h1:+ecwDQj4fneJCh2uLNNAonm4cUJdGmlfxUsFhQRI9Ko=
gitoa.ru/go-4devs/config v0.0.3/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
go.etcd.io/etcd/api/v3 v3.5.11 h1:B54KwXbWDHyD3XYAwprxNzTe7vlhR69LuBgZnMVvS7E=
go.etcd.io/etcd/api/v3 v3.5.11/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
go.etcd.io/etcd/client/pkg/v3 v3.5.11 h1:bT2xVspdiCj2910T0V+/KHcVKjkUrCZVtk8J2JF2z1A=
go.etcd.io/etcd/client/pkg/v3 v3.5.11/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4=
go.etcd.io/etcd/client/v3 v3.5.11 h1:ajWtgoNSZJ1gmS8k+icvPtqsqEav+iUorF7b0qozgUU=
go.etcd.io/etcd/client/v3 v3.5.11/go.mod h1:a6xQUEqFJ8vztO1agJh/KQKOMfFI8og52ZconzcDJwE=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Some files were not shown because too many files have changed in this diff Show More