Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: knadh/koanf
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.2.4
Choose a base ref
...
head repository: knadh/koanf
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.3.0
Choose a head ref
  • 3 commits
  • 5 files changed
  • 2 contributors

Commits on Oct 18, 2021

  1. feat: add aws appconfig provider

    * fix: pass ClientConfigurationVersion only when defined
    * feat: implement Watch() for appconfig provider
    * chore: update documentation for ClientConfigurationVersion
    * chore: add example for appconfig provider
    * chore: make review changes
    * fix: do not convert WatchInterval to seconds
    
    Signed-off-by: Chinmay D. Pai <chinmay.pai@zerodha.com>
    Thunderbottom committed Oct 18, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    43400ca View commit details
  2. Merge pull request #112 from Thunderbottom/feat/aws-appconfig

    feat: add provider for AWS AppConfig
    knadh authored Oct 18, 2021
    Copy the full SHA
    421bf3a View commit details
  3. Copy the full SHA
    66d5d02 View commit details
Showing with 232 additions and 2 deletions.
  1. +2 −0 README.md
  2. +44 −0 examples/read-appconfig/main.go
  3. +5 −0 go.mod
  4. +27 −2 go.sum
  5. +154 −0 providers/appconfig/appconfig.go
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -615,6 +615,7 @@ Writing Providers and Parsers are easy. See the bundled implementations in the `
| providers/s3 | `s3.Provider(s3.S3Config{})` | Takes a s3 config struct. |
| providers/rawbytes | `rawbytes.Provider(b []byte)` | Takes a raw `[]byte` slice to be parsed with a koanf.Parser |
| providers/vault | `vault.Provider(vault.Config{})` | Hashicorp Vault provider |
| providers/appconfig | `vault.AppConfig(appconfig.Config{})` | AWS AppConfig provider |

### Bundled parsers

@@ -625,6 +626,7 @@ Writing Providers and Parsers are easy. See the bundled implementations in the `
| parsers/toml | `toml.Parser()` | Parses TOML bytes into a nested map |
| parsers/dotenv | `dotenv.Parser()` | Parses DotEnv bytes into a flat map |
| parsers/hcl | `hcl.Parser(flattenSlices bool)` | Parses Hashicorp HCL bytes into a nested map. `flattenSlices` is recommended to be set to true. [Read more](https://github.com/hashicorp/hcl/issues/162). |

### Instance functions

| Method | Description |
44 changes: 44 additions & 0 deletions examples/read-appconfig/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"log"
"os"

"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/appconfig"
)

// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")

