Skip to content

feat(google): Directory API client and reconcile engine#92

Merged
BK1031 merged 5 commits into
mainfrom
bk1031/google-reconcile-engine
Jun 23, 2026
Merged

feat(google): Directory API client and reconcile engine#92
BK1031 merged 5 commits into
mainfrom
bk1031/google-reconcile-engine

Conversation

@BK1031

@BK1031 BK1031 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

PR 3 of the Sentinel-group → Google-Group sync feature. This is where the actual Google integration lands.

What's here

  • Directory client (service/google.go): builds the Admin SDK Directory service from GOOGLE_SERVICE_ACCOUNT (JSON key) with domain-wide delegation, impersonating GOOGLE_ADMIN_SUBJECT, scope admin.directory.group.member. Member list (paginated) / insert / delete helpers; 409 (already member) and 404 (not a member) treated as success for idempotency.
  • Reconcile engine (service/group_sync.go): per binding, desired = lowercased emails of the Sentinel group's members (resolved via core); actual = the Google Group's members. Adds desired emails not present in any role as MEMBER; removes only role=MEMBER members not desired. OWNER/MANAGER are never touched (manually-added people). Reads core via /api/groups/:id/members + /api/core/entity/:id.
  • Authority guard: GOOGLE_SYNC_MAX_REMOVALS (default 25) — a per-group reconcile that wants to delete more than this skips removals and logs loudly (protects against draining a group when core returns an empty/partial set). Errors fetching membership abort that binding without touching Google.
  • Cron (GOOGLE_SYNC_INTERVAL, default 1h) + manual trigger POST /google/reconcile (sentinel:all). Sweeps are serialized (a trigger during an in-flight sweep is dropped).
  • Disabled-by-default: with GOOGLE_SERVICE_ACCOUNT/GOOGLE_ADMIN_SUBJECT unset, the client is nil, the cron is off, and sweeps no-op — the service still boots and serves binding CRUD. Env wired into example.env + compose.

Verification

  • API surface verified against the actual google.golang.org/api@v0.286.0 source (scope const, Members.List/Insert/Delete, Member.Role, googleapi.Error.Code) and golang.org/x/oauth2 (JWTConfigFromJSON, jwt.Config.Subject)
  • go build ./... + go vet ./... + gofmt clean

Not in this PR

  • Infra: k8s manifest + secrets (GOOGLE_SERVICE_ACCOUNT), deploy.yml wiring, kerbecs gateway route for inbound /api/google/* (PR 4)
  • Web UI for managing bindings (separate)

One-way model recap

Sentinel = source of truth. The Google Group's role=MEMBER set is the sync's authoritative state; manual members live as OWNER/MANAGER and are left alone.

BK1031 added 5 commits June 22, 2026 19:04
Add the Admin SDK Directory client (service account + domain-wide
delegation) and the one-way reconcile engine: per binding, mirror the
Sentinel group's member emails into the Google Group as role MEMBER.
The role=MEMBER set is the sync's authoritative state — OWNER/MANAGER
(manually added) are never touched. Includes a cron (GOOGLE_SYNC_INTERVAL),
a manual POST /google/reconcile trigger, idempotent add/remove (409/404
tolerated), and a GOOGLE_SYNC_MAX_REMOVALS guard against draining a group.

Google sync is disabled (no-op) when GOOGLE_SERVICE_ACCOUNT /
GOOGLE_ADMIN_SUBJECT are unset, so the service still boots and serves
binding CRUD.
Make GoogleSyncInterval (1h) and GoogleSyncMaxRemovals (25) hardcoded
consts instead of env vars — they're tuning knobs, not deploy config.
Register the sentinel-google upstream and /api/google/* route in
kerbecs.yaml so the binding/reconcile API is reachable through the
gateway.
Replace the skip-if-running sweep guard with the discord-style syncJob
(latest-wins): a new trigger cancels the in-flight sweep and runs a
fresh one with the latest state. ReconcileAll now returns on context
cancellation. Bump GoogleSyncMaxRemovals 25 -> 100.
Shorten GoogleSyncInterval to 15m for near-real-time membership
propagation without coupling core to the google service.
@BK1031 BK1031 merged commit d843c45 into main Jun 23, 2026
18 checks passed
@BK1031 BK1031 deleted the bk1031/google-reconcile-engine branch June 23, 2026 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant