diff --git a/engine.go b/engine.go index cb55e653..beb206fe 100644 --- a/engine.go +++ b/engine.go @@ -997,6 +997,8 @@ func expandConfigStrings(resolver *secrets.MultiResolver, cfg map[string]any) { if expanded, err := resolver.Expand(ctx, s); err == nil { val[i] = expanded } + } else if m, ok := item.(map[string]any); ok { + expandConfigStrings(resolver, m) } } } diff --git a/engine_secrets_test.go b/engine_secrets_test.go index a73518c2..4f4b8d12 100644 --- a/engine_secrets_test.go +++ b/engine_secrets_test.go @@ -115,6 +115,86 @@ func TestExpandConfigStrings_ArrayValues(t *testing.T) { } } +func TestExpandConfigStrings_ArrayOfMaps(t *testing.T) { + t.Setenv("OAUTH_CLIENT_ID_1", "test1") + t.Setenv("OAUTH_CLIENT_SECRET_1", "secret1") + t.Setenv("OAUTH_CLIENT_ID_2", "test2") + t.Setenv("OAUTH_CLIENT_SECRET_2", "secret2") + + resolver := secrets.NewMultiResolver() + cfg := map[string]any{ + "clients": []any{ + map[string]any{ + "clientId": "${OAUTH_CLIENT_ID_1}", + "clientSecret": "${OAUTH_CLIENT_SECRET_1}", + }, + map[string]any{ + "clientId": "${OAUTH_CLIENT_ID_2}", + "clientSecret": "${OAUTH_CLIENT_SECRET_2}", + }, + }, + } + + expandConfigStrings(resolver, cfg) + + clients := cfg["clients"].([]any) + if len(clients) != 2 { + t.Fatalf("expected 2 clients, got %d", len(clients)) + } + + client1 := clients[0].(map[string]any) + if client1["clientId"] != "test1" { + t.Errorf("expected first clientId 'test1', got %v", client1["clientId"]) + } + if client1["clientSecret"] != "secret1" { + t.Errorf("expected first clientSecret 'secret1', got %v", client1["clientSecret"]) + } + + client2 := clients[1].(map[string]any) + if client2["clientId"] != "test2" { + t.Errorf("expected second clientId 'test2', got %v", client2["clientId"]) + } + if client2["clientSecret"] != "secret2" { + t.Errorf("expected second clientSecret 'secret2', got %v", client2["clientSecret"]) + } +} + +func TestExpandConfigStrings_DeeplyNestedArrayOfMaps(t *testing.T) { + t.Setenv("SCOPE1", "read") + t.Setenv("SCOPE2", "write") + + resolver := secrets.NewMultiResolver() + cfg := map[string]any{ + "providers": []any{ + map[string]any{ + "name": "oauth-provider", + "scopes": []any{"${SCOPE1}", "${SCOPE2}"}, + }, + }, + } + + expandConfigStrings(resolver, cfg) + + providers := cfg["providers"].([]any) + if len(providers) != 1 { + t.Fatalf("expected 1 provider, got %d", len(providers)) + } + provider := providers[0].(map[string]any) + scopes, ok := provider["scopes"].([]any) + if !ok { + t.Fatalf("expected scopes to be []any, got %T", provider["scopes"]) + } + if len(scopes) != 2 { + t.Fatalf("expected 2 scopes, got %d", len(scopes)) + } + if scopes[0] != "read" { + t.Errorf("expected first scope 'read', got %v", scopes[0]) + } + if scopes[1] != "write" { + t.Errorf("expected second scope 'write', got %v", scopes[1]) + } +} + func TestExpandConfigStrings_UnresolvablePreserved(t *testing.T) { resolver := secrets.NewMultiResolver() cfg := map[string]any{