From 17acf7b86f8081d11f332caf71028aeff5715a44 Mon Sep 17 00:00:00 2001 From: Alexander Amiri Date: Thu, 12 Mar 2026 10:26:57 +0100 Subject: [PATCH] =?UTF-8?q?Make=20group=20sync=20additive-only=20=E2=80=94?= =?UTF-8?q?=20never=20remove=20members=20automatically?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lambda: remove deletion logic from handle_sync_groups_and_heros, only add missing members. Members added manually via Google Admin UI are preserved. Extra members are logged but not removed. - provision-groups.py: remove implicit helter logic, all groups use explicit memberships from heros.yaml - sync-heros.py: always include helter in mapped groups This prevents accidental mass-removal of group members when heros.yaml is empty or incomplete. --- scripts/provision-groups.py | 16 ++++++---------- scripts/sync-heros.py | 2 ++ .../lambda-src/team_provisioner/handler.py | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/scripts/provision-groups.py b/scripts/provision-groups.py index a8ac129..08ae630 100644 --- a/scripts/provision-groups.py +++ b/scripts/provision-groups.py @@ -33,16 +33,12 @@ def resolve_memberships(groups, heros): for group in groups.get("groups", []): name = group["name"] - if name == "helter": - # All heroes are implicitly in helter - member_emails = [h["javabin_google_email"] for h in members_list if h.get("javabin_google_email")] - else: - # Heroes whose memberships list includes this group name - member_emails = [ - h["javabin_google_email"] - for h in members_list - if h.get("javabin_google_email") and name in (h.get("memberships") or []) - ] + # All groups use explicit memberships — no implicit membership + member_emails = [ + h["javabin_google_email"] + for h in members_list + if h.get("javabin_google_email") and name in (h.get("memberships") or []) + ] resolved = dict(group) resolved["members"] = member_emails diff --git a/scripts/sync-heros.py b/scripts/sync-heros.py index e2ae4dc..25fd0d2 100644 --- a/scripts/sync-heros.py +++ b/scripts/sync-heros.py @@ -190,6 +190,8 @@ def map_groups(groups_str): mapped.append(group_id) else: print(f" Warning: unknown group '{g}' — not in GROUP_MAP, skipping", file=sys.stderr) + # Always include helter — all heroes are helter members + mapped.append("helter") return sorted(set(mapped)) diff --git a/terraform/lambda-src/team_provisioner/handler.py b/terraform/lambda-src/team_provisioner/handler.py index 03cd06a..5253bd8 100644 --- a/terraform/lambda-src/team_provisioner/handler.py +++ b/terraform/lambda-src/team_provisioner/handler.py @@ -1247,19 +1247,22 @@ def handle_sync_groups_and_heros(event): ) added += 1 - # Remove stale members - removed = 0 - for email in current_emails - desired_emails: - member_key = urllib.parse.quote(email, safe="") - _google_api("DELETE", f"/groups/{group_key}/members/{member_key}", access_token) - removed += 1 + # Additive only — never remove members automatically. + # Members added manually via Google Admin UI are preserved. + # To remove members, use a separate explicit process. + extra = current_emails - desired_emails + if extra: + logger.info( + "Group %s has %d members not in YAML (preserved): %s", + google_email, len(extra), ", ".join(sorted(extra)[:5]), + ) gr["google"] = { "synced": True, "group": google_email, - "member_count": len(desired_emails), + "member_count": len(desired_emails | current_emails), "added": added, - "removed": removed, + "extra_preserved": len(extra), } except Exception as e: logger.error("Google group sync failed for %s: %s", name, e)