Repository navigation
Partner teams: devzero_team, and a team_id on devzero_cluster that never moves a cluster - #31
Merged
Merged
Conversation
A team started from the provider's team: Create calls CreateTeam with the provider's team_id and turns invites_not_sent into a warning; Read fails rather than dropping a team it cannot read; Delete leaves the team; import by id. The values the team is started with are refused when changed, except after an import.
team_id is optional and computed, defaults to the provider's team, and a planned change of a known value is refused. Read fills it in for a state written without one, and fails instead of dropping a cluster that is not in the provider's own team. Import also takes <team_id>/<cluster_id>.
The devzero_team example is a partner's onboarding configuration: the provider as the partner's team with a service account's token, the customer's team, and the customer's cluster registered in it.
… the provider's own team alone, and the docs say what a destroy deletes
…n refuses a known new email beside an unknown one
…nd its docs say who stays after offboarding
There was a problem hiding this comment.
🟡 Changes recommended
Legacy-team lookup failures can remove existing clusters from state and trigger duplicate registration, while cross-team token-reset coverage and standalone team-rename guidance remain incomplete.
3 open issues
2 files not reviewed
internal/gen/api/v1/apiv1connect/team.connect.go: Generated fileinternal/gen/api/v1/user.pb.go: Generated file
What changed in this PR
Adds partner-customer onboarding to the Terraform provider through devzero_team and per-cluster team selection, with safeguards intended to preserve existing clusters.
Changes:
- Introduces team creation, import, refresh, and leave behavior.
- Adds cluster
team_id, team-scoped API calls, and cross-team imports. - Adds API bindings, lifecycle tests, and onboarding and offboarding documentation.
| File | Description |
|---|---|
Makefile |
Adds team and user API files to copy lists. |
internal/provider/team_resource.go |
Implements the customer-team resource lifecycle. |
internal/provider/team_resource_test.go |
Tests team lifecycle and create-only attributes. |
internal/provider/schema_model_consistency_test.go |
Includes the team schema/model checks. |
internal/provider/provider.go |
Registers the team resource and API client. |
internal/provider/fake_server_test.go |
Adds team-service support and team-scoped test state. |
internal/provider/fake_server_lifecycle_extra_test.go |
Enforces cluster team scoping in the fake backend. |
internal/provider/cluster_team_test.go |
Tests cluster team planning, routing, and imports. |
internal/provider/cluster_resource.go |
Adds team selection, change guards, and scoped imports. |
internal/proto/api/v1/user.proto |
Adds user message definitions required by the team API. |
internal/proto/api/v1/team.proto |
Adds team-service API definitions. |
internal/gen/api/v1/user.pb.go |
Adds generated user protobuf bindings. |
internal/gen/api/v1/apiv1connect/team.connect.go |
Adds generated team-service clients and handlers. |
examples/resources/devzero_team/resource.tf |
Demonstrates customer-team and cluster onboarding. |
examples/resources/devzero_team/import.sh |
Shows team import syntax. |
examples/resources/devzero_cluster/import.sh |
Adds cross-team import syntax and token warnings. |
docs/resources/team.md |
Documents team configuration and offboarding behavior. |
docs/resources/cluster.md |
Documents cluster team selection and imports. |
Review effort: Balanced
… to the imported team The fake's ResetClusterToken now answers NotFound in a team the cluster is not in, as its other cluster RPCs do. The new test imports <team_id>/<cluster_id> and runs the token-issuing Update; it fails if that reset goes to the provider's team.
…d block Without one, a standalone devzero_team starts a second team, sends its invites again and leaves the original; only a devzero_cluster whose team_id comes from the team turns the rename into a plan refusal.
…at its first refresh cannot find A state without team_id takes the provider's team at its first refresh. When the cluster is not found there, Read now fails and keeps state instead of dropping it, since the provider's team_id may have changed in the same run and the next apply would register a second cluster. A gone cluster whose stored team is the provider's is still dropped.
A removed block offered for a cluster or team that cannot be read is one that sets lifecycle { destroy = false }, on Terraform 1.9.3 or later, since an older one, or one that destroys, refreshes the object into the same error. The old-state refusal names -target for its refresh-only remedy and the new token a re-import issues. A plain <cluster_id> import stores the provider's team, so one that misses gets Terraform's own error rather than the old-state refusal. A test row reaches the no-cluster answer.
There was a problem hiding this comment.
🔵 Needs a closer look
Human validation is needed because the partner API is not yet in production and live acceptance tests have not verified cross-team authorization or the service-account LeaveTeam behavior underlying destroy semantics.
No open issues 🎉
3 resolved since last review
2 files not reviewed
internal/gen/api/v1/apiv1connect/team.connect.go: Generated fileinternal/gen/api/v1/user.pb.go: Generated file
Review effort: Balanced
MauroKinderknecht
approved these changes
Nov 4, 2026
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.

