Skip to content

Commit

Permalink
Add parameterstore provider (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
tchssk authored Sep 12, 2023
1 parent fd02e90 commit 8a4cf2a
Show file tree
Hide file tree
Showing 7 changed files with 512 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ Install with `go get -u github.com/knadh/koanf/providers/$provider`
| appconfig | `vault.AppConfig(appconfig.Config{})` | AWS AppConfig provider |
| etcd | `etcd.Provider(etcd.Config{})` | CNCF etcd provider |
| consul | `consul.Provider(consul.Config{})` | Hashicorp Consul provider |
| parameterstore | `parameterstore.Provider(parameterstore.Config{})` | AWS Systems Manager Parameter Store provider |


### Bundled Parsers
Expand Down
2 changes: 1 addition & 1 deletion examples/read-parameterstore/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AWS Parameter Store Example

## Link
[example](https://github.com/defensestation/koanf/blob/main/examples/read-parameterstore/main.go)
[another example](https://github.com/defensestation/koanf/blob/main/examples/read-parameterstore/main.go)
67 changes: 67 additions & 0 deletions examples/read-parameterstore/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"context"
"fmt"
"log"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ssm"
"github.com/aws/aws-sdk-go-v2/service/ssm/types"
"github.com/knadh/koanf/providers/parameterstore"
"github.com/knadh/koanf/v2"
)

var k = koanf.New(".")

func main() {
// The configuration values are read from the environment variables.
c, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatal(err)
}
client := ssm.NewFromConfig(c)

for k, v := range map[string]string{
"parent1": "alice",
"parent2.child1": "bob",
"parent2.child2.grandchild1": "carol",
} {
if _, err := client.PutParameter(context.TODO(), &ssm.PutParameterInput{
Name: aws.String(k),
Value: aws.String(v),
Type: types.ParameterTypeSecureString,
Overwrite: aws.Bool(true),
}); err != nil {
log.Fatal(err)
}
}

// Get a parameter.
if err := k.Load(parameterstore.ProviderWithClient(parameterstore.Config[ssm.GetParameterInput]{
Delim: ".",
Input: ssm.GetParameterInput{Name: aws.String("parent1"), WithDecryption: aws.Bool(true)},
}, client), nil); err != nil {
log.Fatalf("error loading config: %v", err)
}
fmt.Println(k.Sprint())

// Get parameters.
if err := k.Load(parameterstore.ProviderWithClient(parameterstore.Config[ssm.GetParametersInput]{
Delim: ".",
Input: ssm.GetParametersInput{Names: []string{"parent1", "parent2.child1"}, WithDecryption: aws.Bool(true)},
}, client), nil); err != nil {
log.Fatalf("error loading config: %v", err)
}
fmt.Println(k.Sprint())

// Get parameters by path.
if err := k.Load(parameterstore.ProviderWithClient(parameterstore.Config[ssm.GetParametersByPathInput]{
Delim: ".",
Input: ssm.GetParametersByPathInput{Path: aws.String("/"), WithDecryption: aws.Bool(true)},
}, client), nil); err != nil {
log.Fatalf("error loading config: %v", err)
}
fmt.Println(k.Sprint())
}
32 changes: 32 additions & 0 deletions providers/parameterstore/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module github.com/knadh/koanf/providers/parameterstore

go 1.18

require (
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.37
github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5
github.com/aws/smithy-go v1.14.2
github.com/knadh/koanf/maps v0.1.1
github.com/knadh/koanf/v2 v2.0.1
github.com/stretchr/testify v1.8.4
)

require (
github.com/aws/aws-sdk-go-v2/credentials v1.13.35 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
56 changes: 56 additions & 0 deletions providers/parameterstore/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
github.com/aws/aws-sdk-go-v2/config v1.18.37 h1:RNAfbPqw1CstCooHaTPhScz7z1PyocQj0UL+l95CgzI=
github.com/aws/aws-sdk-go-v2/config v1.18.37/go.mod h1:8AnEFxW9/XGKCbjYDCJy7iltVNyEI9Iu9qC21UzhhgQ=
github.com/aws/aws-sdk-go-v2/credentials v1.13.35 h1:QpsNitYJu0GgvMBLUIYu9H4yryA5kMksjeIVQfgXrt8=
github.com/aws/aws-sdk-go-v2/credentials v1.13.35/go.mod h1:o7rCaLtvK0hUggAGclf76mNGGkaG5a9KWlp+d9IpcV8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 h1:GPUcE/Yq7Ur8YSUk6lVkoIMWnJNO0HT18GUzCWCgCI0=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5 h1:s9QR0F1W5+11lq04OJ/mihpRpA2VDFIHmu+ktgAbNfg=
github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5/go.mod h1:JjBzoceyKkpQY3v1GPIdg6kHqUFHRJ7SDlwtwoH0Qh8=
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 h1:oCvTFSDi67AX0pOX3PuPdGFewvLRU2zzFSrTsgURNo0=
github.com/aws/aws-sdk-go-v2/service/sso v1.13.5/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 h1:dnInJb4S0oy8aQuri1mV6ipLlnZPfnsDNB9BGO9PDNY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
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/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
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/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
145 changes: 145 additions & 0 deletions providers/parameterstore/parameterstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Package parameterstore implements a koanf.Provider for AWS Systems Manager Parameter Store.
package parameterstore

import (
"context"
"errors"

awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ssm"
"github.com/knadh/koanf/maps"
)

// Input is a constraint that permits any input type
// to get paramers from AWS Systems Manager Parameter Store.
type Input interface {
ssm.GetParameterInput | ssm.GetParametersInput | ssm.GetParametersByPathInput
}

// Config represents a ParameterStore provider configuration.
type Config[T Input] struct {
// Delim is the delimiter to use
// when specifying config key paths, for instance a . for `parent.child.key`
// or a / for `parent/child/key`.
Delim string

// Input is the input to get parameters.
Input T

// OptFns is the additional functional options to get parameters.
OptFns []func(*ssm.Options)

// Callback is an optional callback that takes a (key, value)
// with the variable name and value and allows you to modify both.
// If the callback returns an empty key, the variable will be ignored.
Callback func(key, value string) (string, interface{})
}

// ParameterStore implements an AWS Systems Manager Parameter Store provider.
type ParameterStore[T Input] struct {
client *ssm.Client
config Config[T]
}

// Provider returns a ParameterStore provider.
// The AWS Systems Manager Client is configured via environment variables.
// The configuration values are read from the environment variables.
// - AWS_REGION
// - AWS_ACCESS_KEY_ID
// - AWS_SECRET_ACCESS_KEY
// - AWS_SESSION_TOKEN
func Provider[T Input](config Config[T]) *ParameterStore[T] {
c, err := awsconfig.LoadDefaultConfig(context.TODO())
if err != nil {
return nil
}
return ProviderWithClient[T](config, ssm.NewFromConfig(c))
}

// ProviderWithClient returns a ParameterStore provider
// using an existing AWS Systems Manager client.
func ProviderWithClient[T Input](config Config[T], client *ssm.Client) *ParameterStore[T] {
return &ParameterStore[T]{
client: client,
config: config,
}
}

// ReadBytes is not supported by the ParameterStore provider.
func (ps *ParameterStore[T]) ReadBytes() ([]byte, error) {
return nil, errors.New("parameterstore provider does not support this method")
}

// Read returns a nested config map.
func (ps *ParameterStore[T]) Read() (map[string]interface{}, error) {
var (
mp = make(map[string]interface{})
)
switch input := interface{}(ps.config.Input).(type) {
case ssm.GetParameterInput:
output, err := ps.client.GetParameter(context.TODO(), &input, ps.config.OptFns...)
if err != nil {
return nil, err
}
// If there's a transformation callback, run it.
if ps.config.Callback != nil {
name, value := ps.config.Callback(*output.Parameter.Name, *output.Parameter.Value)
// If the callback blanked the key, it should be omitted.
if name == "" {
break
}
mp[name] = value
} else {
mp[*output.Parameter.Name] = *output.Parameter.Value
}
case ssm.GetParametersInput:
output, err := ps.client.GetParameters(context.TODO(), &input, ps.config.OptFns...)
if err != nil {
return nil, err
}
for _, p := range output.Parameters {
// If there's a transformation callback, run it.
if ps.config.Callback != nil {
name, value := ps.config.Callback(*p.Name, *p.Value)
// If the callback blanked the key, it should be omitted.
if name == "" {
break
}
mp[name] = value
} else {
mp[*p.Name] = *p.Value
}
}
case ssm.GetParametersByPathInput:
var nextToken *string
for {
input.NextToken = nextToken
output, err := ps.client.GetParametersByPath(context.TODO(), &input, ps.config.OptFns...)
if err != nil {
return nil, err
}
for _, p := range output.Parameters {
// If there's a transformation callback, run it.
if ps.config.Callback != nil {
name, value := ps.config.Callback(*p.Name, *p.Value)
// If the callback blanked the key, it should be omitted.
if name == "" {
break
}
mp[name] = value
} else {
mp[*p.Name] = *p.Value
}
}
if output.NextToken == nil {
break
}
nextToken = output.NextToken
}
}
// Unflatten only when a delimiter is specified.
if ps.config.Delim != "" {
mp = maps.Unflatten(mp, ps.config.Delim)
}
return mp, nil
}
Loading

0 comments on commit 8a4cf2a

Please sign in to comment.