- Azure DevOps
- Terraform
In this tutorial we will configure the policies to apply for each repository inside our Azure DevOps project using Terraform!
This tutorial is part of a full series of tutorials on configuring Azure DevOps using Terraform. You can download the project from the previous part and follow along.
In a previous tutorial you created your repositories, but to contribute to it using Pull Requests, you must to setup a list of good practices to avoid having bad code deployed to production. So, to achieve this you can use the concept of policies
and apply it to all the branches you need.
For the purpose of this tutorial we will apply the policies
only on the default branch, however It’s really recommanded you to apply it on each branch that make sense for your project.
You have multiple policies to set to lock and standardize your Pull Requests:
Let’s see how to setup the first five by creating a new file called repos_policies.tf
.
Also notice in the next examples of codes, that you need to add the
depends_on
property for each of the policies declared, because the default files you added in the repository must be pushed before activating the policies. If you don’t do that, you will get an error telling you that the policies are already set and you are unable to push your default files without passing by a Pull Request, which is not the behavior you want when initializing the repository.
As you can see below you define an azuredevops_branch_policy_min_reviewers
and iterate over a list of repository like we defined in a previous tutorial.
resource "azuredevops_branch_policy_min_reviewers" "this" {
count = length(var.repositories)
project_id = azuredevops_project.this.id
enabled = true
blocking = true
settings {
reviewer_count = 1
submitter_can_vote = false
last_pusher_cannot_approve = true
allow_completion_with_rejects_or_waits = false
on_push_reset_approved_votes = false
on_last_iteration_require_vote = false
scope {
repository_id = azuredevops_git_repository.this[count.index].id
repository_ref = azuredevops_git_repository.this[count.index].default_branch
match_type = "Exact"
}
}
depends_on = [
azuredevops_git_repository_file.default_pipeline,
azuredevops_git_repository_file.default_gitignore,
]
}
resource "azuredevops_branch_policy_work_item_linking" "this" {
count = length(var.repositories)
project_id = azuredevops_project.this.id
enabled = true
blocking = true
settings {
scope {
repository_id = azuredevops_git_repository.this[count.index].id
repository_ref = azuredevops_git_repository.this[count.index].default_branch
match_type = "Exact"
}
}
depends_on = [
azuredevops_git_repository_file.default_pipeline,
azuredevops_git_repository_file.default_gitignore,
]
}
Above you ask the user to add the work item linked to the branch for a Pull Request. This is useful to undestand the code added.
Now, something really important regarding Pull Requests, be sure that the comments where all put to resolved before being able to merge it:
resource "azuredevops_branch_policy_comment_resolution" "this" {
count = length(var.repositories)
project_id = azuredevops_project.this.id
enabled = true
blocking = true
settings {
scope {
repository_id = azuredevops_git_repository.this[count.index].id
repository_ref = azuredevops_git_repository.this[count.index].default_branch
match_type = "Exact"
}
}
depends_on = [
azuredevops_git_repository_file.default_pipeline,
azuredevops_git_repository_file.default_gitignore,
]
}
Depending on the rules you fixed with your team, it’s important to define the merge type you want for each branch. For instance the squash
can be a good one when you merge a feature on the develop
branch. So, you end up with only one commit with a formatted message instead of having all the commits from the feature branch which can be a bit messy.
resource "azuredevops_branch_policy_merge_types" "this" {
count = length(var.repositories)
project_id = azuredevops_project.this.id
enabled = true
blocking = true
settings {
allow_squash = true
allow_rebase_and_fast_forward = false
allow_basic_no_fast_forward = true
allow_rebase_with_merge = false
scope {
repository_id = azuredevops_git_repository.this[count.index].id
repository_ref = azuredevops_git_repository.this[count.index].default_branch
match_type = "Exact"
}
}
depends_on = [
azuredevops_git_repository_file.default_pipeline,
azuredevops_git_repository_file.default_gitignore,
]
}
The last one that you will see is probably the most important one. Adding a build validation ensure that an Azure DevOps pipeline ran your code. This pipeline must have at least, the execution of your Unit Tests, the build of the project and if you can, other scaning tools like Sonar, Checkmarks, etc.. to validate the quality of your code.
resource "azuredevops_branch_policy_build_validation" "this" {
count = length(var.repositories)
project_id = azuredevops_project.this.id
enabled = true
blocking = true
settings {
display_name = "Pull Request Check"
build_definition_id = azuredevops_build_definition.build_definitions[count.index].id
valid_duration = 720 # minutes => 12 hours
scope {
repository_id = azuredevops_git_repository.this[count.index].id
repository_ref = azuredevops_git_repository.this[count.index].default_branch
match_type = "Exact"
}
}
depends_on = [
azuredevops_git_repository_file.default_pipeline,
azuredevops_git_repository_file.default_gitignore,
]
}
You can find all the policies available in the Terraform documentation.
Let’s run a new Terraform plan command and apply it:
terraform plan -var-file=env.tfvars --out=plan.out
Then:
terraform apply plan.out
As result, all your repositories will have policies in the default branch and all the new code will be only added to this branch by a Pull Request.
You now have your default branches protected by some policies using Terraform. You will find full source code in this Github repository.