Skip to content

feat(cli): butleradm team member-role + group access rules - #56

Merged
atbagan merged 1 commit into
mainfrom
feat/team-access
Jun 11, 2026
Merged

atbagan merged 1 commit into
mainfrom
feat/team-access

Conversation

@atbagan

@atbagan atbagan commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Team member-role update + group access rules

Maps to audit P1 #7 (the last open P1 in the CLI/console gap audit). Completes CLI parity with the console for Team access management: butleradm team had add-member/remove-member but no way to change a member's role or manage group access rules.

Verbs (all CRD-direct, pure-CLI)

Verb What it does
team update-member-role NAME --email E --role R Change an existing member's role
team add-group NAME --group G --role R [--identity-provider IDP] Add a group-to-role access rule
team update-group-role NAME --group G --role R [--identity-provider IDP] Change a group rule's role
team remove-group NAME --group G [--identity-provider IDP] Remove a group rule

Mechanism

All four are CRD-direct edits to Team.spec.access, mirroring the existing add-member/remove-member: Get the Team, modify the targeted entry in the users/groups slice, SetNestedSlice, and Update the same object. Because the Update carries the same fetched object, status and resourceVersion are preserved without a separate merge step. No serverhttp, no auth-track exposure.

Field-path note: the user identifier in spec.access.users[] is the name field (it holds the email), not email.

Group identity is (name + optional identityProvider)

A group rule can be scoped to a specific IdP. update-group-role and remove-group match on name, plus identityProvider when --identity-provider is given. When multiple rules share a name and no --identity-provider is given, they error and ask the operator to disambiguate rather than editing the wrong rule. This fails safe.

Decisions

  • IdP-existence validation on add-group: skipped for v1. The server validates the referenced IdP exists; the CLI is CRD-direct and the apiserver does not enforce it. An optional cheap IdP Get could be added later.
  • Group ambiguity: error, never guess.

Tests

  • editMemberRole preserves status + resourceVersion + the other member (mutation-checked: clobbering status/rv fails).
  • The ambiguity path errors and does not mutate when two rules share a name with no IdP given (mutation-checked: picking-first fails).
  • role validation, add-group dup-check (name+idp), group find/remove, not-found errors.
  • build (both binaries) + vet + team pkg tests green.

Pending: live E2E against butler-beta before merge.

Add four verbs to the butleradm team group, completing CLI parity with the
console for Team access management (previously add-member/remove-member only):

- team update-member-role NAME --email E --role R: change a member's role.
- team add-group NAME --group G --role R [--identity-provider IDP]: add a
  group-to-role access rule.
- team update-group-role NAME --group G --role R [--identity-provider IDP]:
  change a group rule's role.
- team remove-group NAME --group G [--identity-provider IDP]: remove a rule.

All are CRD-direct edits to spec.access (Get, modify the targeted slice entry,
Update the same object), so status and resourceVersion are preserved without a
separate merge. The user identifier in spec.access.users is the name field,
which holds the email.

A group rule is identified by name plus optional identityProvider. When more
than one rule shares a name and no --identity-provider is given, update-group-role
and remove-group error and ask the operator to disambiguate rather than editing
the wrong rule.

Maps to the last open P1 in the CLI/console gap audit.
@atbagan

atbagan commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Live E2E validation (butler-beta)

Full lifecycle exercised CRD-direct against the real management apiserver, on a throwaway team (e2e-p7-team), cleaned up to pre-state. The real platform-engineering team was untouched.

Step Result
pre-state team list PASS (1 real team captured, not touched)
create throwaway + add-member PASS (member added, team reconciled with populated status)
update-member-role (viewer -> admin) PASS (load-bearing): role changed, status preserved (phase=Ready, all status keys intact), resourceVersion incremented cleanly 118649996 -> 118650127 with no conflict, against the real apiserver
add-group + update-group-role (viewer -> operator) PASS
D2 ambiguity (two dupe rules on okta + google) PASS: remove-group --group dupe with no --identity-provider errored and left both rules intact; with --identity-provider google removed only that one
remove-group (single match, no idp) PASS
team delete + cleanup PASS (team terminated and removed, namespace gone, back to pre-state)

No verb failed live. The status/resourceVersion preservation on update-member-role and the fail-safe ambiguity error on remove-group both held against the real apiserver.

@atbagan
atbagan merged commit 89263e6 into main Jun 11, 2026
1 check passed
@atbagan
atbagan deleted the feat/team-access branch June 11, 2026 01:50
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