Khi doanh nghiệp đang phát triển nhanh chóng, kỹ sư DevOps phải đối mặt với thách thức tự động hóa các quy trình trong tất cả các hệ thống được sử dụng cho CI/CD, bao gồm cả quản lý truy cập thủ công. Những công việc thường ngày như vậy có thể được chấp nhận khi bạn có một nhóm nhỏ, tối đa 10 bản phát hành, mỗi bản có 3 giai đoạn và 10 nhóm biến đổi.
Tuy nhiên, hãy tưởng tượng nếu nhóm của bạn phát triển lên hàng trăm người cần vận hành thủ công với tối đa 500 bản phát hành, mỗi bản có 10 giai đoạn và 200 nhóm biến đổi. Trong trường hợp này, quản lý truy cập thủ công làm chậm đáng kể việc giải quyết các yêu cầu của khách hàng.
Ở giai đoạn này, mọi doanh nghiệp đều phải đối mặt với nhu cầu tự động hóa hiệu quả.
Trong Social Discovery Group, chúng tôi dựa vào Azure DevOps làm công nghệ đám mây chính. Tuy nhiên, hiện tại, nó thiếu chức năng tự động hóa các quyền, quyền và cấp độ truy cập. Để giải quyết kịp thời nhu cầu của người dùng, chúng tôi quyết định tìm một số giải pháp tùy chỉnh và tự động hóa việc cấp quyền.
Trong bài viết này, chúng tôi sẽ chia sẻ một số mẹo hay về cách tự động hóa một số tác vụ thông thường nhất định trong Azure DevOps, tập trung vào:
Hiện tại, Azure cung cấp 3 tùy chọn để gán quyền: sử dụng giao diện đồ họa của trang web, sử dụng az DevOps CLI và yêu cầu API. Tùy chọn đầu tiên là đơn giản và nhanh chóng nhất; tuy nhiên, khi công ty mở rộng quy mô và số lượng quy trình CI/CD cũng như các biến tăng lên, việc gán quyền theo cách thủ công sẽ trở nên tốn thời gian.
Tùy chọn thứ hai để cấp quyền bị hạn chế về chức năng; nói cách khác, người dùng không thể vượt xa những gì một nhóm cụ thể cung cấp. Tùy chọn thứ ba thông qua REST API là tùy chọn linh hoạt nhất ở thời điểm hiện tại. Hãy xem xét một ví dụ cụ thể trong đó chúng tôi có nhiều quy trình phát hành với nhiều giai đoạn:
Mỗi giai đoạn triển khai cần có sự cho phép của một người dùng/nhóm cụ thể (được gọi là chức năng phê duyệt trước khi triển khai). Một vấn đề xuất hiện ở đây khi một người dùng hoặc một nhóm cụ thể cần được thêm hoặc xóa khỏi danh sách người phê duyệt nhất định.
Khi công ty mở rộng, khi số lượng quy trình và giai đoạn tăng lên N lần, trường hợp thông thường sẽ trở thành một vấn đề và giải pháp cho vấn đề này chỉ có thể thực hiện được thông qua các yêu cầu API. Đây là cách triển khai từ phía chúng tôi:
" 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' "
Một vấn đề tương tự phát sinh với phần Thư viện khi nhiều nhóm biến được tạo, trong đó một từ cụ thể được sử dụng để biểu thị sự liên kết với một thứ gì đó. Hãy chia nhỏ một ví dụ trong đó nhiều nhóm biến được tạo có chứa từ "DỊCH VỤ" để biểu thị mối liên kết của chúng với các biến liên quan đến dịch vụ.
Đồng thời kiểm tra tình huống trong đó một nhóm cụ thể cần quyền truy cập vào tất cả các biến với một số quyền nhất định.
Giải pháp cho trường hợp phổ biến này cũng chỉ có thể thực hiện được thông qua API REST:
" 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' "
Bằng cách tự động hóa các quy trình này, chúng tôi đã hiểu chi tiết hơn về các quyền và cấu trúc quyền trong Azure DevOps, từ đó nâng cao tính bảo mật của dự án.
Hiện tại, chúng tôi có các quy trình ổn định và linh hoạt cho nhiều nhiệm vụ, tăng tốc thời gian xử lý các yêu cầu của người dùng và cải thiện chất lượng thực thi của chúng.
Ngoài ra, tốc độ và chất lượng phát triển hệ thống tổng thể của chúng tôi đã được cải thiện do chúng tôi phân bổ nhiều thời gian hơn cho các nhiệm vụ CI/CD khác, tránh xa các hoạt động thông thường.
Chúng tôi đã nỗ lực đáng kể để tự động hóa các tác vụ hàng ngày, khám phá và phân tích các tùy chọn triển khai khác nhau, gỡ lỗi và tinh chỉnh. Việc có sẵn các quy trình và tập lệnh được tạo sẵn là một sự tiện lợi to lớn vì chúng có thể hoàn thành nhiệm vụ chỉ trong vài giây.