Skip to content

Commit bb9b04b

Browse files
authored
feat: add support for az table storage (#23)
* feat: add support for az table storage for non-sensitive config items support for AZ TableStorage has been added semver: feature closes #22 * chore(docs): update readme * fix: add missing implementation * fix: docs * fix: unit test for success empty +semver: feature +semver: feat +semver:minor * fix: add value concept as a property to aztablestore add readme around it * fix: clean up comments and delete unusued * fix: normalize getter/setter * fix: update docs * fix: tests for adjust non exchanged/errored tokens
1 parent 5589130 commit bb9b04b

22 files changed

+630
-131
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ LDFLAGS := -ldflags="-s -w -X \"github.com/$(OWNER)/$(NAME)/cmd/configmanager.Ve
1010
.PHONY: test test_ci tidy install cross-build
1111

1212
test: test_prereq
13-
go test `go list ./... | grep -v */generated/` -v -buildvcs=false -mod=readonly -coverprofile=.coverage/out | go-junit-report > .coverage/report-junit.xml && \
13+
go test `go list ./... | grep -v */generated/` -v -buildvcs=false -mod=readonly -coverprofile=.coverage/out ; \
14+
cat .coverage/out | go-junit-report > .coverage/report-junit.xml && \
1415
gocov convert .coverage/out | gocov-xml > .coverage/report-cobertura.xml
1516

1617
test_ci:

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Currently supported variable and secrets implementations:
2828
- [AzureKeyvault Secrets](https://azure.microsoft.com/en-gb/products/key-vault/)
2929
- Implementation Indicator: `AZKVSECRET`
3030
- see [Special consideration for AZKVSECRET](#special-consideration-for-azkvsecret) around how to structure the token in this case.
31+
- [Azure TableStorage](https://azure.microsoft.com/en-gb/products/storage/tables/)
32+
- Implementation Indicator: `AZTABLESTORE`
33+
- see [Special consideration for AZTABLESTORE](#special-consideration-for-aztablestore) around how to structure the token in this case.
3134
- [GCP Secrets](https://cloud.google.com/secret-manager)
3235
- Implementation Indicator: `GCPSECRETS`
3336
- [Hashicorp Vault](https://developer.hashicorp.com/vault/docs/secrets/kv)
@@ -177,6 +180,28 @@ For Azure KeyVault the first part of the token needs to be the name of the vault
177180

178181
> The preceeding slash to the vault name is optional - `AZKVSECRET#/test-vault/no-slash-token-1` and `AZKVSECRET#test-vault/no-slash-token-1` will both identify the vault of name `test-vault`
179182

183+
### Special consideration for AZTABLESTORE
184+
185+
The token itself must contain all of the following properties, so that it would look like this `AZTABLESTORE://STORAGE_ACCOUNT_NAME/TABLE_NAME/PARTITION_KEY/ROW_KEY`:
186+
187+
- Storage account name [`STORAGE_ACCOUNT_NAME`]
188+
- Table Name [`TABLE_NAME`]
189+
- > It might make sense to make this table global to the domain or project
190+
- Partition Key [`PARTITION_KEY`]
191+
- > This could correspond to the component/service name
192+
- Row Key [`ROW_KEY`]
193+
- > This could correspond to the property itself or a group of properties
194+
- > e.g. `AZTABLESTORE://globalconfigstorageaccount/domainXyz/serviceXyz/db` => `{"value":{"host":"foo","port":1234,"enabled":true}}`
195+
- > It will continue to work the same way with additional keyseparators inside values.
196+
197+
> NOTE: if you store a more complex object inside a top level `value` property this will reduce the number of columns and normalize the table - **THE DATA INSIDE THE VALUE MUST BE JSON PARSEABLE**
198+
199+
All the usual token rules apply e.g. of `keySeparator`
200+
201+
`AZTABLESTORE://account/app1Config/db/config` => `{host: foo.bar, port: 8891}`
202+
203+
`AZTABLESTORE://account/app1Config/db/config|host` => `foo.bar`
204+
180205
### Special consideration for HashicorpVault
181206

182207
For HashicorpVault the first part of the token needs to be the name of the mountpath. In Dev Vaults this is `"secret"`,

configmanager.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func (c *ConfigManager) Retrieve(tokens []string, config generator.GenVarsConfig
2525
return retrieve(tokens, gv)
2626
}
2727

28+
// GenerateAPI
2829
type GenerateAPI interface {
2930
Generate(tokens []string) (generator.ParsedMap, error)
3031
}
@@ -34,7 +35,7 @@ func retrieve(tokens []string, gv GenerateAPI) (generator.ParsedMap, error) {
3435
}
3536

3637
// RetrieveWithInputReplaced parses given input against all possible token strings
37-
// using regex to grab a list of found tokens in the given string and return the replaced string
38+
// using regex to grab a list of found tokens in the given string and returns the replaced string
3839
func (c *ConfigManager) RetrieveWithInputReplaced(input string, config generator.GenVarsConfig) (string, error) {
3940
gv := generator.NewGenerator().WithConfig(&config)
4041
return retrieveWithInputReplaced(input, gv)
@@ -98,12 +99,12 @@ type CMRetrieveWithInputReplacediface interface {
9899
RetrieveWithInputReplaced(input string, config generator.GenVarsConfig) (string, error)
99100
}
100101

101-
// @deprecated
102-
// left for compatibility
103102
// KubeControllerSpecHelper is a helper method, it marshalls an input value of that type into a string and passes it into the relevant configmanger retrieve method
104-
// and returns the unmarshalled object back
103+
// and returns the unmarshalled object back.
105104
//
106-
// It accepts a DI of configmanager and the config (for testability) to replace all occurences of replaceable tokens inside a Marshalled string of that type
105+
// # It accepts a DI of configmanager and the config (for testability) to replace all occurences of replaceable tokens inside a Marshalled string of that type
106+
//
107+
// Deprecated: Left for compatibility reasons
107108
func KubeControllerSpecHelper[T any](inputType T, cm CMRetrieveWithInputReplacediface, config generator.GenVarsConfig) (*T, error) {
108109
outType := new(T)
109110
rawBytes, err := json.Marshal(inputType)

configmanager_test.go

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,12 @@ db:
218218
}
219219

220220
type MockCfgMgr struct {
221-
RetrieveWithInputReplacedTest func(input string, config generator.GenVarsConfig) (string, error)
221+
retrieveInput func(input string, config generator.GenVarsConfig) (string, error)
222+
// retrieve func(input string, config generator.GenVarsConfig) (string, error)
222223
}
223224

224225
func (m *MockCfgMgr) RetrieveWithInputReplaced(input string, config generator.GenVarsConfig) (string, error) {
225-
if m.RetrieveWithInputReplacedTest != nil {
226-
return m.RetrieveWithInputReplacedTest(input, config)
227-
}
228-
return "", nil
226+
return m.retrieveInput(input, config)
229227
}
230228

231229
func (m *MockCfgMgr) Insert(force bool) error {
@@ -280,7 +278,7 @@ func Test_KubeControllerSpecHelper(t *testing.T) {
280278
},
281279
cfmgr: func(t *testing.T) mockConfigManageriface {
282280
mcm := &MockCfgMgr{}
283-
mcm.RetrieveWithInputReplacedTest = func(input string, config generator.GenVarsConfig) (string, error) {
281+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
284282
return `{"foo":"baz","bar":"quz"}`, nil
285283
}
286284
return mcm
@@ -298,7 +296,7 @@ func Test_KubeControllerSpecHelper(t *testing.T) {
298296
},
299297
cfmgr: func(t *testing.T) mockConfigManageriface {
300298
mcm := &MockCfgMgr{}
301-
mcm.RetrieveWithInputReplacedTest = func(input string, config generator.GenVarsConfig) (string, error) {
299+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
302300
return `{"foo":"baz2","bar":"quz"}`, nil
303301
}
304302
return mcm
@@ -353,7 +351,7 @@ func Test_KubeControllerComplex(t *testing.T) {
353351
},
354352
cfmgr: func(t *testing.T) mockConfigManageriface {
355353
mcm := &MockCfgMgr{}
356-
mcm.RetrieveWithInputReplacedTest = func(input string, config generator.GenVarsConfig) (string, error) {
354+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
357355
return `{"foo":"baz","bar":"quz", "lol":{"bla":"booo","another":{"number": 1235, "float": 123.09}}}`, nil
358356
}
359357
return mcm
@@ -407,7 +405,7 @@ func Test_YamlRetrieveMarshalled(t *testing.T) {
407405
},
408406
cfmgr: func(t *testing.T) mockConfigManageriface {
409407
mcm := &MockCfgMgr{}
410-
mcm.RetrieveWithInputReplacedTest = func(input string, config generator.GenVarsConfig) (string, error) {
408+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
411409
return `{"foo":"baz","bar":"quz", "lol":{"bla":"booo","another":{"number": 1235, "float": 123.09}}}`, nil
412410
}
413411
return mcm
@@ -426,7 +424,7 @@ func Test_YamlRetrieveMarshalled(t *testing.T) {
426424
},
427425
cfmgr: func(t *testing.T) mockConfigManageriface {
428426
mcm := &MockCfgMgr{}
429-
mcm.RetrieveWithInputReplacedTest = func(input string, config generator.GenVarsConfig) (string, error) {
427+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
430428
return `{"foo":"baz","bar":"quz", "lol":{"bla":"","another":{"number": 0, "float": 0}}}`, nil
431429
}
432430
return mcm
@@ -448,6 +446,58 @@ func Test_YamlRetrieveMarshalled(t *testing.T) {
448446
}
449447
}
450448

449+
func Test_YamlRetrieveMarshalled_errored(t *testing.T) {
450+
tests := []struct {
451+
name string
452+
testType *testNestedStruct
453+
expect error
454+
cfmgr func(t *testing.T) mockConfigManageriface
455+
}{
456+
{
457+
name: "complex struct - complete",
458+
testType: &testNestedStruct{
459+
Foo: testTokenAWS,
460+
Bar: "quz",
461+
Lol: testLol{
462+
Bla: "booo",
463+
Another: testAnotherNEst{
464+
Number: 1235,
465+
Float: 123.09,
466+
},
467+
},
468+
},
469+
// expect: testNestedStruct{
470+
// Foo: "baz",
471+
// Bar: "quz",
472+
// Lol: testLol{
473+
// Bla: "booo",
474+
// Another: testAnotherNEst{
475+
// Number: 1235,
476+
// Float: 123.09,
477+
// },
478+
// },
479+
// },
480+
cfmgr: func(t *testing.T) mockConfigManageriface {
481+
mcm := &MockCfgMgr{}
482+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
483+
return ``, fmt.Errorf("%s", "error decoding")
484+
}
485+
return mcm
486+
},
487+
},
488+
}
489+
for _, tt := range tests {
490+
t.Run(tt.name, func(t *testing.T) {
491+
config := generator.NewConfig().WithTokenSeparator("://")
492+
493+
_, err := RetrieveMarshalledYaml(tt.testType, tt.cfmgr(t), *config)
494+
if err == nil {
495+
t.Errorf(testutils.TestPhrase, nil, err.Error())
496+
}
497+
})
498+
}
499+
}
500+
451501
func Test_RetrieveMarshalledJson(t *testing.T) {
452502
tests := []struct {
453503
name string
@@ -481,7 +531,7 @@ func Test_RetrieveMarshalledJson(t *testing.T) {
481531
},
482532
cfmgr: func(t *testing.T) mockConfigManageriface {
483533
mcm := &MockCfgMgr{}
484-
mcm.RetrieveWithInputReplacedTest = func(input string, config generator.GenVarsConfig) (string, error) {
534+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
485535
return `{"foo":"baz","bar":"quz", "lol":{"bla":"booo","another":{"number": 1235, "float": 123.09}}}`, nil
486536
}
487537
return mcm
@@ -500,7 +550,7 @@ func Test_RetrieveMarshalledJson(t *testing.T) {
500550
},
501551
cfmgr: func(t *testing.T) mockConfigManageriface {
502552
mcm := &MockCfgMgr{}
503-
mcm.RetrieveWithInputReplacedTest = func(input string, config generator.GenVarsConfig) (string, error) {
553+
mcm.retrieveInput = func(input string, config generator.GenVarsConfig) (string, error) {
504554
return `{"foo":"baz","bar":"quz", "lol":{"bla":"","another":{"number": 0, "float": 0}}}`, nil
505555
}
506556
return mcm

go.mod

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
module github.com/dnitsch/configmanager
22

3-
go 1.20
3+
go 1.21
44

55
require (
66
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
7-
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0
87
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v0.13.0
98
github.com/aws/aws-sdk-go-v2 v1.18.0
109
github.com/aws/aws-sdk-go-v2/config v1.18.25
@@ -19,14 +18,14 @@ require (
1918
cloud.google.com/go/compute v1.19.3 // indirect
2019
cloud.google.com/go/compute/metadata v0.2.3 // indirect
2120
cloud.google.com/go/iam v1.0.1 // indirect
22-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
21+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect
2322
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
24-
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
2523
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 // indirect
2624
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
2725
github.com/aws/aws-sdk-go v1.44.267 // indirect
2826
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect
2927
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
28+
github.com/davecgh/go-spew v1.1.1 // indirect
3029
github.com/fatih/color v1.15.0 // indirect
3130
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
3231
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -52,13 +51,15 @@ require (
5251
github.com/mitchellh/mapstructure v1.5.0 // indirect
5352
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
5453
github.com/pkg/errors v0.9.1 // indirect
54+
github.com/pmezard/go-difflib v1.0.0 // indirect
5555
github.com/rogpeppe/go-internal v1.10.0 // indirect
5656
github.com/ryanuber/go-glob v1.0.0 // indirect
57+
github.com/stretchr/testify v1.8.4 // indirect
5758
go.opencensus.io v0.24.0 // indirect
58-
golang.org/x/crypto v0.9.0 // indirect
59-
golang.org/x/net v0.10.0 // indirect
59+
golang.org/x/crypto v0.11.0 // indirect
60+
golang.org/x/net v0.12.0 // indirect
6061
golang.org/x/oauth2 v0.8.0 // indirect
61-
golang.org/x/text v0.9.0 // indirect
62+
golang.org/x/text v0.11.0 // indirect
6263
golang.org/x/time v0.3.0 // indirect
6364
google.golang.org/api v0.123.0 // indirect
6465
google.golang.org/appengine v1.6.7 // indirect
@@ -71,6 +72,7 @@ require (
7172

7273
require (
7374
cloud.google.com/go/secretmanager v1.10.1
75+
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.2
7476
github.com/aws/aws-sdk-go-v2/credentials v1.13.24 // indirect
7577
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
7678
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
@@ -89,6 +91,6 @@ require (
8991
github.com/mattn/go-isatty v0.0.19 // indirect
9092
github.com/spf13/pflag v1.0.5 // indirect
9193
github.com/spyzhov/ajson v0.8.0
92-
golang.org/x/sys v0.8.0 // indirect
94+
golang.org/x/sys v0.10.0 // indirect
9395
gopkg.in/yaml.v3 v3.0.1
9496
)

0 commit comments

Comments
 (0)