Skip to content

Commit 8088db9

Browse files
author
Baton Admin
committed
chore: update connector skills via baton-admin
1 parent 143dcc9 commit 8088db9

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# concepts-identifiers
2+
3+
How identifiers work in connectors. Four "ExternalId" concepts exist - only one matters.
4+
5+
---
6+
7+
## The Four ExternalId Concepts
8+
9+
| Name | Type | Used by C1? |
10+
|------|------|-------------|
11+
| `connector_v2.Resource.ExternalId` | proto field | **YES - provisioning** |
12+
| `ConnectorResource.ExternalId` | v1 model string | YES - v1 sync only |
13+
| `ConnectorV2Resource.ExternalID()` | method | YES - returns ResourceId |
14+
| `SourceConnectorIds[conn_id]` | map value | YES - sync + provisioning |
15+
16+
**Key insight:** The SDK's `WithExternalID()` function sets the native system identifier that provisioning operations need to call the target API. See "WithExternalID - REQUIRED for Provisioning" section below.
17+
18+
---
19+
20+
## What Actually Matters: ResourceId
21+
22+
When creating resources, the `objectID` parameter becomes the matching key:
23+
24+
```go
25+
// This is what matters for sync and provisioning
26+
resource, err := rs.NewUserResource(
27+
user.DisplayName, // displayName
28+
userResourceType, // type -> "user"
29+
user.ID, // objectID -> becomes ResourceId.Resource
30+
traitOptions,
31+
)
32+
```
33+
34+
The SDK serializes this as `"type::id"` (e.g., `"user::12345"`) and stores it in `SourceConnectorIds`.
35+
36+
---
37+
38+
## Sync Flow
39+
40+
```
41+
Connector: NewUserResource(name, type, "12345")
42+
|
43+
v
44+
Resource.Id = ResourceId{ResourceType: "user", Resource: "12345"}
45+
|
46+
v
47+
SDK: ResourceIDToString(Resource.Id) = "user::12345"
48+
|
49+
v
50+
C1 Database: AppResource.SourceConnectorIds["conn_id"] = "user::12345"
51+
```
52+
53+
---
54+
55+
## Provisioning Flow
56+
57+
```
58+
C1 Database: SourceConnectorIds["conn_id"] = "user::12345"
59+
|
60+
v
61+
SDK: ParseV2ExternalID("user::12345")
62+
|
63+
v
64+
ResourceId{ResourceType: "user", Resource: "12345"}
65+
|
66+
v
67+
Connector.Grant() receives this ResourceId
68+
```
69+
70+
---
71+
72+
## ID Stability Requirements
73+
74+
**IDs must be stable across syncs.** If you change how IDs are calculated:
75+
- C1 sees old resources as deleted
76+
- C1 sees new resources as created
77+
- Grant history is lost
78+
- Access reviews break
79+
80+
```go
81+
// WRONG: ID changes based on data availability
82+
id := user.Id
83+
if id == "" {
84+
id = user.Email // Different format!
85+
}
86+
87+
// WRONG: Using mutable values
88+
rs.NewUserResource(name, type, user.Email, ...) // Email can change
89+
90+
// CORRECT: Use immutable system ID
91+
rs.NewUserResource(name, type, user.Id, ...)
92+
```
93+
94+
---
95+
96+
## WithExternalID - REQUIRED for Provisioning
97+
98+
```go
99+
// ExternalId stores the native system identifier for provisioning
100+
rs.WithExternalID(&v2.ExternalId{
101+
Id: user.NativeAPIId, // The ID the target API expects
102+
Link: fmt.Sprintf("https://admin.example.com/users/%s", user.NativeAPIId),
103+
})
104+
```
105+
106+
**Why it matters:** ConductorOne assigns its own resource IDs (`match_baton_id`) that differ from the target system's native IDs. During provisioning, the SDK passes `ExternalId` to Grant/Revoke operations so the connector can call the target API.
107+
108+
**During sync:** Set `WithExternalID()` with the native identifier
109+
**During provisioning:** Retrieve via `principal.GetExternalId().Id`
110+
111+
```go
112+
// In Grant operation
113+
externalId := principal.GetExternalId()
114+
if externalId == nil {
115+
return nil, nil, fmt.Errorf("baton-myservice: principal missing external ID")
116+
}
117+
nativeUserID := externalId.Id // Use this to call target API
118+
```
119+
120+
---
121+
122+
## match_baton_id (Advanced)
123+
124+
For pre-created manual resources that need to merge with connector-discovered resources:
125+
126+
```go
127+
// Connector sets RawId annotation
128+
rs.WithAnnotation(&v2.RawId{Id: user.Email})
129+
```
130+
131+
C1 matches this against `AppResource.MatchBatonId` for manually-managed resources.
132+
133+
Most connectors don't need this. Only use when:
134+
- External resources are provisioned outside C1
135+
- HR systems create accounts before connectors discover them
136+
- Pre-staging resources before first sync

0 commit comments

Comments
 (0)