func main() {
provider := appconfig.Provider(appconfig.Config{
Application: os.Getenv("AWS_APPCONFIG_APPLICATION"),
ClientID: os.Getenv("AWS_APPCONFIG_CLIENT_ID"),
Configuration: os.Getenv("AWS_APPCONFIG_CONFIG_NAME"),
Environment: os.Getenv("AWS_APPCONFIG_ENVIRONMENT"),
})
// Load the provider and parse configuration as JSON.
if err := k.Load(provider, json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}

k.Print()

// Watch for all configuration updates.
provider.Watch(func(event interface{}, err error) {
if err != nil {
log.Printf("watch error: %v", err)
return
}

log.Println("config changed. Reloading ...")
k = koanf.New(".")
k.Load(provider, json.Parser())
k.Print()
})

log.Println("waiting forever.")
<-make(chan bool)
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -3,6 +3,11 @@ module github.com/knadh/koanf
go 1.12

require (
github.com/aws/aws-sdk-go-v2 v1.9.2
github.com/aws/aws-sdk-go-v2/config v1.8.3
github.com/aws/aws-sdk-go-v2/credentials v1.4.3
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2
github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.4.9
github.com/hashicorp/hcl v1.0.0
29 changes: 27 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -3,6 +3,26 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.9.2 h1:dUFQcMNZMLON4BOe273pl0filK9RqyQMhCK/6xssL6s=
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2/config v1.8.3 h1:o5583X4qUfuRrOGOgmOcDgvr5gJVSu57NK08cWAhIDk=
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
github.com/aws/aws-sdk-go-v2/credentials v1.4.3 h1:LTdD5QhK073MpElh9umLLP97wxphkgVC/OjQaEbBwZA=
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0 h1:9tfxW/icbSu98C2pcNynm5jmDwU3/741F11688B6QnU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4 h1:leSJ6vCqtPpTmBIgE7044B1wql1E4n//McF+mEgNrYg=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2 h1:5fez51yE//mtmaEkh9JTAcLl4xg60Ha86pE+FIqinGc=
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2 h1:r7jel2aa4d9Duys7wEmWqDd5ebpC9w6Kxu6wIjjp18E=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2 h1:pZwkxZbspdqRGzddDB92bkZBoB7lg85sMRE7OqdB3V0=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2 h1:ol2Y5DWqnJeKqNd8th7JWzBtqu63xpOfs1Is+n1t8/4=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -22,6 +42,9 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -52,6 +75,8 @@ github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -87,10 +112,8 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -131,6 +154,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
154 changes: 154 additions & 0 deletions providers/appconfig/appconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Package appconfig implements a koanf.Provider for AWS AppConfig
// and provides it to koanf to be parsed by a koanf.Parser.
package appconfig

import (
"context"
"errors"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/appconfig"
"github.com/aws/aws-sdk-go-v2/service/sts"
)

// Config holds the AWS AppConfig Configuration.
type Config struct {
// The AWS AppConfig Application to get. Specify either the application
// name or the application ID.
Application string

// The Client ID for the AppConfig. Enables AppConfig to deploy the
// configuration in intervals, as defined in the deployment strategy.
ClientID string

// The AppConfig configuration to fetch. Specify either the configuration
// name or the configuration ID.
Configuration string

// The AppConfig environment to get. Specify either the environment
// name or the environment ID.
Environment string

// The AppConfig Configuration Version to fetch. Specifying a ClientConfigurationVersion
// ensures that the configuration is only fetched if it is updated. If not specified,
// the latest available configuration is fetched always.
// Setting this to the latest configuration version will return an empty slice of bytes.
ClientConfigurationVersion string

// The AWS Access Key ID to use. This value is fetched from the environment
// if not specified.
AWSAccessKeyID string

// The AWS Secret Access Key to use. This value is fetched from the environment
// if not specified.
AWSSecretAccessKey string

// The AWS IAM Role ARN to use. Useful for access requiring IAM AssumeRole.
AWSRoleARN string

// The AWS Region to use. This value is fetched from teh environment if not specified.
AWSRegion string

// Time interval at which the watcher will refresh the configuration.
// Defaults to 60 seconds.
WatchInterval time.Duration
}

// AppConfig implements an AWS AppConfig provider.
type AppConfig struct {
client *appconfig.Client
config Config
input appconfig.GetConfigurationInput
}

// Provider returns an AWS AppConfig provider.
func Provider(cfg Config) *AppConfig {
c, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return nil
}

if cfg.AWSRegion != "" {
c.Region = cfg.AWSRegion
}

// Check if AWS Access Key ID and Secret Key are specified.
if cfg.AWSAccessKeyID != "" && cfg.AWSSecretAccessKey != "" {
c.Credentials = credentials.NewStaticCredentialsProvider(cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, "")
}

// Check if AWS Role ARN is present.
if cfg.AWSRoleARN != "" {
stsSvc := sts.NewFromConfig(c)
credentials := stscreds.NewAssumeRoleProvider(stsSvc, cfg.AWSRoleARN)
c.Credentials = aws.NewCredentialsCache(credentials)
}
client := appconfig.NewFromConfig(c)

return &AppConfig{client: client, config: cfg}
}

// ReadBytes returns the raw bytes for parsing.
func (ac *AppConfig) ReadBytes() ([]byte, error) {
ac.input = appconfig.GetConfigurationInput{
Application: &ac.config.Application,
ClientId: &ac.config.ClientID,
Configuration: &ac.config.Configuration,
Environment: &ac.config.Environment,
}
if ac.config.ClientConfigurationVersion != "" {
ac.input.ClientConfigurationVersion = &ac.config.ClientConfigurationVersion
}

conf, err := ac.client.GetConfiguration(context.TODO(), &ac.input)
if err != nil {
return nil, err
}

// Set the response configuration version as the current configuration version.
// Useful for Watch().
ac.input.ClientConfigurationVersion = conf.ConfigurationVersion

return conf.Content, nil
}

// Read is not supported by the appconfig provider.
func (ac *AppConfig) Read() (map[string]interface{}, error) {
return nil, errors.New("appconfig provider does not support this method")
}

// Watch polls AWS AppConfig for configuration updates.
func (ac *AppConfig) Watch(cb func(event interface{}, err error)) error {
if ac.config.WatchInterval == 0 {
// Set default watch interval to 60 seconds.
ac.config.WatchInterval = 60 * time.Second
}

go func() {
loop:
for {
conf, err := ac.client.GetConfiguration(context.TODO(), &ac.input)
if err != nil {
cb(nil, err)
break loop
}

// Check if the the configuration has been updated.
if len(conf.Content) == 0 {
// Configuration is not updated and we have the latest version.
// Sleep for WatchInterval and retry watcher.
time.Sleep(ac.config.WatchInterval)
continue
}

// Trigger event.
cb(nil, nil)
}
}()

return nil
}