Gestire le policy Git su centinaia di repository in Azure DevOps è una sfida comune nelle grandi organizzazioni. Per farlo in modo affidabile, molti team utilizzano servizi di automazione che verificano e correggono periodicamente la configurazione delle policy. Fino a poco fa, questa automazione era penalizzata da un’importante limitazione dell’API REST di Azure DevOps: non esisteva un modo per recuperare tutte le policy applicabili a un dato repository in una singola chiamata.
Con un singolo miglioramento all’endpoint REST, il team di Azure DevOps ha ottenuto una riduzione del 50% nell’utilizzo CPU lato server e un’accelerazione da 10x a 15x nei tempi di esecuzione complessivi. Vediamo come funziona e perché è rilevante per chi gestisce pipeline di governance automatizzata.
Come funzionano le policy Git in Azure Repos
Azure Repos supporta due categorie di policy:
- Push policies (o repository policies): si applicano all’intero repository, indipendentemente dal branch. Esempio: blocco dei commit contenenti segreti o credenziali.
- Branch policies: proteggono branch specifici e richiedono che le modifiche passino attraverso pull request. Esempio: numero minimo di reviewer, stato delle build CI.
Tutte le policy di un progetto sono memorizzate in un contenitore logico a livello di progetto, non per singolo repository o branch. Ogni policy ha un campo Scope che specifica dove nella gerarchia si applica:
# Push policy per uno specifico repo (senza ref)
2c938d1f6e6f458d816484fc51e7cf74
# Branch policy per il branch main di un repo specifico
2c938d1f6e6f458d816484fc51e7cf74:refs/heads/main
# Branch policy cross-repo (tutti i branch releases/* in tutti i repo)
*:refs/heads/releases/*
Il problema: due endpoint, nessuno sufficiente
Azure DevOps espone due endpoint per recuperare le configurazioni delle policy:
GET /_apis/policy/configurations
L’endpoint più datato. Consente di filtrare per valore esatto dello scope, ma senza supporto per l’ereditarietà. Se si passa lo scope 2c938d...74:refs/heads/releases/v1, non vengono restituite le policy con scope *:refs/heads/releases/* che pure si applicano a quel branch.
GET /_apis/git/policy/configurations
L’endpoint più moderno, con supporto all’ereditarietà. Accetta repositoryId e refName e restituisce tutte le policy che si applicano a quel branch, incluse quelle ereditate da scope più generici. È utile per rispondere alla domanda “cosa protegge il branch releases/v1 nel repo X?”
Il problema: passando solo repositoryId senza refName, questo endpoint restituisce solo le push policy applicabili all’intero repository. Le branch policy non vengono incluse perché non si applicano all’intero repo ma a branch specifici.
Il risultato pratico: un servizio di governance che deve conoscere tutte le policy applicabili a un repository — push e branch — era costretto a recuperare l’intero set di policy del progetto e filtrare lato client. In progetti con migliaia di repository e centinaia di migliaia di policy, questo significava serializzare e trasferire centinaia di megabyte di dati per ogni singola chiamata.
La soluzione: refName=~all
L’endpoint GET /_apis/git/policy/configurations supporta ora un valore speciale per il parametro refName: ~all.
Quando si passa repositoryId=<ID>&refName=~all, la risposta include:
- Tutte le branch policy che si applicano a qualsiasi branch nel repository (dirette e ereditate)
- Tutte le push policy applicabili all’intero repository
- Le policy di progetto che si applicano a tutti i repository (scope
*)
Internamente, il server filtra l’intero set di policy del progetto mantenendo solo quelle con scope che inizia con * (policy a livello di progetto) o con l’ID del repository richiesto. Tutta la logica di filtering si sposta dal client al server, con payload di risposta enormemente ridotti.
Esempio di chiamata REST
GET https://dev.azure.com/{organization}/{project}/_apis/git/policy/configurations
?repositoryId=2c938d1f-6e6f-458d-8164-84fc51e7cf74
&refName=~all
&api-version=7.2
Authorization: Basic {token}
Prima di questa modifica, la stessa informazione richiedeva una chiamata all’endpoint /_apis/policy/configurations senza filtri (o con il solo repositoryId), seguita da filtering lato client su tutto il set di policy del progetto.
Impatto sulle prestazioni
Il team di Microsoft ha misurato l’impatto dopo aver migrato il proprio servizio interno di governance delle policy a usare refName=~all:
- CPU lato server: riduzione del 50% del consumo complessivo per questo client
- Tempo di esecuzione totale: da 1.000–3.000 ore/giorno a circa 100–150 ore/giorno, con un miglioramento da 10x a 15x
I guadagni sono proporzionali alla dimensione del progetto: più repository e policy contiene, maggiore è il vantaggio del filtering server-side rispetto al trasferimento dell’intero dataset.
Come aggiornare l’automazione esistente
Se si dispone di un servizio o script che recupera le policy per singolo repository, il refactoring è minimo. Ecco un esempio di migrazione in Python con la libreria requests:
import requests
ORG = "myorg"
PROJECT = "myproject"
REPO_ID = "2c938d1f-6e6f-458d-8164-84fc51e7cf74"
TOKEN = "..." # PAT Azure DevOps
headers = {
"Authorization": f"Basic {TOKEN}",
"Content-Type": "application/json"
}
# PRIMA: recupero di tutte le policy del progetto + filtering client-side
# url = f"https://dev.azure.com/{ORG}/{PROJECT}/_apis/policy/configurations?api-version=7.2"
# response = requests.get(url, headers=headers)
# all_policies = response.json()["value"]
# repo_policies = [p for p in all_policies if REPO_ID in str(p.get("settings", {}).get("scope", []))]
# DOPO: server-side filtering con refName=~all
url = (
f"https://dev.azure.com/{ORG}/{PROJECT}/_apis/git/policy/configurations"
f"?repositoryId={REPO_ID}&refName=~all&api-version=7.2"
)
response = requests.get(url, headers=headers)
repo_policies = response.json()["value"]
print(f"Policy trovate per il repository: {len(repo_policies)}")
Il risultato è lo stesso, ma il server restituisce solo le policy rilevanti per quel repository, eliminando il traffico inutile e il carico di elaborazione lato client.
Disponibilità
La funzionalità è già disponibile per tutti gli utenti di Azure DevOps Services (cloud). Per quanto riguarda Azure DevOps Server (on-premise), sarà inclusa nel prossimo aggiornamento major previsto per la seconda metà del 2026.
Conclusione
Questo tipo di ottimizzazione — spostare il lavoro dal client al server tramite un parametro aggiuntivo — è un esempio classico di come miglioramenti relativamente semplici all’API possano avere impatti drastici sulle prestazioni a scala. Per chi gestisce governance automatizzata su organizzazioni Azure DevOps con molti repository, l’adozione di refName=~all è immediata e i guadagni in termini di latenza e carico sono significativi.
La documentazione completa degli endpoint è disponibile su Microsoft Learn:
Fonte originale: Optimizing Git policy management at scale — Azure DevOps Blog