Skip to content

Commit 6d6e3fb

Browse files
author
Baton Admin
committed
chore: update connector skills via baton-admin
1 parent 5af6e1f commit 6d6e3fb

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# build-syncer
2+
3+
Implementing the ResourceSyncer interface.
4+
5+
---
6+
7+
## Interface Definition
8+
9+
```go
10+
type ResourceSyncer interface {
11+
ResourceType(ctx context.Context) *v2.ResourceType
12+
List(ctx context.Context, parentResourceID *v2.ResourceId, token *pagination.Token) (
13+
[]*v2.Resource, string, annotations.Annotations, error)
14+
Entitlements(ctx context.Context, resource *v2.Resource, token *pagination.Token) (
15+
[]*v2.Entitlement, string, annotations.Annotations, error)
16+
Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) (
17+
[]*v2.Grant, string, annotations.Annotations, error)
18+
}
19+
```
20+
21+
---
22+
23+
## Resource Type Definition
24+
25+
Define at package level for stability:
26+
27+
```go
28+
// pkg/connector/resource_types.go
29+
var (
30+
userResourceType = &v2.ResourceType{
31+
Id: "user",
32+
DisplayName: "User",
33+
Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_USER},
34+
}
35+
36+
groupResourceType = &v2.ResourceType{
37+
Id: "group",
38+
DisplayName: "Group",
39+
Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_GROUP},
40+
}
41+
)
42+
```
43+
44+
**Critical:** IDs must be stable across versions. Changing `Id` breaks grant history.
45+
46+
---
47+
48+
## User Builder Example
49+
50+
```go
51+
type userBuilder struct {
52+
client *client.Client
53+
}
54+
55+
func (u *userBuilder) ResourceType(ctx context.Context) *v2.ResourceType {
56+
return userResourceType
57+
}
58+
59+
func (u *userBuilder) List(ctx context.Context, parentID *v2.ResourceId,
60+
token *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
61+
62+
bag := &pagination.Bag{}
63+
if err := bag.Unmarshal(token.Token); err != nil {
64+
return nil, "", nil, err
65+
}
66+
67+
users, nextToken, err := u.client.ListUsers(ctx, bag.PageToken(), 100)
68+
if err != nil {
69+
return nil, "", nil, fmt.Errorf("baton-myservice: failed to list users: %w", err)
70+
}
71+
72+
var resources []*v2.Resource
73+
for _, user := range users {
74+
resource, err := rs.NewUserResource(
75+
user.Name,
76+
userResourceType,
77+
user.ID, // Stable, immutable ID
78+
[]rs.UserTraitOption{
79+
rs.WithEmail(user.Email, true),
80+
rs.WithStatus(mapStatus(user.Active)),
81+
rs.WithUserLogin(user.Username),
82+
},
83+
)
84+
if err != nil {
85+
return nil, "", nil, err
86+
}
87+
resources = append(resources, resource)
88+
}
89+
90+
nextPage, err := bag.NextToken(nextToken)
91+
if err != nil {
92+
return nil, "", nil, err
93+
}
94+
95+
return resources, nextPage, nil, nil
96+
}
97+
98+
func (u *userBuilder) Entitlements(ctx context.Context, resource *v2.Resource,
99+
token *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
100+
// Users don't offer entitlements - they receive grants
101+
return nil, "", nil, nil
102+
}
103+
104+
func (u *userBuilder) Grants(ctx context.Context, resource *v2.Resource,
105+
token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
106+
// Users don't have grants on them - they receive grants elsewhere
107+
return nil, "", nil, nil
108+
}
109+
```
110+
111+
---
112+
113+
## Group Builder Example
114+
115+
Groups offer entitlements and have grants:
116+
117+
```go
118+
func (g *groupBuilder) Entitlements(ctx context.Context, resource *v2.Resource,
119+
token *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
120+
121+
entitlement := sdkEntitlement.NewAssignmentEntitlement(
122+
resource,
123+
"member",
124+
sdkEntitlement.WithDisplayName("Member"),
125+
sdkEntitlement.WithDescription("Member of "+resource.DisplayName),
126+
sdkEntitlement.WithGrantableTo(userResourceType),
127+
)
128+
129+
return []*v2.Entitlement{entitlement}, "", nil, nil
130+
}
131+
132+
func (g *groupBuilder) Grants(ctx context.Context, resource *v2.Resource,
133+
token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
134+
135+
groupID := resource.Id.Resource
136+
137+
bag := &pagination.Bag{}
138+
if err := bag.Unmarshal(token.Token); err != nil {
139+
return nil, "", nil, err
140+
}
141+
142+
members, nextToken, err := g.client.GetGroupMembers(ctx, groupID, bag.PageToken())
143+
if err != nil {
144+
return nil, "", nil, fmt.Errorf("baton-myservice: failed to get group members: %w", err)
145+
}
146+
147+
var grants []*v2.Grant
148+
for _, member := range members {
149+
grant := sdkGrant.NewGrant(
150+
resource,
151+
"member",
152+
&v2.ResourceId{
153+
ResourceType: "user",
154+
Resource: member.UserID,
155+
},
156+
)
157+
grants = append(grants, grant)
158+
}
159+
160+
nextPage, err := bag.NextToken(nextToken)
161+
if err != nil {
162+
return nil, "", nil, err
163+
}
164+
165+
return grants, nextPage, nil, nil
166+
}
167+
```
168+
169+
---
170+
171+
## Registering Syncers
172+
173+
```go
174+
// pkg/connector/connector.go
175+
func (c *Connector) ResourceSyncers(ctx context.Context) []connectorbuilder.ResourceSyncer {
176+
return []connectorbuilder.ResourceSyncer{
177+
newUserBuilder(c.client),
178+
newGroupBuilder(c.client),
179+
newRoleBuilder(c.client),
180+
}
181+
}
182+
```
183+
184+
---
185+
186+
## Key Points
187+
188+
- `ResourceType()` called once per sync to learn metadata
189+
- `List()` may be called multiple times for pagination
190+
- `Entitlements()` called once per resource instance
191+
- `Grants()` called once per resource instance, may paginate
192+
- Return empty string for nextToken when done paginating
193+
- Users typically have empty Entitlements() and Grants()
194+
- Groups/Roles have entitlements and grants

0 commit comments

Comments
 (0)