Skip to content

Commit 5d4b7f1

Browse files
authored
[BB-1886] Add option to sync all environments (#37)
* add All internal environment * add environment resource type * environment resource cleanup * make environment a parent to role resource types * set parent resource for roles * remove environment resource * add base roles only once when custom roles are enabled * create a base role per environment * create a custom role per environment * rework grant role * cleanup * fix collaborator role grants * simplify role resource ID logic * fix error * added comments * fix role grant * simplify and fix role Grant() * fixup * debug logging * fix role Grant() contains duplicate roles for env * cleanup * update readme * fix role grant * convert workato-env to select field * convert datacenter config to select field
1 parent f7cd606 commit 5d4b7f1

File tree

6 files changed

+163
-106
lines changed

6 files changed

+163
-106
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Flags:
114114
-v, --version version for baton-workato
115115
--workato-api-key string required: Your workato API key ($BATON_WORKATO_API_KEY)
116116
--workato-data-center string Your workato data center (us, eu, jp, sg, au) default is 'us' see more on https://docs.workato.com/workato-api.html#base-url ($BATON_WORKATO_DATA_CENTER) (default "us")
117-
--workato-env string Your workato environment (dev, test, prod) default is 'dev' ($BATON_WORKATO_ENV) (default "dev")
117+
--workato-env string Your workato environment (dev, test, prod, all) default is 'dev' ($BATON_WORKATO_ENV) (default "dev")
118118
119119
Use "baton-workato [command] --help" for more information about a command.
120120
```

pkg/config/config.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,19 @@ var (
1313
field.WithIsSecret(true),
1414
)
1515

16-
WorkatoDataCenterFiekd = field.StringField(
16+
WorkatoDataCenterFiekd = field.SelectField(
1717
"workato-data-center",
18+
[]string{"us", "eu", "jp", "sg", "au", "il", "sandbox"},
1819
field.WithDisplayName("Data center"),
19-
field.WithDescription("Your workato data center (us, eu, jp, sg, au) default is 'us' see more on https://docs.workato.com/workato-api.html#base-url"),
20+
field.WithDescription("Your workato data center (us, eu, jp, sg, au, il, sandbox). See more on https://docs.workato.com/workato-api.html#base-url"),
2021
field.WithDefaultValue("us"),
2122
)
2223

23-
WorkatoEnv = field.StringField(
24+
WorkatoEnv = field.SelectField(
2425
"workato-env",
26+
[]string{"dev", "test", "prod", "all"},
2527
field.WithDisplayName("Environment"),
26-
field.WithDescription("Your workato environment (dev, test, prod) default is 'dev'"),
28+
field.WithDescription("Your workato environment (dev, test, prod, all)"),
2729
field.WithDefaultValue("dev"),
2830
)
2931

pkg/connector/client/helpers.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ var (
1616
// WorkatoDataCenters
1717
// https://docs.workato.com/workato-api.html#base-url
1818
WorkatoDataCenters = map[string]string{
19-
"us": "https://www.workato.com",
20-
"eu": "https://app.eu.workato.com",
21-
"jp": "https://app.jp.workato.com",
22-
"sg": "https://app.sg.workato.com",
23-
"au": "https://app.au.workato.com",
19+
"us": "https://www.workato.com",
20+
"eu": "https://app.eu.workato.com",
21+
"jp": "https://app.jp.workato.com",
22+
"sg": "https://app.sg.workato.com",
23+
"au": "https://app.au.workato.com",
24+
"il": "https://app.il.workato.com",
25+
"sandbox": "https://app.trial.workato.com",
2426
}
2527
)
2628

pkg/connector/collaborator.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func (o *collaboratorBuilder) Grants(ctx context.Context, resource *v2.Resource,
9696
}
9797

9898
for _, collaboratorRole := range collaboratorRoles {
99-
if collaboratorRole.EnvironmentType != o.env.String() {
99+
if o.env != workato.All && collaboratorRole.EnvironmentType != o.env.String() {
100100
l.Debug("Collaborator role environment type does not match, skipping",
101101
zap.String("environment", collaboratorRole.EnvironmentType),
102102
zap.String("collaborator_role_name", collaboratorRole.Name),
@@ -177,7 +177,7 @@ func (o *collaboratorBuilder) collaboratorRoleGrants(ctx context.Context, sessio
177177

178178
// Build for roles
179179
for _, role := range collaborator.Roles {
180-
if role.EnvironmentType != o.env.String() {
180+
if o.env != workato.All && role.EnvironmentType != o.env.String() {
181181
continue
182182
}
183183

@@ -190,21 +190,32 @@ func (o *collaboratorBuilder) collaboratorRoleGrants(ctx context.Context, sessio
190190
l.Error("failed to get base role %s", zap.String("role_name", role.RoleName), zap.Error(err))
191191
return nil, fmt.Errorf("failed to get base role: %w", err)
192192
}
193+
targetEnv, err := workato.EnvFromString(role.EnvironmentType)
194+
if err != nil {
195+
return nil, fmt.Errorf("failed to get target environment from role environment type: %w", err)
196+
}
197+
roleId := GetRoleResourceID(baseRole.RoleName, targetEnv, o.env)
193198
roleResource = &v2.Resource{
194199
Id: &v2.ResourceId{
195200
ResourceType: roleResourceType.Id,
196-
Resource: baseRole.RoleName,
201+
Resource: roleId,
197202
},
198203
}
199204
case !o.disableCustomRolesSync:
200205
customRole := getRoleByName(ctx, session, role.RoleName)
201206
if customRole == nil {
202207
return nil, fmt.Errorf("custom role %s not found", role.RoleName)
203208
}
209+
customRoleId := strconv.Itoa(customRole.Id)
210+
targetEnv, err := workato.EnvFromString(role.EnvironmentType)
211+
if err != nil {
212+
return nil, fmt.Errorf("failed to get target environment from role environment type: %w", err)
213+
}
214+
roleId := GetRoleResourceID(customRoleId, targetEnv, o.env)
204215
roleResource = &v2.Resource{
205216
Id: &v2.ResourceId{
206217
ResourceType: roleResourceType.Id,
207-
Resource: strconv.Itoa(customRole.Id),
218+
Resource: roleId,
208219
},
209220
}
210221
default:

pkg/connector/role.go

Lines changed: 106 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package connector
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
6-
"slices"
77
"strconv"
88

99
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
@@ -34,6 +34,14 @@ type roleBuilder struct {
3434
disableCustomRolesSync bool
3535
}
3636

37+
func GetRoleResourceID(roleId string, targetEnv workato.Environment, configEnv workato.Environment) string {
38+
// For backward compatibility, do not change the role IDs if the environment configuration is set to a specific environment.
39+
if configEnv != workato.All {
40+
return roleId
41+
}
42+
return fmt.Sprintf("%s-%s", roleId, targetEnv.String())
43+
}
44+
3745
func (o *roleBuilder) ResourceType(ctx context.Context) *v2.ResourceType {
3846
return roleResourceType
3947
}
@@ -47,6 +55,13 @@ func (o *roleBuilder) List(ctx context.Context, _ *v2.ResourceId, attr rs.SyncOp
4755

4856
var nextToken string
4957

58+
var envs []workato.Environment
59+
if o.env == workato.All {
60+
envs = workato.AllEnvironments()
61+
} else {
62+
envs = append(envs, o.env)
63+
}
64+
5065
if !o.disableCustomRolesSync {
5166
var roles []client.Role
5267
var err error
@@ -61,23 +76,28 @@ func (o *roleBuilder) List(ctx context.Context, _ *v2.ResourceId, attr rs.SyncOp
6176
return nil, nil, err
6277
}
6378

64-
for _, role := range roles {
65-
us, err := roleResource(&role)
66-
if err != nil {
67-
return nil, nil, err
79+
for _, targetEnv := range envs {
80+
for _, role := range roles {
81+
us, err := roleResource(&role, o.env, targetEnv)
82+
if err != nil {
83+
return nil, nil, err
84+
}
85+
rv = append(rv, us)
6886
}
69-
rv = append(rv, us)
7087
}
7188
}
7289

73-
// Add base roles
74-
for _, role := range workato.BaseRoles {
75-
us, err := workatoBaseRoleResource(&role)
76-
if err != nil {
77-
return nil, nil, err
90+
if nextToken == "" {
91+
// Add base roles
92+
for _, targetEnv := range envs {
93+
for _, role := range workato.BaseRoles {
94+
us, err := workatoBaseRoleResource(&role, o.env, targetEnv)
95+
if err != nil {
96+
return nil, nil, err
97+
}
98+
rv = append(rv, us)
99+
}
78100
}
79-
80-
rv = append(rv, us)
81101
}
82102

83103
return rv, &rs.SyncOpResults{
@@ -180,73 +200,62 @@ func (o *roleBuilder) Grants(ctx context.Context, resource *v2.Resource, attr rs
180200
return rv, nil, nil
181201
}
182202

183-
func (o *roleBuilder) Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) {
184-
// Grant a role to a collaborator
185-
if resource.Id.ResourceType == collaboratorResourceType.Id {
186-
grants := make([]*v2.Grant, 0)
187-
188-
roleName := entitlement.Resource.Id.Resource
189-
userID, err := strconv.Atoi(resource.Id.Resource)
190-
if err != nil {
191-
return nil, nil, err
192-
}
203+
func (o *roleBuilder) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) {
204+
if principal.Id.ResourceType != collaboratorResourceType.Id {
205+
return nil, nil, fmt.Errorf("grant not implemented for %s", principal.Id.ResourceType)
206+
}
193207

194-
collaborator, err := o.client.GetCollaboratorPrivileges(ctx, userID)
195-
if err != nil {
196-
return nil, nil, err
197-
}
208+
// Grant a role to a collaborator
209+
userID, err := strconv.Atoi(principal.Id.Resource)
210+
if err != nil {
211+
return nil, nil, err
212+
}
198213

199-
roles := toSimpleRole(collaborator)
214+
roleTrait, err := rs.GetRoleTrait(entitlement.Resource)
215+
if err != nil {
216+
return nil, nil, err
217+
}
218+
profile := roleTrait.GetProfile()
219+
if profile == nil {
220+
return nil, nil, fmt.Errorf("role profile not found")
221+
}
222+
roleName, ok := profile.AsMap()["name"].(string)
223+
if !ok {
224+
return nil, nil, fmt.Errorf("role name is missing or invalid")
225+
}
226+
environmentType, ok := profile.AsMap()["environment"].(string)
227+
if !ok {
228+
return nil, nil, fmt.Errorf("environment value is missing or invalid")
229+
}
200230

201-
newRole := client.SimpleRole{
231+
roles := []client.SimpleRole{
232+
{
202233
RoleName: roleName,
203-
EnvironmentType: o.env.String(),
204-
}
205-
206-
index := slices.IndexFunc(roles, func(other client.SimpleRole) bool {
207-
return other.Equals(newRole)
208-
})
209-
210-
if index >= 0 {
211-
return []*v2.Grant{}, annotations.New(&v2.GrantAlreadyExists{}), nil
212-
}
213-
214-
// Workato just accept one role per environment
215-
sameEnvIndex := slices.IndexFunc(roles, func(other client.SimpleRole) bool {
216-
return other.EnvironmentType == o.env.String()
217-
})
218-
219-
if sameEnvIndex >= 0 {
220-
roles[sameEnvIndex] = newRole
221-
} else {
222-
roles = append(roles, newRole)
223-
}
224-
225-
err = o.client.UpdateCollaboratorRoles(ctx, userID, roles)
226-
if err != nil {
227-
return nil, nil, err
228-
}
229-
230-
collaboratorId, err := rs.NewResourceID(collaboratorResourceType, userID)
231-
if err != nil {
232-
return nil, nil, err
233-
}
234-
235-
newGrant := grant.NewGrant(
236-
resource,
237-
collaboratorHasRoleEntitlement,
238-
collaboratorId,
239-
grant.WithGrantMetadata(map[string]interface{}{
240-
"environment_type": o.env.String(),
241-
}),
242-
)
243-
244-
grants = append(grants, newGrant)
234+
EnvironmentType: environmentType,
235+
},
236+
}
245237

246-
return grants, nil, nil
238+
l := ctxzap.Extract(ctx)
239+
rolesJSON, err := json.Marshal(roles)
240+
if err != nil {
241+
return nil, nil, err
242+
}
243+
l.Info("Updating collaborator roles", zap.Int("user_id", userID), zap.String("roles", string(rolesJSON)))
244+
err = o.client.UpdateCollaboratorRoles(ctx, userID, roles)
245+
if err != nil {
246+
return nil, nil, err
247247
}
248248

249-
return nil, nil, fmt.Errorf("grant not implemented for %s", resource.Id.ResourceType)
249+
newGrant := grant.NewGrant(
250+
entitlement.Resource,
251+
collaboratorHasRoleEntitlement,
252+
principal.Id,
253+
grant.WithGrantMetadata(map[string]interface{}{
254+
"environment_type": environmentType,
255+
}),
256+
)
257+
258+
return []*v2.Grant{newGrant}, nil, nil
250259
}
251260

252261
func (o *roleBuilder) Revoke(_ context.Context, grant *v2.Grant) (annotations.Annotations, error) {
@@ -262,10 +271,17 @@ func newRoleBuilder(client *client.WorkatoClient, env workato.Environment, disab
262271
}
263272
}
264273

265-
func roleResource(role *client.Role) (*v2.Resource, error) {
274+
func roleResource(role *client.Role, envConfig workato.Environment, targetEnv workato.Environment) (*v2.Resource, error) {
275+
if targetEnv == workato.All {
276+
return nil, fmt.Errorf("target environment %s is not supported for role resources", targetEnv.String())
277+
}
278+
279+
id := strconv.Itoa(role.Id)
280+
266281
profile := map[string]interface{}{
267-
"id": role.Id,
282+
"id": id,
268283
"name": role.Name,
284+
"environment": targetEnv.String(),
269285
"create_at": role.CreatedAt.String(),
270286
"inheritable": role.Inheritable,
271287
"updated_at": role.UpdatedAt.String(),
@@ -276,9 +292,9 @@ func roleResource(role *client.Role) (*v2.Resource, error) {
276292
}
277293

278294
ret, err := rs.NewRoleResource(
279-
role.Name,
295+
fmt.Sprintf("%s (%s)", role.Name, targetEnv.String()),
280296
roleResourceType,
281-
role.Id,
297+
GetRoleResourceID(id, targetEnv, envConfig),
282298
traits,
283299
)
284300
if err != nil {
@@ -288,20 +304,28 @@ func roleResource(role *client.Role) (*v2.Resource, error) {
288304
return ret, nil
289305
}
290306

291-
func workatoBaseRoleResource(role *workato.Role) (*v2.Resource, error) {
307+
// workatoBaseRoleResource creates a new role resource for a base role.
308+
// envConfig is the environment configured for the connector.
309+
// targetEnv is the environment to create the role resource for.
310+
func workatoBaseRoleResource(role *workato.Role, envConfig workato.Environment, targetEnv workato.Environment) (*v2.Resource, error) {
311+
if targetEnv == workato.All {
312+
return nil, fmt.Errorf("target environment %s is not supported for base roles", targetEnv.String())
313+
}
314+
292315
profile := map[string]interface{}{
293-
"id": role.RoleName,
294-
"name": role.RoleName,
316+
"id": role.RoleName,
317+
"name": role.RoleName,
318+
"environment": targetEnv.String(),
295319
}
296320

297321
traits := []rs.RoleTraitOption{
298322
rs.WithRoleProfile(profile),
299323
}
300324

301325
ret, err := rs.NewRoleResource(
302-
role.RoleName,
326+
fmt.Sprintf("%s (%s)", role.RoleName, targetEnv.String()),
303327
roleResourceType,
304-
role.RoleName,
328+
GetRoleResourceID(role.RoleName, targetEnv, envConfig),
305329
traits,
306330
)
307331
if err != nil {
@@ -310,12 +334,3 @@ func workatoBaseRoleResource(role *workato.Role) (*v2.Resource, error) {
310334

311335
return ret, nil
312336
}
313-
314-
func toSimpleRole(collaboratorRoles []*client.CollaboratorPrivilege) []client.SimpleRole {
315-
roles := make([]client.SimpleRole, 0)
316-
for _, role := range collaboratorRoles {
317-
roles = append(roles, role.SimpleRole())
318-
}
319-
320-
return roles
321-
}

0 commit comments

Comments
 (0)