Reputation: 577
I have a terraform script to manage members of my GitHub team. It works fine if:
Where the script is puzzling to me is when a team exits, it contains users, and a new user is being added. Instead of adding the new user to the team and thus what I expect to be an update in place operation, a destroy plus replace is done instead. Terraform will delete the original team [ that consisted of me and the user I added when the script was ran for the first time, when this team was also created for the first time ] create a new team and replace the second user with the this new third one [ the first user being me I am added by default my credentials are the ones being used to create the team in the first place ].
Concrete example : currently i have no teams, but i would like to add user A to test_team_1. I fill in these requirements and run terraform plan. Terraform lets me know that a team called test_team_1 will be created and to it we will add user A. On terraform apply I see a new team on GitHub called test_team_1 which has 2 members, me and user A.
Now i am asked to add user B to the same team. When I run terraform plan i am told that instead of updating the team to include one more user, test_team_1 will be deleted. Terraform further tells me that this is happening because the user B has a different username that user A and that this forces a replace operation to occur. If i now run terraform apply, the original test_team_1 is deleted, a new one created and now it has two members me and the new user B.
I need to make sure terraform can add more users overtime without having to delete the team and have only me, the creator, and the new user as members.
Code example:
Child Module :
module Test_Child_Module {
source = "./modules/GitHubAccessRequest"
owner = var.owner
team_name = ["test_team_1", "test_team_2"]
username = "user-a"
role = "member"
}
Parent Module Code
resource "github_team" "team" {
for_each = toset(var.team_name)
name = each.key
}
resource "github_team_membership" "some_team_membership" {
for_each = toset(var.team_name)
team_id = github_team.team[each.key].id
username = var.username
role = var.role
}
for a given user and list of teams, that user will be added to the team for a specific role, teams will be created if they dont exist.
Upvotes: 0
Views: 446
Reputation: 3056
I am not certain I fully understand how this code is being applied, but if it is the case that when you come to add user-b
you simply modify the username
param in the module definition, then the behaviour does make sense.
Terraform works on the principle that it will manage each resource during its lifetime. Therefore you cannot write your code to only describe an incremental change, it has to describe the entire state of your infrastructure. Each time you run Terraform, it will compare the entire state as described by your code, with the actual entire state, and then apply any differences.
As I am reading your code at the moment, you only ever have a single github_team_membership
resource per team, which you sometimes have the username
set to user-a
and sometimes is set to user-b
. Terraform will interpret that is a single github_team_membership
. I think you in fact want one per user, per team. To be more specific, if you have 2 teams with 2 members each, then your code needs to describe 4 github_team_membership
resources. If you have 10 in each, then you need 20 github_team_membership
resources. Each github_team_membership
resource describes the individual membership of a specific user.
If you are using a recent version of Terraform (and therefore can loop modules), this might be a better way to go, where the code iterates through each team, calling a module, which then iterates through each team member that should be a member of that team:
(Disclaimer: untested but hopefully gives you the idea)
main.tf
variable "owner" {}
variable "default_users" {
type = list(map(string))
default = [
{ name = "user-a", role = "member" },
{ name = "user-b", role = "member" }
]
}
variable "teams" {
type = map()
default = {
test_team_1 = {},
test_team_2 = {
users = [ { name = "user-a", role = "member" },
{ name = "user-c", role = "some_other_role" }
]
}
}
}
module "github_team" {
source = "./modules/GitHubTeam"
for_each = var.teams
owner = var.owner
team_name = each.key
users = lookup(each.value, "users", var.default_users)
}
Note: the lookup()
function will check a map for the specified key, and in the event it does not exist, return the 3rd param. So using this technique in the case of test_team_1
, var.default_users
will be returned, where as in the case of test_team_2
the custom supplied list of users will be used instead.
./modules/GitHubTeam/main.tf
variable "owner" {}
variable "team_name" {}
variable "users" {
type = list(map(string))
}
resource "github_team" "team" {
name = var.team_name
}
# Create one team membership per user passed in the users list
resource "github_team_membership" "team_membership" {
for_each = toset(var.users)
team_id = github_team.team.id
username = each.value["name"]
role = each.value["role"]
}
Apologies if I have entirely misunderstood and this is no use.
Upvotes: 1