Terraform
Manage GitHub Users, Teams, and Repository Permissions
GitHub hosts code repositories so developers can build software for open-source and private projects in organizations. GitHub organization owners can control access to projects and data by managing their organization's settings, users, teams, and permissions. The Terraform GitHub provider allows you to programmatically manage your repositories, organization, teams, permissions, and projects.
Terraform provides several benefits over using the GitHub dashboard or CLI to manage your GitHub resources:
Safety and consistency Defining your resources as code reduces the risk of human error. With Terraform, you can version control your configuration and ensure that the proper reviewers approve changes before merging them. Terraform allows you to track and update changes as your organization grows.
Improved automation Terraform can create, update, and delete tracked resources without requiring you to manage dependencies. With Terraform, you can develop modules for your organization, repositories, teams, and users that comply with your organization's policies. For example, you can use Terraform to ensure that your organization's security team has access to every new GitHub repository.
In this tutorial, you will use Terraform and the GitHub provider to create resources and invite users to your GitHub organization as defined in CSV files. In the process, you will learn how to use Terraform to simplify and automate your workflows.
Prerequisites
For this tutorial, you will need:
- the Terraform 1.0.5+ CLI installed locally.
- a GitHub account.
- a GitHub organization that you own. You can use an existing organization since you will destroy all the resources provisioned in this tutorial at the end. Otherwise, follow these instructions to create a new organization.
Configure your credentials
The GitHub provider needs to authenticate to GitHub so it can manage its resources. In this tutorial, you will authenticate the provider using a personal access token.
Follow the instructions in GitHub's documentation to create a personal access token for Terraform. To complete this tutorial, your personal access token must have at least the following permissions:
- The
repo
permisison for full control of private repositories. - The
admin:org
permission for full control of orgs and teams, read and write org projects - The
delete_repo
permission to delete repositories
Tip
This token has access to your GitHub organization. Set an appropriate expiration date for your token, limit the token's scope to what is required, and save it in a safe place.
Your personal access token will work with Terraform until it expires.
Once you generate a personal access token, set it as an environment variable
named GITHUB_TOKEN
.
$ export GITHUB_TOKEN=
Then, create an environment variable named GITHUB_OWNER
and set it to your
GitHub organization. If you do not specify a Github organization, Terraform
will authenticate and manage resources on the access token user's individual
user account.
$ export GITHUB_OWNER=
Terraform will use these environment variables to authenticate with GitHub and manage your organization's resources.
Clone repository
In your terminal, clone the example repository , which contains Terraform configuration to define a GitHub repository and team.
$ git clone https://github.com/hashicorp-education/learn-terraform-github-user-teams
Change into the repository directory.
$ cd learn-terraform-github-user-teams
Terraform configuration consists of blocks of code written in HashiCorp Configuration Language (HCL). This configuration contains provider, local, data source, and resource blocks. You will learn more about each type of block as you review the configuration.
Review users and team data
Resource blocks are the primary way you interact with Terraform to manage a provider's objects. The example configuration references CSV files to set resource block arguments. This repository organizes the resource details across multiple CSV files.
$ tree
.
├── README.md
├── main.tf
├── members.csv
├── members.tf
├── repos-team
│ ├── api.csv
│ ├── application.csv
│ └── infrastructure.csv
├── repositories.tf
├── team-members
│ ├── developer-team.csv
│ ├── devops-team.csv
│ └── security-team.csv
├── teams.csv
├── teams.tf
└── versions.tf
There are two files at the top level of this directory:
- The
members.csv
file stores your organization members' data. - The
teams.csv
file stores your organization teams' data.
The configuration is further divided into subdirectories to manage the teams and repository membership:
The
team-members
directory contains CSV files that define team memberships. The CSV file name corresponds to the team name, and its contents contain the username and role of each team member.The
repos-team
directory contains CSV files that define repository and team permissions. The CSV file name corresponds to the repository name, and its contents contain the team name and permission for the relationship.
Using CSV files to populate Terraform resources and this specific file/directory structure is not required. However, this workflow enables you to simplify your Terraform configuration and better organize your resources.
Open members.csv
.
members.csv
username,role
alex-tf-edu,member
terraform-education1,member
The first line in the CSV file is the header. Each field in the header is a key for the corresponding value at the same position in the remaining rows. Each remaining row represents a user and must contain the same number of fields as the header. The header keys allow you to reference a specific user attribute in your Terraform configuration.
Review the other CSV files if you would like.
Initialize the configuration
Initialize the Terraform configuration, which
downloads and installs the github
provider. Later in this tutorial you will use
terraform console
,
which requires initialization, to inspect the configuration in more detail.
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of integrations/github from the dependency lock file
- Installing integrations/github v4.13.0...
- Installed integrations/github v4.13.0 (signed by a HashiCorp partner, key ID 38027F80D7FD5FB2)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Review configuration
Terraform parses all *.tf
files in your directory and
maintains a single state file that tracks the resources managed by the
configuration in the directory. Defining resources in separate files makes your
configuration easier to read and organize while still allowing you to define
dependencies across files.
Most of the Terraform configuration in this repository dynamically determines resource attributes from CSV files, so you will not need to modify most of the *.tf
files for the workflow in this tutorial. The only exception is the repositories.tf
file, which gives you more control over repository-level settings like permissions and access.
The example repository you cloned contains the following files:
The
versions.tf
file defines Terraform settings, including the required providers Terraform will use to provision your resources. Providers are plugins that Terraform uses to interact with APIs.The
main.tf
file defines your GitHub provider and retrieves user information about the owner of the personal access token.The
members.tf
file defines your organization's users. The resource block in this file manages the users specified inmembers.csv
.The
teams.tf
file defines your organization's teams and adds members to teams as defined in the CSV files.The
repositories.tf
file defines your organization's repositories. It also manages team permissions for the repositories as specified in the files in therepos-team
directory.The
locals.tf
file contains configuration that parses the team memberships and repository permissions defined in the CSV files.
Explore versions.tf
Open versions.tf
. This file contains a terraform {}
block that defines
Terraform settings, including the required providers Terraform will use to
provision your resources.
The source
attribute defines an optional hostname, a namespace, and the
provider type for each provider. Terraform installs providers from the
Terraform Registry by default.
You can also set a version constraint for each provider defined in the
required_providers
block. The version attribute is optional, but we recommend
using it to constrain the provider version so that Terraform does not install
a provider version that may not work with your configuration. If you do not
specify a provider version, Terraform will automatically download the most
recently released version during initialization.
versions.tf
terraform {
required_providers {
github = {
source = "integrations/github"
version = "4.13.0"
}
}
required_version = "~> 1.0.5"
}
Explore main.tf
Open main.tf
to review the Terraform configuration that defines your GitHub
provider and retrieves user information from the personal access
token-authenticated user.
Provider block
To use the GitHub provider, you must define a provider block for it in your configuration.
main.tf
provider "github" {}
The github
provider block is empty because it automatically uses the personal access token
and organization name you set as environment variables
earlier. You can configure other optional, provider-specific settings
in this block, like base URL (GitHub Enterprise).
Data source
A data source
block allows you to retrieve information defined outside of Terraform and
reference it in your configuration.
Data source blocks have two strings in the first line of the block: the data
source type and the name, which together form a data source ID. This
github_user
data source block is named self
, so the data source ID is
data.github_user.self
.
main.tf
data "github_user" "self" {
username = ""
}
The data.github_user.self
block retrieves information about the currently
authenticated user. The configuration will use this to assign your user to the
newly created Github teams.
Explore members.tf
Open members.tf
to review the Terraform configuration that defines your
organization's members. The resource block in this file manages all the users
specified in members.csv
. Terraform dynamically generates resources for
members with a for_each
meta-argument that iterates over the CSV — you do not
need to modify members.tf
to add or remove users.
This configuration lets you avoid creating unique resource blocks for each member, which can become difficult to manage as your organization scales.
Similarly to data sources, resource blocks have two strings in the first line
of the block: the resource type and the resource name, which together form a
resource ID. This github_membership
resource block is named all
, so the
resource ID is github_membership.all
.
members.tf
resource "github_membership" "all" {
for_each = {
for member in csvdecode(file("members.csv")) :
member.username => member
}
username = each.value.username
role = each.value.role
}
The for_each
meta-argument tells Terraform that this block defines multiple
members, each one mapped to a member in the members.csv
file. The
github_membership
resource takes a required username, and an optional
argument, role
, to specify which role this user will have in your
organization. If you do not assign a role, it defaults to member
.
The resource block references a specific user's information with
each.value.attribute_name
, where attribute_name
maps to the header row in
the CSV file. For example, each.value.username
maps to the username column.
Refer to the resource's documentation in Terraform Registry to find a complete list of supported arguments.
Note
You could also add users to your organization using the
github_team_membership
resource, but that would not allow you to remove the
user from the organization with the same workflow. Take note of the difference
when configuring production systems.
Explore teams.tf
Open teams.tf
to review the Terraform configuration that defines your
organization's teams. The resource blocks in this file manage the teams defined
in teams.csv
. The configuration also adds the currently authenticated user as
a team maintainer and assigns users to teams as specified by the*.csv
files
in the team-members/
directory. Terraform dynamically generates the resource
configurations for teams and their members based on the contents of teams.csv
each time you run a Terraform operation — you do not need to modify teams.tf
.
The github_team.all
resource block parses the teams.csv
file to create the
teams in your organization.
teams.tf
resource "github_team" "all" {
for_each = {
for team in csvdecode(file("teams.csv")) :
team.name => team
}
name = each.value.name
description = each.value.description
privacy = each.value.privacy
create_default_maintainer = true
}
The resource block uses the team name as the key in the for_each
meta-argument. This lets you reference the team attributes with the team name.
For example, you can query the security team's attributes with
github_team.all["Security Team"]
.
teams.tf
resource "github_team" "all" {
for_each = {
for team in csvdecode(file("teams.csv")) :
team.name => team
}
## ...
}
The github_team_membership.members
resource assigns members to teams as
defined in the respective team CSV files in /team-members
. This resource
references the local.team_members
value defined in the locals.tf
to
determine the resource arguments.
teams.tf
resource "github_team_membership" "members" {
for_each = { for tm in local.team_members : tm.name => tm }
team_id = each.value.team_id
username = each.value.username
role = each.value.role
}
This configuration uses the github_team.all
resource to generate the
team_members
local value, and team_members
local value to create the
github_team_membership
resource.
Terraform manages most resource dependencies for you, but using
dynamically-sized elements in for_each
is an edge case. Terraform cannot
determine the number of team membership resources to create since the number of
elements in local.team_members
is determined by the contents of your CSV
files. To address this, you must apply changes to the github_team
resource
before Terraform can update the team memberships, which you will do later in
this tutorial.
Explore repositories.tf
Open repositories.tf
. This configuration defines your
organization's repositories and sets team permissions for repositories
as specified in the repos-team
directory. Unlike members.tf
and teams.tf
,
this configuration defines each repository in individual resource blocks. This
gives you greater control over your repository resources.
The first two blocks define a repository named learn-tf-infrastructure
and
assign teams to the repository.
repositories.tf
# Create infrastructure repository
resource "github_repository" "infrastructure" {
name = "learn-tf-infrastructure"
}
# Add memberships for infrastructure repository
resource "github_team_repository" "infrastructure" {
for_each = {
for team in local.repo_teams_files["infrastructure"] :
team.team_name => {
team_id = github_team.all[team.team_name].id
permission = team.permission
} if lookup(github_team.all, team.team_name, false) != false
}
team_id = each.value.team_id
repository = github_repository.infrastructure.id
permission = each.value.permission
}
Note that the
github_team_repository.infrastructure
resource:
references the
local.repo_teams_files
value and specifies"infrastructure"
as the key. This populates the resource with data fromrepos-team/infrastructure.csv
. You will review how Terraform evaluates the local value in a later section.retrieves the team ID by referencing the
github_team.all
resource with the team name as the key.uses the
lookup
function to ensure the team exists in thegithub_team.all
resource.repositories.tf
resource "github_team_repository" "infrastructure" { for_each = { for team in local.repo_teams_files["infrastructure"] : team.team_name => { team_id = github_team.all[team.team_name].id permission = team.permission } if lookup(github_team.all, team.team_name, false) != false } ## ... }
The next two blocks define a repository named learn-tf-application
and assign
the application team to it. The github_team_repository.application
resource
references the local.repo_teams_files
value and specifies "application"
as
the key. This populates the resource with data from
repos-team/application.csv
.
repositories.tf
# Create application repository
resource "github_repository" "application" {
name = "learn-tf-application"
}
# Add memberships for application repository
resource "github_team_repository" "application" {
for_each = {
for team in local.repo_teams_files["application"] :
team.team_name => {
team_id = github_team.all[team.team_name].id
permission = team.permission
} if lookup(github_team.all, team.team_name, false) != false
}
team_id = each.value.team_id
repository = github_repository.application.id
permission = each.value.permission
}
Explore locals.tf
Open locals.tf
to review the Terraform configuration that parses your CSV
files into objects Terraform can use.
Local values
A locals
block
allows you to define values that you reference throughout your configuration.
Locals make your configuration easier to read and less repetitive. Terraform
evaluates local values independently of your resource definitions, so you can
experiment with local values before including them in your resource
configuration and keep any pre-processing separate.
The local values below parse the files that define team membership in the
team-members
directory, and the files that define repository permissions in
the repos-team
directory, into objects that are easier to use in your
Terraform configuration.
Team memberships - The
github_team_membership
resource inteams.tf
requires the team ID, username, and role arguments. The GitHub team names correspond to the file names in theteam-members
directory, and the file contents define each team's member's role.The following four local values parse the files in the
team-members
directory, retrieve the team's ID, and transform the data into an object thatgithub_team_membership
can use.The
team_members_path
local value defines the directory Terraform should look for the team membership CSV files.main.tf
locals { # Parse team member files team_members_path = "team-members" ## ...
The
team_members_file
local value parses each file inteam_members_path
directory using thefileset()
function. Then, it creates an object with the file name as the key and the file contents as the value. It uses thecsvdecode
function to transform the file contents into a format Terraform can use.main.tf
## ... team_members_files = { for file in fileset(local.team_members_path, "*.csv") : trimsuffix(file, ".csv") => csvdecode(file("${local.team_members_path}/${file}")) } ## …
In your terminal, use the Terraform console to evaluate and view the
team_members_files
value. Since this value does not reference the teams resource, you can evaluate it now.$ echo 'local.team_members_files' | terraform console { "developer-team" = tolist([ { "role" = "member" "username" = "terraform-education1" }, ]) "security-team" = tolist([ { "role" = "member" "username" = "alex-tf-edu" }, ]) }
The
team_members_temp
local value retrieves the GitHub team ID and creates a temporary list of objects containing the team ID, members, and roles. This local value references thegithub_team.all
resource to retrieve the team ID, creating an implicit dependency. Terraform must create the resource before the local value can use it. You will evaluate and view this value after you create the team in the Apply Configuration section.main.tf
## ... # Create temp object that has team ID and CSV contents team_members_temp = flatten([ for team, members in local.team_members_files : [ for tn, t in github_team.all : { name = t.name id = t.id slug = t.slug members = members } if t.slug == team ] ]) ## ...
The
team_members
local value unnests the members from the team, creating a list of objects containing the relationship name ("${team.slug}-${member.username}"
), the team ID, the username, and the role. This simplifies yourgithub_team_membership
resource configuration. You will evaluate and view this value after you create the team in the Apply Configuration section.main.tf
## ... # Create object for each team-user relationship team_members = flatten([ for team in local.team_members_temp1 : [ for member in team.members : { name = "${team.slug}-${member.username}" team_id = team.id username = member.username role = member.role } ] ]) ## …
Repository and team permissions - The
github_team_repository
resource requires a repository ID, team ID, and the team's permissions to the repository. The name for each CSV file in therepos-team
directory corresponds to the repository name, and the file contents define each team's permissions.The following two local values parse the data in
repos-team
directory. Since this data is not nested, thegithub_team_repository
resource can handle the data transformation.The
repo_teams_path
local value defines the directory containing the repositories' team permissions CSV files.main.tf
## ... repo_teams_path = "repos-team" ## ...
The
repo_teams_files
local value parses each file inrepo_teams_path
directory and creates an object with the file name as the key and the file contents as the value.main.tf
## ... team_members_files = { for file in fileset(local.team_members_path, "*.csv") : trimsuffix(file, ".csv") => csvdecode(file("${local.team_members_path}/${file}")) } ## ...
Apply configuration
Since you already initialized the configuration in the Review configuration section, you can apply it.
The local.team_members
value depends on the github_team.all
resource, so
you must create your teams before you can add members to them.
Apply the configuration using the -target
flag to create the GitHub teams
first. The -target
flag instructs Terraform to only change a single resource
rather than all resources in the current directory. When you apply your
configuration, Terraform will create two new teams.
Respond yes
when prompted to add the new teams.
$ terraform apply -target github_team.all
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:
# github_team.all["Developer Team"] will be created
+ resource "github_team" "all" {
+ create_default_maintainer = false
+ description = "Developer Team [Learn TF]"
+ etag = (known after apply)
+ id = (known after apply)
+ members_count = (known after apply)
+ name = "Developer Team"
+ node_id = (known after apply)
+ privacy = "closed"
+ slug = (known after apply)
}
# github_team.all["Security Team"] will be created
+ resource "github_team" "all" {
+ create_default_maintainer = false
+ description = "Security Team [Learn TF]"
+ etag = (known after apply)
+ id = (known after apply)
+ members_count = (known after apply)
+ name = "Security Team"
+ node_id = (known after apply)
+ privacy = "closed"
+ slug = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
╷
│ Warning: Resource targeting is in effect
│
│ You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current
│ configuration.
│
│ The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform
│ specifically suggests to use it as part of an error message.
╵
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
github_team.all["Developer Team"]: Creating...
github_team.all["Security Team"]: Creating...
github_team.all["Security Team"]: Creation complete after 9s [id=0000000]
github_team.all["Developer Team"]: Creation complete after 9s [id=0000000]
╷
│ Warning: Applied changes may be incomplete
│
│ The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may not be
│ fully updated. Run the following command to verify that no other changes are pending:
│ terraform plan
│
│ Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or
│ when Terraform specifically suggests to use it as part of an error message.
╵
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Now, apply the full configuration. Respond yes
when prompted to invite
members to your organization, add them to their respective teams, create two
repositories, and assign teams to your newly created repositories.
$ terraform apply
github_team.all["Developer Team"]: Refreshing state... [id=0000000]
github_team.all["Security Team"]: Refreshing state... [id=0000000]
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:
# github_membership.all["alex-tf-edu"] will be created
+ resource "github_membership" "all" {
+ etag = (known after apply)
+ id = (known after apply)
+ role = "member"
+ username = "alex-tf-edu"
}
# github_membership.all["terraform-education1"] will be created
+ resource "github_membership" "all" {
+ etag = (known after apply)
+ id = (known after apply)
+ role = "member"
+ username = "terraform-education1"
}
## ...
Plan: 10 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
github_membership.all["alex-tf-edu"]: Creating...
github_membership.all["terraform-education1"]: Creating...
## ...
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
Verify GitHub resources
Open your GitHub organization's dashboard. You will find the infrastructure and application repositories Terraform created.
Click on "People" in the top navigation bar, then click on
"Pending invitations". You will find the two users defined in your
members.csv
file.
Click on "Teams", you will find the "Developer Team" and "Security Team" as
defined in your teams.csv
file. Click on the developer team. This team will
only have one user because the invited users have not accepted the invitation
to join your organization yet. Once they accept, Github will assign them to the
teams defined by your Terraform configuration.
In the top navigation bar of the Teams page, click on "Repositories". Terraform
granted the team permissions to the repositories as defined by the CSV files in
the repos-team
directory. As expected, the developer team has write access to
the application repository, but only has read access to the infrastructure
repository.
Inspect state using console
The terraform console
command lets you inspect a Terraform's state file and
evaluate expressions. In this section, you will use Terraform's console to
inspect the local values to better understand the configuration. Alternatively,
you can skip to the next section to
invite a new user to your GitHub organization.
Retrieve information from the github_team.all
resource.
$ echo 'github_team.all' | terraform console
{
"Developer Team" = {
"create_default_maintainer" = true
"description" = "Developer Team [Learn TF]"
## ...
"privacy" = "closed"
"slug" = "developer-team"
}
"Security Team" = {
"create_default_maintainer" = true
"description" = "Security Team [Learn TF]"
## ...
"privacy" = "closed"
"slug" = "security-team"
}
}
Since the github_team.all
resource uses the for_each
attribute to create
multiple teams from one resource block, you can query a specific team by
providing the team name. The key defined in the for_each
attribute becomes
the resource's key.
teams.tf
resource "github_team" "all" {
for_each = {
for team in csvdecode(file("teams.csv")) :
team.name => team
}
## ...
}
Retrieve the Security Team
's information using the Terraform console.
$ echo 'github_team.all["Security Team"]' | terraform console
{
"create_default_maintainer" = true
"description" = "Security Team [Learn TF]"
## ...
"privacy" = "closed"
"slug" = "security-team"
}
The console can also evaluate local values. Retrieve the local.team_members
value, which parses the files in the team-members
directory and transforms
them into an object the github_team_membership.members
resource can use.
$ echo 'local.team_members' | terraform console
[
{
"name" = "developer-team-terraform-education1"
"role" = "member"
"team_id" = "0000000"
"username" = "terraform-education1"
},
{
"name" = "developer-team-terraform-education2"
"role" = "member"
"team_id" = "0000000"
"username" = "terraform-education2"
},
{
"name" = "security-team-alex-tf-edu"
"role" = "member"
"team_id" = "0000000"
"username" = "alex-tf-edu"
},
]
Notice that 'local.team_members'
is a list of objects containing the name,
role, team ID, and username. The for_each
meta-argument expects objects for
resources, so you must transform this into an object.
teams.tf
resource "github_team_membership" "members" {
for_each = { for tm in local.team_members : tm.name => tm }
## ...
}
Since this resource uses the team and member name as a key in the for_each
meta-argument, the github_team_membership.members
resource also stores the
information using the name as the key. Query the resource for the
security-team
team and alex-tf-edu
username.
$ echo 'github_team_membership.members["security-team-alex-tf-edu"]' | terraform console
{
"etag" = "W/\"c556c130708bc6c23f6741b54c86d85e755664f3c0c79e7bb3ac1f1bb2275a9a\""
"id" = "0000000:alex-tf-edu"
"role" = "member"
"team_id" = "0000000"
"username" = "alex-tf-edu"
}
Use the terraform state list
command to view all the resources you can query.
$ terraform state list
data.github_user.self
github_membership.all["alex-tf-edu"]
github_membership.all["terraform-education1"]
github_membership.all["terraform-education2"]
github_repository.application
github_repository.infrastructure
github_team.all["Developer Team"]
github_team.all["Security Team"]
github_team_membership.members["developer-team-terraform-education1"]
github_team_membership.members["developer-team-terraform-education2"]
github_team_membership.members["security-team-alex-tf-edu"]
github_team_membership.self["Developer Team"]
github_team_membership.self["Security Team"]
github_team_repository.infrastructure["Developer Team"]
github_team_repository.infrastructure["Security Team"]
github_team_repository.application["Developer Team"]
github_team_repository.application["Security Team"]
Tip
Terraform does not include local values in the state list
output.
Invite user to organization
To invite a user to your organization, you need to update the members.csv
file. The github_membership.all
resource dynamically populates data from
members.csv
to create new users.
Add a new user named terraform-education2
with a member
role to members.csv
.
members.csv
username,role
alex-tf-edu,member
terraform-education1,member
terraform-education2,member
To add this newly created user to a team, update the respective file in the
team-members
directory. Add terraform-education2
as a member to the
developer team by updating team-members/developer-team.csv
.
team-members/developer-team.csv
username,role
terraform-education1,member
terraform-education2,member
Apply your configuration to invite a new member to your organization and invite
them to the developer team. Respond yes
when prompted.
$ terraform apply
## ...
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:
# github_membership.all["terraform-education2"] will be created
+ resource "github_membership" "all" {
+ etag = (known after apply)
+ id = (known after apply)
+ role = "member"
+ username = "terraform-education2"
}
# github_team_membership.members["developer-team-terraform-education2"] will be created
+ resource "github_team_membership" "members" {
+ etag = (known after apply)
+ id = (known after apply)
+ role = "member"
+ team_id = "0000000"
+ username = "terraform-education2"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
github_membership.all["terraform-education2"]: Creating...
github_team_membership.members["developer-team-terraform-education2"]: Creating...
github_team_membership.members["developer-team-terraform-education2"]: Creation complete after 3s [id=0000000:terraform-education2]
github_membership.all["terraform-education2"]: Creation complete after 3s [id=a-demo-organization:terraform-education2]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Verify new user invitation
Open your GitHub organization's dashboard. Click on "People" in the top
navigation bar, then click on "Pending invitations". You will find a new
invitation for terraform-education2
.
Create new team
To create a new team, you need to update the teams.csv
file. The
github_team.all
resource dynamically populates data from teams.csv
to
create new teams.
Add a new DevOps team to teams.csv
.
teams.csv
name,description,privacy
Security Team,Security Team [Learn TF],closed
Developer Team,Developer Team [Learn TF],closed
DevOps Team,Developer Team [Learn TF],closed
To assign members to this team, update the respective file in the team-members
directory.
Since this is a new team, create a file named devops-team.csv
,
then add alex-tf-edu
as a member.
team-members/devops-team.csv
username,role
alex-tf-edu,member
To assign the DevOps team to your repositories, update the CSV files in the
repos-team
directory. Add the DevOps team as a maintainer
to the
application repository by updating repos-team/application.csv
.
repos-team/application.csv
team_name,permission
Security Team,maintain
Developer Team,pull
DevOps Team,maintain
Add the DevOps team as a maintainer
to the infrastructure repository by
updating repos-team/infrastructure.csv
.
repos-team/infrastructure.csv
team_name,permission
Security Team,maintain
Developer Team,pull
DevOps Team,maintain
Since you changed the number of teams Terraform manages, you need to target
the github_team.all
resource before you can apply the rest of your
configuration.
Apply your changes, targeting only the github_team.all
resource. Respond yes
when prompted to create the DevOps team.
$ terraform apply -target github_team.all
github_team.all["Developer Team"]: Refreshing state... [id=0000000]
github_team.all["Security Team"]: Refreshing state... [id=0000000]
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:
# github_team.all["DevOps Team"] will be created
+ resource "github_team" "all" {
+ create_default_maintainer = false
+ description = "Developer Team [Learn TF]"
+ etag = (known after apply)
+ id = (known after apply)
+ members_count = (known after apply)
+ name = "DevOps Team"
+ node_id = (known after apply)
+ privacy = "closed"
+ slug = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
╷
│ Warning: Resource targeting is in effect
│
│ You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current
│ configuration.
│
│ The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform
│ specifically suggests to use it as part of an error message.
╵
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
github_team.all["DevOps Team"]: Creating...
github_team.all["DevOps Team"]: Creation complete after 4s [id=0000000]
╷
│ Warning: Applied changes may be incomplete
│
│ The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may not be
│ fully updated. Run the following command to verify that no other changes are pending:
│ terraform plan
│
│ Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or
│ when Terraform specifically suggests to use it as part of an error message.
╵
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Now, apply the rest of the configuration. Respond yes
when prompted to assign
alex-tf-edu
to the DevOps team, and assign the DevOps team to your two
repositories.
$ terraform apply
## ...
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:
# github_team_membership.members["devops-team-alex-tf-edu"] will be created
+ resource "github_team_membership" "members" {
+ etag = (known after apply)
+ id = (known after apply)
+ role = "member"
+ team_id = "5071226"
+ username = "alex-tf-edu"
}
# github_team_membership.self["DevOps Team"] will be created
+ resource "github_team_membership" "self" {
+ etag = (known after apply)
+ id = (known after apply)
+ role = "maintainer"
+ team_id = "5071226"
+ username = "im2nguyen"
}
# github_team_repository.infrastructure["DevOps Team"] will be created
+ resource "github_team_repository" "infrastructure" {
+ etag = (known after apply)
+ id = (known after apply)
+ permission = "maintain"
+ repository = "learn-tf-infrastructure"
+ team_id = "5071226"
}
# github_team_repository.application["DevOps Team"] will be created
+ resource "github_team_repository" "application" {
+ etag = (known after apply)
+ id = (known after apply)
+ permission = "maintain"
+ repository = "learn-tf-application"
+ team_id = "5071226"
}
Plan: 4 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
github_team_membership.self["DevOps Team"]: Creating...
github_team_membership.members["devops-team-alex-tf-edu"]: Creating...
github_team_repository.application["DevOps Team"]: Creating...
github_team_repository.infrastructure["DevOps Team"]: Creating...
github_team_membership.members["devops-team-alex-tf-edu"]: Creation complete after 4s [id=0000000:terraform-education]
github_team_membership.self["DevOps Team"]: Creation complete after 4s [id=0000000:im2nguyen]
github_team_repository.application["DevOps Team"]: Creation complete after 7s [id=0000000:learn-tf-application]
github_team_repository.infrastructure["DevOps Team"]: Creation complete after 7s [id=0000000:learn-tf-infrastructure]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Verify DevOps team in GitHUb
Open your GitHub organization's dashboard. Click on "Team" in the top navigation bar to find the newly created DevOps team.
Click on "DevOps Team", then "Repositories" from the top navigation bar. The team has "maintain" permissions to the application and infrastructure repositories.
Create new repository
To create a new repository, you need to define new resource blocks in
repositories.tf
.
Add the following resources to repositories.tf
to create a repository to host
your API code and add teams to the repository. Notice how the repository
argument references the github_repository.api
resource.
repositories.tf
# Create API repository
resource "github_repository" "api" {
name = "learn-tf-api"
}
# Add memberships for application repository
resource "github_team_repository" "api" {
for_each = {
for team in local.repo_teams_files["api"] :
team.team_name => {
team_id = github_team.all[team.team_name].id
permission = team.permission
} if lookup(github_team.all, team.team_name, false) != false
}
team_id = each.value.team_id
repository = github_repository.api.id
permission = each.value.permission
}
Create a new file in repos-team
named api.csv
with the following contents.
repos-team/api.csv
team_name,permission
Security Team,maintain
Developer Team,push
DevOps Team,maintain
Apply your configuration. Respond yes
when prompted to create the new
repository and assign teams to the repository.
$ terraform apply
## ...
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:
# github_repository.api will be created
+ resource "github_repository" "api" {
+ allow_merge_commit = true
+ allow_rebase_merge = true
+ allow_squash_merge = true
+ archived = false
+ default_branch = (known after apply)
+ delete_branch_on_merge = false
+ etag = (known after apply)
+ full_name = (known after apply)
+ git_clone_url = (known after apply)
+ html_url = (known after apply)
+ http_clone_url = (known after apply)
+ id = (known after apply)
+ name = "learn-tf-api"
+ node_id = (known after apply)
+ private = (known after apply)
+ repo_id = (known after apply)
+ ssh_clone_url = (known after apply)
+ svn_url = (known after apply)
+ visibility = (known after apply)
}
# github_team_repository.api["DevOps Team"] will be created
+ resource "github_team_repository" "api" {
+ etag = (known after apply)
+ id = (known after apply)
+ permission = "maintain"
+ repository = "learn-tf-api"
+ team_id = "5071226"
}
# github_team_repository.api["Developer Team"] will be created
+ resource "github_team_repository" "api" {
+ etag = (known after apply)
+ id = (known after apply)
+ permission = "push"
+ repository = "learn-tf-api"
+ team_id = "5071013"
}
# github_team_repository.api["Security Team"] will be created
+ resource "github_team_repository" "api" {
+ etag = (known after apply)
+ id = (known after apply)
+ permission = "maintain"
+ repository = "learn-tf-api"
+ team_id = "5071012"
}
Plan: 4 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
github_repository.api: Creating...
github_team_repository.api["DevOps Team"]: Creating...
github_team_repository.api["Security Team"]: Creating...
github_team_repository.api["Developer Team"]: Creating...
github_team_repository.api["Security Team"]: Creation complete after 8s [id=0000000:learn-tf-application]
github_team_repository.api["DevOps Team"]: Creation complete after 9s [id=0000000:learn-tf-application]
github_team_repository.api["Developer Team"]: Creation complete after 9s [id=0000000:learn-tf-application]
github_repository.api: Creation complete after 9s [id=learn-tf-api]
Verify new repository in GitHub
Open your GitHub organization's dashboard. Click on "Repository" in the top navigation bar to find the newly created API repository.
Click on learn-tf-api
repository. Click on "Settings" on the top navigation
menu, then "Manage access" from the left navigation menu. You will find three
teams assigned to the repository, as defined by your repos-team/api.csv
file.
Clean up your resources
Use Terraform to destroy the teams, repositories, and user invitations you
created in this tutorial. Confirm your changes by typing yes
when prompted.
$ terraform destroy
## ...
Plan: 0 to add, 0 to change, 22 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
## …
Destroy complete! Resources: 22 destroyed.
Next steps
Congratulations! Over the course of this tutorial, you used Terraform to invite
users to your GitHub organization and create new repositories and teams. In the
process, you used a wide variety of Terraform's configuration language elements
and functions like resource blocks, locals, functions such as csvdecode
and
format
, and learned about the resources the GitHub provider can manage.
By using Terraform to codify your GitHub resources, you can adopt development best practices such as testing, code review, and version control in your GitHub organization management. This configuration adapts to your organization as it scales, enabling your teams to do their best work safely and efficiently.
For more information on topics covered in this tutorial, review the documentation below:
Learn more about input variables and dynamic expressions, which you used in this tutorial.
Complete the Reuse Configuration with Modules tutorials to learn how to create reusable modules to enable repeatable workflows.
Visit the GitHub Provider documentation to learn more about the GitHub resources and data sources you can manage using Terraform.