[x] Handle team members in such a way that ordering does not matter. To do this, I had to remove the deprecated blocks and replace it with the unordered SetNestedAttributetype.THIS IS A BREAKING CHANGE!
[ ] TODO: write a follow up ticket for us to stop using blocks since they are deprecated and order sensitive, which is generally really bad for UX since it causes infinite update loops.
[x] Add logic so that team members that were never part of the terraform plan OR terraform state are not considered by terraform. This effectively allows customers to manage some users on a team in terraform and some users separately from terraform (like from web)
[x] Remove TeamMember, convertTeamMember since those are duplicates for a model that's already defined in terraform.
[x] Add in docs that the two possible roles are contributor and manager (this is not an enum!)
[x] Make a changie entry
[x] Tophatting
Tophatting
main.tf
# main.tf
resource "opslevel_team" "team_1" {
name = "Team One"
parent = "team_platform3"
# is required, can be []
member = [
{
email = "kyle+pat@opslevel.com"
role = "manager"
},
{
email = "taimoor+pat@opslevel.com"
role = "manager"
},
]
}
For each step, I am making sure to run terraform plan afterwards to check for infinite loops.
Create a team with no members, no parent
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# opslevel_team.team_1 will be created
+ resource "opslevel_team" "team_1" {
+ id = (known after apply)
+ last_updated = (known after apply)
+ member = []
+ name = "Team One"
}
Plan: 1 to add, 0 to change, 0 to destroy.
opslevel_team.team_1: Creating...
opslevel_team.team_1: Creation complete after 0s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Assign 2x members and a parent (use primary alias)
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
+ last_updated = (known after apply)
~ member = [
+ {
+ email = "kyle+pat@opslevel.com"
+ role = "manager"
},
+ {
+ email = "taimoor+pat@opslevel.com"
+ role = "manager"
},
]
name = "Team One"
+ parent = "team_platform3"
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
opslevel_team.team_1: Modifications complete after 0s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Unassign 1x member and set the same parent, but with non-default alias
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
+ last_updated = (known after apply)
~ member = [
- {
- email = "kyle+pat@opslevel.com" -> null
- role = "manager" -> null
},
# (1 unchanged element hidden)
]
name = "Team One"
~ parent = "team_platform3" -> "team_platform_3_a"
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
opslevel_team.team_1: Modifications complete after 0s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Unassign 1x remaining member and unset the parent
opslevel_team.team_1: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
+ last_updated = (known after apply)
~ member = [
- {
- email = "taimoor+pat@opslevel.com" -> null
- role = "manager" -> null
},
]
name = "Team One"
- parent = "team_platform_3_a" -> null
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
opslevel_team.team_1: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
State:
# opslevel_team.team_1:
resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
last_updated = "Thursday, 09-May-24 15:25:56 EDT"
member = []
name = "Team One"
}
Add 1x member on the website - should not be visible.
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
+ last_updated = (known after apply)
name = "Team One"
- parent = "team_platform_3_a" -> null
# (1 unchanged attribute hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
opslevel_team.team_1: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
State:
# opslevel_team.team_1:
resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
last_updated = "Thursday, 09-May-24 15:39:22 EDT"
member = []
name = "Team One"
}
Add back in 2 members in terraform
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
+ last_updated = (known after apply)
~ member = [
+ {
+ email = "kyle+pat@opslevel.com"
+ role = "manager"
},
+ {
+ email = "taimoor+pat@opslevel.com"
+ role = "manager"
},
]
name = "Team One"
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
opslevel_team.team_1: Modifications complete after 0s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
State:
# opslevel_team.team_1:
resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg2NQ"
last_updated = "Thursday, 09-May-24 15:40:21 EDT"
member = [
{
email = "kyle+pat@opslevel.com"
role = "manager"
},
{
email = "taimoor+pat@opslevel.com"
role = "manager"
},
]
name = "Team One"
}
Remove ALL 2 members in web 1 from terraform, 1 from web - should see terraform realize one of the TF managed members was realized
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
+ last_updated = (known after apply)
~ member = [
+ {
+ email = "kyle+pat@opslevel.com"
+ role = "manager"
},
# (1 unchanged element hidden)
]
name = "Team One"
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
opslevel_team.team_1: Modifications complete after 0s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
State:
# opslevel_team.team_1:
resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
last_updated = "Friday, 10-May-24 13:22:17 EDT"
member = [
{
email = "kyle+pat@opslevel.com"
role = "manager"
},
{
email = "taimoor+pat@opslevel.com"
role = "manager"
},
]
name = "Team One"
}
Add web member to terraform, also flip terraform member roles from manager to contributors
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
+ last_updated = (known after apply)
~ member = [
- {
- email = "kyle+pat@opslevel.com" -> null
- role = "manager" -> null
},
- {
- email = "taimoor+pat@opslevel.com" -> null
- role = "manager" -> null
},
+ {
+ email = "david+pat@opslevel.com"
+ role = "contributor"
},
+ {
+ email = "kyle+pat@opslevel.com"
+ role = "contributor"
},
+ {
+ email = "taimoor+pat@opslevel.com"
+ role = "contributor"
},
]
name = "Team One"
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
opslevel_team.team_1: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
State:
# opslevel_team.team_1:
resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
last_updated = "Friday, 10-May-24 13:23:43 EDT"
member = [
{
email = "david+pat@opslevel.com"
role = "contributor"
},
{
email = "kyle+pat@opslevel.com"
role = "contributor"
},
{
email = "taimoor+pat@opslevel.com"
role = "contributor"
},
]
name = "Team One"
}
Set 2x in web to be a manager, TF should detect this and make them contributors again.
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
+ last_updated = (known after apply)
~ member = [
+ {
+ email = "david+pat@opslevel.com"
+ role = "contributor"
},
+ {
+ email = "kyle+pat@opslevel.com"
+ role = "contributor"
},
# (1 unchanged element hidden)
]
name = "Team One"
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
opslevel_team.team_1: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
State:
# opslevel_team.team_1:
resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
last_updated = "Friday, 10-May-24 13:25:54 EDT"
member = [
{
email = "david+pat@opslevel.com"
role = "contributor"
},
{
email = "kyle+pat@opslevel.com"
role = "contributor"
},
{
email = "taimoor+pat@opslevel.com"
role = "contributor"
},
]
name = "Team One"
}
Remove all members using terraform
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# opslevel_team.team_1 will be updated in-place
~ resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
+ last_updated = (known after apply)
~ member = [
- {
- email = "david+pat@opslevel.com" -> null
- role = "contributor" -> null
},
- {
- email = "kyle+pat@opslevel.com" -> null
- role = "contributor" -> null
},
- {
- email = "taimoor+pat@opslevel.com" -> null
- role = "contributor" -> null
},
]
name = "Team One"
}
Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_team.team_1: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
opslevel_team.team_1: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
State:
# opslevel_team.team_1:
resource "opslevel_team" "team_1" {
id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw"
last_updated = "Friday, 10-May-24 13:26:53 EDT"
member = []
name = "Team One"
}
Destroy
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# opslevel_team.team_1 will be destroyed
- resource "opslevel_team" "team_1" {
- id = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw" -> null
- member = [] -> null
- name = "Team One" -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
opslevel_team.team_1: Destroying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xODg3Mw]
opslevel_team.team_1: Destruction complete after 0s
Destroy complete! Resources: 1 destroyed.
Issues
https://github.com/OpsLevel/terraform-provider-opslevel/issues/183
Approach
Create()
, add all members from the TF plan to the team and to the TF state.Read()
, read only the members that are defined in the TF state.Update()
, add all members from the TF plan, remove all members in the TF state but not in the TF plan.Changelog
opslevel_service
owner
field: https://github.com/OpsLevel/terraform-provider-opslevel/pull/333SetNestedAttribute
type. THIS IS A BREAKING CHANGE!TeamMember
,convertTeamMember
since those are duplicates for a model that's already defined in terraform.contributor
andmanager
(this is not an enum!)changie
entryTophatting
main.tf
For each step, I am making sure to run
terraform plan
afterwards to check for infinite loops.Create a team with no members, no parent
Assign 2x members and a parent (use primary alias)
Unassign 1x member and set the same parent, but with non-default alias
Unassign 1x remaining member and unset the parent
State:
Add 1x member on the website - should not be visible.
State:
Add back in 2 members in terraform
State:
Remove ALL 2 members in web 1 from terraform, 1 from web - should see terraform realize one of the TF managed members was realized
State:
Add web member to terraform, also flip terraform member roles from manager to contributors
State:
Set 2x in web to be a manager, TF should detect this and make them contributors again.
State:
Remove all members using terraform
State:
Destroy