diff --git a/terraform/team-members-datadog/.terraform.lock.hcl b/terraform/team-members-datadog/.terraform.lock.hcl new file mode 100644 index 000000000..33c9bfbcf --- /dev/null +++ b/terraform/team-members-datadog/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/datadog/datadog" { + version = "3.30.0" + constraints = "3.30.0" + hashes = [ + "h1:oN61IZ1rl+rfzXw0UVVnmFZ/Ulk8bKBsALN6O+p5vqs=", + "zh:01fc43eae96ea3801d95f81e83f151e64ef91dc4b1aedd3ee857c073a891a05d", + "zh:169ca1d8ae0cbb613fa9a7039babffa0a9bc62200d5ac5a027e259b98d8df33e", + "zh:22d75dc6dd9fde39ca1b2b5ceac7026aed2ad801cf81a587dd93e25b94c56ca4", + "zh:4bccc0639a881d44be4e7e2868fe1e0dbc1f0a6ebad1e1e554808bc80979c02c", + "zh:6295eb99ff106e00da52dba699e0b7fcba8b4d0535eac26d61eb17ddde5c2c53", + "zh:68c7de436a0e33ae06e93a5e989445dcc944a102faaec7e9f84762688520bd3c", + "zh:7c3ab23ebe2269c9952a8e91ddc6b05889186c81bb472eed0fdd6cd730350d6f", + "zh:85508c9c377211bd44531824464bf81830badb47e02877b008fce5de02204639", + "zh:8bb6a27c39e7d81d5d8bae00a159aa6a9e665e365393e65602839268893da1cc", + "zh:97f542f3e33df80a245b9f85fb29582fd3598bca4be3ceafd8aa012ba15f88c2", + "zh:cc051226ba800ae5c73a219c775c4fd883203381a4baaa2b1e211545407e42f3", + "zh:cea8b53a4bba00b1c452af5bb5a16d46e56f9d04dacfbcf794397aea105e17c6", + "zh:d56c39142843e945a08b23da9b147817b9b67f8f293b6a8253cd932c095f61b4", + "zh:d7aa186ec0dff53ee776472c902685437d76abe31e9a6872d74c03f0ae25ff3b", + ] +} diff --git a/terraform/team-members-datadog/README.md b/terraform/team-members-datadog/README.md new file mode 100644 index 000000000..85cf4a4ea --- /dev/null +++ b/terraform/team-members-datadog/README.md @@ -0,0 +1,59 @@ +# Team Members on Datadog + +This Terraform configuration manages user accounts on Datadog. When a user is +added to [`users.tf`](users.tf), they get invited to join our Datadog account. + +## Usage + +Applying the Terraform configuration requires an API key as well as an app key. +Both can be found on the [Datadog API keys page][api-keys]. + +```shell +export DD_API_KEY="datadog-api-key" +export DD_APP_KEY="datadog-app-key" +terraform plan +``` + +### Add a user + +Adding a user is a two-step process. First, add the user to the `users.tf` file. +Then, add them to their respective team, referencing the user in `users.tf`. + +For example, `jdn` has first been added as a users in `users.tf`: + +```hcl +locals { + users = { + "jdn" = { + // ... + } + } +} +``` + +And then to the `infra` team in `infra.tf`: + +```hcl +locals { + infra = { + "jdn" = local.users.jdn + } +} +``` + +### Add a Team + +The easiest way to add a team is to copy an existing team and update its +resources. Go through the following steps before applying the configuration: + +1. At the top of the file, update the `locals` block to include the correct team + members. +2. Then update the `datadog_role` for the team and assign the appropriate + permissions to the team. +3. Update the `datadog_team` with the proper name and description. + +Then, register the team in `users.tf` in the `_do_not_use_all_teams` local. +Without this step, no team memberships will be assigned and users won't be +created. + +[api-keys]: https://app.datadoghq.com/organization-settings/api-keys diff --git a/terraform/team-members-datadog/_data.tf b/terraform/team-members-datadog/_data.tf new file mode 100644 index 000000000..58e29fae4 --- /dev/null +++ b/terraform/team-members-datadog/_data.tf @@ -0,0 +1,2 @@ +# Fetch all available permissions +data "datadog_permissions" "all" {} diff --git a/terraform/team-members-datadog/_terraform.tf b/terraform/team-members-datadog/_terraform.tf new file mode 100644 index 000000000..4e7036be4 --- /dev/null +++ b/terraform/team-members-datadog/_terraform.tf @@ -0,0 +1,22 @@ +// Configuration for Terraform itself. + +terraform { + required_version = "~> 1" + + required_providers { + datadog = { + source = "datadog/datadog" + version = "3.30.0" + } + } + + backend "s3" { + bucket = "rust-terraform" + key = "simpleinfra/team-members-datadog.tfstate" + region = "us-west-1" + dynamodb_table = "terraform-state-lock" + encrypt = true + } +} + +provider "datadog" {} diff --git a/terraform/team-members-datadog/crater.tf b/terraform/team-members-datadog/crater.tf new file mode 100644 index 000000000..89829f64f --- /dev/null +++ b/terraform/team-members-datadog/crater.tf @@ -0,0 +1,34 @@ +locals { + crater = { + "walter" = local.users.walter + } +} + +resource "datadog_role" "crater" { + name = "crater" + + dynamic "permission" { + for_each = toset([ + data.datadog_permissions.all.permissions.dashboards_write, + data.datadog_permissions.all.permissions.api_keys_read, + data.datadog_permissions.all.permissions.user_app_keys, + ]) + + content { + id = permission.value + } + } +} + +resource "datadog_team" "crater" { + name = "crater" + description = "The team maintaining crater" + handle = "crater" +} + +resource "datadog_team_membership" "crater" { + for_each = local.crater + + team_id = datadog_team.crater.id + user_id = datadog_user.users[each.key].id +} diff --git a/terraform/team-members-datadog/crates-io.tf b/terraform/team-members-datadog/crates-io.tf new file mode 100644 index 000000000..19d46fd6d --- /dev/null +++ b/terraform/team-members-datadog/crates-io.tf @@ -0,0 +1,37 @@ +locals { + crates_io = { + "adam" = local.users.adam + "tobias" = local.users.tobias + } +} + +resource "datadog_role" "crates_io" { + name = "crates.io" + + dynamic "permission" { + for_each = toset([ + data.datadog_permissions.all.permissions.logs_read_index_data, + data.datadog_permissions.all.permissions.logs_read_data, + data.datadog_permissions.all.permissions.logs_live_tail, + data.datadog_permissions.all.permissions.logs_read_archives, + data.datadog_permissions.all.permissions.dashboards_write, + ]) + + content { + id = permission.value + } + } +} + +resource "datadog_team" "crates_io" { + name = "crates.io" + description = "The team working on crates.io" + handle = "crates-io" +} + +resource "datadog_team_membership" "crates_io" { + for_each = local.crates_io + + team_id = datadog_team.crates_io.id + user_id = datadog_user.users[each.key].id +} diff --git a/terraform/team-members-datadog/foundation-board.tf b/terraform/team-members-datadog/foundation-board.tf new file mode 100644 index 000000000..ddbbe0599 --- /dev/null +++ b/terraform/team-members-datadog/foundation-board.tf @@ -0,0 +1,34 @@ +locals { + foundation_board = { + "nell" = local.users.nell + "peixin" = local.users.peixin + "seth" = local.users.seth + } +} + +resource "datadog_role" "board_member" { + name = "Board Member" + + dynamic "permission" { + for_each = toset([ + data.datadog_permissions.all.permissions.dashboards_write, + ]) + + content { + id = permission.value + } + } +} + +resource "datadog_team" "foundation_board" { + name = "Rust Foundation Board" + description = "The board of the Rust Foundation" + handle = "foundation-board" +} + +resource "datadog_team_membership" "foundation_board" { + for_each = local.foundation_board + + team_id = datadog_team.foundation_board.id + user_id = datadog_user.users[each.key].id +} diff --git a/terraform/team-members-datadog/foundation.tf b/terraform/team-members-datadog/foundation.tf new file mode 100644 index 000000000..966b040e8 --- /dev/null +++ b/terraform/team-members-datadog/foundation.tf @@ -0,0 +1,38 @@ +locals { + foundation = { + "adam" = local.users.adam + "jdn" = local.users.jdn + "joel" = local.users.joel + "paullenz" = local.users.paullenz + "rustfoundation" = local.users.rustfoundation + "tobias" = local.users.tobias + "walter" = local.users.walter + } +} + +resource "datadog_role" "foundation" { + name = "Rust Foundation" + + dynamic "permission" { + for_each = toset([ + data.datadog_permissions.all.permissions.dashboards_write, + ]) + + content { + id = permission.value + } + } +} + +resource "datadog_team" "foundation" { + name = "Rust Foundation" + description = "The staff of the Rust Foundation" + handle = "foundation" +} + +resource "datadog_team_membership" "foundation" { + for_each = local.foundation + + team_id = datadog_team.foundation.id + user_id = datadog_user.users[each.key].id +} diff --git a/terraform/team-members-datadog/infra-admins.tf b/terraform/team-members-datadog/infra-admins.tf new file mode 100644 index 000000000..c6919fedf --- /dev/null +++ b/terraform/team-members-datadog/infra-admins.tf @@ -0,0 +1,23 @@ +locals { + infra_admins = { + "admin" = local.users.admin + "jdn" = local.users.jdn + "joel" = local.users.joel + "mark" = local.users.mark + "pietro" = local.users.pietro + "rustfoundation" = local.users.rustfoundation + } +} + +resource "datadog_team" "infra_admins" { + name = "Infrastructure Admins" + description = "The infra-admins" + handle = "infra-admins" +} + +resource "datadog_team_membership" "infra_admins" { + for_each = local.infra_admins + + team_id = datadog_team.infra_admins.id + user_id = datadog_user.users[each.key].id +} diff --git a/terraform/team-members-datadog/infra.tf b/terraform/team-members-datadog/infra.tf new file mode 100644 index 000000000..aa4d668dd --- /dev/null +++ b/terraform/team-members-datadog/infra.tf @@ -0,0 +1,44 @@ +locals { + infra = { + "admin" = local.users.admin + "jakub" = local.users.jakub + "jdn" = local.users.jdn + "mark" = local.users.mark + "pietro" = local.users.pietro + } +} + +resource "datadog_role" "infra" { + name = "infra" + + dynamic "permission" { + for_each = toset([ + data.datadog_permissions.all.permissions.logs_read_index_data, + data.datadog_permissions.all.permissions.logs_read_data, + data.datadog_permissions.all.permissions.logs_live_tail, + data.datadog_permissions.all.permissions.logs_read_archives, + data.datadog_permissions.all.permissions.dashboards_write, + data.datadog_permissions.all.permissions.saved_views_write, + data.datadog_permissions.all.permissions.api_keys_read, + data.datadog_permissions.all.permissions.api_keys_write, + data.datadog_permissions.all.permissions.user_app_keys, + ]) + + content { + id = permission.value + } + } +} + +resource "datadog_team" "infra" { + name = "infra-team" + description = "The infra-team" + handle = "infra" +} + +resource "datadog_team_membership" "infra" { + for_each = local.infra + + team_id = datadog_team.infra.id + user_id = datadog_user.users[each.key].id +} diff --git a/terraform/team-members-datadog/users.tf b/terraform/team-members-datadog/users.tf new file mode 100644 index 000000000..e59da0c3a --- /dev/null +++ b/terraform/team-members-datadog/users.tf @@ -0,0 +1,126 @@ +locals { + users = { + "adam" = { + login = "adamharvey@rustfoundation.org" + name = "Adam Harvey" + } + "admin" = { + login = "admin@rust-lang.org" + name = "Rust Admin" + } + "jakub" = { + login = "berykubik@gmail.com" + name = "Jakub Beránek" + } + "jdn" = { + login = "jandavidnose@rustfoundation.org" + name = "Jan David Nose" + } + "joel" = { + login = "joelmarcey@rustfoundation.org" + name = "Joel Marcey" + } + "mark" = { + login = "mark.simulacrum@gmail.com" + name = "Mark Rousskov" + } + "nell" = { + login = "nells@microsoft.com" + name = "Nell Shamrell-Harrington" + } + "paullenz" = { + login = "paullenz@rustfoundation.org" + name = "Paul Lenz" + } + "peixin" = { + login = "peixin.hou@gmail.com" + name = "Peixin Hou" + } + "pietro" = { + login = "pietro@pietroalbini.org" + name = "Pietro Albini" + } + "rustfoundation" = { + login = "infra@rustfoundation.org" + name = "Rust Foundation Infrastructure" + } + "seth" = { + login = "smarkle.aws@gmail.com" + name = "Seth Markle" + } + "tobias" = { + login = "tobiasbieniek@rustfoundation.org" + name = "Tobias Bieniek" + } + "walter" = { + login = "walterpearce@rustfoundation.org" + name = "Walter Pearce" + } + } + + # This is a list of all users from all teams. When a user is part of multiple teams, this list will contain multiple + # entries for that user (one for each team). These entries will have different roles. + # + # Example: + # + # [ + # { "alice" = { login = "Alice", email = "alice@example.com", roles = ["crates.io"] } }, + # { "bob" = { login = "Bob", email = "bob@example.com", roles = ["Board Member"] } }, + # { "alice" = { login = "Alice", email = "alice@example.com", roles = ["Foundation Staff"] } }, + # ] + _do_not_use_all_teams = [ + { for name, user in local.crater : name => merge(user, { roles = [datadog_role.crater.name] }) }, + { for name, user in local.crates_io : name => merge(user, { roles = [datadog_role.crates_io.name] }) }, + { for name, user in local.foundation : name => merge(user, { roles = [datadog_role.foundation.name] }) }, + { for name, user in local.foundation_board : name => merge(user, { roles = [datadog_role.board_member.name] }) }, + { for name, user in local.infra : name => merge(user, { roles = [datadog_role.infra.name] }) }, + { for name, user in local.infra_admins : name => merge(user, { roles = ["Datadog Admin Role"] }) }, + ] + + # This is an intermediate list that contains a single entry per user, but only with the roles from the first team. + # The list is used in the next step to merge all roles for each user. + # Example: + # + # { + # "alice" = { login = "Alice", email = "alice@example.com", roles = ["crates.io"] }, + # "bob" = { login = "Bob", email = "bob@example.com", roles = ["Board Member"] } + # } + _do_not_use_all_users_with_single_role = merge(local._do_not_use_all_teams...) + + # This list contains a single entry per user, with all the roles that user has across all teams. + # + # Example: + # + # [ + # { "alice" = { login = "Alice", email = "alice@example.com", roles = ["crates.io", "Foundation Staff"] } }, + # { "bob" = { login = "Bob", email = "bob@example.com", roles = ["Board Member"] } }, + # ] + all_user_with_merged_roles = { + for name, user in local._do_not_use_all_users_with_single_role : name => { + login = user.login + name = user.name + roles = distinct(flatten([ + for team in local._do_not_use_all_teams : concat([ + for inner_name, user in team : user.roles if name == inner_name + ]) + ])) + } + } +} + +data "datadog_role" "role" { + for_each = toset(flatten(values({ + for index, user in local.all_user_with_merged_roles : user.login => user.roles + }))) + + filter = each.value +} + +resource "datadog_user" "users" { + for_each = local.all_user_with_merged_roles + + email = each.value.login + name = each.value.name + roles = [for role in each.value.roles : data.datadog_role.role[role].id] + send_user_invitation = true +}