From cd9348825c2a2929a197095629b9ccde385a92e3 Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Sun, 15 Dec 2024 20:56:16 +0100 Subject: [PATCH 1/8] WIP: Azure app config parser --- README.md | 49 ++++++--- go.mod | 25 ++++- go.sum | 61 +++++++++-- options.go | 12 +++ options_test.go | 8 ++ parsers/azure-app-config.go | 157 +++++++++++++++++++++++++++ parsers/azure-app-config_test.go | 179 +++++++++++++++++++++++++++++++ 7 files changed, 469 insertions(+), 22 deletions(-) create mode 100644 parsers/azure-app-config.go create mode 100644 parsers/azure-app-config_test.go diff --git a/README.md b/README.md index f2df119..dd7fd47 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Binder is a configuration reader that parses different types of configurations and adds the possibility to bind them to one or many typed instances. -It can read configuration values from files, environment variables, `flag` flags, `spf13/pflags` flags, remote URLs, Kubernetes volumes, and is flexible enough to enable custom configuration parsers. Binder is also able to listen for file changes/volume changes, and re-bind configurations when a backing file or backing volume has been updated. +It can read configuration values from files, environment variables, `flag` flags, `spf13/pflags` flags, remote URLs, Kubernetes volumes, Azure App Configs, and is flexible enough to enable custom configuration parsers. Binder is also able to listen for file changes/volume changes, and re-bind configurations when a backing file or backing volume has been updated. Example: ```go @@ -71,24 +71,49 @@ package main var ( theCommand = &cobra.Command{ Use: "cmd", - RunE: func(cmd *cobra.Command, args []string) error { - bnd := binder.New( - binder.WithFlagSet(cmd.Flags())) + RunE: func(cmd *cobra.Command, args []string) error { + bnd := binder.New( + binder.WithFlagSet(cmd.Flags())) - cmd.ParseFlags(args) + cmd.ParseFlags(args) - var cfg Config - bnd.Bind(&cfg) + var cfg Config + bnd.Bind(&cfg) - fmt.Printf("Key: %s\n", cfg.Key) + fmt.Printf("Key: %s\n", cfg.Key) - return nil - } - } + return nil + } + } ) func init() { - theCommand.Flags().String("key", "", "the key to use") + theCommand.Flags().String("key", "", "the key to use") +} + +``` + +Azure App Config parser, usable for Azure App Configs: +```go +package main + +import "github.com/ourstudio-se/binder" + +type Config struct { + KeyOne string `config:"external_key_one"` + KeyTwo string `config:"external_key_two"` +} + +func main() { + bnd := binder.New( + binder.WithAzureConfig("https://appconfig-name.azconfig.io", ["tenant-id-for-key-vault"])) + defer bnd.Close() + + var cfg Config + bnd.Bind(&cfg); + + fmt.Printf("KeyOne: %s\n", cfg.KeyOne) + fmt.Printf("KeyTwo: %d\n", cfg.KeyTwo) } ``` diff --git a/go.mod b/go.mod index 9906d97..37c1d84 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,30 @@ module github.com/ourstudio-se/binder -go 1.14 +go 1.23 require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 + github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 github.com/radovskyb/watcher v1.0.7 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 959b2b1..779a421 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,64 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= +github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.1.0 h1:AdaGDU3FgoUC2tsd3vsd9JblRrpFLUsS38yh1eLYfwM= +github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.1.0/go.mod h1:6tpINME7dnF7bLlb8Ubj6FtM9CFZrCn7aT02pcYrklM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0/go.mod h1:hd8hTTIY3VmUVPRHNH7GVCHO3SHgXkJKZHReby/bnUQ= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1 h1:gUDtaZk8heteyfdmv+pcfHvhR9llnh7c7GMwZ8RVG04= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 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/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 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/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/options.go b/options.go index f9433a0..65be758 100644 --- a/options.go +++ b/options.go @@ -72,6 +72,18 @@ func WithValue(key string, value interface{}) Option { return WithParser(parsers.NewKeyValueParser(r, parsers.WithKeyValueSeparator("="))) } +// WithAzureConfig is an Option to instantiate +// a parser which reads an App Config in Azure +// when instantiating a Config. +func WithAzureConfig(appConfig string, additionallyAllowTenants []string) Option { + azureConfigParser, err := parsers.NewAzureConfigParser(appConfig, additionallyAllowTenants) + if err != nil { + return nil + } + + return WithParser(azureConfigParser) +} + // WithWatch adds a file path watch, which can be // used to reload configuration values that originates // from a FileParser or a KubernetesVolumeParser when diff --git a/options_test.go b/options_test.go index a9f2d02..075bd74 100644 --- a/options_test.go +++ b/options_test.go @@ -66,3 +66,11 @@ func Test_WithWatch(t *testing.T) { assert.NotNil(t, c.watch) } + +func Test_WithAzureConfig(t *testing.T) { + _, err := parsers.NewAzureConfigParser("", nil) + c := New(WithAzureConfig("", nil)) + + assert.NoError(t, err) + assert.Len(t, c.parsers, 1) +} diff --git a/parsers/azure-app-config.go b/parsers/azure-app-config.go new file mode 100644 index 0000000..5dac187 --- /dev/null +++ b/parsers/azure-app-config.go @@ -0,0 +1,157 @@ +package parsers + +import ( + "context" + "encoding/json" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" + "strings" +) + +const ( + keyVaultRef = "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8" + secretLengthWithVersion = 3 +) + +type AppConfigPager interface { + More() bool + NextPage(ctx context.Context) (azappconfig.ListSettingsPageResponse, error) +} + +type KeyVaultClient interface { + GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) +} + +// AzureConfigParser is a configuration parser +// which reads configuration-values from an Azure AppConfig and the backing KeyVault if config values is +// key vault reference. +type AzureConfigParser struct { + settingsPager AppConfigPager + secretClientFactory func(url string) (KeyVaultClient, error) +} + +// NewAzureConfigParser returns a new AzureConfigParser. +// A AzureConfigParser reads an Azure AppConfig store and returns a map +// with key/value pairs. +func NewAzureConfigParser(appConfig string, additionallyAllowedTenants []string) (*AzureConfigParser, error) { + cred, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ + AdditionallyAllowedTenants: additionallyAllowedTenants, + }) + if err != nil { + return nil, err + } + + client, err := azappconfig.NewClient(appConfig, cred, nil) + if err != nil { + return nil, err + } + + pager := client.NewListSettingsPager(azappconfig.SettingSelector{}, nil) + + keyVaultClientFactoryFunc := func(url string) (KeyVaultClient, error) { + kvClient, err := azsecrets.NewClient(url, cred, nil) + if err != nil { + return nil, err + } + return kvClient, nil + } + + return &AzureConfigParser{pager, keyVaultClientFactoryFunc}, nil +} + +// Parse returns the key/value pairs as a map[string]interface{}. +// TODO: Parse with context as parameter. +func (p *AzureConfigParser) Parse() (map[string]interface{}, error) { + settings := make(map[string]interface{}) + + for p.settingsPager.More() { + snapshotPage, err := p.settingsPager.NextPage(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get next page of pager %v", err) + } + + for _, setting := range snapshotPage.Settings { + value := "" + if setting.ContentType != nil && strings.EqualFold(*setting.ContentType, keyVaultRef) { + secret, err := getSecret(context.Background(), p.secretClientFactory, *setting.Value) + if err != nil { + return nil, fmt.Errorf("failed to get secret value: %v", err) + } + + value = secret + } else { + value = *setting.Value + } + + settings[*setting.Key] = value + } + } + + return settings, nil +} + +type KeyVaultSecretRequestObject struct { + vaultURL string + secretName string + secretVersion string +} + +func getSecret(ctx context.Context, secretClientFactory func(url string) (KeyVaultClient, error), keyVaultReference string) (string, error) { + var kvRef struct { + URI string `json:"uri"` + } + if err := json.Unmarshal([]byte(keyVaultReference), &kvRef); err != nil { + return "", fmt.Errorf("failed to parse config value %s value that is Key Vault reference: %v", keyVaultReference, err) + } + + secretRequestObject, err := getSecretRequestObject(kvRef.URI) + if err != nil { + return "", fmt.Errorf("failed to get secret request object: %v", err) + } + + keyVaultSecretClient, err := secretClientFactory(secretRequestObject.vaultURL) + if err != nil { + return "", fmt.Errorf("failed to create key vault client: %v", err) + } + + secret, err := getSecretValue(ctx, keyVaultSecretClient, secretRequestObject.secretName, secretRequestObject.secretVersion) + if err != nil { + return "", fmt.Errorf("failed to get secret value: %v", err) + } + + return secret, nil +} + +func getSecretRequestObject(kVRef string) (KeyVaultSecretRequestObject, error) { + // https://vault-name.vault.azure.net/secrets/secret-name/secret-version + parts := strings.Split(strings.TrimPrefix(kVRef, "https://"), "/") + + if len(parts) < 3 { + return KeyVaultSecretRequestObject{}, fmt.Errorf("invalid Key Vault reference: %s", kVRef) + } + + kv := KeyVaultSecretRequestObject{ + vaultURL: "https://" + parts[0], + secretName: parts[2], + } + if len(parts) > secretLengthWithVersion { + kv.secretVersion = parts[3] + } + + return kv, nil +} + +func getSecretValue(ctx context.Context, client KeyVaultClient, secretName, secretVersion string) (string, error) { + secretResp, err := client.GetSecret(ctx, secretName, secretVersion, nil) + if err != nil { + return "", fmt.Errorf("failed to get secret from Key Vault: %v", err) + } + + if secretResp.Value != nil { + return *secretResp.Value, nil + } + + return "", fmt.Errorf("secret with name %s has no value", secretName) +} diff --git a/parsers/azure-app-config_test.go b/parsers/azure-app-config_test.go new file mode 100644 index 0000000..f863a53 --- /dev/null +++ b/parsers/azure-app-config_test.go @@ -0,0 +1,179 @@ +package parsers + +import ( + "context" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" + "github.com/stretchr/testify/assert" + "testing" +) + +var numberOfFetches = 0 + +func (m MockAppConfigPager) More() bool { + if numberOfFetches < m.availablePages { + numberOfFetches++ + return true + } + + return false +} + +func (m MockAppConfigPager) NextPage(_ context.Context) (azappconfig.ListSettingsPageResponse, error) { + return azappconfig.ListSettingsPageResponse{ + Settings: m.settings, + SyncToken: "", + }, nil +} + +type MockAppConfigPager struct { + currentPage int + availablePages int + settings []azappconfig.Setting +} + +type MockSecretClient struct { + secret string +} + +func (m MockSecretClient) GetSecret(_ context.Context, _ string, _ string, _ *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { + return azsecrets.GetSecretResponse{ + Secret: azsecrets.Secret{ + Value: &m.secret, + }, + }, nil +} + +func Test_Azure_App_Config_Parse(t *testing.T) { + firstKey := "firstKey" + firstValue := "firstValue" + secondKey := "secondKey" + secondValue := "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}" + secretContentType := keyVaultRef + mock := MockAppConfigPager{ + availablePages: 1, + settings: []azappconfig.Setting{ + { + Key: &firstKey, + Value: &firstValue, + }, + { + Key: &secondKey, + Value: &secondValue, + ContentType: &secretContentType, + }, + }, + } + mockParser := AzureConfigParser{ + settingsPager: mock, + secretClientFactory: func(url string) (KeyVaultClient, error) { + return MockSecretClient{ + secret: "secretValue", + }, nil + }, + } + + numberOfFetches = 0 + configValues, err := mockParser.Parse() + assert.NoError(t, err) + assert.Equal(t, 2, len(configValues)) + assert.Equal(t, firstValue, configValues[firstKey]) + assert.Equal(t, "secretValue", configValues[secondKey]) +} + +func Test_getSecretRequestObject(t *testing.T) { + type args struct { + kVRef string + } + tests := []struct { + name string + args args + want KeyVaultSecretRequestObject + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Test getSecretRequestObject with valid kVRef", + args: args{ + kVRef: "https://vault-name.vault.azure.net/secrets/secret-name/secret-version", + }, + want: KeyVaultSecretRequestObject{ + vaultURL: "https://vault-name.vault.azure.net", + secretName: "secret-name", + secretVersion: "secret-version", + }, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getSecretRequestObject(tt.args.kVRef) + if !tt.wantErr(t, err, fmt.Sprintf("getSecretRequestObject(%v)", tt.args.kVRef)) { + return + } + assert.Equalf(t, tt.want, got, "getSecretRequestObject(%v)", tt.args.kVRef) + }) + } +} + +func Test_getSecret(t *testing.T) { + secretValue := "secretValue" + + type args struct { + secretClientFactory func(url string) (KeyVaultClient, error) + keyVaultReference string + } + tests := []struct { + name string + args args + want string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Test getSecret with valid keyVaultReference", + args: args{ + secretClientFactory: func(url string) (KeyVaultClient, error) { + return MockSecretClient{ + secret: secretValue, + }, nil + }, + keyVaultReference: "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}", + }, + want: secretValue, + wantErr: assert.NoError, + }, + { + name: "Test getSecret with invalid keyVaultReference", + args: args{ + secretClientFactory: func(url string) (KeyVaultClient, error) { + return MockSecretClient{ + secret: secretValue, + }, nil + }, + keyVaultReference: "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets\"}", + }, + want: "", + wantErr: assert.Error, + }, + { + name: "Test getSecret when creating key vault client fails", + args: args{ + secretClientFactory: func(url string) (KeyVaultClient, error) { + return nil, fmt.Errorf("failed to create key vault client") + }, + keyVaultReference: "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}", + }, + want: "", + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getSecret(context.Background(), tt.args.secretClientFactory, tt.args.keyVaultReference) + if !tt.wantErr(t, err, fmt.Sprintf("getSecret %s", tt.name)) { + return + } + assert.Equalf(t, tt.want, got, "getSecret(%v, %v)", tt.args.secretClientFactory, tt.args.keyVaultReference) + }) + } +} From c6a8287c948adff0281bc7d1f3ffc674828ca99b Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Fri, 20 Dec 2024 10:03:30 +0100 Subject: [PATCH 2/8] Cleanup --- parsers/azure-app-config.go | 72 +++++----- parsers/azure-app-config_test.go | 219 +++++++++++++++++-------------- 2 files changed, 159 insertions(+), 132 deletions(-) diff --git a/parsers/azure-app-config.go b/parsers/azure-app-config.go index 5dac187..f21bf02 100644 --- a/parsers/azure-app-config.go +++ b/parsers/azure-app-config.go @@ -61,78 +61,83 @@ func NewAzureConfigParser(appConfig string, additionallyAllowedTenants []string) return &AzureConfigParser{pager, keyVaultClientFactoryFunc}, nil } -// Parse returns the key/value pairs as a map[string]interface{}. +// Parse retrieves all settings from AppConfig and resolves any KeyVault references. // TODO: Parse with context as parameter. func (p *AzureConfigParser) Parse() (map[string]interface{}, error) { settings := make(map[string]interface{}) for p.settingsPager.More() { - snapshotPage, err := p.settingsPager.NextPage(context.Background()) + settingsPage, err := p.settingsPager.NextPage(context.Background()) if err != nil { - return nil, fmt.Errorf("failed to get next page of pager %v", err) + return nil, fmt.Errorf("failed to get next page of pager %w", err) } - for _, setting := range snapshotPage.Settings { - value := "" + for _, setting := range settingsPage.Settings { + if setting.Key == nil || setting.Value == nil { + continue + } + + var finalValue string if setting.ContentType != nil && strings.EqualFold(*setting.ContentType, keyVaultRef) { - secret, err := getSecret(context.Background(), p.secretClientFactory, *setting.Value) + secretValue, err := p.resolveKeyVaultSecret(context.Background(), *setting.Value) if err != nil { - return nil, fmt.Errorf("failed to get secret value: %v", err) + return nil, fmt.Errorf("failed to get secret value: %w", err) } - value = secret + finalValue = secretValue } else { - value = *setting.Value + finalValue = *setting.Value } - settings[*setting.Key] = value + settings[*setting.Key] = finalValue } } return settings, nil } -type KeyVaultSecretRequestObject struct { - vaultURL string - secretName string - secretVersion string -} - -func getSecret(ctx context.Context, secretClientFactory func(url string) (KeyVaultClient, error), keyVaultReference string) (string, error) { +func (p *AzureConfigParser) resolveKeyVaultSecret(ctx context.Context, keyVaultReference string) (string, error) { var kvRef struct { URI string `json:"uri"` } if err := json.Unmarshal([]byte(keyVaultReference), &kvRef); err != nil { - return "", fmt.Errorf("failed to parse config value %s value that is Key Vault reference: %v", keyVaultReference, err) + return "", fmt.Errorf("failed to parse config value %s value that is Key Vault reference: %w", keyVaultReference, err) } - secretRequestObject, err := getSecretRequestObject(kvRef.URI) + reqObj, err := parseKeyVaultURI(kvRef.URI) if err != nil { - return "", fmt.Errorf("failed to get secret request object: %v", err) + return "", fmt.Errorf("failed to get secret request object: %w", err) } - keyVaultSecretClient, err := secretClientFactory(secretRequestObject.vaultURL) + keyVaultClient, err := p.secretClientFactory(reqObj.vaultURL) if err != nil { - return "", fmt.Errorf("failed to create key vault client: %v", err) + return "", fmt.Errorf("failed to create KeyVault client for %s: %w", reqObj.vaultURL, err) } - secret, err := getSecretValue(ctx, keyVaultSecretClient, secretRequestObject.secretName, secretRequestObject.secretVersion) + secret, err := fetchSecretValue(ctx, keyVaultClient, reqObj.secretName, reqObj.secretVersion, nil) if err != nil { - return "", fmt.Errorf("failed to get secret value: %v", err) + return "", fmt.Errorf("failed to get secret value: %w", err) } return secret, nil } -func getSecretRequestObject(kVRef string) (KeyVaultSecretRequestObject, error) { - // https://vault-name.vault.azure.net/secrets/secret-name/secret-version +type keyVaultRequestObject struct { + vaultURL string + secretName string + secretVersion string +} + +// parseKeyVaultURI extracts vault URL, secret name and optionally secret version from a KeyVault URI. +// Example URI: https://myvault.vault.azure.net/secrets/mysecret/myversion +func parseKeyVaultURI(kVRef string) (keyVaultRequestObject, error) { parts := strings.Split(strings.TrimPrefix(kVRef, "https://"), "/") if len(parts) < 3 { - return KeyVaultSecretRequestObject{}, fmt.Errorf("invalid Key Vault reference: %s", kVRef) + return keyVaultRequestObject{}, fmt.Errorf("invalid Key Vault reference: %s", kVRef) } - kv := KeyVaultSecretRequestObject{ + kv := keyVaultRequestObject{ vaultURL: "https://" + parts[0], secretName: parts[2], } @@ -143,14 +148,15 @@ func getSecretRequestObject(kVRef string) (KeyVaultSecretRequestObject, error) { return kv, nil } -func getSecretValue(ctx context.Context, client KeyVaultClient, secretName, secretVersion string) (string, error) { - secretResp, err := client.GetSecret(ctx, secretName, secretVersion, nil) +// fetchSecretValue retrieves the secret value from KeyVault. +func fetchSecretValue(ctx context.Context, client KeyVaultClient, secretName, secretVersion string, options *azsecrets.GetSecretOptions) (string, error) { + resp, err := client.GetSecret(ctx, secretName, secretVersion, options) if err != nil { - return "", fmt.Errorf("failed to get secret from Key Vault: %v", err) + return "", fmt.Errorf("failed to get secret from Key Vault: %w", err) } - if secretResp.Value != nil { - return *secretResp.Value, nil + if resp.Value != nil { + return *resp.Value, nil } return "", fmt.Errorf("secret with name %s has no value", secretName) diff --git a/parsers/azure-app-config_test.go b/parsers/azure-app-config_test.go index f863a53..dc93f65 100644 --- a/parsers/azure-app-config_test.go +++ b/parsers/azure-app-config_test.go @@ -6,31 +6,65 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" ) -var numberOfFetches = 0 +func TestAzureConfigParser_Parse_MultiPageAndKeyVaultRef(t *testing.T) { + firstKey := "firstKey" + firstValue := "firstValue" + + keyVaultRefSettingKey := "kvSecretKey" + secretUriValue := "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}" + secretContentType := keyVaultRef + + thirdKey := "thirdKey" + thirdValue := "thirdValue" + + mockPager := &MockMultiPagePager{ + pages: [][]azappconfig.Setting{ + { + {Key: &firstKey, Value: &firstValue}, + {Key: &keyVaultRefSettingKey, Value: &secretUriValue, ContentType: &secretContentType}, + }, + { + {Key: &thirdKey, Value: &thirdValue}, + }, + }, + } -func (m MockAppConfigPager) More() bool { - if numberOfFetches < m.availablePages { - numberOfFetches++ - return true + parser := AzureConfigParser{ + settingsPager: mockPager, + secretClientFactory: func(url string) (KeyVaultClient, error) { + return MockSecretClient{secret: "resolvedSecretValue"}, nil + }, } - return false + configValues, err := parser.Parse() + require.NoError(t, err) + + assert.Equal(t, 3, len(configValues)) + assert.Equal(t, firstValue, configValues[firstKey]) + assert.Equal(t, "resolvedSecretValue", configValues[keyVaultRefSettingKey]) + assert.Equal(t, thirdValue, configValues[thirdKey]) } -func (m MockAppConfigPager) NextPage(_ context.Context) (azappconfig.ListSettingsPageResponse, error) { - return azappconfig.ListSettingsPageResponse{ - Settings: m.settings, - SyncToken: "", - }, nil +type MockMultiPagePager struct { + current int + pages [][]azappconfig.Setting } -type MockAppConfigPager struct { - currentPage int - availablePages int - settings []azappconfig.Setting +func (m *MockMultiPagePager) More() bool { + return m.current < len(m.pages) +} + +func (m *MockMultiPagePager) NextPage(_ context.Context) (azappconfig.ListSettingsPageResponse, error) { + if !m.More() { + return azappconfig.ListSettingsPageResponse{}, fmt.Errorf("no more pages") + } + page := m.pages[m.current] + m.current++ + return azappconfig.ListSettingsPageResponse{Settings: page}, nil } type MockSecretClient struct { @@ -45,122 +79,64 @@ func (m MockSecretClient) GetSecret(_ context.Context, _ string, _ string, _ *az }, nil } -func Test_Azure_App_Config_Parse(t *testing.T) { - firstKey := "firstKey" - firstValue := "firstValue" - secondKey := "secondKey" - secondValue := "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}" - secretContentType := keyVaultRef - mock := MockAppConfigPager{ - availablePages: 1, - settings: []azappconfig.Setting{ - { - Key: &firstKey, - Value: &firstValue, - }, - { - Key: &secondKey, - Value: &secondValue, - ContentType: &secretContentType, - }, - }, - } - mockParser := AzureConfigParser{ - settingsPager: mock, - secretClientFactory: func(url string) (KeyVaultClient, error) { - return MockSecretClient{ - secret: "secretValue", - }, nil - }, - } - - numberOfFetches = 0 - configValues, err := mockParser.Parse() - assert.NoError(t, err) - assert.Equal(t, 2, len(configValues)) - assert.Equal(t, firstValue, configValues[firstKey]) - assert.Equal(t, "secretValue", configValues[secondKey]) -} - -func Test_getSecretRequestObject(t *testing.T) { - type args struct { - kVRef string - } - tests := []struct { - name string - args args - want KeyVaultSecretRequestObject - wantErr assert.ErrorAssertionFunc - }{ - { - name: "Test getSecretRequestObject with valid kVRef", - args: args{ - kVRef: "https://vault-name.vault.azure.net/secrets/secret-name/secret-version", - }, - want: KeyVaultSecretRequestObject{ - vaultURL: "https://vault-name.vault.azure.net", - secretName: "secret-name", - secretVersion: "secret-version", - }, - wantErr: assert.NoError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getSecretRequestObject(tt.args.kVRef) - if !tt.wantErr(t, err, fmt.Sprintf("getSecretRequestObject(%v)", tt.args.kVRef)) { - return - } - assert.Equalf(t, tt.want, got, "getSecretRequestObject(%v)", tt.args.kVRef) - }) - } -} - -func Test_getSecret(t *testing.T) { +func TestAzureConfigParser_resolveKeyVaultSecret(t *testing.T) { secretValue := "secretValue" - - type args struct { + type fields struct { secretClientFactory func(url string) (KeyVaultClient, error) - keyVaultReference string + } + type args struct { + ctx context.Context + keyVaultReference string } tests := []struct { name string + fields fields args args want string wantErr assert.ErrorAssertionFunc }{ { - name: "Test getSecret with valid keyVaultReference", - args: args{ + name: "Test resolveKeyVaultSecret with valid keyVaultReference", + fields: fields{ secretClientFactory: func(url string) (KeyVaultClient, error) { return MockSecretClient{ secret: secretValue, }, nil }, + }, + args: args{ + ctx: context.Background(), keyVaultReference: "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}", }, want: secretValue, wantErr: assert.NoError, }, { - name: "Test getSecret with invalid keyVaultReference", - args: args{ + name: "Test resolveKeyVaultSecret with invalid keyVaultReference", + fields: fields{ secretClientFactory: func(url string) (KeyVaultClient, error) { return MockSecretClient{ secret: secretValue, }, nil }, + }, + + args: args{ + ctx: context.Background(), keyVaultReference: "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets\"}", }, want: "", wantErr: assert.Error, }, { - name: "Test getSecret when creating key vault client fails", - args: args{ + name: "Test resolveKeyVaultSecret creating key vault client fails", + fields: fields{ secretClientFactory: func(url string) (KeyVaultClient, error) { return nil, fmt.Errorf("failed to create key vault client") }, + }, + args: args{ + ctx: context.Background(), keyVaultReference: "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}", }, want: "", @@ -169,11 +145,56 @@ func Test_getSecret(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getSecret(context.Background(), tt.args.secretClientFactory, tt.args.keyVaultReference) - if !tt.wantErr(t, err, fmt.Sprintf("getSecret %s", tt.name)) { + p := &AzureConfigParser{ + secretClientFactory: tt.fields.secretClientFactory, + } + got, err := p.resolveKeyVaultSecret(tt.args.ctx, tt.args.keyVaultReference) + if !tt.wantErr(t, err, fmt.Sprintf("resolveKeyVaultSecret(%v, %v)", tt.args.ctx, tt.args.keyVaultReference)) { + return + } + assert.Equalf(t, tt.want, got, "resolveKeyVaultSecret(%v, %v)", tt.args.ctx, tt.args.keyVaultReference) + }) + } +} + +func Test_getSecretRequestObject(t *testing.T) { + type args struct { + kVRef string + } + tests := []struct { + name string + args args + want keyVaultRequestObject + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Test parseKeyVaultURI with valid kVRef", + args: args{ + kVRef: "https://vault-name.vault.azure.net/secrets/secret-name/secret-version", + }, + want: keyVaultRequestObject{ + vaultURL: "https://vault-name.vault.azure.net", + secretName: "secret-name", + secretVersion: "secret-version", + }, + wantErr: assert.NoError, + }, + { + name: "Test parseKeyVaultURI with invalid kVRef", + args: args{ + kVRef: "https://vault-name.vault.azure.net/secrets", + }, + want: keyVaultRequestObject{}, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseKeyVaultURI(tt.args.kVRef) + if !tt.wantErr(t, err, fmt.Sprintf("parseKeyVaultURI(%v)", tt.args.kVRef)) { return } - assert.Equalf(t, tt.want, got, "getSecret(%v, %v)", tt.args.secretClientFactory, tt.args.keyVaultReference) + assert.Equalf(t, tt.want, got, "parseKeyVaultURI(%v)", tt.args.kVRef) }) } } From 2dec6426152548e3903710fbafba6577ea14eeb8 Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Fri, 20 Dec 2024 13:13:45 +0100 Subject: [PATCH 3/8] Ctx with timeout --- parsers/azure-app-config.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/parsers/azure-app-config.go b/parsers/azure-app-config.go index f21bf02..7ecbdf1 100644 --- a/parsers/azure-app-config.go +++ b/parsers/azure-app-config.go @@ -8,6 +8,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" "strings" + "time" ) const ( @@ -64,10 +65,13 @@ func NewAzureConfigParser(appConfig string, additionallyAllowedTenants []string) // Parse retrieves all settings from AppConfig and resolves any KeyVault references. // TODO: Parse with context as parameter. func (p *AzureConfigParser) Parse() (map[string]interface{}, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + settings := make(map[string]interface{}) for p.settingsPager.More() { - settingsPage, err := p.settingsPager.NextPage(context.Background()) + settingsPage, err := p.settingsPager.NextPage(ctx) if err != nil { return nil, fmt.Errorf("failed to get next page of pager %w", err) } @@ -79,7 +83,7 @@ func (p *AzureConfigParser) Parse() (map[string]interface{}, error) { var finalValue string if setting.ContentType != nil && strings.EqualFold(*setting.ContentType, keyVaultRef) { - secretValue, err := p.resolveKeyVaultSecret(context.Background(), *setting.Value) + secretValue, err := p.resolveKeyVaultSecret(ctx, *setting.Value) if err != nil { return nil, fmt.Errorf("failed to get secret value: %w", err) } From b5d878dcc05755516a6f0874f0456fd27ccbeb22 Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Fri, 20 Dec 2024 13:28:22 +0100 Subject: [PATCH 4/8] Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd7fd47..b3ca51a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Binder is a configuration reader that parses different types of configurations and adds the possibility to bind them to one or many typed instances. -It can read configuration values from files, environment variables, `flag` flags, `spf13/pflags` flags, remote URLs, Kubernetes volumes, Azure App Configs, and is flexible enough to enable custom configuration parsers. Binder is also able to listen for file changes/volume changes, and re-bind configurations when a backing file or backing volume has been updated. +It can read configuration values from files, environment variables, `flag` flags, `spf13/pflags` flags, remote URLs, Kubernetes volumes, Azure App Configs with backing Azure Key Vaults, and is flexible enough to enable custom configuration parsers. Binder is also able to listen for file changes/volume changes, and re-bind configurations when a backing file or backing volume has been updated. Example: ```go @@ -106,7 +106,7 @@ type Config struct { func main() { bnd := binder.New( - binder.WithAzureConfig("https://appconfig-name.azconfig.io", ["tenant-id-for-key-vault"])) + binder.WithAzureConfig("https://appconfig-name.azconfig.io", []string{"additional-tenant-id"})) defer bnd.Close() var cfg Config From cbf3697a30c3b85772270872f03530ecae3e5709 Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Fri, 20 Dec 2024 13:32:02 +0100 Subject: [PATCH 5/8] Workflow updates --- .github/workflows/go.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7b77550..8092363 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,10 +13,10 @@ jobs: steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: ^1.14 - id: go + go-version-file: 'go.mod' + check-latest: true - name: Check out code into the Go module directory uses: actions/checkout@v2 @@ -34,4 +34,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.49 + version: v1.62.2 From ae9bcfa2d0747e530e7607336091d857939c5d6f Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Fri, 20 Dec 2024 13:35:39 +0100 Subject: [PATCH 6/8] Workflow updates --- .github/workflows/go.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8092363..eb18930 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,6 +11,8 @@ jobs: name: Build runs-on: ubuntu-latest steps: + - name: Check out code into the Go module directory + uses: actions/checkout@main - name: Set up Go 1.x uses: actions/setup-go@v4 @@ -18,9 +20,6 @@ jobs: go-version-file: 'go.mod' check-latest: true - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - name: Get dependencies run: | go mod download From f700795dfe870378a7c412d67847b482ffd1931f Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Fri, 20 Dec 2024 13:43:55 +0100 Subject: [PATCH 7/8] Workflow updates --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index eb18930..16b5adc 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -31,6 +31,6 @@ jobs: run: go test -race -cover -v ./... - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v6 with: version: v1.62.2 From 47864dfb1e339a3beff227f0e7cfdda0c4220720 Mon Sep 17 00:00:00 2001 From: Anders Gustafsson Date: Fri, 20 Dec 2024 13:51:26 +0100 Subject: [PATCH 8/8] lint fixes --- go.mod | 2 +- parsers/azure-app-config_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 37c1d84..cc320ee 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/ourstudio-se/binder go 1.23 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 @@ -13,6 +12,7 @@ require ( ) require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1 // indirect diff --git a/parsers/azure-app-config_test.go b/parsers/azure-app-config_test.go index dc93f65..d93d07a 100644 --- a/parsers/azure-app-config_test.go +++ b/parsers/azure-app-config_test.go @@ -15,7 +15,7 @@ func TestAzureConfigParser_Parse_MultiPageAndKeyVaultRef(t *testing.T) { firstValue := "firstValue" keyVaultRefSettingKey := "kvSecretKey" - secretUriValue := "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}" + secretUriValue := "{\"uri\":\"https://mykeyvault.vault.azure.net/secrets/mySecretName/0123456789abcdef\"}" //nolint secretContentType := keyVaultRef thirdKey := "thirdKey"