feat(google): Directory API client and reconcile engine#92
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 3 of the Sentinel-group → Google-Group sync feature. This is where the actual Google integration lands.
What's here
service/google.go): builds the Admin SDK Directory service fromGOOGLE_SERVICE_ACCOUNT(JSON key) with domain-wide delegation, impersonatingGOOGLE_ADMIN_SUBJECT, scopeadmin.directory.group.member. Member list (paginated) / insert / delete helpers; 409 (already member) and 404 (not a member) treated as success for idempotency.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 asMEMBER; removes onlyrole=MEMBERmembers not desired. OWNER/MANAGER are never touched (manually-added people). Reads core via/api/groups/:id/members+/api/core/entity/:id.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.GOOGLE_SYNC_INTERVAL, default 1h) + manual triggerPOST /google/reconcile(sentinel:all). Sweeps are serialized (a trigger during an in-flight sweep is dropped).GOOGLE_SERVICE_ACCOUNT/GOOGLE_ADMIN_SUBJECTunset, the client is nil, the cron is off, and sweeps no-op — the service still boots and serves binding CRUD. Env wired intoexample.env+ compose.Verification
google.golang.org/api@v0.286.0source (scope const,Members.List/Insert/Delete,Member.Role,googleapi.Error.Code) andgolang.org/x/oauth2(JWTConfigFromJSON,jwt.Config.Subject)go build ./...+go vet ./...+gofmtcleanNot in this PR
deploy.ymlwiring, kerbecs gateway route for inbound/api/google/*(PR 4)One-way model recap
Sentinel = source of truth. The Google Group's
role=MEMBERset is the sync's authoritative state; manual members live as OWNER/MANAGER and are left alone.