Skip to content

Commit

Permalink
Update documentation and add examples
Browse files Browse the repository at this point in the history
  • Loading branch information
ianlopshire committed Dec 3, 2019
1 parent 132be53 commit 33fa6f7
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 19 deletions.
105 changes: 95 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
129 changes: 129 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
}
19 changes: 12 additions & 7 deletions ssmconfig.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions ssmconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 33fa6f7

Please sign in to comment.