diff --git a/README.md b/README.md index b7ce660..3cbf01b 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,108 @@ `import "github.com/ianlopshire/go-ssm-config"` -SSM Config is a utility for loading configuration values from AWS SSM (Parameter Store). +SSMConfig is a utility for loading configuration parameters from AWS SSM (Parameter Store) directly into a struct. This +package is largely inspired by [kelseyhightower/envconfig](https://github.com/kelseyhightower/envconfig). + +## Motivation + +This package was created to reduce the boilerplate code required when using Parameter Store to provide configuration to +AWS Lambda functions. It should be suitable for additional applications. ## Usage -SSM Config can be used to load configuration values directly into a struct. +Set some parameters in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html): -The path for each struct field is controlled by the `ssm` tag. If the `ssm` tag is omitted or empty it will be ignored. The field is set to the vale of the `default` tag if the path is invalid. If the `required` flag is set to `true` and the path is invalid, an error will be returned. +| Name | Value | Type | Key ID | +| ---------------------------- | -------------------- | ------------ | ------------- | +| /exmaple_service/prod/debug | false | String | - | +| /exmaple_service/prod/port | 8080 | String | - | +| /exmaple_service/prod/user | Ian | String | - | +| /exmaple_service/prod/rate | 0.5 | String | - | +| /exmaple_service/prod/secret | zOcZkAGB6aEjN7SAoVBT | SecureString | alias/aws/ssm | + +Write some code: ```go -var config struct { - Value1 string `ssm:"/path/to/value_1"` - Value2 int `ssm:"/path/to/value_2" default:"88"` - Value3 string `ssm:"/path/to/value_3" required:"true"` +package main + +import ( + "fmt" + "log" + "time" + + ssmconfig "github.com/ianlopshire/go-ssm-config" +) + +type Config struct { + Debug bool `smm:"debug" default:"true"` + Port int `smm:"port"` + User string `smm:"user"` + Rate float32 `smm:"rate"` + Secret string `smm:"secret" required:"true"` +} + +func main() { + var c Config + err := ssmconfig.Process("/example_service/prod/", &c) + if err != nil { + log.Fatal(err.Error()) + } + + format := "Debug: %v\nPort: %d\nUser: %s\nRate: %f\nSecret: %s\n" + _, err = fmt.Printf(format, c.Debug, c.Port, c.User, c.Rate, c.Secret) + if err != nil { + log.Fatal(err.Error()) + } } +``` + +Result: + +``` +Debug: false +Port: 8080 +User: Ian +Rate: 0.500000 +Secret: zOcZkAGB6aEjN7SAoVBT +``` + +[Additional examples](https://godoc.org/github.com/ianlopshire/go-ssm-config#pkg-examples) can be found in godoc. -err := ssmconfig.Process("/base_path/", &config) -if err != nil { - log.Fatal(err) +### Struct Tag Support + +ssmconfig supports the use of struct tags to specify parameter name, default value, and required parameters. + +```go +type Config struct { + Param string `ssm:"param"` + RequiredParam string `ssm:"required_param" required:"true"` + DefaultParam string `ssm:"default_param" default:"foobar"` } ``` + +The `ssm` tag is used to lookup the parameter in Parameter Store. It is joined to the base path passed into `Process()`. +If the `ssm` tag is missing ssmconfig will ignore the struct field. + +The `default` tag is used to set the default value of a parameter. The default value will only be set if Parameter Store +returns the parameter as invalid. + +The `required` tag is used to mark a parameter as required. If Parameter Store returns a required parameter as invalid, +ssmconfig will return an error. + +The behavior of using the `default` and `required` tags on the same struct field is currently undefined. + +### Supported Struct Field Types + +ssmconfig supports these struct field types: + +* string +* int, int8, int16, int32, int64 +* bool +* float32, float64 + +More supported types may be added in the future. + +## Licence + +MIT \ No newline at end of file diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..2635531 --- /dev/null +++ b/example_test.go @@ -0,0 +1,129 @@ +package ssmconfig_test + +import ( + "fmt" + "log" + "os" + + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ssm" + ssmconfig "github.com/ianlopshire/go-ssm-config" +) + +func ExampleProcess() { + // Assuming the following Parameter Store parameters: + // + // | Name | Value | Type | Key ID | + // | ---------------------------- | -------------------- | ------------ | ------------- | + // | /example_service/prod/debug | false | String | - | + // | /example_service/prod/port | 8080 | String | - | + // | /example_service/prod/user | Ian | String | - | + // | /example_service/prod/rate | 0.5 | String | - | + // | /example_service/prod/secret | zOcZkAGB6aEjN7SAoVBT | SecureString | alias/aws/ssm | + + type Config struct { + Debug bool `smm:"debug" default:"true"` + Port int `smm:"port"` + User string `smm:"user"` + Rate float32 `smm:"rate"` + Secret string `smm:"secret" required:"true"` + } + + var c Config + err := ssmconfig.Process("/example_service/prod/", &c) + if err != nil { + log.Fatal(err.Error()) + } + + format := "Debug: %v\nPort: %d\nUser: %s\nRate: %f\nSecret: %s\n" + _, err = fmt.Printf(format, c.Debug, c.Port, c.User, c.Rate, c.Secret) + if err != nil { + log.Fatal(err.Error()) + } +} + +// ExampleProcess_multipleEnvironments demonstrates how ssmcofig can be used to configure +// multiple environments. +func ExampleProcess_multipleEnvironments() { + // Assuming the following Parameter Store parameters: + // + // | Name | Value | Type | Key ID | + // | ---------------------------- | -------------------- | ------------ | ------------- | + // | /example_service/prod/debug | false | String | - | + // | /example_service/prod/port | 8080 | String | - | + // | /example_service/prod/user | Ian | String | - | + // | /example_service/prod/rate | 0.5 | String | - | + // | /example_service/prod/secret | zOcZkAGB6aEjN7SAoVBT | SecureString | alias/aws/ssm | + // | ---------------------------- | -------------------- | ------------ | ------------- | + // | /example_service/test/debug | true | String | - | + // | /example_service/test/port | 8080 | String | - | + // | /example_service/test/user | Ian | String | - | + // | /example_service/test/rate | 0.9 | String | - | + // | /example_service/test/secret | TBVoAS7NjEa6BGAkZcOz | SecureString | alias/aws/ssm | + + type Config struct { + Debug bool `smm:"debug" default:"true"` + Port int `smm:"port"` + User string `smm:"user"` + Rate float32 `smm:"rate"` + Secret string `smm:"secret" required:"true"` + } + + // An environment variable is used to set the config path. In this example it would be + // set to `/example_service/test/` for test and `/example_service/prod/` for + // production. + configPath := os.Getenv("CONFIG_PATH") + + var c Config + err := ssmconfig.Process(configPath, &c) + if err != nil { + log.Fatal(err.Error()) + } + + format := "Debug: %v\nPort: %d\nUser: %s\nRate: %f\nSecret: %s\n" + _, err = fmt.Printf(format, c.Debug, c.Port, c.User, c.Rate, c.Secret) + if err != nil { + log.Fatal(err.Error()) + } +} + +func ExampleProvider_Process() { + // Assuming the following Parameter Store parameters: + // + // | Name | Value | Type | Key ID | + // | ---------------------------- | -------------------- | ------------ | ------------- | + // | /example_service/prod/debug | false | String | - | + // | /example_service/prod/port | 8080 | String | - | + // | /example_service/prod/user | Ian | String | - | + // | /example_service/prod/rate | 0.5 | String | - | + // | /example_service/prod/secret | zOcZkAGB6aEjN7SAoVBT | SecureString | alias/aws/ssm | + + type Config struct { + Debug bool `smm:"debug" default:"true"` + Port int `smm:"port"` + User string `smm:"user"` + Rate float32 `smm:"rate"` + Secret string `smm:"secret" required:"true"` + } + + sess, err := session.NewSession() + if err != nil { + log.Fatal(err) + } + + provider := &ssmconfig.Provider{ + SSM: ssm.New(sess), + } + + var c Config + err = provider.Process("/example_service/prod/", &c) + if err != nil { + log.Fatal(err.Error()) + } + + format := "Debug: %v\nPort: %d\nUser: %s\nRate: %f\nSecret: %s\n" + _, err = fmt.Printf(format, c.Debug, c.Port, c.User, c.Rate, c.Secret) + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/ssmconfig.go b/ssmconfig.go index f9d034a..5c1e22a 100755 --- a/ssmconfig.go +++ b/ssmconfig.go @@ -1,3 +1,5 @@ +// Package ssmconfig is a utility for loading configuration values from AWS SSM (Parameter +// Store) directly into a struct. package ssmconfig import ( @@ -31,17 +33,20 @@ type Provider struct { SSM ssmiface.SSMAPI } -// Process loads config values from smm (parameter store) into c. +// Process loads config values from smm (parameter store) into c. Encrypted parameters +// will automatically be decrypted. c must be a pointer to a struct. // -// The path for each struct field is controlled by the `ssm` tag. If the `ssm` tag is -// omitted or empty it will be ignored. +// The `ssm` tag is used to lookup the parameter in Parameter Store. It is joined to the +// provided base path. If the `ssm` tag is missing the struct field will be ignored. // -// The field is set to the vale of the `default` tag if the path is invalid. +// The `default` tag is used to set the default value of a parameter. The default value +// will only be set if Parameter Store returns the parameter as invalid. // -// If the `required` flag is set to `true` and the path is invalid, an error will be -// returned. +// The `required` tag is used to mark a parameter as required. If Parameter Store returns +// a required parameter as invalid an error will be returned. // -// c must be a pointer to a struct. +// The behavior of using the `default` and `required` tags on the same struct field is +// currently undefined. func (p *Provider) Process(configPath string, c interface{}) error { v := reflect.ValueOf(c) diff --git a/ssmconfig_test.go b/ssmconfig_test.go index 138edcb..b09adba 100755 --- a/ssmconfig_test.go +++ b/ssmconfig_test.go @@ -5,11 +5,10 @@ import ( "reflect" "testing" - ssmconfig "github.com/ianlopshire/go-ssm-config" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/aws-sdk-go/service/ssm/ssmiface" + ssmconfig "github.com/ianlopshire/go-ssm-config" ) type mockSSMClient struct {