Skip to content

Introduce azdo team member command group #287

@tmeckel

Description

@tmeckel

Command Group Description

Azure DevOps teams are backed by security groups, but the Azure DevOps REST API does not expose direct "add/remove member" endpoints for a team. az do pipelines and az devops work around this by either wrapping the Graph API on the team's underlying security group, or omitting the commands entirely. azdo should fill this gap by introducing an opinionated team member subgroup that wraps graph.Client.AddMembership / RemoveMembership against the team's Identity.SubjectDescriptor, and moves the existing team list-member command into the new subgroup as team member list for consistent subgroup ergonomics.

This umbrella establishes the new azdo team member subgroup beneath the existing azdo team group (#221). It does NOT add a team-member admin subgroup (out of scope; team administrators are managed via a separate security group inside the team — see "Out of scope" below).

Locked Decisions

  • Hierarchy: azdo team (existing feat: Implement azdo team command group #221) → azdo team member (new) → 3 leaves (add, remove, list).
  • Subgroup location: internal/cmd/team/member/member.go defines NewCmd(ctx util.CmdContext) *cobra.Command with Use: "member <command>", Short: "Manage members of a team.", and wires add.NewCmd(ctx), remove.NewCmd(ctx), list.NewCmd(ctx).
  • Wired from: internal/cmd/team/team.go — replace cmd.AddCommand(listmember.NewCmd(ctx)) with cmd.AddCommand(member.NewCmd(ctx)); remove the listmember import.
  • Aliases on the subgroup itself: none (mirrors the security group membership subgroup convention).
  • No backward-compat alias for the old azdo team list-member form (clean move per user direction 2026-06-06).
  • Member-input acceptance: all 3 leaves accept user/group identifiers through the existing extensions.Client.ResolveSubject helper (internal/azdo/extensions/member_lookup.go:19), which already handles subject descriptors, email addresses, principal names, SIDs, and identity IDs.
  • Team target parsing: [ORGANIZATION/]PROJECT/TEAM (project-scoped; same parser as the existing team list-member and team show commands).
  • All 3 leaves are project-scoped (teams are project-scoped in AzDO).
  • No new SDK client, no new mocks: every primitive is already vendored and mocked.

Command Signature

Command Use Aliases Notes
add add [ORGANIZATION/]PROJECT/TEAM ["a"] Requires --user; idempotent (no error if already a member).
remove remove [ORGANIZATION/]PROJECT/TEAM ["r", "rm", "del", "d"] Requires --user; destructive with confirmation prompt unless --yes.
list list [ORGANIZATION/]PROJECT/TEAM ["members", "ls", "l"] MOVE of the existing team list-member (#220). Pure refactor, no behavior change.

Subgroup Wiring

  • internal/cmd/team/member/member.go registers all 3 leaves.
  • internal/cmd/team/team.go is updated to import the new member package and remove the listmember import.
  • The old internal/cmd/team/listmember/ directory is deleted after the move (refactor: Move azdo team list-member to azdo team member list #290) is verified (build + tests + lint pass).

Sub-Issues

Implementation Outline

  1. Implement the 3 leaves in order: feat: Implement azdo team member add command #288 (add) and feat: Implement azdo team member remove command #289 (remove) first, then refactor: Move azdo team list-member to azdo team member list #290 (list move) last (because refactor: Move azdo team list-member to azdo team member list #290's member.go file won't compile until feat: Implement azdo team member add command #288 and feat: Implement azdo team member remove command #289 exist).
  2. After all three leaves are merged, the umbrella implementation step is simply: cmd.AddCommand(member.NewCmd(ctx)) in team.go (replacing cmd.AddCommand(listmember.NewCmd(ctx))).
  3. Run go build ./..., make lint, go test ./..., and make docs.

Out of Scope (explicit non-goals)

  • Team administrator management (e.g. azdo team admin add/remove). Team administrators are managed via a separate security group inside the team; the API requires a different call path (mutating a separate "Administrators" group, not the team's primary group). If needed, file a follow-up issue titled Introduce azdo team admin command group that follows the same pattern. This is intentionally NOT bundled here to keep scope tight.
  • Group-as-member (adding an AzDO security group as a team member). The graph API supports it (subject kind = "Group"), but the existing extensionsClient.ResolveSubject already handles group descriptors transparently, so this works without a new code path. Documentation-only concern.
  • Bulk add/remove (e.g. --user user1,user2,user3). The single-user-per-call design matches the existing security group membership add precedent in the codebase; bulk can be added later as a non-breaking enhancement.
  • Project-level membership management (e.g. azdo project member add). Out of scope; the user explicitly asked about team members.

Why this is a new azdo capability (not in reference CLIs)

  • MS az devops team: only has create / delete / list / list-member / show / update. The official Microsoft Learn docs say: "There's no comparable command for adding users to a team or project."
  • Python AzDO Extension (Azure/azure-devops-cli-extension): 0 occurrences of add_team_member / remove_team_member.
  • AzDO MCP Server (microsoft/azure-devops-mcp): 0 dedicated team-member management tools.
  • Vendored Go SDK core client: has GetTeamMembersWithExtendedProperties but no add/remove methods. The actual mechanism is graph.Client.AddMembership / RemoveMembership on the team's underlying security group.

So this issue introduces a new azdo capability that is technically possible (via the Graph API) but not implemented in any of the three reference CLIs. The implementation is straightforward because the project already has extensionsClient.ResolveSubject for user resolution and core.Client.GetTeam to surface the team's group descriptor.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions