andrey
12 months ago
4 changed files with 0 additions and 253 deletions
@ -1,119 +0,0 @@ |
|||
package vault |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/json" |
|||
"fmt" |
|||
"log" |
|||
"strings" |
|||
|
|||
"github.com/hashicorp/vault/api" |
|||
"gitoa.ru/go-4devs/config" |
|||
"gitoa.ru/go-4devs/config/value" |
|||
) |
|||
|
|||
const ( |
|||
Name = "vault" |
|||
Separator = "/" |
|||
Prefix = "secret/data/" |
|||
ValueName = "value" |
|||
) |
|||
|
|||
var _ config.Provider = (*SecretKV2)(nil) |
|||
|
|||
type SecretOption func(*SecretKV2) |
|||
|
|||
func WithSecretResolve(f func(key []string) (string, string)) SecretOption { |
|||
return func(s *SecretKV2) { s.resolve = f } |
|||
} |
|||
|
|||
func NewSecretKV2(namespace, appName string, client *api.Client, opts ...SecretOption) *SecretKV2 { |
|||
prov := SecretKV2{ |
|||
client: client, |
|||
resolve: func(key []string) (string, string) { |
|||
keysLen := len(key) |
|||
if keysLen == 1 { |
|||
return "", key[0] |
|||
} |
|||
|
|||
return strings.Join(key[:keysLen-1], Separator), key[keysLen-1] |
|||
}, |
|||
name: Name, |
|||
prefix: Prefix + namespace + Separator + appName, |
|||
} |
|||
|
|||
for _, opt := range opts { |
|||
opt(&prov) |
|||
} |
|||
|
|||
return &prov |
|||
} |
|||
|
|||
type SecretKV2 struct { |
|||
client *api.Client |
|||
resolve func(key []string) (string, string) |
|||
name string |
|||
prefix string |
|||
} |
|||
|
|||
func (p *SecretKV2) Name() string { |
|||
return p.name |
|||
} |
|||
func (p *SecretKV2) Key(in []string) (string, string) { |
|||
path, val := p.resolve(in) |
|||
if path == "" { |
|||
return p.prefix, val |
|||
} |
|||
|
|||
return p.prefix + Separator + path, val |
|||
} |
|||
func (p *SecretKV2) read(path, key string) (*api.Secret, error) { |
|||
secret, err := p.client.Logical().Read(path) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if secret == nil && key != ValueName { |
|||
return p.read(path+Separator+key, ValueName) |
|||
} |
|||
|
|||
return secret, err |
|||
} |
|||
|
|||
func (p *SecretKV2) Value(ctx context.Context, key ...string) (config.Value, error) { |
|||
path, field := p.Key(key) |
|||
|
|||
secret, err := p.read(path, field) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("%w: path:%s, field:%s, provider:%s", err, path, field, p.Name()) |
|||
} |
|||
|
|||
if secret == nil || len(secret.Data) == 0 { |
|||
log.Println(secret == nil) |
|||
return nil, fmt.Errorf("%w: path:%s, field:%s, provider:%s", config.ErrValueNotFound, path, field, p.Name()) |
|||
} |
|||
|
|||
if len(secret.Warnings) > 0 { |
|||
return nil, |
|||
fmt.Errorf("%w: warn: %s, path:%s, field:%s, provider:%s", config.ErrValueNotFound, secret.Warnings, path, field, p.Name()) |
|||
} |
|||
|
|||
data, ok := secret.Data["data"].(map[string]interface{}) |
|||
if !ok { |
|||
return nil, fmt.Errorf("%w: path:%s, field:%s, provider:%s", config.ErrValueNotFound, path, field, p.Name()) |
|||
} |
|||
|
|||
if val, ok := data[field]; ok { |
|||
return value.JString(fmt.Sprint(val)), nil |
|||
} |
|||
|
|||
if val, ok := data[ValueName]; ok { |
|||
return value.JString(fmt.Sprint(val)), nil |
|||
} |
|||
|
|||
md, err := json.Marshal(data) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("%w: %w", config.ErrInvalidValue, err) |
|||
} |
|||
|
|||
return value.JBytes(md), nil |
|||
} |
@ -1,26 +0,0 @@ |
|||
package vault_test |
|||
|
|||
import ( |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/config/provider/vault" |
|||
"gitoa.ru/go-4devs/config/test" |
|||
) |
|||
|
|||
func TestProvider(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
cl, err := test.NewVault() |
|||
require.NoError(t, err) |
|||
|
|||
provider := vault.NewSecretKV2("fdevs", "config", cl) |
|||
|
|||
read := []test.Read{ |
|||
test.NewReadConfig("database"), |
|||
test.NewRead(test.DSN, "db", "dsn"), |
|||
test.NewRead(time.Minute, "db", "timeout"), |
|||
} |
|||
test.Run(t, provider, read) |
|||
} |
@ -1,89 +0,0 @@ |
|||
package test |
|||
|
|||
import ( |
|||
"bytes" |
|||
"context" |
|||
"encoding/json" |
|||
"net/http" |
|||
"os" |
|||
|
|||
"github.com/hashicorp/vault/api" |
|||
) |
|||
|
|||
const token = "dev" |
|||
|
|||
func NewVault() (*api.Client, error) { |
|||
address, ok := os.LookupEnv("VAULT_DEV_LISTEN_ADDRESS") |
|||
if !ok { |
|||
address = "http://127.0.0.1:8200" |
|||
} |
|||
|
|||
tokenID, ok := os.LookupEnv("VAULT_DEV_ROOT_TOKEN_ID") |
|||
if !ok { |
|||
tokenID = token |
|||
} |
|||
|
|||
cl, err := api.NewClient(&api.Config{ |
|||
Address: address, |
|||
}) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
cl.SetToken(tokenID) |
|||
|
|||
values := map[string]map[string]interface{}{ |
|||
"database": { |
|||
"duration": 1260000000000, |
|||
"enabled": true, |
|||
}, |
|||
"db": { |
|||
"dsn": DSN, |
|||
"timeout": "60s", |
|||
}, |
|||
"example": { |
|||
"dsn": DSN, |
|||
"timeout": "60s", |
|||
}, |
|||
} |
|||
|
|||
for name, val := range values { |
|||
if err := create(address, tokenID, name, val); err != nil { |
|||
return nil, err |
|||
} |
|||
} |
|||
|
|||
return cl, nil |
|||
} |
|||
|
|||
func create(host, token, path string, data map[string]interface{}) error { |
|||
type Req struct { |
|||
Data interface{} `json:"data"` |
|||
} |
|||
|
|||
b, err := json.Marshal(Req{Data: data}) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
body := bytes.NewBuffer(b) |
|||
|
|||
req, err := http.NewRequestWithContext( |
|||
context.Background(), |
|||
http.MethodPost, |
|||
host+"/v1/secret/data/fdevs/config/"+path, |
|||
body, |
|||
) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
req.Header.Set("X-Vault-Token", token) |
|||
|
|||
res, err := http.DefaultClient.Do(req) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
return res.Body.Close() |
|||
} |
Loading…
Reference in new issue