From ca3b3c2a9b5e52ffaa90a9be5db3d65f1fc2d0ae Mon Sep 17 00:00:00 2001 From: Alex Guerrieri Date: Fri, 11 Oct 2024 12:57:02 +0200 Subject: [PATCH 1/2] Use hook to set map key after setter --- collector.go | 21 ++++++++++++++------- collector_test.go | 2 +- hydrate.go | 5 ++++- hydrate_test.go | 17 +++++++++++++++++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/collector.go b/collector.go index 95d65af..8d55432 100644 --- a/collector.go +++ b/collector.go @@ -11,6 +11,7 @@ type secretField struct { value reflect.Value fieldPath string secretName string + hook func() } type collector struct { @@ -19,7 +20,7 @@ type collector struct { } // Walks given reflect value recursively and collects any string fields with $SECRET: prefix. -func (g *collector) collectSecretFields(v reflect.Value, path string) { +func (g *collector) collectSecretFields(v reflect.Value, path string, hook func()) { switch v.Kind() { case reflect.Ptr: if v.IsNil() { @@ -27,18 +28,18 @@ func (g *collector) collectSecretFields(v reflect.Value, path string) { } // Dereference pointer - g.collectSecretFields(v.Elem(), path) + g.collectSecretFields(v.Elem(), path, hook) case reflect.Struct: for i := 0; i < v.NumField(); i++ { field := v.Field(i) - g.collectSecretFields(field, fmt.Sprintf("%v.%v", path, v.Type().Field(i).Name)) + g.collectSecretFields(field, fmt.Sprintf("%v.%v", path, v.Type().Field(i).Name), hook) } case reflect.Slice, reflect.Array: for i := 0; i < v.Len(); i++ { item := v.Index(i) - g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, i)) + g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, i), hook) } case reflect.Map: @@ -50,12 +51,17 @@ func (g *collector) collectSecretFields(v reflect.Value, path string) { ptr := reflect.New(item.Type()) ptr.Elem().Set(item) - g.collectSecretFields(ptr, fmt.Sprintf("%v[%v]", path, key)) + g.collectSecretFields(ptr, fmt.Sprintf("%v[%v]", path, key), func() { + v.SetMapIndex(key, ptr.Elem()) + if hook != nil { + hook() + } + }) // Set the modified struct back into the map - v.SetMapIndex(key, ptr.Elem()) + } else { - g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, key)) + g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, key), hook) } } @@ -74,6 +80,7 @@ func (g *collector) collectSecretFields(v reflect.Value, path string) { value: v, fieldPath: path, secretName: secretName, + hook: hook, }) default: diff --git a/collector_test.go b/collector_test.go index fab6f56..25a38a8 100644 --- a/collector_test.go +++ b/collector_test.go @@ -125,7 +125,7 @@ func TestCollectFields(t *testing.T) { v := reflect.ValueOf(tc.Input) c := &collector{} - c.collectSecretFields(v, fmt.Sprintf("tt[%v].input", i)) + c.collectSecretFields(v, fmt.Sprintf("tt[%v].input", i), nil) if tc.Error { if c.err == nil { diff --git a/hydrate.go b/hydrate.go index 5d8128e..eeb95e2 100644 --- a/hydrate.go +++ b/hydrate.go @@ -54,7 +54,7 @@ func hydrateConfig(ctx context.Context, provider secretsProvider, v reflect.Valu } c := &collector{} - c.collectSecretFields(v, "config") + c.collectSecretFields(v, "config", nil) if c.err != nil { return fmt.Errorf("failed to collect fields: %w", c.err) } @@ -69,6 +69,9 @@ func hydrateConfig(ctx context.Context, provider secretsProvider, v reflect.Valu return fmt.Errorf("failed to fetch secret %v=%q: %w", field.fieldPath, field.value.String(), err) } field.value.SetString(secretValue) + if field.hook != nil { + field.hook() + } return nil }) diff --git a/hydrate_test.go b/hydrate_test.go index e58cfb9..c29d0e0 100644 --- a/hydrate_test.go +++ b/hydrate_test.go @@ -14,6 +14,12 @@ type config struct { Analytics analytics Pass string JWTSecrets []string + Services map[string]service +} + +type service struct { + URL string + Auth string } type db struct { @@ -52,6 +58,7 @@ func TestReplacePlaceholdersWithSecrets(t *testing.T) { "pass": "secret", "jwtSecretV1": "some-old-secret", "jwtSecretV2": "changeme-now", + "auth": "auth-secret", }, conf: &config{ Pass: "$SECRET:pass", @@ -66,6 +73,11 @@ func TestReplacePlaceholdersWithSecrets(t *testing.T) { AuthToken: "$SECRET:analyticsPassword", }, JWTSecrets: []string{"$SECRET:jwtSecretV2", "$SECRET:jwtSecretV1"}, + Services: map[string]service{ + "a": { + Auth: "$SECRET:auth", + }, + }, }, wantErr: false, wantConf: &config{ @@ -84,6 +96,11 @@ func TestReplacePlaceholdersWithSecrets(t *testing.T) { "changeme-now", "some-old-secret", }, + Services: map[string]service{ + "a": { + Auth: "auth-secret", + }, + }, }, }, { From 223d0487e118cad3ad51e343b557f156a08b34f4 Mon Sep 17 00:00:00 2001 From: Alex Guerrieri Date: Fri, 11 Oct 2024 13:27:39 +0200 Subject: [PATCH 2/2] move hooks to collector --- collector.go | 20 +++++++++----------- collector_test.go | 2 +- hydrate.go | 15 ++++++++++----- hydrate_test.go | 5 +++++ 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/collector.go b/collector.go index 8d55432..d09150e 100644 --- a/collector.go +++ b/collector.go @@ -11,16 +11,16 @@ type secretField struct { value reflect.Value fieldPath string secretName string - hook func() } type collector struct { fields []*secretField + hooks []func() err error } // Walks given reflect value recursively and collects any string fields with $SECRET: prefix. -func (g *collector) collectSecretFields(v reflect.Value, path string, hook func()) { +func (g *collector) collectSecretFields(v reflect.Value, path string) { switch v.Kind() { case reflect.Ptr: if v.IsNil() { @@ -28,18 +28,18 @@ func (g *collector) collectSecretFields(v reflect.Value, path string, hook func( } // Dereference pointer - g.collectSecretFields(v.Elem(), path, hook) + g.collectSecretFields(v.Elem(), path) case reflect.Struct: for i := 0; i < v.NumField(); i++ { field := v.Field(i) - g.collectSecretFields(field, fmt.Sprintf("%v.%v", path, v.Type().Field(i).Name), hook) + g.collectSecretFields(field, fmt.Sprintf("%v.%v", path, v.Type().Field(i).Name)) } case reflect.Slice, reflect.Array: for i := 0; i < v.Len(); i++ { item := v.Index(i) - g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, i), hook) + g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, i)) } case reflect.Map: @@ -51,17 +51,16 @@ func (g *collector) collectSecretFields(v reflect.Value, path string, hook func( ptr := reflect.New(item.Type()) ptr.Elem().Set(item) - g.collectSecretFields(ptr, fmt.Sprintf("%v[%v]", path, key), func() { + g.hooks = append(g.hooks, func() { v.SetMapIndex(key, ptr.Elem()) - if hook != nil { - hook() - } }) + g.collectSecretFields(ptr, fmt.Sprintf("%v[%v]", path, key)) + // Set the modified struct back into the map } else { - g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, key), hook) + g.collectSecretFields(item, fmt.Sprintf("%v[%v]", path, key)) } } @@ -80,7 +79,6 @@ func (g *collector) collectSecretFields(v reflect.Value, path string, hook func( value: v, fieldPath: path, secretName: secretName, - hook: hook, }) default: diff --git a/collector_test.go b/collector_test.go index 25a38a8..fab6f56 100644 --- a/collector_test.go +++ b/collector_test.go @@ -125,7 +125,7 @@ func TestCollectFields(t *testing.T) { v := reflect.ValueOf(tc.Input) c := &collector{} - c.collectSecretFields(v, fmt.Sprintf("tt[%v].input", i), nil) + c.collectSecretFields(v, fmt.Sprintf("tt[%v].input", i)) if tc.Error { if c.err == nil { diff --git a/hydrate.go b/hydrate.go index eeb95e2..411deea 100644 --- a/hydrate.go +++ b/hydrate.go @@ -54,7 +54,7 @@ func hydrateConfig(ctx context.Context, provider secretsProvider, v reflect.Valu } c := &collector{} - c.collectSecretFields(v, "config", nil) + c.collectSecretFields(v, "config") if c.err != nil { return fmt.Errorf("failed to collect fields: %w", c.err) } @@ -69,13 +69,18 @@ func hydrateConfig(ctx context.Context, provider secretsProvider, v reflect.Valu return fmt.Errorf("failed to fetch secret %v=%q: %w", field.fieldPath, field.value.String(), err) } field.value.SetString(secretValue) - if field.hook != nil { - field.hook() - } return nil }) } - return g.Wait() + if err := g.Wait(); err != nil { + return fmt.Errorf("failed to hydrate config: %w", err) + } + + for _, hook := range c.hooks { + hook() + } + + return nil } diff --git a/hydrate_test.go b/hydrate_test.go index c29d0e0..3058a15 100644 --- a/hydrate_test.go +++ b/hydrate_test.go @@ -20,6 +20,7 @@ type config struct { type service struct { URL string Auth string + Pass string } type db struct { @@ -75,7 +76,9 @@ func TestReplacePlaceholdersWithSecrets(t *testing.T) { JWTSecrets: []string{"$SECRET:jwtSecretV2", "$SECRET:jwtSecretV1"}, Services: map[string]service{ "a": { + URL: "http://localhost:8000", Auth: "$SECRET:auth", + Pass: "$SECRET:jwtSecretV2", }, }, }, @@ -98,7 +101,9 @@ func TestReplacePlaceholdersWithSecrets(t *testing.T) { }, Services: map[string]service{ "a": { + URL: "http://localhost:8000", Auth: "auth-secret", + Pass: "changeme-now", }, }, },