Configuring standard policies for all repositories in Azure Repos

By default you can only configure policies on specific branches in Azure Repos. If you're using a strict naming pattern for your branches (Release Flow or GitHub Flow), you may want to set a policy for all future Release Branches, or all Feature branches.

A couple of weeks ago I blogged about setting collection level permissions on Azure Repos. That sparked questions whether the same was possible on Branch Policies in the comments, twitter and the Azure DevOps Club slack channel.

By default you can only configure policies on specific branches in Azure Repos. You access the policies through the Branch's [...] menu and set the policy from there. But if you're using a strict naming pattern for your branches (e.g. when using Release Flow or GitHub Flow), you may want to set a policy for all future Release Branches, or all Feature branches.

It would be nice if you could write these policies into law, that way you don't have to set them for every future branch.

Let's start with the bad news: the policy API is specific to a Project. Because of that you can't set the policies for all Git Repositories in an account, but you can specify the policy for all repositories in a Project.
Set a policy on a branch.

If you look at the request that's generated when saving a Branch Policy, you can see the UI sending a POST request to the /{Project Guid}/api/policy/Configurations REST API when creating a new policy. That request contains the scope for each policy:

Each policy has a scope in Azure Repos

As you can see, the policy has a Scope. You can have multiple active policies and each can have its own scope. The UI will always create a specific scope that contains the repositoryId and the exact branch name.

"scope": [
    {
        "refName": "refs/heads/master",
        "matchKind": "Exact",
        "repositoryId": "7317f685-3e85-41d6-8e20-10d2319262a7"
    }
]
Scope: (default) Specific Git Repo and single branch.

But if you look at the docs for this API, you'll find that this is not the only option available. The widest scope you can create has no repository scope at all and applies to all repositories in that project:

"scope": [
    {
        "repositoryId": null
    }
]
Scope: All Git Repos in the project.

But there are other cool options as well. You can configure a policy for all branches with a specific prefix by setting the matchKind from exact to prefix.

"settings": {
    "scope": [
      {
        "repositoryId": null,
        "refName": "refs/heads/features/",
        "matchKind": "prefix"
      }
    ]
  }
Scope: All feature branches for all repositories in the project.

Unfortunately, it looks like this API exists at the Project level only. One can't set the policy for all future projects. But, think about it, that makes sense. You can't predict all the future group names, Build Definition IDs and such for projects that don't exist yet. But it's less restricted than the UI would let you believe.

To figure out how each of the policies is specified,  configure one branch the way you want ant then open /{Project Guid}/_apis/policy/Configurations/ on your account. you'll be treated with the JSON for your current configuration:

{
    "count": 1,
    "value": [
        {
            "isEnabled": true,
            "isBlocking": true,
            "settings": {
                "useSquashMerge": false,
                "scope": [
                    {
                        "refName": "refs/heads/master",
                        "matchKind": "Exact",
                        "repositoryId": "7317f685-3e85-41d6-8e20-10d2319262a7"
                    }
                ]
            }
        }
    ]
}

Find out all you need to know about policy types by querying them from your account as well, my account returns these:

[
    {
        "description": "GitRepositorySettingsPolicyName",
        "id": "0517f88d-4ec5-4343-9d26-9930ebd53069",
        "displayName": "GitRepositorySettingsPolicyName"
    },
    {
        "description": "This policy will reject pushes to a repository for paths which exceed the specified length.",
        "id": "001a79cf-fda1-4c4e-9e7c-bac40ee5ead8",
        "displayName": "Path Length restriction"
    },
    {
        "description": "This policy will reject pushes to a repository for names which aren't valid on all supported client OSes.",
        "id": "db2b9b4c-180d-4529-9701-01541d19f36b",
        "displayName": "Reserved names restriction"
    },
    {
        "description": "This policy ensures that pull requests use a consistent merge strategy.",
        "id": "fa4e907d-c16b-4a4c-9dfa-4916e5d171ab",
        "displayName": "Require a merge strategy"
    },
    {
        "description": "Check if the pull request has any active comments",
        "id": "c6a1889d-b943-4856-b76f-9e46bb6b0df2",
        "displayName": "Comment requirements"
    },
    {
        "description": "This policy will require a successfull status to be posted before updating protected refs.",
        "id": "cbdc66da-9728-4af8-aada-9a5a32e4a226",
        "displayName": "Status"
    },
    {
        "description": "Git repository settings",
        "id": "7ed39669-655c-494e-b4a0-a08b4da0fcce",
        "displayName": "Git repository settings"
    },
    {
        "description": "This policy will require a successful build has been performed before updating protected refs.",
        "id": "0609b952-1397-4640-95ec-e00a01b2c241",
        "displayName": "Build"
    },
    {
        "description": "This policy will reject pushes to a repository for files which exceed the specified size.",
        "id": "2e26e725-8201-4edd-8bf5-978563c34a80",
        "displayName": "File size restriction"
    },
    {
        "description": "This policy will ensure that required reviewers are added for modified files matching specified patterns.",
        "id": "fd2167ab-b0be-447a-8ec8-39368250530e",
        "displayName": "Required reviewers"
    },
    {
        "description": "This policy will ensure that a minimum number of reviewers have approved a pull request before completion.",
        "id": "fa4e907d-c16b-4a4c-9dfa-4906e5d171dd",
        "displayName": "Minimum number of reviewers"
    },
    {
        "description": "This policy encourages developers to link commits to work items.",
        "id": "40e92b44-2fe1-4dd6-b3d8-74a9c21d0c6e",
        "displayName": "Work item linking"
    }
]
All policy types available in my account.

The configuration for each policy is a bit of a mystery. I tend to configure a policy through the UI, then retrieve the configured policy to see what the JSON looks like.

Now that you understand the underlying concepts, guids and things, you can use the raw REST requests from PowerShell or... You could use the new Azure CLI for Azure DevOps:

az extension add --name "azure-devops"
az login

az repos policy create --org {your org} --project {your project name or guid} --config "path/to/config/file"

For reference:

Leave a comment.