grafana/apps/iam/pkg
Misi ffb4fea402
IAM: Persist team members on the Team resource (step 1) (#123468)
* IAM: Persist team members on the Team resource (step 1)

* Add shared CUE TeamMember + TeamPermission types and expose
  TeamSpec.Members as a list of those.
* team LegacyStore: hydrate spec.members on Get/List and reconcile
  add/update/delete deltas on Create/Update against the legacy team
  tables. Reconciliation runs in the same SQL transaction as the team
  row update; team-internal-ID resolution happens before BEGIN to avoid
  a SQLite self-deadlock when the write tx holds the file lock.
* Admission rejects duplicate members and flips of the immutable
  `external` flag so the error is consistent regardless of dual-write
  mode.
* Legacy CreateTeam now returns apierrors.NewAlreadyExists on a unique
  constraint violation so clients see a proper 409 instead of a 500
  wrapping the raw SQL error.
* Index member UIDs on the Team search document.
* Integration tests cover CRUD with members, hydration, and the new
  admission rules.

Scope of this commit is the data model + legacy store + index path.
The user-teams / team-members subresources, the enterprise team-group
synchronizer, and the legacy member-search adapter land in follow-up
steps on this branch.

* Regenerate openapi spec

* IAM: fix testdata path in team integration subtests

The team integration test file lives in `pkg/tests/apis/iam/team/`
while the shared testdata fixtures live one level up in
`pkg/tests/apis/iam/testdata/`. The new members/AlreadyExists subtests
referenced `testdata/…` instead of `../testdata/…`, so CI failed with
"no such file or directory" when loading the fixture. Use the correct
relative path and switch the helper deletions from `defer` to
`t.Cleanup` for consistency with the rest of the file.

* Fix frontend

* IAM: address review on team members path

* Create is now transactional. CreateTeamCommand gained MemberCreates
  and legacy.CreateTeam inserts the team row and all seed members in
  one WithTransaction; a failing member rolls back the team too,
  matching Update's atomicity.
* Update now maps team.ErrTeamMemberAlreadyAdded → apierrors.NewConflict
  so the concurrent-add race returns 409 instead of 500, matching the
  Create path.
* buildCreateCommand / buildUpdateCommand translate GetUserInternalID
  failures into apierrors.NewBadRequest so an unknown user UID in
  spec.members returns 400 rather than a 500.
* Replace the per-team hydration on List with a single bulk query via
  a new ListTeamBindingsQuery.TeamUIDs + ArgList in team_bindings_query.sql,
  backed by listTeamMembersForTeams. Drops the N+1.
* mapTeamPermission / toLegacyPermission use explicit switches with a
  default panic so adding a new permission variant upstream without
  updating the mapping fails loud instead of silently collapsing to
  "member".
* Clarify comments on CreateTeamCommand.MemberCreates and
  UpdateTeamCommand.Member{Deletes,Updates,Creates} to spell out the
  atomicity guarantee and which fields the caller must pre-resolve.
* Document the remaining TOCTOU gap between listAllTeamMembers and the
  write tx in Update, and why the race that matters (add-add) is
  covered by the unique-constraint → 409 path.

* IAM: integration test for Update 409 on concurrent member adds

Exercises the ErrTeamMemberAlreadyAdded → apierrors.NewConflict mapping
on the team Update path by racing 10 goroutines that each add the same
user to an empty team. The test asserts no goroutine ever receives a
500 (the pre-fix failure mode), at least one succeeds, and the final
team state contains the user exactly once — i.e. the race converges
through the 409/retry path rather than duplicating or corrupting rows.

* IAM: propagate permission-mapping errors and bulk team-member writes

* mapTeamPermission / toLegacyPermission / mapToTeamMember / toTeamObject
  now return (value, error) instead of panicking on unknown variants.
  diffMembers, buildCreateCommand, buildUpdateCommand, and the Get /
  List / Create / Update flows in team/store.go propagate the error so
  a corrupt SQL row or a new upstream variant surfaces as a 500 at the
  HTTP boundary instead of crashing the apiserver.
* legacy.CreateTeam and legacy.UpdateTeam now issue one bulk INSERT
  (multi-row VALUES) for MemberCreates and one bulk DELETE (… WHERE
  uid IN (...)) for MemberDeletes, replacing the per-row loops.
  DeleteTeamMembersBulkCommand is scoped by OrgID so a UID collision
  across orgs cannot remove the wrong row. Permission UPDATEs stay as
  one statement per row — each has a distinct target value and a
  CASE-WHEN bulk isn't worth the complexity today.
* New golden-SQL tests (single + many variants) cover the two bulk
  templates across mysql/postgres/sqlite.
2026-04-24 17:10:45 +02:00
..
apis IAM: Persist team members on the Team resource (step 1) (#123468) 2026-04-24 17:10:45 +02:00
app IAM: Remove folder reconciler from IAM app (#120731) 2026-03-19 14:45:58 -06:00