Wenn das Unternehmen schnell wächst, steht ein DevOps-Ingenieur vor der Herausforderung, Prozesse in allen für CI/CD verwendeten Systemen zu automatisieren, einschließlich der manuellen Zugriffsverwaltung. Solche Routineaufgaben können akzeptabel sein, wenn Sie ein kleines Team, bis zu 10 Releases mit jeweils 3 Phasen und 10 variable Gruppen haben.
Stellen Sie sich jedoch vor, dass Ihr Team auf Hunderte von Personen anwächst, die manuell mit bis zu 500 Releases mit jeweils 10 Phasen und 200 variablen Gruppen arbeiten müssen. In diesem Fall verlangsamt die manuelle Zugriffsverwaltung die Lösung von Kundenanfragen erheblich.
In dieser Phase steht jedes Unternehmen vor der Notwendigkeit einer effizienten Automatisierung.
In der Social Discovery Group verlassen wir uns auf Azure DevOps als primäre Cloud-Technologie. Derzeit fehlt jedoch die Funktionalität zur Automatisierung von Rechten, Berechtigungen und Zugriffsebenen. Um schnell auf die Bedürfnisse der Benutzer eingehen zu können, haben wir uns entschieden, einige benutzerdefinierte Lösungen zu finden und die Zuweisung von Berechtigungen zu automatisieren.
In diesem Artikel geben wir einige gute Tipps zur Automatisierung bestimmter Routineaufgaben in Azure DevOps und konzentrieren uns dabei auf Folgendes:
Derzeit bietet Azure drei Optionen zum Zuweisen von Berechtigungen: Verwendung der grafischen Benutzeroberfläche der Site, Verwendung der az DevOps-CLI und der API-Anfragen. Die erste Option ist die einfachste und schnellste; Wenn das Unternehmen jedoch wächst und die Anzahl der CI/CD-Pipelines und -Variablen zunimmt, wird die manuelle Zuweisung von Berechtigungen zeitaufwändig.
Die zweite Möglichkeit der Berechtigungsvergabe ist im Funktionsumfang eingeschränkt; Mit anderen Worten: Der Benutzer kann nicht über das Angebot eines bestimmten Teams hinausgehen. Die dritte Option über die REST-API ist derzeit die vielseitigste. Betrachten wir ein konkretes Beispiel, bei dem wir über zahlreiche Release-Pipelines mit mehreren Phasen verfügen:
Jede Bereitstellungsphase erfordert die Genehmigung eines bestimmten Benutzers/einer bestimmten Gruppe (die sogenannte Funktion zur Genehmigung vor der Bereitstellung). Hier tritt ein Problem auf, wenn ein bestimmter Benutzer oder eine Gruppe zu einer bestimmten Liste von Genehmigern hinzugefügt oder daraus entfernt werden muss.
Wenn das Unternehmen expandiert und die Anzahl der Pipelines und Stufen um das N-fache steigt, wird der Routinefall zu einem Problem, und die Lösung dieses Problems ist nur durch API-Anfragen möglich. So sieht die Umsetzung von unserer Seite aus:
" pool: vmImage: ubuntu-latest parameters: - name: folder_names type: string default: '\' - name: stage_names type: string default: 'Dev' - name: group_names type: string default: 'Devops' variables: - name: organization value: ORGANIZATION - name: project value: PROJECT - name: pat Value: PAT steps: - bash: | export organization="$(organization)" export project="$(project)" export pat="$(pat)" export folder_names='${{ parameters.folder_names }}' export stage_names='${{ parameters.stage_names }}' export group_names='${{ parameters.group_names }}' export pipeline_name_array=() export stage_name_array=() export group_name_array=() IFS=',' read -ra folder_name_array <<< "$folder_names" IFS=',' read -ra stage_name_array <<< "$stage_names" IFS=',' read -ra group_name_array <<< "$group_names" # Make a GET request to retrieve the release pipelines within the specified project pipeline_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/definitions?api-version=7.0" pipeline_response=$(curl -s -u ":$pat" "$pipeline_url") # Check for errors in the response if [[ "$pipeline_response" == *"error"* ]]; then echo "Error fetching release pipeline data." exit 1 fi pipeline_names=($(echo "$pipeline_response" | jq -r '.value[].name')) pipeline_ids=($(echo "$pipeline_response" | jq -r '.value[].id')) for ((i=0; i<${#pipeline_names[@]}; i++)); do pipeline_name="${pipeline_names[i]}" pipeline_id="${pipeline_ids[i]}" # Make a GET request to retrieve the release pipeline details pipeline_detail_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/definitions/$pipeline_id?api-version=7.0" pipeline_detail_response=$(curl -s -u ":$pat" "$pipeline_detail_url") # Extract the releaseDefinition.path from the pipeline details pipeline_path=$(echo "$pipeline_detail_response" | jq -r '.path') # Check if the pipeline_path matches any of the specified folder_names for folder_name in "${folder_name_array[@]}"; do if [[ "$pipeline_path" == *"$folder_name"* ]]; then # Make a GET request to retrieve all releases for the specified pipeline releases_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/releases?api-version=7.0&definitionId=$pipeline_id" releases_response=$(curl -s -u ":$pat" "$releases_url") # Extract the release names release_names=$(echo "$releases_response" | jq -r '.value[].id') release_definition_url="https://vsrm.dev.azure.com/$organization/$project/_apis/release/definitions/$pipeline_id?api-version=7.0" release_definition_response=$(curl -s -u ":$pat" "$release_definition_url") # Iterate through each group name for group_name in "${group_name_array[@]}"; do # Make a GET request to retrieve the list of groups groups_response=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/graph/groups?api-version=7.1-preview.1") # Find the origin_id for the specified group name origin_id=$(echo "$groups_response" | jq -r ".value[] | select(.displayName == \"$group_name\") | .originId") if [ -z "$origin_id" ]; then echo "Group '$group_name' not found or origin_id not available for release '$release_name'." else # Iterate through each stage name for stage_name in "${stage_name_array[@]}"; do # Construct the JSON structure for the new approval new_approval='{ "rank": 1, "isAutomated": false, "isNotificationOn": false, "approver": { "id": "'"$origin_id"'" } }' # Use jq to update the JSON structure for the specified stage updated_definition=$(echo "$release_definition_response" | jq --argjson new_approval "$new_approval" '.environments |= map(if .name == "'"$stage_name"'" then .preDeployApprovals.approvals += [$new_approval] else . end)') # Make a PUT request to update the release definition for the specified stage put_response=$(curl -s -u ":$pat" -X PUT -H "Content-Type: application/json" -d "$updated_definition" "$release_definition_url") release_definition_response=$(curl -s -u ":$pat" "$release_definition_url") # Check if the update was successful if [[ "$put_response" == *"The resource could not be found."* ]]; then echo "Error updating release definition for stage '$stage_name' and group '$group_name'." else echo "Pre-deployment approval added successfully to stage '$stage_name' for group '$group_name' in '$pipeline_id'." fi done fi done fi done done displayName: 'PreDeployApprovals' "
Ein ähnliches Problem tritt im Abschnitt „Bibliothek“ auf, wenn zahlreiche Variablengruppen erstellt werden, in denen ein bestimmtes Wort verwendet wird, um die Zugehörigkeit zu etwas anzuzeigen. Lassen Sie uns ein Beispiel aufschlüsseln, in dem mehrere Variablengruppen erstellt werden, die das Wort „SERVICES“ enthalten, um ihre Zugehörigkeit zu servicebezogenen Variablen anzugeben.
Untersuchen Sie außerdem ein Szenario, in dem eine bestimmte Gruppe mit bestimmten Berechtigungen Zugriff auf alle Variablen benötigt.
Auch die Lösung dieses häufigen Falles ist nur über die REST-API möglich:
" variables: - name: organization value: ORGANIZATION - name: project value: PROJECT - name: pat value: PAT parameters: - name: searchWord type: string default: 'SERVICES' - name: UserOrGroupName type: string default: 'Devops' - name: UserRights type: string default: 'Administrator' steps: - bash: | export organization="$(organization)" export project="$(project)" export pat="$(pat)" export userOrGroupName='${{ parameters.UserOrGroupName }}' export UserRights='${{ parameters.UserRights }}' export searchWord='${{ parameters.searchWord }}' # Perform the API request and store the JSON response in a variable response=$(curl -s -u ":$pat" "https://dev.azure.com/$organization/$project/_apis/distributedtask/variablegroups?api-version=6.0") # Initialize an empty array to store matching JSON objects matching_json=() # Loop through the JSON objects and append matching objects to the array while read -r json; do if [[ $(echo "$json" | jq -r '.name' | grep "$searchWord") ]]; then matching_json+=("$json") fi done < <(echo "$response" | jq -c '.value[]') # Iterate through the matching variable groups and assign permissions for group in "${matching_json[@]}"; do # Extract the variable group ID and name variableGroupId=$(echo "$group" | jq -r '.id') variableGroupName=$(echo "$group" | jq -r '.name') # Determine the type of userOrGroupName (username or group name) if [[ $myString != *'@'* ]]; then # If userOrGroupName matches the username format, it's treated as a username roleName=$UserRights assignType="group" groupsResponse=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/graph/groups?api-version=6.0-preview.1") #groupOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].originId') groupOriginId=$(echo "$groupsResponse" | jq -r ".value[] | select(.displayName == \"$userOrGroupName\") | .originId") # Get the user's originId using Azure DevOps REST API #userOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].principalName') else # Otherwise, it's treated as a group name roleName=$UserRights assignType="user" userOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].principalName') # Get the group's originId using Azure DevOps REST API #groupOriginId=$(curl -s -u ":$pat" "https://vssps.dev.azure.com/$organization/_apis/identities?searchFilter=$userOrGroupName&api-version=6.0" | jq -r '.value[0].originId') fi # Construct the API URL for assigning permissions apiUrl="https://dev.azure.com/$organization/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/19802563-1b7b-43d6-81e0-16cf29d68c0d$"$variableGroupId"?api-version=5.1-preview" # Assign permissions based on the userOrGroupName type if [ "$assignType" == "group" ]; then # Assign permissions to a group # Construct the request body with group's originId requestBody="[{ \"roleName\": \"$roleName\", \"userId\": \"$groupOriginId\" }]" else # Assign permissions to a user # Construct the request body with the user's uniqueName requestBody="[{ \"roleName\": \"$roleName\", \"uniqueName\": \"$userOrGroupName\" }]" fi # Execute the REST API call to assign permissions response=$(curl -s -u ":$pat" -X PUT -H "Content-Type: application/json" -d "$requestBody" "$apiUrl") # Check if the permissions were successfully assigned if [[ "$response" == *"error"* ]]; then echo "Error assigning permissions to $assignType '$userOrGroupName' in variable group '$variableGroupName'." else echo "Permissions assigned successfully to $assignType '$userOrGroupName' in variable group '$variableGroupName'." fi done displayName: 'variable-groups' "
Durch die Automatisierung dieser Prozesse haben wir ein detaillierteres Verständnis der Rechte- und Berechtigungsstrukturen in Azure DevOps gewonnen und so die Sicherheit des Projekts erhöht.
Wir verfügen jetzt über stabile und vielseitige Pipelines für eine Vielzahl von Aufgaben, die die Bearbeitungszeit von Benutzeranfragen beschleunigen und deren Ausführungsqualität verbessern.
Darüber hinaus haben sich unsere Gesamtgeschwindigkeit und -qualität bei der Systementwicklung verbessert, da wir mehr Zeit für andere CI/CD-Aufgaben aufwenden und uns von Routinetätigkeiten entfernen.
Wir haben erhebliche Anstrengungen unternommen, um tägliche Aufgaben zu automatisieren, verschiedene Implementierungsoptionen zu erkunden und zu analysieren, Fehler zu beheben und zu verfeinern. Vorgefertigte Pipelines und Skripte sind ein enormer Komfort, da sie Aufgaben in Sekundenschnelle erledigen können.