A partner team can start a DevZero team for each of its customers with
devzero_team, and register the customer's clusters there withdevzero_cluster.team_id, all from Terraform.Check that an upgrade leaves existing clusters alone: a state without
team_idplans no diff, and no change of team ever replaces or moves a cluster. Risk: medium, a published resource schema.Why
Partners install DevZero into their customers' clusters and want each customer in a team of its own, paid by the customer, set up end to end from Terraform with no clicks. The DevZero API now lets a partner team start such a team (
TeamService/CreateTeamwithteam_id), and lets the partner's service account act in it. This release is held until that API is in production.Read first
internal/provider/cluster_resource.go:team_idis optional, computed and kept from state; every call goes to the cluster's team; ModifyPlan refuses any change of team on an existing cluster; Read fails, rather than dropping the cluster, when one outside the provider's team is gone or one with no storedteam_idis not found; import takes<cluster_id>(the provider's team) or<team_id>/<cluster_id>.internal/provider/team_resource.go:devzero_teamstarts the team once; its starting values are create-only, Read never re-creates a lost team, Delete is the token owner's leave.docs/resources/team.md,docs/resources/cluster.mdandexamples/: drive it with a service account's token; what a destroy deletes; how to offboard a customer withremovedblocks.internal/provider/cluster_team_test.go,team_resource_test.go: plan rows run through the framework's provider server, as under Terraform.The rest: the test fake serves
TeamServiceand scopes clusters by team;internal/protoandinternal/gencopy DevZero'steam.protoanduser.protowith their generated code.Evidence
On
75c384c:go build,go vetandgo test -count=1 ./internal/provider/...pass;make generateleaves no diff; golangci-lint v2.12.2: 0 issues. Each new test fails under the bug it names (the token reset in the wrong team, the old Read's drop, a missed plain-id import's message).Left out
TF_ACC); the partner API is not in production yet.team_idinput ships separately, after this release, so the example usesdevzero_clusterdirectly.-refresh=falseon a state from the release beforeteam_id, a renamed cluster can report an inconsistent result after apply, and a destroy reports done while the cluster stays registered in its old team. Refresh, the default, avoids both.Review and detail
team_iddecodes it as null; the first refresh fills it with the provider's team, and an unsetteam_idkeeps its state value when the provider'steam_idlater changes.devzero_teamrenamed in configuration without amovedblock is refused at plan when adevzero_clustertakes itsteam_idfrom it, before anything is started or left: otherwise the apply would start a second team, send its invites again, and leave the customer's real team. A standalonedevzero_teamis not refused, and its docs say to add amovedblock.team_idyet, not found by its first refresh, fails and stays in state, since the provider's team may have changed in the same run; the error names the ways out:terraform state rm, aremovedblock withlifecycle { destroy = false }(Terraform 1.9.3 or later),terraform apply -refresh-only -target=<address>with the provider's oldteam_id, or a re-import as<team_id>/<cluster_id>.team_id = ""is refused at validate: it would register the cluster in the provider's team and taint it on every apply.devzero_teamis that person's leave, and the last member's leave deletes the team; DevZero refuses it in several cases (the docs name the likely ones).<team_id>/<cluster_id>import issues a new cluster token, which the cluster's agents then need (the docs say so).0.1.0 (Unreleased).team_idordevzero_teamcannot go back to an older provider version without removing them from configuration.