Skip to content

Commit 32777f0

Browse files
committed
updating devops pipeline
1 parent 01904c8 commit 32777f0

4 files changed

Lines changed: 229 additions & 121 deletions

File tree

src/services/identity-guide-service.ts

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,9 @@ fi
297297
298298
---
299299
300-
## Step 2: Create Managed Identity
300+
## Step 2: Create Managed Identity and Assign RBAC Roles
301+
302+
### Create the Managed Identity
301303
302304
**PowerShell:**
303305
\`\`\`powershell
@@ -309,13 +311,6 @@ $TENANT_ID = az account show --query tenantId -o tsv
309311
Write-Host "Managed Identity Client ID: $MI_CLIENT_ID"
310312
Write-Host "Managed Identity Principal ID: $MI_PRINCIPAL_ID"
311313
Write-Host "Tenant ID: $TENANT_ID"
312-
313-
# Assign API Management Service Contributor role for each environment's resource group
314-
foreach ($env in $ENVIRONMENTS) {
315-
$envUpper = $env.ToUpper()
316-
$apimRg = Get-Variable -Name "APIM_RG_$envUpper" -ValueOnly
317-
az role assignment create --assignee-object-id $MI_PRINCIPAL_ID --assignee-principal-type ServicePrincipal --role "API Management Service Contributor" --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$apimRg"
318-
}
319314
\`\`\`
320315
321316
**Git Bash:**
@@ -328,16 +323,102 @@ TENANT_ID=$(az account show --query tenantId -o tsv)
328323
echo "Managed Identity Client ID: $MI_CLIENT_ID"
329324
echo "Managed Identity Principal ID: $MI_PRINCIPAL_ID"
330325
echo "Tenant ID: $TENANT_ID"
326+
\`\`\`
327+
328+
---
329+
330+
### Assign RBAC Roles to the Managed Identity
331+
332+
> **Important:** The managed identity needs appropriate RBAC roles to access APIM resources:
333+
> - **API Management Service Reader Role** — Required for the **extract** pipeline to read APIM configurations (APIs, policies, backends, tags, etc.)
334+
> - **API Management Service Contributor** — Required for the **publish** pipeline to create/update APIM resources
335+
>
336+
> For security best practice, assign roles at the **APIM service scope** (not the resource group) to follow the principle of least privilege.
337+
338+
**PowerShell:**
339+
\`\`\`powershell
340+
# Assign RBAC roles for each environment's APIM instance
341+
foreach ($env in $ENVIRONMENTS) {
342+
$envUpper = $env.ToUpper()
343+
$apimInstanceId = Get-Variable -Name "APIM_INSTANCE_$envUpper" -ValueOnly
344+
345+
Write-Host "Assigning roles for $env environment (APIM: $apimInstanceId)"
346+
347+
# API Management Service Reader Role (for extract pipeline)
348+
az role assignment create \`
349+
--assignee-object-id $MI_PRINCIPAL_ID \`
350+
--assignee-principal-type ServicePrincipal \`
351+
--role "API Management Service Reader Role" \`
352+
--scope $apimInstanceId
353+
354+
# API Management Service Contributor (for publish pipeline)
355+
az role assignment create \`
356+
--assignee-object-id $MI_PRINCIPAL_ID \`
357+
--assignee-principal-type ServicePrincipal \`
358+
--role "API Management Service Contributor" \`
359+
--scope $apimInstanceId
360+
}
361+
362+
Write-Host "\`nℹ️ Note: RBAC role assignments can take 5-10 minutes to propagate."
363+
\`\`\`
331364
332-
# Assign API Management Service Contributor role for each environment's resource group
365+
**Git Bash:**
366+
\`\`\`bash
367+
# Assign RBAC roles for each environment's APIM instance
333368
for env in "\${ENVIRONMENTS[@]}"; do
334369
env_upper=$(echo "$env" | tr '[:lower:]' '[:upper:]')
335-
apim_rg_var="APIM_RG_\${env_upper}"
336-
apim_rg="\${!apim_rg_var}"
337-
az role assignment create --assignee-object-id "$MI_PRINCIPAL_ID" --assignee-principal-type ServicePrincipal --role "API Management Service Contributor" --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$apim_rg"
370+
apim_instance_var="APIM_INSTANCE_\${env_upper}"
371+
apim_instance_id="\${!apim_instance_var}"
372+
373+
echo "Assigning roles for $env environment (APIM: $apim_instance_id)"
374+
375+
# API Management Service Reader Role (for extract pipeline)
376+
az role assignment create \\
377+
--assignee-object-id "$MI_PRINCIPAL_ID" \\
378+
--assignee-principal-type ServicePrincipal \\
379+
--role "API Management Service Reader Role" \\
380+
--scope "$apim_instance_id"
381+
382+
# API Management Service Contributor (for publish pipeline)
383+
az role assignment create \\
384+
--assignee-object-id "$MI_PRINCIPAL_ID" \\
385+
--assignee-principal-type ServicePrincipal \\
386+
--role "API Management Service Contributor" \\
387+
--scope "$apim_instance_id"
338388
done
389+
390+
echo ""
391+
echo "ℹ️ Note: RBAC role assignments can take 5-10 minutes to propagate."
339392
\`\`\`
340393
394+
### Verify Role Assignments
395+
396+
Verify the roles were assigned correctly:
397+
398+
**PowerShell:**
399+
\`\`\`powershell
400+
foreach ($env in $ENVIRONMENTS) {
401+
$envUpper = $env.ToUpper()
402+
$apimInstanceId = Get-Variable -Name "APIM_INSTANCE_$envUpper" -ValueOnly
403+
Write-Host "\`nRole assignments for $env environment:"
404+
az role assignment list --assignee $MI_PRINCIPAL_ID --scope $apimInstanceId --query "[].{Role:roleDefinitionName, Scope:scope}" -o table
405+
}
406+
\`\`\`
407+
408+
**Git Bash:**
409+
\`\`\`bash
410+
for env in "\${ENVIRONMENTS[@]}"; do
411+
env_upper=$(echo "$env" | tr '[:lower:]' '[:upper:]')
412+
apim_instance_var="APIM_INSTANCE_\${env_upper}"
413+
apim_instance_id="\${!apim_instance_var}"
414+
echo ""
415+
echo "Role assignments for $env environment:"
416+
az role assignment list --assignee "$MI_PRINCIPAL_ID" --scope "$apim_instance_id" --query "[].{Role:roleDefinitionName, Scope:scope}" -o table
417+
done
418+
\`\`\`
419+
420+
> **Expected Output:** You should see both "API Management Service Reader Role" and "API Management Service Contributor" roles listed for each environment.
421+
341422
---
342423
343424
## Step 3: Configure Azure DevOps CLI
@@ -524,7 +605,7 @@ az pipelines variable-group create \`
524605
--name "apim-common" \`
525606
--project $AZDO_PROJECT \`
526607
--organization $AZDO_ORG \`
527-
--variables AZURE_SUBSCRIPTION_ID=$SUBSCRIPTION_ID AZURE_SERVICE_CONNECTION="AZURE_SERVICE_CONNECTION"
608+
--variables AZURE_SUBSCRIPTION_ID=$SUBSCRIPTION_ID APIOPS_CLI_VERSION="latest"
528609
\`\`\`
529610
530611
**Git Bash:**
@@ -533,7 +614,7 @@ az pipelines variable-group create \\
533614
--name "apim-common" \\
534615
--project "$AZDO_PROJECT" \\
535616
--organization "$AZDO_ORG" \\
536-
--variables AZURE_SUBSCRIPTION_ID="$SUBSCRIPTION_ID" AZURE_SERVICE_CONNECTION="AZURE_SERVICE_CONNECTION"
617+
--variables AZURE_SUBSCRIPTION_ID="$SUBSCRIPTION_ID" APIOPS_CLI_VERSION="latest"
537618
\`\`\`
538619
539620
Create environment-specific variable groups using values extracted from APIM instance IDs:
@@ -549,7 +630,7 @@ foreach ($env in $ENVIRONMENTS) {
549630
--name "apim-$env" \`
550631
--project $AZDO_PROJECT \`
551632
--organization $AZDO_ORG \`
552-
--variables APIM_RESOURCE_GROUP=$apimRg APIM_SERVICE_NAME=$apimName AZURE_SUBSCRIPTION_ID=$SUBSCRIPTION_ID AZURE_SERVICE_CONNECTION="AZURE_SERVICE_CONNECTION_$envUpper"
633+
--variables APIM_RESOURCE_GROUP=$apimRg APIM_SERVICE_NAME=$apimName AZURE_SUBSCRIPTION_ID=$SUBSCRIPTION_ID
553634
}
554635
\`\`\`
555636
@@ -566,7 +647,7 @@ for env in "\${ENVIRONMENTS[@]}"; do
566647
--name "apim-$env" \\
567648
--project "$AZDO_PROJECT" \\
568649
--organization "$AZDO_ORG" \\
569-
--variables APIM_RESOURCE_GROUP="$apim_rg" APIM_SERVICE_NAME="$apim_name" AZURE_SUBSCRIPTION_ID="$SUBSCRIPTION_ID" AZURE_SERVICE_CONNECTION="AZURE_SERVICE_CONNECTION_$env_upper"
650+
--variables APIM_RESOURCE_GROUP="$apim_rg" APIM_SERVICE_NAME="$apim_name" AZURE_SUBSCRIPTION_ID="$SUBSCRIPTION_ID"
570651
done
571652
\`\`\`
572653

src/services/init-service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ class InitServiceImpl implements InitService {
348348
// Extract pipeline
349349
const extractPipelineConfig: ExtractPipelineConfig = {
350350
artifactDir: config.artifactDir,
351+
environments: config.environments,
351352
};
352353
const extractContent = generateExtractPipeline(extractPipelineConfig);
353354
const extractPath = path.join(pipelinesDir, 'run-apim-extractor.yml');

src/templates/azure-devops/extract-pipeline.ts

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,73 +5,89 @@
55

66
export interface ExtractPipelineConfig {
77
artifactDir: string;
8+
environments: string[];
89
}
910

1011
export function generateExtractPipeline(config: ExtractPipelineConfig): string {
12+
const envValues = config.environments.map((env) => ` - '${env}'`).join('\n');
13+
1114
return `# Azure DevOps Pipeline: Run APIM Extractor
1215
1316
trigger: none
1417
1518
parameters:
19+
- name: ENVIRONMENT
20+
type: string
21+
displayName: 'Choose which environment to extract from'
22+
default: '${config.environments[0]}'
23+
values:
24+
${envValues}
1625
- name: CONFIGURATION_YAML_PATH
1726
type: string
1827
displayName: 'Choose whether to extract all APIs or use the extraction configuration file'
1928
default: 'Extract All APIs'
2029
values:
2130
- 'Extract All APIs'
2231
- 'configuration.extract.yaml'
23-
- name: resourceGroup
24-
type: string
25-
displayName: 'Azure Resource Group'
26-
default: $(APIM_RESOURCE_GROUP)
27-
- name: serviceName
28-
type: string
29-
displayName: 'APIM Service Name'
30-
default: $(APIM_SERVICE_NAME)
3132
3233
pool:
3334
vmImage: 'ubuntu-latest'
3435
3536
variables:
3637
- group: apim-common
38+
- \${{ if eq(parameters.ENVIRONMENT, '${config.environments[0]}') }}:
39+
- group: apim-${config.environments[0]}
40+
${config.environments.slice(1).map(env => ` - \${{ if eq(parameters.ENVIRONMENT, '${env}') }}:
41+
- group: apim-${env}`).join('\n')}
3742
3843
steps:
39-
- task: NodeTool@0
44+
- task: UseNode@1
4045
displayName: 'Setup Node.js'
4146
inputs:
42-
versionSpec: '22.x'
47+
version: '22.x'
4348
44-
- script: npm ci
49+
- script: |
50+
az extension add --name azure-devops --upgrade
51+
az devops configure --defaults organization=$(System.TeamFoundationCollectionUri) project=$(System.TeamProject)
52+
displayName: 'Install Azure DevOps CLI extension'
53+
env:
54+
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
55+
56+
- script: npm install @peterhauge/apiops-cli@$(APIOPS_CLI_VERSION)
4557
displayName: 'Install dependencies'
4658
4759
- task: AzureCLI@2
4860
displayName: 'Run APIM Extract (All APIs)'
4961
condition: eq('\${{ parameters.CONFIGURATION_YAML_PATH }}', 'Extract All APIs')
5062
inputs:
51-
azureSubscription: '$(AZURE_SERVICE_CONNECTION)'
63+
azureSubscription: 'AZURE_SERVICE_CONNECTION_\${{ upper(parameters.ENVIRONMENT) }}'
5264
scriptType: 'bash'
5365
scriptLocation: 'inlineScript'
66+
addSpnToEnvironment: true
67+
workingDirectory: '$(Build.SourcesDirectory)'
5468
inlineScript: |
5569
npx apiops extract \\
56-
--resource-group \${{ parameters.resourceGroup }} \\
57-
--service-name \${{ parameters.serviceName }} \\
58-
--output ${config.artifactDir} \\
59-
--subscription-id $(AZURE_SUBSCRIPTION_ID)
70+
--resource-group "$(APIM_RESOURCE_GROUP)" \\
71+
--service-name "$(APIM_SERVICE_NAME)" \\
72+
--output $(Build.SourcesDirectory)/${config.artifactDir} \\
73+
--subscription-id "$(AZURE_SUBSCRIPTION_ID)"
6074
6175
- task: AzureCLI@2
6276
displayName: 'Run APIM Extract (With Configuration)'
6377
condition: ne('\${{ parameters.CONFIGURATION_YAML_PATH }}', 'Extract All APIs')
6478
inputs:
65-
azureSubscription: '$(AZURE_SERVICE_CONNECTION)'
79+
azureSubscription: 'AZURE_SERVICE_CONNECTION_\${{ upper(parameters.ENVIRONMENT) }}'
6680
scriptType: 'bash'
6781
scriptLocation: 'inlineScript'
82+
addSpnToEnvironment: true
83+
workingDirectory: '$(Build.SourcesDirectory)'
6884
inlineScript: |
6985
npx apiops extract \\
70-
--resource-group \${{ parameters.resourceGroup }} \\
71-
--service-name \${{ parameters.serviceName }} \\
72-
--output ${config.artifactDir} \\
73-
--filter configuration.extract.yaml \\
74-
--subscription-id $(AZURE_SUBSCRIPTION_ID)
86+
--resource-group "$(APIM_RESOURCE_GROUP)" \\
87+
--service-name "$(APIM_SERVICE_NAME)" \\
88+
--output $(Build.SourcesDirectory)/${config.artifactDir} \\
89+
--filter $(Build.SourcesDirectory)/configuration.extract.yaml \\
90+
--subscription-id "$(AZURE_SUBSCRIPTION_ID)"
7591
7692
- task: PublishPipelineArtifact@1
7793
displayName: 'Publish artifacts'
@@ -80,20 +96,62 @@ steps:
8096
artifactName: apim-artifacts
8197
8298
- script: |
83-
BRANCH_NAME="apim-extract-$(Build.BuildId)"
99+
BRANCH_NAME="apim-extract-\${{ parameters.ENVIRONMENT }}-$(Build.BuildId)"
100+
101+
# Configure git authentication using System.AccessToken
84102
git config user.name "Azure DevOps"
85103
git config user.email "azuredevops@microsoft.com"
104+
git config --global http.extraheader "AUTHORIZATION: bearer $SYSTEM_ACCESSTOKEN"
105+
106+
# Create and commit changes
86107
git checkout -b "$BRANCH_NAME"
87108
git add ${config.artifactDir}
109+
88110
if git diff --cached --quiet; then
89111
echo "No changes to commit"
112+
echo "##vso[task.setvariable variable=HAS_CHANGES]false"
90113
else
91-
git commit -m "chore: update APIM artifacts from extract"
114+
git commit -m "chore: update APIM artifacts from \${{ parameters.ENVIRONMENT }} extract"
92115
git push origin "$BRANCH_NAME"
93-
echo "##vso[task.logissue type=warning]Branch '$BRANCH_NAME' pushed. Please create a pull request to merge the changes."
116+
echo "##vso[task.setvariable variable=HAS_CHANGES]true"
117+
echo "##vso[task.setvariable variable=BRANCH_NAME]$BRANCH_NAME"
118+
echo "Branch '$BRANCH_NAME' created and pushed successfully"
94119
fi
95120
displayName: 'Create branch with changes'
96121
env:
97122
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
123+
124+
- task: PowerShell@2
125+
displayName: 'Create pull request'
126+
condition: and(succeeded(), eq(variables['HAS_CHANGES'], 'true'))
127+
env:
128+
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
129+
inputs:
130+
targetType: 'inline'
131+
pwsh: true
132+
script: |
133+
$branchName = "$(BRANCH_NAME)"
134+
$title = "chore: APIM \${{ parameters.ENVIRONMENT }} extract (Build $(Build.BuildId))"
135+
$description = "Automated extraction of APIM artifacts from **\${{ parameters.ENVIRONMENT }}** environment.\`n\`nBuild: [$(Build.BuildId)]($(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId))"
136+
137+
Write-Host "Creating pull request from branch: $branchName"
138+
139+
az repos pr create \`
140+
--organization "$(System.TeamFoundationCollectionUri)" \`
141+
--project "$(System.TeamProject)" \`
142+
--repository "$(Build.Repository.Name)" \`
143+
--source-branch "$branchName" \`
144+
--target-branch "$(Build.SourceBranchName)" \`
145+
--title "$title" \`
146+
--description "$description" \`
147+
--auto-complete false \`
148+
--output table
149+
150+
if ($LASTEXITCODE -eq 0) {
151+
Write-Host "##[section]✅ Pull request created successfully"
152+
} else {
153+
Write-Host "##[error]Failed to create pull request"
154+
exit 1
155+
}
98156
`;
99157
}

0 commit comments

Comments
 (0)