From 80b0244b52886b37c0997935f4db2e3af721de81 Mon Sep 17 00:00:00 2001 From: andrey Date: Thu, 25 Jan 2024 22:51:31 +0300 Subject: [PATCH] add provider vault --- .drone.yml | 34 ++++++- provider/vault/go.mod | 29 ++++++ provider/vault/go.sum | 92 ++++++++++++++++++ provider/vault/provider.go | 120 ++++++++++++++++++++++++ provider/vault/provider_example_test.go | 47 ++++++++++ provider/vault/provider_test.go | 26 +++++ provider/vault/vault_test.go | 90 ++++++++++++++++++ 7 files changed, 434 insertions(+), 4 deletions(-) create mode 100644 provider/vault/go.mod create mode 100644 provider/vault/go.sum create mode 100644 provider/vault/provider.go create mode 100644 provider/vault/provider_example_test.go create mode 100644 provider/vault/provider_test.go create mode 100644 provider/vault/vault_test.go diff --git a/.drone.yml b/.drone.yml index 6baf7bc..103e426 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,10 +2,6 @@ kind: pipeline name: default -environment: - VAULT_DEV_LISTEN_ADDRESS: http://vault:8200 - VAULT_DEV_ROOT_TOKEN_ID: dev - steps: - name: test image: golang @@ -119,3 +115,33 @@ steps: - cd provider/etcd - golangci-lint run +--- +kind: pipeline +type: docker +name: vault + +environment: + VAULT_DEV_LISTEN_ADDRESS: http://vault:8200 + VAULT_DEV_ROOT_TOKEN_ID: dev + +services: + - name: vault + image: vault:1.13.3 + environment: + VAULT_DEV_ROOT_TOKEN_ID: dev + VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 + +steps: +- name: test + image: golang + failure: ignore # runtime/cgo: pthread_create failed: Operation not permitted + commands: + - cd provider/vault + - go test ./... + +- name: golangci-lint + image: golangci/golangci-lint:v1.55 + commands: + - cd provider/vault + - golangci-lint run + diff --git a/provider/vault/go.mod b/provider/vault/go.mod new file mode 100644 index 0000000..9fa5ed2 --- /dev/null +++ b/provider/vault/go.mod @@ -0,0 +1,29 @@ +module gitoa.ru/go-4devs/config/provider/vault + +go 1.21 + +require ( + github.com/hashicorp/vault/api v1.11.0 + gitoa.ru/go-4devs/config v0.0.2 +) + +require ( + github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect +) diff --git a/provider/vault/go.sum b/provider/vault/go.sum new file mode 100644 index 0000000..1fb96dd --- /dev/null +++ b/provider/vault/go.sum @@ -0,0 +1,92 @@ +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +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/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +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-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +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/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.11.0 h1:AChWByeHf4/P9sX3Y1B7vFsQhZO2BgQiCMQ2SA1P1UY= +github.com/hashicorp/vault/api v1.11.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +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-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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= +gitoa.ru/go-4devs/config v0.0.2 h1:bkTxW57kDDMf4cj/8W7fxPSN7JCPWEqlhCmL6LP3Vzg= +gitoa.ru/go-4devs/config v0.0.2/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/provider/vault/provider.go b/provider/vault/provider.go new file mode 100644 index 0000000..d76da80 --- /dev/null +++ b/provider/vault/provider.go @@ -0,0 +1,120 @@ +package vault + +import ( + "context" + "encoding/json" + "fmt" + "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 = (*Provider)(nil) + +type SecretOption func(*Provider) + +func WithSecretResolve(f func(key []string) (string, string)) SecretOption { + return func(s *Provider) { s.resolve = f } +} + +func New(namespace, appName string, client *api.Client, opts ...SecretOption) *Provider { + prov := Provider{ + 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 Provider struct { + client *api.Client + resolve func(key []string) (string, string) + name string + prefix string +} + +func (p *Provider) Name() string { + return p.name +} + +func (p *Provider) Key(in []string) (string, string) { + path, val := p.resolve(in) + if path == "" { + return p.prefix, val + } + + return p.prefix + Separator + path, val +} + +func (p *Provider) read(path, key string) (*api.Secret, error) { + secret, err := p.client.Logical().Read(path) + if err != nil { + return nil, fmt.Errorf("read[%s:%s]:%w", path, key, err) + } + + if secret == nil && key != ValueName { + return p.read(path+Separator+key, ValueName) + } + + return secret, nil +} + +func (p *Provider) Value(_ 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 { + 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 +} diff --git a/provider/vault/provider_example_test.go b/provider/vault/provider_example_test.go new file mode 100644 index 0000000..abd5146 --- /dev/null +++ b/provider/vault/provider_example_test.go @@ -0,0 +1,47 @@ +package vault_test + +import ( + "context" + "fmt" + "log" + + "gitoa.ru/go-4devs/config" + "gitoa.ru/go-4devs/config/provider/vault" +) + +func ExampleClient_Value() { + const ( + namespace = "fdevs" + appName = "config" + ) + + ctx := context.Background() + + // configure vault client + vaultClient, err := NewVault() + if err != nil { + log.Print(err) + + return + } + + config, err := config.New( + vault.New(namespace, appName, vaultClient), + ) + if err != nil { + log.Print(err) + + return + } + + dsn, err := config.Value(ctx, "example", "dsn") + if err != nil { + log.Print("example:dsn ", err) + + return + } + + fmt.Printf("dsn from vault: %s\n", dsn.String()) + // Output: + // dsn from vault: pgsql://user@pass:127.0.0.1:5432 +} diff --git a/provider/vault/provider_test.go b/provider/vault/provider_test.go new file mode 100644 index 0000000..007aaaf --- /dev/null +++ b/provider/vault/provider_test.go @@ -0,0 +1,26 @@ +package vault_test + +import ( + "testing" + "time" + + "gitoa.ru/go-4devs/config/provider/vault" + "gitoa.ru/go-4devs/config/test" + "gitoa.ru/go-4devs/config/test/require" +) + +func TestProvider(t *testing.T) { + t.Parallel() + + cl, err := NewVault() + require.NoError(t, err) + + provider := vault.New("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) +} diff --git a/provider/vault/vault_test.go b/provider/vault/vault_test.go new file mode 100644 index 0000000..fd78532 --- /dev/null +++ b/provider/vault/vault_test.go @@ -0,0 +1,90 @@ +package vault_test + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "os" + + "github.com/hashicorp/vault/api" + "gitoa.ru/go-4devs/config/test" +) + +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": test.DSN, + "timeout": "60s", + }, + "example": { + "dsn": test.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() +}