Skip to content

Commit 081e6c3

Browse files
authored
feat: update config to add account details with destination in workspaceConfig (#5753)
# Description Updated config processing in backend-config module to attach account details to the destination ## Linear Ticket https://linear.app/rudderstack/issue/INT-3447/integrating-workspace-config-to-rudder-server ## Security - [ ] The code changed/added as part of this pull request won't create any security issues with how the software is being used.
1 parent ef0a15e commit 081e6c3

File tree

5 files changed

+353
-11
lines changed

5 files changed

+353
-11
lines changed

backend-config/account_association.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package backendconfig
2+
3+
import "github.com/samber/lo"
4+
5+
// processAccountAssociations processes account configurations and merges them with their corresponding
6+
// account definitions. It then associates these merged accounts with destinations based on
7+
// their configuration settings.
8+
//
9+
// The process involves three main steps:
10+
// 1. Creating a map of account definitions for quick lookup
11+
// 2. Merging account configurations with their definitions
12+
// 3. Associating the merged accounts with destinations
13+
func (c *ConfigT) processAccountAssociations() {
14+
// Create a lookup map for account definitions using their names as keys
15+
accountDefMap := lo.SliceToMap(c.AccountDefinitions, func(accDef AccountDefinition) (string, AccountDefinition) {
16+
return accDef.Name, accDef
17+
})
18+
19+
// Create a map of accounts merged with their definitions
20+
// This combines the account-specific settings with the shared definition settings
21+
accountWithDefMap := lo.SliceToMap(c.Accounts, func(acc Account) (string, AccountWithDefinition) {
22+
return acc.Id, AccountWithDefinition{
23+
Id: acc.Id,
24+
Options: acc.Options,
25+
Secret: acc.Secret,
26+
AccountDefinition: accountDefMap[acc.AccountDefinitionName],
27+
}
28+
})
29+
30+
// Iterate through all sources and their destinations to set up account associations
31+
if len(accountWithDefMap) > 0 {
32+
for i := range c.Sources {
33+
for j := range c.Sources[i].Destinations {
34+
dest := &c.Sources[i].Destinations[j]
35+
c.setDestinationAccounts(dest, accountWithDefMap)
36+
}
37+
}
38+
}
39+
}
40+
41+
// setDestinationAccounts assigns accounts to a destination based on its configuration.
42+
// It handles two types of account associations:
43+
// 1. Regular account (rudderAccountId)
44+
// 2. Delete account (rudderDeleteAccountId)
45+
//
46+
// Parameters:
47+
// - dest: Pointer to the destination being configured
48+
// - accountMap: Map of available accounts that can be associated with destinations
49+
func (c *ConfigT) setDestinationAccounts(dest *DestinationT, accountMap map[string]AccountWithDefinition) {
50+
// Check and set the regular account if specified in the destination config
51+
if accountID, ok := dest.Config["rudderAccountId"].(string); ok {
52+
if account, exists := accountMap[accountID]; exists {
53+
dest.DeliveryAccount = &account
54+
}
55+
}
56+
57+
// Check and set the delete account if specified in the destination config
58+
if deleteAccountID, ok := dest.Config["rudderDeleteAccountId"].(string); ok {
59+
if account, exists := accountMap[deleteAccountID]; exists {
60+
dest.DeleteAccount = &account
61+
}
62+
}
63+
}
+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package backendconfig
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestAccountAssociations(t *testing.T) {
10+
t.Run("basic account merge", func(t *testing.T) {
11+
c := &ConfigT{
12+
Sources: []SourceT{
13+
{
14+
ID: "source-1",
15+
Destinations: []DestinationT{
16+
{
17+
ID: "dest-1",
18+
Config: map[string]interface{}{
19+
"rudderAccountId": "acc-1",
20+
},
21+
},
22+
},
23+
},
24+
},
25+
Accounts: []Account{
26+
{
27+
Id: "acc-1",
28+
AccountDefinitionName: "oauth-def",
29+
Options: map[string]interface{}{"key1": "value1"},
30+
Secret: map[string]interface{}{"secret1": "secretValue1"},
31+
},
32+
},
33+
AccountDefinitions: []AccountDefinition{
34+
{
35+
Name: "oauth-def",
36+
Config: map[string]interface{}{
37+
"OAuth": map[string]interface{}{
38+
"generateOAuthToken": true,
39+
"refreshTokenInDataplane": true,
40+
},
41+
},
42+
},
43+
},
44+
}
45+
46+
c.processAccountAssociations()
47+
48+
require.Equal(t, "acc-1", c.Sources[0].Destinations[0].DeliveryAccount.Id)
49+
require.Equal(t, map[string]interface{}{"key1": "value1"}, c.Sources[0].Destinations[0].DeliveryAccount.Options)
50+
require.Equal(t, map[string]interface{}{"secret1": "secretValue1"}, c.Sources[0].Destinations[0].DeliveryAccount.Secret)
51+
require.Equal(t, map[string]interface{}{
52+
"OAuth": map[string]interface{}{
53+
"generateOAuthToken": true,
54+
"refreshTokenInDataplane": true,
55+
},
56+
}, c.Sources[0].Destinations[0].DeliveryAccount.AccountDefinition.Config)
57+
})
58+
59+
t.Run("multiple destinations with same account", func(t *testing.T) {
60+
c := &ConfigT{
61+
Sources: []SourceT{
62+
{
63+
ID: "source-1",
64+
Destinations: []DestinationT{
65+
{
66+
ID: "dest-1",
67+
Config: map[string]interface{}{
68+
"rudderAccountId": "acc-1",
69+
},
70+
},
71+
{
72+
ID: "dest-2",
73+
Config: map[string]interface{}{
74+
"rudderAccountId": "acc-1",
75+
},
76+
},
77+
},
78+
},
79+
},
80+
Accounts: []Account{
81+
{
82+
Id: "acc-1",
83+
AccountDefinitionName: "oauth-def",
84+
Options: map[string]interface{}{"key1": "value1"},
85+
},
86+
},
87+
AccountDefinitions: []AccountDefinition{
88+
{
89+
Name: "oauth-def",
90+
Config: map[string]interface{}{"oauth": true},
91+
},
92+
},
93+
}
94+
95+
c.processAccountAssociations()
96+
97+
for _, dest := range c.Sources[0].Destinations {
98+
require.Equal(t, "acc-1", dest.DeliveryAccount.Id)
99+
require.Equal(t, AccountDefinition{
100+
Name: "oauth-def",
101+
Config: map[string]interface{}{"oauth": true},
102+
}, dest.DeliveryAccount.AccountDefinition)
103+
}
104+
})
105+
106+
t.Run("destination with delete account", func(t *testing.T) {
107+
c := &ConfigT{
108+
Sources: []SourceT{
109+
{
110+
ID: "source-1",
111+
Destinations: []DestinationT{
112+
{
113+
ID: "dest-1",
114+
Config: map[string]interface{}{
115+
"rudderDeleteAccountId": "acc-1",
116+
},
117+
},
118+
},
119+
},
120+
},
121+
Accounts: []Account{
122+
{
123+
Id: "acc-1",
124+
AccountDefinitionName: "oauth-def",
125+
Options: map[string]interface{}{"key1": "value1"},
126+
},
127+
},
128+
AccountDefinitions: []AccountDefinition{
129+
{
130+
Name: "oauth-def",
131+
Config: map[string]interface{}{"oauth": true},
132+
},
133+
},
134+
}
135+
136+
c.processAccountAssociations()
137+
138+
require.Equal(t, "acc-1", c.Sources[0].Destinations[0].DeleteAccount.Id)
139+
require.Equal(t, AccountDefinition{
140+
Name: "oauth-def",
141+
Config: map[string]interface{}{"oauth": true},
142+
}, c.Sources[0].Destinations[0].DeleteAccount.AccountDefinition)
143+
})
144+
145+
t.Run("destination with no account configuration", func(t *testing.T) {
146+
c := &ConfigT{
147+
Sources: []SourceT{
148+
{
149+
ID: "source-1",
150+
Destinations: []DestinationT{
151+
{
152+
ID: "dest-1",
153+
Config: map[string]interface{}{},
154+
},
155+
},
156+
},
157+
},
158+
Accounts: []Account{
159+
{
160+
Id: "acc-1",
161+
AccountDefinitionName: "oauth-def",
162+
},
163+
},
164+
AccountDefinitions: []AccountDefinition{
165+
{
166+
Name: "oauth-def",
167+
Config: map[string]interface{}{},
168+
},
169+
},
170+
}
171+
172+
c.processAccountAssociations()
173+
174+
require.Empty(t, c.Sources[0].Destinations[0].DeliveryAccount)
175+
require.Empty(t, c.Sources[0].Destinations[0].DeleteAccount)
176+
})
177+
178+
t.Run("non-existent account id", func(t *testing.T) {
179+
c := &ConfigT{
180+
Sources: []SourceT{
181+
{
182+
ID: "source-1",
183+
Destinations: []DestinationT{
184+
{
185+
ID: "dest-1",
186+
Config: map[string]interface{}{
187+
"rudderAccountId": "non-existent",
188+
},
189+
},
190+
},
191+
},
192+
},
193+
Accounts: []Account{
194+
{
195+
Id: "acc-1",
196+
AccountDefinitionName: "oauth-def",
197+
},
198+
},
199+
AccountDefinitions: []AccountDefinition{
200+
{
201+
Name: "oauth-def",
202+
Config: map[string]interface{}{},
203+
},
204+
},
205+
}
206+
207+
c.processAccountAssociations()
208+
209+
require.Empty(t, c.Sources[0].Destinations[0].DeliveryAccount)
210+
})
211+
212+
t.Run("non-existent account definition", func(t *testing.T) {
213+
c := &ConfigT{
214+
Sources: []SourceT{
215+
{
216+
ID: "source-1",
217+
Destinations: []DestinationT{
218+
{
219+
ID: "dest-1",
220+
Config: map[string]interface{}{
221+
"rudderAccountId": "acc-1",
222+
},
223+
},
224+
},
225+
},
226+
},
227+
Accounts: []Account{
228+
{
229+
Id: "acc-1",
230+
AccountDefinitionName: "non-existent-def",
231+
},
232+
},
233+
AccountDefinitions: []AccountDefinition{
234+
{
235+
Name: "oauth-def",
236+
Config: map[string]interface{}{},
237+
},
238+
},
239+
}
240+
241+
c.processAccountAssociations()
242+
243+
require.Equal(t, "acc-1", c.Sources[0].Destinations[0].DeliveryAccount.Id)
244+
require.Empty(t, c.Sources[0].Destinations[0].DeliveryAccount.AccountDefinition)
245+
})
246+
}

backend-config/namespace_config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ func (nc *namespaceConfig) getFromAPI(ctx context.Context) (map[string]ConfigT,
167167
workspace = &previousConfig
168168
} else {
169169
workspace.ApplyReplaySources()
170+
workspace.processAccountAssociations()
170171
}
171-
172172
// always set connection flags to true for hosted and multi-tenant warehouse service
173173
workspace.ConnectionFlags.URL = nc.cpRouterURL
174174
workspace.ConnectionFlags.Services = map[string]bool{"warehouse": true}

backend-config/single_workspace.go

+2
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func (wc *singleWorkspaceConfig) getFromAPI(ctx context.Context) (map[string]Con
118118
return conf, err
119119
}
120120
sourcesJSON.ApplyReplaySources()
121+
sourcesJSON.processAccountAssociations()
121122
workspaceID := sourcesJSON.WorkspaceID
122123

123124
wc.workspaceIDOnce.Do(func() {
@@ -152,6 +153,7 @@ func (wc *singleWorkspaceConfig) getFromFile() (map[string]ConfigT, error) {
152153
wc.logger.Infon("Read workspace config from JSON file")
153154
wc.workspaceID = workspaceID
154155
})
156+
configJSON.processAccountAssociations()
155157
conf[workspaceID] = configJSON
156158
return conf, nil
157159
}

0 commit comments

Comments
 (0)