mdan-cli 2.5.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +76 -1
- package/README.md +274 -4
- package/agents/auto-orchestrator.md +343 -0
- package/agents/devops.md +511 -94
- package/cli/mdan.py +111 -6
- package/cli/mdan_crewai.py +539 -0
- package/core/crewai_orchestrator.md +419 -0
- package/core/debate-protocol.md +454 -0
- package/core/universal-envelope.md +113 -0
- package/integrations/__init__.py +33 -0
- package/integrations/crewai/__init__.py +27 -0
- package/integrations/crewai/agents/__init__.py +21 -0
- package/integrations/crewai/agents/architect_agent.py +264 -0
- package/integrations/crewai/agents/dev_agent.py +271 -0
- package/integrations/crewai/agents/devops_agent.py +421 -0
- package/integrations/crewai/agents/doc_agent.py +388 -0
- package/integrations/crewai/agents/product_agent.py +203 -0
- package/integrations/crewai/agents/security_agent.py +386 -0
- package/integrations/crewai/agents/test_agent.py +358 -0
- package/integrations/crewai/agents/ux_agent.py +257 -0
- package/integrations/crewai/flows/__init__.py +13 -0
- package/integrations/crewai/flows/auto_flow.py +451 -0
- package/integrations/crewai/flows/build_flow.py +297 -0
- package/integrations/crewai/flows/debate_flow.py +422 -0
- package/integrations/crewai/flows/discovery_flow.py +267 -0
- package/integrations/crewai/orchestrator.py +558 -0
- package/integrations/crewai/skills/__init__.py +8 -0
- package/integrations/crewai/skills/skill_router.py +534 -0
- package/integrations/crewai/tools/__init__.py +11 -0
- package/integrations/crewai/tools/file_tool.py +355 -0
- package/integrations/crewai/tools/serper_tool.py +169 -0
- package/integrations/crewai/tools/sql_tool.py +435 -0
- package/memory/CONTEXT-SAVE-FORMAT.md +328 -0
- package/memory/MEMORY-AUTO.json +66 -0
- package/memory/RESUME-PROTOCOL.md +379 -0
- package/package.json +1 -1
- package/phases/auto-01-load.md +165 -0
- package/phases/auto-02-discover.md +207 -0
- package/phases/auto-03-plan.md +509 -0
- package/phases/auto-04-architect.md +567 -0
- package/phases/auto-05-implement.md +713 -0
- package/phases/auto-06-test.md +559 -0
- package/phases/auto-07-deploy.md +510 -0
- package/phases/auto-08-doc.md +970 -0
- package/skills/azure-devops/skill.md +1757 -0
- package/templates/dotnet-blazor/README.md +415 -0
- package/templates/external-services/ExampleService.cs +361 -0
- package/templates/external-services/IService.cs +113 -0
- package/templates/external-services/README.md +325 -0
- package/templates/external-services/ServiceBase.cs +492 -0
- package/templates/external-services/ServiceProvider.cs +243 -0
- package/templates/prompts/devops-agent.yaml +327 -0
- package/templates/prompts.json +15 -1
- package/templates/sql-server/README.md +37 -0
- package/templates/sql-server/functions.sql +158 -0
- package/templates/sql-server/schema.sql +188 -0
- package/templates/sql-server/stored-procedures.sql +284 -0
|
@@ -0,0 +1,1757 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: azure-devops
|
|
3
|
+
description: Senior Azure DevOps engineering capabilities for comprehensive cloud DevOps operations. Use PROACTIVELY for Azure DevOps pipelines, Azure CLI commands, AKS deployments, infrastructure as code, and CI/CD workflows.
|
|
4
|
+
triggers:
|
|
5
|
+
- azure
|
|
6
|
+
- devops
|
|
7
|
+
- pipeline
|
|
8
|
+
- aks
|
|
9
|
+
- azure devops
|
|
10
|
+
- azdo
|
|
11
|
+
- az cli
|
|
12
|
+
- azure cli
|
|
13
|
+
- yaml pipeline
|
|
14
|
+
- azure repos
|
|
15
|
+
- azure boards
|
|
16
|
+
- azure artifacts
|
|
17
|
+
- service connection
|
|
18
|
+
- variable group
|
|
19
|
+
- agent pool
|
|
20
|
+
- bicep
|
|
21
|
+
- arm template
|
|
22
|
+
- terraform azure
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Azure DevOps Skill
|
|
26
|
+
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
This skill provides senior-level Azure DevOps engineering capabilities. It covers the complete Azure DevOps ecosystem including Azure CLI, Azure DevOps CLI extension, YAML pipeline authoring, Infrastructure as Code, and enterprise CI/CD patterns.
|
|
30
|
+
|
|
31
|
+
## Prerequisites
|
|
32
|
+
|
|
33
|
+
### Required Tools
|
|
34
|
+
```bash
|
|
35
|
+
# Azure CLI
|
|
36
|
+
brew install azure-cli # macOS
|
|
37
|
+
winget install Microsoft.AzureCLI # Windows
|
|
38
|
+
|
|
39
|
+
# Azure DevOps CLI extension
|
|
40
|
+
az extension add --name azure-devops
|
|
41
|
+
|
|
42
|
+
# Optional but recommended
|
|
43
|
+
brew install bicep # Bicep CLI
|
|
44
|
+
brew install terraform # Terraform
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Authentication
|
|
48
|
+
```bash
|
|
49
|
+
# Login to Azure
|
|
50
|
+
az login
|
|
51
|
+
|
|
52
|
+
# Login with service principal (CI/CD)
|
|
53
|
+
az login --service-principal -u <app-id> -p <password-or-cert> --tenant <tenant-id>
|
|
54
|
+
|
|
55
|
+
# Set default subscription
|
|
56
|
+
az account set --subscription "<subscription-id-or-name>"
|
|
57
|
+
|
|
58
|
+
# Configure Azure DevOps defaults
|
|
59
|
+
az devops configure --defaults organization=https://dev.azure.com/<org> project=<project>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Core Capabilities
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 1. Azure CLI Reference
|
|
67
|
+
|
|
68
|
+
### Account & Subscription Management
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# List all subscriptions
|
|
72
|
+
az account list --output table
|
|
73
|
+
|
|
74
|
+
# Show current subscription
|
|
75
|
+
az account show
|
|
76
|
+
|
|
77
|
+
# Set subscription
|
|
78
|
+
az account set --subscription "<subscription-id>"
|
|
79
|
+
|
|
80
|
+
# List tenants
|
|
81
|
+
az account tenant list
|
|
82
|
+
|
|
83
|
+
# Clear account cache
|
|
84
|
+
az account clear
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Resource Groups
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Create resource group
|
|
91
|
+
az group create --name myResourceGroup --location eastus
|
|
92
|
+
|
|
93
|
+
# List resource groups
|
|
94
|
+
az group list --output table
|
|
95
|
+
|
|
96
|
+
# Show resource group details
|
|
97
|
+
az group show --name myResourceGroup
|
|
98
|
+
|
|
99
|
+
# Delete resource group (DANGEROUS)
|
|
100
|
+
az group delete --name myResourceGroup --yes --no-wait
|
|
101
|
+
|
|
102
|
+
# Export template from resource group
|
|
103
|
+
az group export --name myResourceGroup > template.json
|
|
104
|
+
|
|
105
|
+
# Wait for resource group operations
|
|
106
|
+
az group wait --name myResourceGroup --created
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Resource Management
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# List all resources in a group
|
|
113
|
+
az resource list --resource-group myResourceGroup --output table
|
|
114
|
+
|
|
115
|
+
# Show specific resource
|
|
116
|
+
az resource show --ids <resource-id>
|
|
117
|
+
|
|
118
|
+
# Delete resource
|
|
119
|
+
az resource delete --ids <resource-id>
|
|
120
|
+
|
|
121
|
+
# Move resource to another group
|
|
122
|
+
az resource move --destination-group newGroup --ids <resource-id>
|
|
123
|
+
|
|
124
|
+
# Tag resources
|
|
125
|
+
az resource tag --ids <resource-id> --tags Environment=Production Team=Platform
|
|
126
|
+
|
|
127
|
+
# Lock resource (prevent deletion)
|
|
128
|
+
az lock create --name MyLock --resource-group myResourceGroup --resource-type Microsoft.Storage/storageAccounts --resource-name mystorageaccount --lock-type CanNotDelete
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 2. Azure DevOps CLI Extension
|
|
134
|
+
|
|
135
|
+
### Organization & Project Management
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# List organizations
|
|
139
|
+
az devops organization list
|
|
140
|
+
|
|
141
|
+
# Show organization details
|
|
142
|
+
az devops organization show --org https://dev.azure.com/<org>
|
|
143
|
+
|
|
144
|
+
# Create project
|
|
145
|
+
az devops project create --name "MyProject" --description "Project description" --visibility private --source-control git --process Agile
|
|
146
|
+
|
|
147
|
+
# List projects
|
|
148
|
+
az devops project list --output table
|
|
149
|
+
|
|
150
|
+
# Show project
|
|
151
|
+
az devops project show --project "MyProject"
|
|
152
|
+
|
|
153
|
+
# Update project
|
|
154
|
+
az devops project update --project "MyProject" --description "New description"
|
|
155
|
+
|
|
156
|
+
# Delete project
|
|
157
|
+
az devops project delete --project "MyProject" --yes
|
|
158
|
+
|
|
159
|
+
# Get project properties
|
|
160
|
+
az devops project show --project "MyProject" --query "properties"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Repositories (Azure Repos)
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Create repository
|
|
167
|
+
az repos create --name "MyRepo" --project "MyProject"
|
|
168
|
+
|
|
169
|
+
# List repositories
|
|
170
|
+
az repos list --project "MyProject" --output table
|
|
171
|
+
|
|
172
|
+
# Show repository
|
|
173
|
+
az repos show --repository "MyRepo" --project "MyProject"
|
|
174
|
+
|
|
175
|
+
# Delete repository
|
|
176
|
+
az repos delete --repository "MyRepo" --project "MyProject" --yes
|
|
177
|
+
|
|
178
|
+
# Create branch policy
|
|
179
|
+
az repos policy branch create --project "MyProject" --repository-id <repo-id> --branch main --policy-type "Minimum number of reviewers" --settings '{"minimumApproverCount": 2, "creatorVoteCounts": false, "allowDownvotes": false}'
|
|
180
|
+
|
|
181
|
+
# List branch policies
|
|
182
|
+
az repos policy list --project "MyProject" --repository-id <repo-id> --branch main
|
|
183
|
+
|
|
184
|
+
# Create PR
|
|
185
|
+
az repos pr create --project "MyProject" --repository "MyRepo" --source-branch feature/my-feature --target-branch main --title "My PR" --description "PR description"
|
|
186
|
+
|
|
187
|
+
# List PRs
|
|
188
|
+
az repos pr list --project "MyProject" --repository "MyRepo" --status active
|
|
189
|
+
|
|
190
|
+
# Update PR
|
|
191
|
+
az repos pr update --id <pr-id> --status completed --merge-commit-message "Merged PR"
|
|
192
|
+
|
|
193
|
+
# Create PR reviewer
|
|
194
|
+
az repos pr reviewer add --id <pr-id> --reviewers <user-id>
|
|
195
|
+
|
|
196
|
+
# Create policy for required reviewers
|
|
197
|
+
az repos policy required-reviewer create --project "MyProject" --repository-id <repo-id> --branch main --enabled true --blocking true --settings '{"requiredReviewerIds": ["<user-id>"], "message": "Requires review"}'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Pipelines
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Create pipeline (from YAML file)
|
|
204
|
+
az pipelines create --name "MyPipeline" --project "MyProject" --repository "MyRepo" --repository-type tfsgit --branch main --yml-path azure-pipelines.yml
|
|
205
|
+
|
|
206
|
+
# List pipelines
|
|
207
|
+
az pipelines list --project "MyProject" --output table
|
|
208
|
+
|
|
209
|
+
# Show pipeline
|
|
210
|
+
az pipelines show --name "MyPipeline" --project "MyProject"
|
|
211
|
+
|
|
212
|
+
# Run pipeline
|
|
213
|
+
az pipelines run --name "MyPipeline" --project "MyProject" --branch main
|
|
214
|
+
|
|
215
|
+
# Run with parameters
|
|
216
|
+
az pipelines run --name "MyPipeline" --project "MyProject" --parameters env=production version=1.0.0
|
|
217
|
+
|
|
218
|
+
# Run with variables
|
|
219
|
+
az pipelines run --name "MyPipeline" --project "MyProject" --variables buildConfiguration=Release
|
|
220
|
+
|
|
221
|
+
# Delete pipeline
|
|
222
|
+
az pipelines delete --name "MyPipeline" --project "MyProject" --yes
|
|
223
|
+
|
|
224
|
+
# List pipeline runs
|
|
225
|
+
az pipelines runs list --project "MyProject" --pipeline-ids <pipeline-id>
|
|
226
|
+
|
|
227
|
+
# Show pipeline run
|
|
228
|
+
az pipelines runs show --id <run-id> --project "MyProject"
|
|
229
|
+
|
|
230
|
+
# Cancel pipeline run
|
|
231
|
+
az pipelines runs cancel --id <run-id> --project "MyProject"
|
|
232
|
+
|
|
233
|
+
# List pipeline definitions
|
|
234
|
+
az pipelines definition list --project "MyProject"
|
|
235
|
+
|
|
236
|
+
# Build (classic) commands
|
|
237
|
+
az pipelines build list --project "MyProject"
|
|
238
|
+
az pipelines build queue --definition-name "MyBuild" --project "MyProject"
|
|
239
|
+
az pipelines build show --id <build-id> --project "MyProject"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Variable Groups
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Create variable group
|
|
246
|
+
az pipelines variable-group create --name "MyVars" --project "MyProject" --variables env=production region=eastus --authorize true
|
|
247
|
+
|
|
248
|
+
# List variable groups
|
|
249
|
+
az pipelines variable-group list --project "MyProject" --output table
|
|
250
|
+
|
|
251
|
+
# Show variable group
|
|
252
|
+
az pipelines variable-group show --group-id <id> --project "MyProject"
|
|
253
|
+
|
|
254
|
+
# Update variable group
|
|
255
|
+
az pipelines variable-group update --group-id <id> --project "MyProject" --variables newVar=newValue
|
|
256
|
+
|
|
257
|
+
# Delete variable group
|
|
258
|
+
az pipelines variable-group delete --group-id <id> --project "MyProject" --yes
|
|
259
|
+
|
|
260
|
+
# Add variable to group
|
|
261
|
+
az pipelines variable-group variable create --group-id <id> --name "apiKey" --value "secret-value" --secret true
|
|
262
|
+
|
|
263
|
+
# Update variable in group
|
|
264
|
+
az pipelines variable-group variable update --group-id <id> --name "apiKey" --value "new-value" --secret true
|
|
265
|
+
|
|
266
|
+
# Delete variable from group
|
|
267
|
+
az pipelines variable-group variable delete --group-id <id> --name "apiKey" --yes
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Service Connections
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# Create Azure Resource Manager service connection (automatic)
|
|
274
|
+
az devops service-endpoint azurerm create --name "MyAzureConnection" --project "MyProject" --azure-rm-service-principal-id <app-id> --azure-rm-subscription-id <sub-id> --azure-rm-subscription-name <sub-name> --azure-rm-tenant-id <tenant-id>
|
|
275
|
+
|
|
276
|
+
# List service connections
|
|
277
|
+
az devops service-endpoint list --project "MyProject" --output table
|
|
278
|
+
|
|
279
|
+
# Show service connection
|
|
280
|
+
az devops service-endpoint show --id <connection-id> --project "MyProject"
|
|
281
|
+
|
|
282
|
+
# Delete service connection
|
|
283
|
+
az devops service-endpoint delete --id <connection-id> --project "MyProject" --yes
|
|
284
|
+
|
|
285
|
+
# Create GitHub service connection
|
|
286
|
+
az devops service-endpoint github create --name "MyGitHub" --project "MyProject" --github-url https://github.com --github-access-token <pat>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Agent Pools
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# List agent pools
|
|
293
|
+
az pipelines pool list --output table
|
|
294
|
+
|
|
295
|
+
# Show agent pool
|
|
296
|
+
az pipelines pool show --pool-id <id>
|
|
297
|
+
|
|
298
|
+
# List agents in pool
|
|
299
|
+
az pipelines agent list --pool-id <id> --output table
|
|
300
|
+
|
|
301
|
+
# Show agent details
|
|
302
|
+
az pipelines agent show --pool-id <id> --agent-id <agent-id>
|
|
303
|
+
|
|
304
|
+
# Create agent pool
|
|
305
|
+
az pipelines pool create --name "MyPool" --pool-type automation --auto-provision false
|
|
306
|
+
|
|
307
|
+
# Delete agent pool
|
|
308
|
+
az pipelines pool delete --pool-id <id> --yes
|
|
309
|
+
|
|
310
|
+
# Create agent queue
|
|
311
|
+
az pipelines queue create --name "MyQueue" --project "MyProject" --pool-id <id>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Environments
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# List environments
|
|
318
|
+
az pipelines environment list --project "MyProject" --output table
|
|
319
|
+
|
|
320
|
+
# Show environment
|
|
321
|
+
az pipelines environment show --environment-id <id> --project "MyProject"
|
|
322
|
+
|
|
323
|
+
# Create environment
|
|
324
|
+
az pipelines environment create --name "Production" --project "MyProject"
|
|
325
|
+
|
|
326
|
+
# Delete environment
|
|
327
|
+
az pipelines environment delete --environment-id <id> --project "MyProject" --yes
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Boards (Work Items)
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
# Create work item
|
|
334
|
+
az boards work-item create --title "New Bug" --type "Bug" --project "MyProject" --description "Bug description" --area "MyArea" --iteration "Sprint 1"
|
|
335
|
+
|
|
336
|
+
# Show work item
|
|
337
|
+
az boards work-item show --id <work-item-id> --project "MyProject"
|
|
338
|
+
|
|
339
|
+
# Update work item
|
|
340
|
+
az boards work-item update --id <work-item-id> --project "MyProject" --state "In Progress" --assigned-to <user-email>
|
|
341
|
+
|
|
342
|
+
# List work items (query)
|
|
343
|
+
az boards query --project "MyProject" --wiql "SELECT [System.Id], [System.Title], [System.State] FROM WorkItems WHERE [System.WorkItemType] = 'Bug'"
|
|
344
|
+
|
|
345
|
+
# Delete work item
|
|
346
|
+
az boards work-item delete --id <work-item-id> --project "MyProject" --yes
|
|
347
|
+
|
|
348
|
+
# List work item types
|
|
349
|
+
az boards work-item-type list --project "MyProject"
|
|
350
|
+
|
|
351
|
+
# List areas
|
|
352
|
+
az boards area project list --project "MyProject"
|
|
353
|
+
|
|
354
|
+
# Create area
|
|
355
|
+
az boards area project create --name "MyArea" --project "MyProject"
|
|
356
|
+
|
|
357
|
+
# List iterations
|
|
358
|
+
az boards iteration project list --project "MyProject"
|
|
359
|
+
|
|
360
|
+
# Create iteration
|
|
361
|
+
az boards iteration project create --name "Sprint 1" --project "MyProject" --start-date "2024-01-01" --finish-date "2024-01-14"
|
|
362
|
+
|
|
363
|
+
# List sprints
|
|
364
|
+
az boards sprint list --project "MyProject"
|
|
365
|
+
|
|
366
|
+
# Show sprint
|
|
367
|
+
az boards sprint show --id <sprint-id> --project "MyProject"
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Artifacts (Feeds & Packages)
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
# List feeds
|
|
374
|
+
az artifacts universal list --project "MyProject" --output table
|
|
375
|
+
|
|
376
|
+
# Create feed
|
|
377
|
+
az artifacts feed create --name "MyFeed" --project "MyProject"
|
|
378
|
+
|
|
379
|
+
# Show feed
|
|
380
|
+
az artifacts feed show --feed "MyFeed" --project "MyProject"
|
|
381
|
+
|
|
382
|
+
# Delete feed
|
|
383
|
+
az artifacts feed delete --feed "MyFeed" --project "MyProject" --yes
|
|
384
|
+
|
|
385
|
+
# Publish universal package
|
|
386
|
+
az artifacts universal publish --feed "MyFeed" --name "MyPackage" --version "1.0.0" --description "Package description" --path ./artifacts
|
|
387
|
+
|
|
388
|
+
# Download universal package
|
|
389
|
+
az artifacts universal download --feed "MyFeed" --name "MyPackage" --version "1.0.0" --path ./download
|
|
390
|
+
|
|
391
|
+
# List package versions
|
|
392
|
+
az artifacts package list --feed "MyFeed" --project "MyProject"
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Test Plans
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
# List test plans
|
|
399
|
+
az pipelines test-plan list --project "MyProject"
|
|
400
|
+
|
|
401
|
+
# Show test plan
|
|
402
|
+
az pipelines test-plan show --test-plan-id <id> --project "MyProject"
|
|
403
|
+
|
|
404
|
+
# Create test plan
|
|
405
|
+
az pipelines test-plan create --name "MyTestPlan" --project "MyProject"
|
|
406
|
+
|
|
407
|
+
# Delete test plan
|
|
408
|
+
az pipelines test-plan delete --test-plan-id <id> --project "MyProject" --yes
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Teams & Security
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
# List teams
|
|
415
|
+
az devops team list --project "MyProject" --output table
|
|
416
|
+
|
|
417
|
+
# Show team
|
|
418
|
+
az devops team show --team "MyTeam" --project "MyProject"
|
|
419
|
+
|
|
420
|
+
# Create team
|
|
421
|
+
az devops team create --name "MyTeam" --project "MyProject"
|
|
422
|
+
|
|
423
|
+
# Add member to team
|
|
424
|
+
az devops team member add --team "MyTeam" --project "MyProject" --member <user-id>
|
|
425
|
+
|
|
426
|
+
# List team members
|
|
427
|
+
az devops team member list --team "MyTeam" --project "MyProject"
|
|
428
|
+
|
|
429
|
+
# List security groups
|
|
430
|
+
az devops security group list --project "MyProject" --output table
|
|
431
|
+
|
|
432
|
+
# Create security group
|
|
433
|
+
az devops security group create --name "MyGroup" --project "MyProject" --description "Custom group"
|
|
434
|
+
|
|
435
|
+
# Add member to security group
|
|
436
|
+
az devops security group membership add --group-id <group-id> --member-id <user-id>
|
|
437
|
+
|
|
438
|
+
# List security namespaces
|
|
439
|
+
az devops security namespace list --output table
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 3. Pipeline Authoring
|
|
445
|
+
|
|
446
|
+
### Basic Pipeline Structure
|
|
447
|
+
|
|
448
|
+
```yaml
|
|
449
|
+
# azure-pipelines.yml
|
|
450
|
+
trigger:
|
|
451
|
+
branches:
|
|
452
|
+
include:
|
|
453
|
+
- main
|
|
454
|
+
- release/*
|
|
455
|
+
paths:
|
|
456
|
+
exclude:
|
|
457
|
+
- README.md
|
|
458
|
+
- docs/*
|
|
459
|
+
|
|
460
|
+
pr:
|
|
461
|
+
branches:
|
|
462
|
+
include:
|
|
463
|
+
- main
|
|
464
|
+
drafts: false
|
|
465
|
+
|
|
466
|
+
pool:
|
|
467
|
+
vmImage: 'ubuntu-latest'
|
|
468
|
+
|
|
469
|
+
variables:
|
|
470
|
+
buildConfiguration: 'Release'
|
|
471
|
+
dotnetVersion: '8.0.x'
|
|
472
|
+
|
|
473
|
+
steps:
|
|
474
|
+
- task: UseDotNet@2
|
|
475
|
+
inputs:
|
|
476
|
+
packageType: 'sdk'
|
|
477
|
+
version: $(dotnetVersion)
|
|
478
|
+
|
|
479
|
+
- script: dotnet build --configuration $(buildConfiguration)
|
|
480
|
+
displayName: 'Build project'
|
|
481
|
+
|
|
482
|
+
- script: dotnet test --configuration $(buildConfiguration) --no-build --logger trx
|
|
483
|
+
displayName: 'Run tests'
|
|
484
|
+
|
|
485
|
+
- task: PublishTestResults@2
|
|
486
|
+
inputs:
|
|
487
|
+
testResultsFiles: '**/*.trx'
|
|
488
|
+
testRunTitle: 'Unit Tests'
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Multi-Stage Pipeline
|
|
492
|
+
|
|
493
|
+
```yaml
|
|
494
|
+
trigger:
|
|
495
|
+
- main
|
|
496
|
+
|
|
497
|
+
stages:
|
|
498
|
+
- stage: Build
|
|
499
|
+
displayName: 'Build Stage'
|
|
500
|
+
jobs:
|
|
501
|
+
- job: Build
|
|
502
|
+
displayName: 'Build Job'
|
|
503
|
+
pool:
|
|
504
|
+
vmImage: 'ubuntu-latest'
|
|
505
|
+
steps:
|
|
506
|
+
- script: echo "Building..."
|
|
507
|
+
- publish: $(Build.SourcesDirectory)/src
|
|
508
|
+
artifact: sourceCode
|
|
509
|
+
|
|
510
|
+
- stage: Test
|
|
511
|
+
displayName: 'Test Stage'
|
|
512
|
+
dependsOn: Build
|
|
513
|
+
jobs:
|
|
514
|
+
- job: UnitTests
|
|
515
|
+
displayName: 'Unit Tests'
|
|
516
|
+
steps:
|
|
517
|
+
- download: current
|
|
518
|
+
artifact: sourceCode
|
|
519
|
+
- script: echo "Running unit tests..."
|
|
520
|
+
|
|
521
|
+
- job: IntegrationTests
|
|
522
|
+
displayName: 'Integration Tests'
|
|
523
|
+
dependsOn: UnitTests
|
|
524
|
+
steps:
|
|
525
|
+
- download: current
|
|
526
|
+
artifact: sourceCode
|
|
527
|
+
- script: echo "Running integration tests..."
|
|
528
|
+
|
|
529
|
+
- stage: Deploy
|
|
530
|
+
displayName: 'Deploy Stage'
|
|
531
|
+
dependsOn: Test
|
|
532
|
+
condition: succeeded()
|
|
533
|
+
jobs:
|
|
534
|
+
- deployment: DeployWeb
|
|
535
|
+
displayName: 'Deploy Web App'
|
|
536
|
+
environment: 'Production'
|
|
537
|
+
strategy:
|
|
538
|
+
runOnce:
|
|
539
|
+
deploy:
|
|
540
|
+
steps:
|
|
541
|
+
- script: echo "Deploying to Production..."
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Pipeline Templates
|
|
545
|
+
|
|
546
|
+
**Template file (templates/build.yml):**
|
|
547
|
+
```yaml
|
|
548
|
+
parameters:
|
|
549
|
+
- name: buildConfiguration
|
|
550
|
+
type: string
|
|
551
|
+
default: 'Release'
|
|
552
|
+
- name: projects
|
|
553
|
+
type: string
|
|
554
|
+
default: '**/*.csproj'
|
|
555
|
+
- name: dotnetVersion
|
|
556
|
+
type: string
|
|
557
|
+
default: '8.0.x'
|
|
558
|
+
|
|
559
|
+
steps:
|
|
560
|
+
- task: UseDotNet@2
|
|
561
|
+
inputs:
|
|
562
|
+
packageType: 'sdk'
|
|
563
|
+
version: ${{ parameters.dotnetVersion }}
|
|
564
|
+
displayName: 'Install .NET SDK'
|
|
565
|
+
|
|
566
|
+
- task: DotNetCoreCLI@2
|
|
567
|
+
inputs:
|
|
568
|
+
command: 'build'
|
|
569
|
+
projects: ${{ parameters.projects }}
|
|
570
|
+
arguments: '--configuration ${{ parameters.buildConfiguration }}'
|
|
571
|
+
displayName: 'Build Projects'
|
|
572
|
+
|
|
573
|
+
- task: DotNetCoreCLI@2
|
|
574
|
+
inputs:
|
|
575
|
+
command: 'test'
|
|
576
|
+
projects: ${{ parameters.projects }}
|
|
577
|
+
arguments: '--configuration ${{ parameters.buildConfiguration }} --no-build'
|
|
578
|
+
displayName: 'Run Tests'
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**Using the template:**
|
|
582
|
+
```yaml
|
|
583
|
+
# azure-pipelines.yml
|
|
584
|
+
trigger:
|
|
585
|
+
- main
|
|
586
|
+
|
|
587
|
+
stages:
|
|
588
|
+
- stage: Build
|
|
589
|
+
jobs:
|
|
590
|
+
- job: BuildAPI
|
|
591
|
+
steps:
|
|
592
|
+
- template: templates/build.yml
|
|
593
|
+
parameters:
|
|
594
|
+
buildConfiguration: 'Release'
|
|
595
|
+
projects: 'src/API/*.csproj'
|
|
596
|
+
dotnetVersion: '8.0.x'
|
|
597
|
+
|
|
598
|
+
- job: BuildWorker
|
|
599
|
+
steps:
|
|
600
|
+
- template: templates/build.yml
|
|
601
|
+
parameters:
|
|
602
|
+
buildConfiguration: 'Release'
|
|
603
|
+
projects: 'src/Worker/*.csproj'
|
|
604
|
+
dotnetVersion: '8.0.x'
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Conditional Logic
|
|
608
|
+
|
|
609
|
+
```yaml
|
|
610
|
+
variables:
|
|
611
|
+
${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
|
|
612
|
+
environment: 'Production'
|
|
613
|
+
${{ if ne(variables['Build.SourceBranch'], 'refs/heads/main') }}:
|
|
614
|
+
environment: 'Development'
|
|
615
|
+
|
|
616
|
+
steps:
|
|
617
|
+
- ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
|
|
618
|
+
- script: echo "Deploying to Production"
|
|
619
|
+
displayName: 'Production Deploy'
|
|
620
|
+
|
|
621
|
+
- ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/main') }}:
|
|
622
|
+
- script: echo "Deploying to Development"
|
|
623
|
+
displayName: 'Development Deploy'
|
|
624
|
+
|
|
625
|
+
# Using conditions on steps
|
|
626
|
+
- script: echo "This runs only on main"
|
|
627
|
+
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
|
|
628
|
+
|
|
629
|
+
# Using stage conditions
|
|
630
|
+
- stage: Deploy
|
|
631
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Matrix Strategy
|
|
635
|
+
|
|
636
|
+
```yaml
|
|
637
|
+
strategy:
|
|
638
|
+
matrix:
|
|
639
|
+
linux:
|
|
640
|
+
imageName: 'ubuntu-latest'
|
|
641
|
+
dotnetVersion: '8.0.x'
|
|
642
|
+
windows:
|
|
643
|
+
imageName: 'windows-latest'
|
|
644
|
+
dotnetVersion: '8.0.x'
|
|
645
|
+
mac:
|
|
646
|
+
imageName: 'macos-latest'
|
|
647
|
+
dotnetVersion: '8.0.x'
|
|
648
|
+
maxParallel: 3
|
|
649
|
+
|
|
650
|
+
pool:
|
|
651
|
+
vmImage: $(imageName)
|
|
652
|
+
|
|
653
|
+
steps:
|
|
654
|
+
- task: UseDotNet@2
|
|
655
|
+
inputs:
|
|
656
|
+
packageType: 'sdk'
|
|
657
|
+
version: $(dotnetVersion)
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Deployment Jobs with Environments
|
|
661
|
+
|
|
662
|
+
```yaml
|
|
663
|
+
stages:
|
|
664
|
+
- stage: Deploy
|
|
665
|
+
jobs:
|
|
666
|
+
- deployment: DeployApp
|
|
667
|
+
displayName: 'Deploy Application'
|
|
668
|
+
environment:
|
|
669
|
+
name: 'Production'
|
|
670
|
+
resourceType: VirtualMachine
|
|
671
|
+
tags: web
|
|
672
|
+
strategy:
|
|
673
|
+
runOnce:
|
|
674
|
+
deploy:
|
|
675
|
+
steps:
|
|
676
|
+
- script: echo "Deploying to Production"
|
|
677
|
+
|
|
678
|
+
- deployment: DeployWithApprovals
|
|
679
|
+
displayName: 'Deploy with Manual Approval'
|
|
680
|
+
environment: 'Staging'
|
|
681
|
+
strategy:
|
|
682
|
+
runOnce:
|
|
683
|
+
deploy:
|
|
684
|
+
steps:
|
|
685
|
+
- script: echo "Deploying to Staging"
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Blue-Green Deployment
|
|
689
|
+
|
|
690
|
+
```yaml
|
|
691
|
+
stages:
|
|
692
|
+
- stage: DeployBlue
|
|
693
|
+
displayName: 'Deploy to Blue Environment'
|
|
694
|
+
jobs:
|
|
695
|
+
- deployment: DeployBlue
|
|
696
|
+
environment: 'Production-Blue'
|
|
697
|
+
strategy:
|
|
698
|
+
runOnce:
|
|
699
|
+
deploy:
|
|
700
|
+
steps:
|
|
701
|
+
- task: AzureWebApp@1
|
|
702
|
+
inputs:
|
|
703
|
+
azureSubscription: 'MyServiceConnection'
|
|
704
|
+
appName: 'myapp-blue'
|
|
705
|
+
package: $(Pipeline.Workspace)/drop/*.zip
|
|
706
|
+
|
|
707
|
+
- stage: SwitchTraffic
|
|
708
|
+
displayName: 'Switch Traffic to Blue'
|
|
709
|
+
dependsOn: DeployBlue
|
|
710
|
+
jobs:
|
|
711
|
+
- deployment: SwitchTraffic
|
|
712
|
+
environment: 'Production'
|
|
713
|
+
strategy:
|
|
714
|
+
runOnce:
|
|
715
|
+
deploy:
|
|
716
|
+
steps:
|
|
717
|
+
- task: AzureAppServiceManage@0
|
|
718
|
+
inputs:
|
|
719
|
+
azureSubscription: 'MyServiceConnection'
|
|
720
|
+
Action: 'Swap Slots'
|
|
721
|
+
WebAppName: 'myapp'
|
|
722
|
+
ResourceGroupName: 'myResourceGroup'
|
|
723
|
+
SourceSlot: 'blue'
|
|
724
|
+
TargetSlot: 'production'
|
|
725
|
+
|
|
726
|
+
- stage: DeployGreen
|
|
727
|
+
displayName: 'Deploy to Green Environment'
|
|
728
|
+
dependsOn: SwitchTraffic
|
|
729
|
+
jobs:
|
|
730
|
+
- deployment: DeployGreen
|
|
731
|
+
environment: 'Production-Green'
|
|
732
|
+
strategy:
|
|
733
|
+
runOnce:
|
|
734
|
+
deploy:
|
|
735
|
+
steps:
|
|
736
|
+
- task: AzureWebApp@1
|
|
737
|
+
inputs:
|
|
738
|
+
azureSubscription: 'MyServiceConnection'
|
|
739
|
+
appName: 'myapp-green'
|
|
740
|
+
package: $(Pipeline.Workspace)/drop/*.zip
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Canary Deployment
|
|
744
|
+
|
|
745
|
+
```yaml
|
|
746
|
+
stages:
|
|
747
|
+
- stage: Canary
|
|
748
|
+
displayName: 'Canary Deployment (10%)'
|
|
749
|
+
jobs:
|
|
750
|
+
- deployment: Canary
|
|
751
|
+
environment: 'Production'
|
|
752
|
+
strategy:
|
|
753
|
+
canary:
|
|
754
|
+
increments: [10, 25, 50, 100]
|
|
755
|
+
preDeploy:
|
|
756
|
+
steps:
|
|
757
|
+
- script: echo "Pre-deploy checks"
|
|
758
|
+
deploy:
|
|
759
|
+
steps:
|
|
760
|
+
- task: KubernetesManifest@1
|
|
761
|
+
inputs:
|
|
762
|
+
action: 'deploy'
|
|
763
|
+
kubernetesServiceConnection: 'aks-connection'
|
|
764
|
+
namespace: 'production'
|
|
765
|
+
manifests: 'k8s/deployment.yml'
|
|
766
|
+
strategy: 'canary'
|
|
767
|
+
percentage: 10
|
|
768
|
+
postRouteTraffic:
|
|
769
|
+
steps:
|
|
770
|
+
- script: echo "Verify canary health"
|
|
771
|
+
- task: AzureMonitor@1
|
|
772
|
+
inputs:
|
|
773
|
+
resourceGroupName: 'myResourceGroup'
|
|
774
|
+
alertType: 'metric'
|
|
775
|
+
action: 'check'
|
|
776
|
+
on:
|
|
777
|
+
failure:
|
|
778
|
+
steps:
|
|
779
|
+
- script: echo "Rollback triggered"
|
|
780
|
+
success:
|
|
781
|
+
steps:
|
|
782
|
+
- script: echo "Canary successful"
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
### Container Jobs
|
|
786
|
+
|
|
787
|
+
```yaml
|
|
788
|
+
resources:
|
|
789
|
+
containers:
|
|
790
|
+
- container: build
|
|
791
|
+
image: mcr.microsoft.com/dotnet/sdk:8.0
|
|
792
|
+
- container: test
|
|
793
|
+
image: mcr.microsoft.com/dotnet/sdk:8.0
|
|
794
|
+
options: --hostname test-container
|
|
795
|
+
|
|
796
|
+
jobs:
|
|
797
|
+
- job: BuildInContainer
|
|
798
|
+
container: build
|
|
799
|
+
steps:
|
|
800
|
+
- script: dotnet build
|
|
801
|
+
displayName: 'Build in Container'
|
|
802
|
+
|
|
803
|
+
- job: TestInContainer
|
|
804
|
+
container: test
|
|
805
|
+
dependsOn: BuildInContainer
|
|
806
|
+
steps:
|
|
807
|
+
- script: dotnet test
|
|
808
|
+
displayName: 'Test in Container'
|
|
809
|
+
|
|
810
|
+
# Custom container with service containers
|
|
811
|
+
- job: IntegrationTests
|
|
812
|
+
container:
|
|
813
|
+
image: mcr.microsoft.com/dotnet/sdk:8.0
|
|
814
|
+
services:
|
|
815
|
+
postgres:
|
|
816
|
+
image: postgres:15
|
|
817
|
+
env:
|
|
818
|
+
POSTGRES_PASSWORD: $(postgresPassword)
|
|
819
|
+
ports:
|
|
820
|
+
- 5432:5432
|
|
821
|
+
steps:
|
|
822
|
+
- script: dotnet test --filter "Category=Integration"
|
|
823
|
+
displayName: 'Run Integration Tests'
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Self-Hosted Agents
|
|
827
|
+
|
|
828
|
+
```yaml
|
|
829
|
+
# Use self-hosted agent pool
|
|
830
|
+
pool:
|
|
831
|
+
name: 'MySelfHostedPool'
|
|
832
|
+
demands:
|
|
833
|
+
- agent.os -equals Linux
|
|
834
|
+
- npm
|
|
835
|
+
|
|
836
|
+
# Or with agent capabilities
|
|
837
|
+
pool:
|
|
838
|
+
name: 'MySelfHostedPool'
|
|
839
|
+
demands:
|
|
840
|
+
- Agent.OS -equals Linux
|
|
841
|
+
- Agent.Version -gtVersion 2.200.0
|
|
842
|
+
|
|
843
|
+
# Job-level pool override
|
|
844
|
+
jobs:
|
|
845
|
+
- job: Build
|
|
846
|
+
pool: 'MySelfHostedPool'
|
|
847
|
+
steps:
|
|
848
|
+
- script: echo "Building on self-hosted agent"
|
|
849
|
+
|
|
850
|
+
- job: Deploy
|
|
851
|
+
pool:
|
|
852
|
+
vmImage: 'ubuntu-latest'
|
|
853
|
+
steps:
|
|
854
|
+
- script: echo "Deploying on Microsoft-hosted agent"
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### Service Connections in Pipelines
|
|
858
|
+
|
|
859
|
+
```yaml
|
|
860
|
+
# Azure Resource Manager
|
|
861
|
+
- task: AzureWebApp@1
|
|
862
|
+
inputs:
|
|
863
|
+
azureSubscription: 'MyArmConnection' # Service connection name
|
|
864
|
+
appName: 'my-web-app'
|
|
865
|
+
package: $(Pipeline.Workspace)/drop/*.zip
|
|
866
|
+
|
|
867
|
+
# Kubernetes
|
|
868
|
+
- task: KubernetesManifest@1
|
|
869
|
+
inputs:
|
|
870
|
+
kubernetesServiceConnection: 'MyAksConnection'
|
|
871
|
+
action: 'deploy'
|
|
872
|
+
namespace: 'default'
|
|
873
|
+
manifests: 'k8s/*.yml'
|
|
874
|
+
|
|
875
|
+
# Docker Registry
|
|
876
|
+
- task: Docker@2
|
|
877
|
+
inputs:
|
|
878
|
+
containerRegistry: 'MyAcrConnection' # Docker registry service connection
|
|
879
|
+
repository: 'myapp'
|
|
880
|
+
command: 'buildAndPush'
|
|
881
|
+
Dockerfile: 'Dockerfile'
|
|
882
|
+
tags: |
|
|
883
|
+
$(Build.BuildId)
|
|
884
|
+
latest
|
|
885
|
+
|
|
886
|
+
# GitHub
|
|
887
|
+
- task: GitHubRelease@1
|
|
888
|
+
inputs:
|
|
889
|
+
gitHubConnection: 'MyGitHubConnection'
|
|
890
|
+
repositoryName: 'myorg/myrepo'
|
|
891
|
+
action: 'create'
|
|
892
|
+
tagSource: 'userSpecifiedTag'
|
|
893
|
+
tag: 'v$(Build.BuildId)'
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### Key Vault Integration
|
|
897
|
+
|
|
898
|
+
```yaml
|
|
899
|
+
# Azure Key Vault task
|
|
900
|
+
- task: AzureKeyVault@2
|
|
901
|
+
inputs:
|
|
902
|
+
azureSubscription: 'MyServiceConnection'
|
|
903
|
+
KeyVaultName: 'my-keyvault'
|
|
904
|
+
SecretsFilter: '*'
|
|
905
|
+
RunAsPreJob: false
|
|
906
|
+
|
|
907
|
+
# Use secrets in subsequent tasks
|
|
908
|
+
- script: |
|
|
909
|
+
echo "Database connection string: $(DatabaseConnectionString)"
|
|
910
|
+
env:
|
|
911
|
+
DB_CONNECTION: $(DatabaseConnectionString)
|
|
912
|
+
|
|
913
|
+
# Map secrets to variables
|
|
914
|
+
variables:
|
|
915
|
+
- group: 'MyVariableGroup'
|
|
916
|
+
- name: dbPassword
|
|
917
|
+
value: $[variables.DatabasePassword]
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
---
|
|
921
|
+
|
|
922
|
+
## 4. Infrastructure as Code
|
|
923
|
+
|
|
924
|
+
### Bicep Templates
|
|
925
|
+
|
|
926
|
+
**main.bicep:**
|
|
927
|
+
```bicep
|
|
928
|
+
@description('Location for all resources')
|
|
929
|
+
param location string = resourceGroup().location
|
|
930
|
+
|
|
931
|
+
@description('Name of the storage account')
|
|
932
|
+
param storageAccountName string
|
|
933
|
+
|
|
934
|
+
@description('Storage account SKU')
|
|
935
|
+
param storageSku string = 'Standard_LRS'
|
|
936
|
+
|
|
937
|
+
@description('Tags to apply to resources')
|
|
938
|
+
param tags object = {
|
|
939
|
+
environment: 'production'
|
|
940
|
+
project: 'myapp'
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
|
|
944
|
+
name: storageAccountName
|
|
945
|
+
location: location
|
|
946
|
+
sku: {
|
|
947
|
+
name: storageSku
|
|
948
|
+
}
|
|
949
|
+
kind: 'StorageV2'
|
|
950
|
+
properties: {
|
|
951
|
+
accessTier: 'Hot'
|
|
952
|
+
minimumTlsVersion: 'TLS1_2'
|
|
953
|
+
supportsHttpsTrafficOnly: true
|
|
954
|
+
}
|
|
955
|
+
tags: tags
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
|
|
959
|
+
name: '${storageAccountName}-plan'
|
|
960
|
+
location: location
|
|
961
|
+
sku: {
|
|
962
|
+
name: 'P1v3'
|
|
963
|
+
tier: 'PremiumV3'
|
|
964
|
+
capacity: 1
|
|
965
|
+
}
|
|
966
|
+
properties: {
|
|
967
|
+
reserved: false
|
|
968
|
+
}
|
|
969
|
+
tags: tags
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
resource webApp 'Microsoft.Web/sites@2023-01-01' = {
|
|
973
|
+
name: '${storageAccountName}-app'
|
|
974
|
+
location: location
|
|
975
|
+
properties: {
|
|
976
|
+
serverFarmId: appServicePlan.id
|
|
977
|
+
siteConfig: {
|
|
978
|
+
appSettings: [
|
|
979
|
+
{
|
|
980
|
+
name: 'StorageConnectionString'
|
|
981
|
+
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=core.windows.net'
|
|
982
|
+
}
|
|
983
|
+
]
|
|
984
|
+
httpsOnly: true
|
|
985
|
+
minTlsVersion: '1.2'
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
tags: tags
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
output storageAccountId string = storageAccount.id
|
|
992
|
+
output webAppUrl string = 'https://${webApp.defaultHostName}'
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
**Deploy Bicep with Azure CLI:**
|
|
996
|
+
```bash
|
|
997
|
+
# Validate template
|
|
998
|
+
az deployment group validate --resource-group myResourceGroup --template-file main.bicep --parameters storageAccountName=mystorage123
|
|
999
|
+
|
|
1000
|
+
# What-if deployment (preview changes)
|
|
1001
|
+
az deployment group what-if --resource-group myResourceGroup --template-file main.bicep --parameters storageAccountName=mystorage123
|
|
1002
|
+
|
|
1003
|
+
# Deploy
|
|
1004
|
+
az deployment group create --resource-group myResourceGroup --template-file main.bicep --parameters storageAccountName=mystorage123
|
|
1005
|
+
|
|
1006
|
+
# Deploy with parameter file
|
|
1007
|
+
az deployment group create --resource-group myResourceGroup --template-file main.bicep --parameters @parameters.json
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
**Bicep Pipeline:**
|
|
1011
|
+
```yaml
|
|
1012
|
+
trigger:
|
|
1013
|
+
- main
|
|
1014
|
+
|
|
1015
|
+
pool:
|
|
1016
|
+
vmImage: 'ubuntu-latest'
|
|
1017
|
+
|
|
1018
|
+
stages:
|
|
1019
|
+
- stage: Validate
|
|
1020
|
+
jobs:
|
|
1021
|
+
- job: Validate
|
|
1022
|
+
steps:
|
|
1023
|
+
- task: AzureCLI@2
|
|
1024
|
+
inputs:
|
|
1025
|
+
azureSubscription: 'MyServiceConnection'
|
|
1026
|
+
scriptType: 'bash'
|
|
1027
|
+
scriptLocation: 'inlineScript'
|
|
1028
|
+
inlineScript: |
|
|
1029
|
+
az deployment group validate \
|
|
1030
|
+
--resource-group $(resourceGroup) \
|
|
1031
|
+
--template-file ./infrastructure/main.bicep \
|
|
1032
|
+
--parameters ./infrastructure/parameters.json
|
|
1033
|
+
|
|
1034
|
+
- stage: Deploy
|
|
1035
|
+
dependsOn: Validate
|
|
1036
|
+
jobs:
|
|
1037
|
+
- deployment: DeployInfrastructure
|
|
1038
|
+
environment: 'Production'
|
|
1039
|
+
strategy:
|
|
1040
|
+
runOnce:
|
|
1041
|
+
deploy:
|
|
1042
|
+
steps:
|
|
1043
|
+
- task: AzureCLI@2
|
|
1044
|
+
inputs:
|
|
1045
|
+
azureSubscription: 'MyServiceConnection'
|
|
1046
|
+
scriptType: 'bash'
|
|
1047
|
+
scriptLocation: 'inlineScript'
|
|
1048
|
+
inlineScript: |
|
|
1049
|
+
az deployment group create \
|
|
1050
|
+
--resource-group $(resourceGroup) \
|
|
1051
|
+
--template-file ./infrastructure/main.bicep \
|
|
1052
|
+
--parameters ./infrastructure/parameters.json
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
### ARM Templates
|
|
1056
|
+
|
|
1057
|
+
```json
|
|
1058
|
+
{
|
|
1059
|
+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
|
1060
|
+
"contentVersion": "1.0.0.0",
|
|
1061
|
+
"parameters": {
|
|
1062
|
+
"storageAccountName": {
|
|
1063
|
+
"type": "string",
|
|
1064
|
+
"minLength": 3,
|
|
1065
|
+
"maxLength": 24,
|
|
1066
|
+
"metadata": {
|
|
1067
|
+
"description": "Name of the storage account"
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
"location": {
|
|
1071
|
+
"type": "string",
|
|
1072
|
+
"defaultValue": "[resourceGroup().location]",
|
|
1073
|
+
"metadata": {
|
|
1074
|
+
"description": "Location for all resources"
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
"storageSku": {
|
|
1078
|
+
"type": "string",
|
|
1079
|
+
"defaultValue": "Standard_LRS",
|
|
1080
|
+
"allowedValues": ["Standard_LRS", "Standard_GRS", "Premium_LRS"],
|
|
1081
|
+
"metadata": {
|
|
1082
|
+
"description": "Storage account SKU"
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
"variables": {
|
|
1087
|
+
"storageApiVersion": "2023-05-01"
|
|
1088
|
+
},
|
|
1089
|
+
"resources": [
|
|
1090
|
+
{
|
|
1091
|
+
"type": "Microsoft.Storage/storageAccounts",
|
|
1092
|
+
"apiVersion": "[variables('storageApiVersion')]",
|
|
1093
|
+
"name": "[parameters('storageAccountName')]",
|
|
1094
|
+
"location": "[parameters('location')]",
|
|
1095
|
+
"sku": {
|
|
1096
|
+
"name": "[parameters('storageSku')]"
|
|
1097
|
+
},
|
|
1098
|
+
"kind": "StorageV2",
|
|
1099
|
+
"properties": {
|
|
1100
|
+
"accessTier": "Hot",
|
|
1101
|
+
"minimumTlsVersion": "TLS1_2"
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
],
|
|
1105
|
+
"outputs": {
|
|
1106
|
+
"storageAccountId": {
|
|
1107
|
+
"type": "string",
|
|
1108
|
+
"value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### Terraform with Azure
|
|
1115
|
+
|
|
1116
|
+
**main.tf:**
|
|
1117
|
+
```hcl
|
|
1118
|
+
provider "azurerm" {
|
|
1119
|
+
features {}
|
|
1120
|
+
subscription_id = var.subscription_id
|
|
1121
|
+
tenant_id = var.tenant_id
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
resource "azurerm_resource_group" "main" {
|
|
1125
|
+
name = "${var.prefix}-rg"
|
|
1126
|
+
location = var.location
|
|
1127
|
+
tags = var.tags
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
resource "azurerm_storage_account" "main" {
|
|
1131
|
+
name = "${var.prefix}storage"
|
|
1132
|
+
resource_group_name = azurerm_resource_group.main.name
|
|
1133
|
+
location = azurerm_resource_group.main.location
|
|
1134
|
+
account_tier = "Standard"
|
|
1135
|
+
account_replication_type = "LRS"
|
|
1136
|
+
min_tls_version = "TLS1_2"
|
|
1137
|
+
|
|
1138
|
+
tags = var.tags
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
resource "azurerm_app_service_plan" "main" {
|
|
1142
|
+
name = "${var.prefix}-plan"
|
|
1143
|
+
location = azurerm_resource_group.main.location
|
|
1144
|
+
resource_group_name = azurerm_resource_group.main.name
|
|
1145
|
+
sku {
|
|
1146
|
+
tier = "PremiumV3"
|
|
1147
|
+
size = "P1v3"
|
|
1148
|
+
}
|
|
1149
|
+
tags = var.tags
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
resource "azurerm_linux_web_app" "main" {
|
|
1153
|
+
name = "${var.prefix}-app"
|
|
1154
|
+
location = azurerm_resource_group.main.location
|
|
1155
|
+
resource_group_name = azurerm_resource_group.main.name
|
|
1156
|
+
service_plan_id = azurerm_app_service_plan.main.id
|
|
1157
|
+
|
|
1158
|
+
site_config {
|
|
1159
|
+
always_on = true
|
|
1160
|
+
application_stack {
|
|
1161
|
+
docker_image = "${var.acr_name}.azurecr.io/${var.image_name}"
|
|
1162
|
+
docker_image_tag = var.image_tag
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
app_settings = {
|
|
1167
|
+
"WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
|
|
1168
|
+
"DOCKER_REGISTRY_SERVER_URL" = "https://${var.acr_name}.azurecr.io"
|
|
1169
|
+
"DOCKER_REGISTRY_SERVER_USERNAME" = var.acr_username
|
|
1170
|
+
"DOCKER_REGISTRY_SERVER_PASSWORD" = var.acr_password
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
tags = var.tags
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
output "web_app_url" {
|
|
1177
|
+
value = azurerm_linux_web_app.main.default_hostname
|
|
1178
|
+
}
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
**Terraform Pipeline with Remote State:**
|
|
1182
|
+
```yaml
|
|
1183
|
+
trigger:
|
|
1184
|
+
- main
|
|
1185
|
+
|
|
1186
|
+
variables:
|
|
1187
|
+
terraformVersion: '1.6.0'
|
|
1188
|
+
storageAccount: 'tfstate'
|
|
1189
|
+
container: 'tfstate'
|
|
1190
|
+
stateKey: 'terraform.tfstate'
|
|
1191
|
+
|
|
1192
|
+
stages:
|
|
1193
|
+
- stage: TerraformPlan
|
|
1194
|
+
displayName: 'Terraform Plan'
|
|
1195
|
+
jobs:
|
|
1196
|
+
- job: Plan
|
|
1197
|
+
steps:
|
|
1198
|
+
- task: TerraformInstaller@0
|
|
1199
|
+
inputs:
|
|
1200
|
+
terraformVersion: $(terraformVersion)
|
|
1201
|
+
|
|
1202
|
+
- task: TerraformTaskV4@4
|
|
1203
|
+
displayName: 'Terraform Init'
|
|
1204
|
+
inputs:
|
|
1205
|
+
provider: 'azurerm'
|
|
1206
|
+
command: 'init'
|
|
1207
|
+
backendServiceArm: 'MyServiceConnection'
|
|
1208
|
+
backendAzureRmResourceGroupName: 'terraform-state-rg'
|
|
1209
|
+
backendAzureRmStorageAccountName: $(storageAccount)
|
|
1210
|
+
backendAzureRmContainerName: $(container)
|
|
1211
|
+
backendAzureRmKey: $(stateKey)
|
|
1212
|
+
|
|
1213
|
+
- task: TerraformTaskV4@4
|
|
1214
|
+
displayName: 'Terraform Plan'
|
|
1215
|
+
inputs:
|
|
1216
|
+
provider: 'azurerm'
|
|
1217
|
+
command: 'plan'
|
|
1218
|
+
commandOptions: '-out=tfplan -var-file="$(Build.SourcesDirectory)/terraform/variables/$(environment).tfvars"'
|
|
1219
|
+
environmentServiceNameAzureRM: 'MyServiceConnection'
|
|
1220
|
+
|
|
1221
|
+
- stage: TerraformApply
|
|
1222
|
+
displayName: 'Terraform Apply'
|
|
1223
|
+
dependsOn: TerraformPlan
|
|
1224
|
+
condition: succeeded()
|
|
1225
|
+
jobs:
|
|
1226
|
+
- deployment: Apply
|
|
1227
|
+
environment: 'Production'
|
|
1228
|
+
strategy:
|
|
1229
|
+
runOnce:
|
|
1230
|
+
deploy:
|
|
1231
|
+
steps:
|
|
1232
|
+
- task: TerraformInstaller@0
|
|
1233
|
+
inputs:
|
|
1234
|
+
terraformVersion: $(terraformVersion)
|
|
1235
|
+
|
|
1236
|
+
- task: TerraformTaskV4@4
|
|
1237
|
+
displayName: 'Terraform Init'
|
|
1238
|
+
inputs:
|
|
1239
|
+
provider: 'azurerm'
|
|
1240
|
+
command: 'init'
|
|
1241
|
+
backendServiceArm: 'MyServiceConnection'
|
|
1242
|
+
backendAzureRmResourceGroupName: 'terraform-state-rg'
|
|
1243
|
+
backendAzureRmStorageAccountName: $(storageAccount)
|
|
1244
|
+
backendAzureRmContainerName: $(container)
|
|
1245
|
+
backendAzureRmKey: $(stateKey)
|
|
1246
|
+
|
|
1247
|
+
- task: TerraformTaskV4@4
|
|
1248
|
+
displayName: 'Terraform Apply'
|
|
1249
|
+
inputs:
|
|
1250
|
+
provider: 'azurerm'
|
|
1251
|
+
command: 'apply'
|
|
1252
|
+
commandOptions: '-auto-approve -var-file="$(Build.SourcesDirectory)/terraform/variables/$(environment).tfvars"'
|
|
1253
|
+
environmentServiceNameAzureRM: 'MyServiceConnection'
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
---
|
|
1257
|
+
|
|
1258
|
+
## 5. Best Practices
|
|
1259
|
+
|
|
1260
|
+
### Branch Policies
|
|
1261
|
+
|
|
1262
|
+
```bash
|
|
1263
|
+
# Configure comprehensive branch policies
|
|
1264
|
+
# 1. Minimum reviewers
|
|
1265
|
+
az repos policy branch create \
|
|
1266
|
+
--project "MyProject" \
|
|
1267
|
+
--repository-id <repo-id> \
|
|
1268
|
+
--branch main \
|
|
1269
|
+
--policy-type "Minimum number of reviewers" \
|
|
1270
|
+
--settings '{
|
|
1271
|
+
"minimumApproverCount": 2,
|
|
1272
|
+
"creatorVoteCounts": false,
|
|
1273
|
+
"allowDownvotes": false,
|
|
1274
|
+
"resetOnSourcePush": true
|
|
1275
|
+
}'
|
|
1276
|
+
|
|
1277
|
+
# 2. Build validation
|
|
1278
|
+
az repos policy build create \
|
|
1279
|
+
--project "MyProject" \
|
|
1280
|
+
--repository-id <repo-id> \
|
|
1281
|
+
--branch main \
|
|
1282
|
+
--build-definition-id <definition-id> \
|
|
1283
|
+
--display-name "Build Validation" \
|
|
1284
|
+
--enabled true \
|
|
1285
|
+
--manual-queue-only false \
|
|
1286
|
+
--queue-on-source-update-only true \
|
|
1287
|
+
--valid-duration 720
|
|
1288
|
+
|
|
1289
|
+
# 3. Required reviewers for specific paths
|
|
1290
|
+
az repos policy required-reviewer create \
|
|
1291
|
+
--project "MyProject" \
|
|
1292
|
+
--repository-id <repo-id> \
|
|
1293
|
+
--branch main \
|
|
1294
|
+
--enabled true \
|
|
1295
|
+
--blocking true \
|
|
1296
|
+
--settings '{
|
|
1297
|
+
"requiredReviewerIds": ["<security-group-id>"],
|
|
1298
|
+
"message": "Security team review required for infrastructure changes",
|
|
1299
|
+
"scope": [{"path": "/infrastructure/*", "repositoryId": null}]
|
|
1300
|
+
}'
|
|
1301
|
+
|
|
1302
|
+
# 4. Work item linking
|
|
1303
|
+
az repos policy work-item-linking create \
|
|
1304
|
+
--project "MyProject" \
|
|
1305
|
+
--repository-id <repo-id> \
|
|
1306
|
+
--branch main \
|
|
1307
|
+
--enabled true \
|
|
1308
|
+
--blocking true
|
|
1309
|
+
|
|
1310
|
+
# 5. Comment resolution
|
|
1311
|
+
az repos policy comment-required create \
|
|
1312
|
+
--project "MyProject" \
|
|
1313
|
+
--repository-id <repo-id> \
|
|
1314
|
+
--branch main \
|
|
1315
|
+
--enabled true \
|
|
1316
|
+
--blocking true
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
### Security Scanning Integration
|
|
1320
|
+
|
|
1321
|
+
```yaml
|
|
1322
|
+
# SonarCloud integration
|
|
1323
|
+
- task: SonarCloudPrepare@1
|
|
1324
|
+
inputs:
|
|
1325
|
+
SonarCloud: 'SonarCloud'
|
|
1326
|
+
organization: 'myorg'
|
|
1327
|
+
scannerMode: 'MSBuild'
|
|
1328
|
+
projectKey: 'my-project'
|
|
1329
|
+
projectName: 'My Project'
|
|
1330
|
+
extraProperties: |
|
|
1331
|
+
sonar.exclusions=**/bin/**,**/obj/**
|
|
1332
|
+
sonar.coverage.exclusions=**/Tests/**
|
|
1333
|
+
|
|
1334
|
+
# Run analysis
|
|
1335
|
+
- task: SonarCloudAnalyze@1
|
|
1336
|
+
|
|
1337
|
+
# Publish results
|
|
1338
|
+
- task: SonarCloudPublish@1
|
|
1339
|
+
inputs:
|
|
1340
|
+
pollingTimeoutSec: '300'
|
|
1341
|
+
|
|
1342
|
+
# OWASP Dependency Check
|
|
1343
|
+
- task: dependency-check-build-task@6
|
|
1344
|
+
inputs:
|
|
1345
|
+
projectName: 'My Project'
|
|
1346
|
+
scanPath: '$(Build.SourcesDirectory)'
|
|
1347
|
+
format: 'HTML'
|
|
1348
|
+
failOnCVSS: '7'
|
|
1349
|
+
|
|
1350
|
+
# Snyk security scan
|
|
1351
|
+
- task: SnykSecurityScan@1
|
|
1352
|
+
inputs:
|
|
1353
|
+
serviceConnectionEndpoint: 'SnykConnection'
|
|
1354
|
+
testType: 'app'
|
|
1355
|
+
monitorWhen: 'always'
|
|
1356
|
+
failOnIssues: true
|
|
1357
|
+
|
|
1358
|
+
# Trivy container scan
|
|
1359
|
+
- task: Docker@2
|
|
1360
|
+
inputs:
|
|
1361
|
+
command: 'build'
|
|
1362
|
+
dockerfile: 'Dockerfile'
|
|
1363
|
+
tags: 'scan-target:latest'
|
|
1364
|
+
|
|
1365
|
+
- script: |
|
|
1366
|
+
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
|
|
1367
|
+
aquasec/trivy:latest image --exit-code 1 --severity HIGH,CRITICAL scan-target:latest
|
|
1368
|
+
displayName: 'Trivy Container Scan'
|
|
1369
|
+
```
|
|
1370
|
+
|
|
1371
|
+
### Secret Management
|
|
1372
|
+
|
|
1373
|
+
```yaml
|
|
1374
|
+
# Pipeline structure for secret management
|
|
1375
|
+
variables:
|
|
1376
|
+
- group: 'NonSecretVariables'
|
|
1377
|
+
- group: 'SecretVariables' # Key Vault linked variable group
|
|
1378
|
+
|
|
1379
|
+
stages:
|
|
1380
|
+
- stage: Build
|
|
1381
|
+
jobs:
|
|
1382
|
+
- job: Build
|
|
1383
|
+
steps:
|
|
1384
|
+
- task: AzureKeyVault@2
|
|
1385
|
+
inputs:
|
|
1386
|
+
azureSubscription: 'MyServiceConnection'
|
|
1387
|
+
KeyVaultName: 'my-keyvault'
|
|
1388
|
+
SecretsFilter: 'DatabasePassword,ApiKey'
|
|
1389
|
+
name: GetSecrets
|
|
1390
|
+
|
|
1391
|
+
- script: |
|
|
1392
|
+
echo "Using secrets..."
|
|
1393
|
+
# Never echo secrets directly
|
|
1394
|
+
env:
|
|
1395
|
+
DB_PASSWORD: $(DatabasePassword)
|
|
1396
|
+
API_KEY: $(ApiKey)
|
|
1397
|
+
|
|
1398
|
+
# Azure Key Vault linked variable group (created via CLI or UI)
|
|
1399
|
+
# az pipelines variable-group create --name "SecretVariables" --project "MyProject" --authorize true --keyvault "my-keyvault" --secrets "DatabasePassword,ApiKey"
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
### CI/CD Strategies
|
|
1403
|
+
|
|
1404
|
+
```yaml
|
|
1405
|
+
# Feature branch workflow
|
|
1406
|
+
trigger:
|
|
1407
|
+
branches:
|
|
1408
|
+
include:
|
|
1409
|
+
- main
|
|
1410
|
+
- release/*
|
|
1411
|
+
paths:
|
|
1412
|
+
exclude:
|
|
1413
|
+
- docs/*
|
|
1414
|
+
- README.md
|
|
1415
|
+
|
|
1416
|
+
pr:
|
|
1417
|
+
branches:
|
|
1418
|
+
include:
|
|
1419
|
+
- main
|
|
1420
|
+
paths:
|
|
1421
|
+
include:
|
|
1422
|
+
- src/*
|
|
1423
|
+
|
|
1424
|
+
# Semantic versioning pipeline
|
|
1425
|
+
variables:
|
|
1426
|
+
major: 1
|
|
1427
|
+
minor: $[counter(variables['major'], 0)]
|
|
1428
|
+
patch: $[counter(format('{0}.{1}', variables['major'], variables['minor']), 0)]
|
|
1429
|
+
version: $(major).$(minor).$(patch)
|
|
1430
|
+
|
|
1431
|
+
stages:
|
|
1432
|
+
- stage: Build
|
|
1433
|
+
jobs:
|
|
1434
|
+
- job: Build
|
|
1435
|
+
steps:
|
|
1436
|
+
- script: |
|
|
1437
|
+
echo "##vso[build.updatebuildnumber]$(version)"
|
|
1438
|
+
- script: dotnet build -p:Version=$(version)
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1441
|
+
### AKS Deployment Pipeline
|
|
1442
|
+
|
|
1443
|
+
```yaml
|
|
1444
|
+
trigger:
|
|
1445
|
+
- main
|
|
1446
|
+
|
|
1447
|
+
resources:
|
|
1448
|
+
repositories:
|
|
1449
|
+
- repository: templates
|
|
1450
|
+
type: git
|
|
1451
|
+
name: 'Pipelines/Templates'
|
|
1452
|
+
|
|
1453
|
+
variables:
|
|
1454
|
+
imageRepository: 'myapp'
|
|
1455
|
+
dockerfilePath: 'Dockerfile'
|
|
1456
|
+
tag: '$(Build.BuildId)'
|
|
1457
|
+
k8sNamespace: 'default'
|
|
1458
|
+
|
|
1459
|
+
stages:
|
|
1460
|
+
- stage: Build
|
|
1461
|
+
displayName: 'Build and Push'
|
|
1462
|
+
jobs:
|
|
1463
|
+
- job: Build
|
|
1464
|
+
displayName: 'Build Docker Image'
|
|
1465
|
+
pool:
|
|
1466
|
+
vmImage: 'ubuntu-latest'
|
|
1467
|
+
steps:
|
|
1468
|
+
- task: Docker@2
|
|
1469
|
+
displayName: 'Build and Push to ACR'
|
|
1470
|
+
inputs:
|
|
1471
|
+
containerRegistry: 'MyAcrConnection'
|
|
1472
|
+
repository: $(imageRepository)
|
|
1473
|
+
command: 'buildAndPush'
|
|
1474
|
+
Dockerfile: $(dockerfilePath)
|
|
1475
|
+
tags: |
|
|
1476
|
+
$(tag)
|
|
1477
|
+
latest
|
|
1478
|
+
|
|
1479
|
+
- stage: Deploy
|
|
1480
|
+
displayName: 'Deploy to AKS'
|
|
1481
|
+
dependsOn: Build
|
|
1482
|
+
condition: succeeded()
|
|
1483
|
+
jobs:
|
|
1484
|
+
- deployment: Deploy
|
|
1485
|
+
displayName: 'Deploy to AKS'
|
|
1486
|
+
environment: 'Production.myakscluster'
|
|
1487
|
+
strategy:
|
|
1488
|
+
runOnce:
|
|
1489
|
+
deploy:
|
|
1490
|
+
steps:
|
|
1491
|
+
- task: KubernetesManifest@1
|
|
1492
|
+
displayName: 'Create namespace'
|
|
1493
|
+
inputs:
|
|
1494
|
+
action: 'createSecrets'
|
|
1495
|
+
kubernetesServiceConnection: 'MyAksConnection'
|
|
1496
|
+
namespace: $(k8sNamespace)
|
|
1497
|
+
secretType: 'dockerRegistry'
|
|
1498
|
+
secretName: 'acr-secret'
|
|
1499
|
+
dockerRegistryEndpoint: 'MyAcrConnection'
|
|
1500
|
+
|
|
1501
|
+
- task: KubernetesManifest@1
|
|
1502
|
+
displayName: 'Bake and Deploy'
|
|
1503
|
+
inputs:
|
|
1504
|
+
action: 'deploy'
|
|
1505
|
+
kubernetesServiceConnection: 'MyAksConnection'
|
|
1506
|
+
namespace: $(k8sNamespace)
|
|
1507
|
+
manifests: |
|
|
1508
|
+
$(Pipeline.Workspace)/manifests/deployment.yml
|
|
1509
|
+
$(Pipeline.Workspace)/manifests/service.yml
|
|
1510
|
+
$(Pipeline.Workspace)/manifests/ingress.yml
|
|
1511
|
+
containers: 'myacr.azurecr.io/$(imageRepository):$(tag)'
|
|
1512
|
+
|
|
1513
|
+
- task: KubernetesManifest@1
|
|
1514
|
+
displayName: 'Verify Deployment'
|
|
1515
|
+
inputs:
|
|
1516
|
+
action: 'deploy'
|
|
1517
|
+
kubernetesServiceConnection: 'MyAksConnection'
|
|
1518
|
+
namespace: $(k8sNamespace)
|
|
1519
|
+
manifests: |
|
|
1520
|
+
$(Pipeline.Workspace)/manifests/deployment.yml
|
|
1521
|
+
rolloutStatusTimeout: '300s'
|
|
1522
|
+
```
|
|
1523
|
+
|
|
1524
|
+
---
|
|
1525
|
+
|
|
1526
|
+
## 6. Common Workflows
|
|
1527
|
+
|
|
1528
|
+
### Create New Project with Full CI/CD
|
|
1529
|
+
|
|
1530
|
+
```bash
|
|
1531
|
+
#!/bin/bash
|
|
1532
|
+
# Setup complete Azure DevOps project
|
|
1533
|
+
|
|
1534
|
+
ORG="https://dev.azure.com/myorg"
|
|
1535
|
+
PROJECT="MyNewProject"
|
|
1536
|
+
REPO="MyApp"
|
|
1537
|
+
SERVICE_CONNECTION="Azure-Connection"
|
|
1538
|
+
|
|
1539
|
+
# Create project
|
|
1540
|
+
az devops project create \
|
|
1541
|
+
--name "$PROJECT" \
|
|
1542
|
+
--organization "$ORG" \
|
|
1543
|
+
--visibility private \
|
|
1544
|
+
--source-control git \
|
|
1545
|
+
--process Agile
|
|
1546
|
+
|
|
1547
|
+
# Create repository
|
|
1548
|
+
az repos create \
|
|
1549
|
+
--name "$REPO" \
|
|
1550
|
+
--project "$PROJECT" \
|
|
1551
|
+
--organization "$ORG"
|
|
1552
|
+
|
|
1553
|
+
# Create variable groups
|
|
1554
|
+
az pipelines variable-group create \
|
|
1555
|
+
--name "SharedVariables" \
|
|
1556
|
+
--project "$PROJECT" \
|
|
1557
|
+
--organization "$ORG" \
|
|
1558
|
+
--variables environment=development region=eastus \
|
|
1559
|
+
--authorize true
|
|
1560
|
+
|
|
1561
|
+
# Create service connection
|
|
1562
|
+
az devops service-endpoint azurerm create \
|
|
1563
|
+
--name "$SERVICE_CONNECTION" \
|
|
1564
|
+
--project "$PROJECT" \
|
|
1565
|
+
--organization "$ORG" \
|
|
1566
|
+
--azure-rm-service-principal-id $SP_ID \
|
|
1567
|
+
--azure-rm-subscription-id $SUB_ID \
|
|
1568
|
+
--azure-rm-subscription-name $SUB_NAME \
|
|
1569
|
+
--azure-rm-tenant-id $TENANT_ID
|
|
1570
|
+
|
|
1571
|
+
# Create build pipeline
|
|
1572
|
+
az pipelines create \
|
|
1573
|
+
--name "CI-Pipeline" \
|
|
1574
|
+
--project "$PROJECT" \
|
|
1575
|
+
--organization "$ORG" \
|
|
1576
|
+
--repository "$REPO" \
|
|
1577
|
+
--repository-type tfsgit \
|
|
1578
|
+
--branch main \
|
|
1579
|
+
--yml-path azure-pipelines.yml
|
|
1580
|
+
|
|
1581
|
+
# Setup branch policies
|
|
1582
|
+
REPO_ID=$(az repos show --repository "$REPO" --project "$PROJECT" --query id -o tsv)
|
|
1583
|
+
|
|
1584
|
+
az repos policy branch create \
|
|
1585
|
+
--project "$PROJECT" \
|
|
1586
|
+
--repository-id "$REPO_ID" \
|
|
1587
|
+
--branch main \
|
|
1588
|
+
--policy-type "Minimum number of reviewers" \
|
|
1589
|
+
--settings '{"minimumApproverCount": 2, "creatorVoteCounts": false}'
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
### GitHub Integration
|
|
1593
|
+
|
|
1594
|
+
```yaml
|
|
1595
|
+
# Pipeline triggered by GitHub
|
|
1596
|
+
resources:
|
|
1597
|
+
repositories:
|
|
1598
|
+
- repository: mygithub
|
|
1599
|
+
type: github
|
|
1600
|
+
name: myorg/myrepo
|
|
1601
|
+
endpoint: 'GitHubConnection' # GitHub service connection
|
|
1602
|
+
ref: main
|
|
1603
|
+
|
|
1604
|
+
trigger: none # Disable CI trigger, use resource trigger
|
|
1605
|
+
|
|
1606
|
+
stages:
|
|
1607
|
+
- stage: Build
|
|
1608
|
+
jobs:
|
|
1609
|
+
- job: Build
|
|
1610
|
+
steps:
|
|
1611
|
+
- checkout: mygithub
|
|
1612
|
+
- script: |
|
|
1613
|
+
echo "Building from GitHub repo"
|
|
1614
|
+
```
|
|
1615
|
+
|
|
1616
|
+
### Docker/ACR Workflow
|
|
1617
|
+
|
|
1618
|
+
```yaml
|
|
1619
|
+
# Complete Docker workflow
|
|
1620
|
+
variables:
|
|
1621
|
+
imageName: 'myapp'
|
|
1622
|
+
acrName: 'myacr'
|
|
1623
|
+
acrLoginServer: 'myacr.azurecr.io'
|
|
1624
|
+
|
|
1625
|
+
stages:
|
|
1626
|
+
- stage: Build
|
|
1627
|
+
jobs:
|
|
1628
|
+
- job: BuildImage
|
|
1629
|
+
pool:
|
|
1630
|
+
vmImage: 'ubuntu-latest'
|
|
1631
|
+
steps:
|
|
1632
|
+
- task: Docker@2
|
|
1633
|
+
displayName: 'Login to ACR'
|
|
1634
|
+
inputs:
|
|
1635
|
+
command: login
|
|
1636
|
+
containerRegistry: 'MyAcrConnection'
|
|
1637
|
+
|
|
1638
|
+
- task: Docker@2
|
|
1639
|
+
displayName: 'Build Image'
|
|
1640
|
+
inputs:
|
|
1641
|
+
repository: $(imageName)
|
|
1642
|
+
command: build
|
|
1643
|
+
Dockerfile: Dockerfile
|
|
1644
|
+
tags: |
|
|
1645
|
+
$(Build.BuildId)
|
|
1646
|
+
latest
|
|
1647
|
+
arguments: '--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')'
|
|
1648
|
+
|
|
1649
|
+
- task: Docker@2
|
|
1650
|
+
displayName: 'Push Image'
|
|
1651
|
+
inputs:
|
|
1652
|
+
command: push
|
|
1653
|
+
repository: $(imageName)
|
|
1654
|
+
containerRegistry: 'MyAcrConnection'
|
|
1655
|
+
tags: |
|
|
1656
|
+
$(Build.BuildId)
|
|
1657
|
+
latest
|
|
1658
|
+
|
|
1659
|
+
- task: Docker@2
|
|
1660
|
+
displayName: 'Run Trivy Scan'
|
|
1661
|
+
inputs:
|
|
1662
|
+
command: build
|
|
1663
|
+
repository: $(imageName)
|
|
1664
|
+
tags: 'scan'
|
|
1665
|
+
Dockerfile: Dockerfile
|
|
1666
|
+
enabled: false
|
|
1667
|
+
|
|
1668
|
+
- script: |
|
|
1669
|
+
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
|
|
1670
|
+
aquasec/trivy image --exit-code 1 --severity HIGH,CRITICAL \
|
|
1671
|
+
$(acrLoginServer)/$(imageName):$(Build.BuildId)
|
|
1672
|
+
displayName: 'Security Scan'
|
|
1673
|
+
```
|
|
1674
|
+
|
|
1675
|
+
---
|
|
1676
|
+
|
|
1677
|
+
## 7. Troubleshooting
|
|
1678
|
+
|
|
1679
|
+
### Pipeline Failures
|
|
1680
|
+
|
|
1681
|
+
```yaml
|
|
1682
|
+
# Debug pipeline issues
|
|
1683
|
+
steps:
|
|
1684
|
+
- script: |
|
|
1685
|
+
echo "Build ID: $(Build.BuildId)"
|
|
1686
|
+
echo "Build Number: $(Build.BuildNumber)"
|
|
1687
|
+
echo "Source Branch: $(Build.SourceBranch)"
|
|
1688
|
+
echo "Source Version: $(Build.SourceVersion)"
|
|
1689
|
+
env | sort
|
|
1690
|
+
displayName: 'Debug Environment Variables'
|
|
1691
|
+
|
|
1692
|
+
- script: |
|
|
1693
|
+
ls -la $(Build.SourcesDirectory)
|
|
1694
|
+
find $(Build.SourcesDirectory) -type f -name "*.yml" | head -20
|
|
1695
|
+
displayName: 'Debug Source Directory'
|
|
1696
|
+
|
|
1697
|
+
# Enable system.debug for detailed logging
|
|
1698
|
+
variables:
|
|
1699
|
+
system.debug: true
|
|
1700
|
+
```
|
|
1701
|
+
|
|
1702
|
+
### Common Error Solutions
|
|
1703
|
+
|
|
1704
|
+
| Error | Solution |
|
|
1705
|
+
|-------|----------|
|
|
1706
|
+
| `No hosted parallelism has been purchased` | Request free parallelism or purchase agents |
|
|
1707
|
+
| `The term 'az' is not recognized` | Install Azure CLI in pipeline: `UsePythonVersion@0` then `pip install azure-cli` |
|
|
1708
|
+
| `Service connection not found` | Verify exact name match, check permissions |
|
|
1709
|
+
| `Unable to find secret` | Check Key Vault permissions, verify secret name |
|
|
1710
|
+
| `Docker build failed` | Check Dockerfile syntax, verify base image availability |
|
|
1711
|
+
| `Kubernetes deployment timeout` | Increase `rolloutStatusTimeout`, check pod logs |
|
|
1712
|
+
| `Permission denied` | Verify service principal has required RBAC roles |
|
|
1713
|
+
|
|
1714
|
+
### Diagnostic Commands
|
|
1715
|
+
|
|
1716
|
+
```bash
|
|
1717
|
+
# Check service principal permissions
|
|
1718
|
+
az role assignment list --assignee <app-id> --output table
|
|
1719
|
+
|
|
1720
|
+
# Test service connection
|
|
1721
|
+
az devops service-endpoint show --id <connection-id> --project "MyProject"
|
|
1722
|
+
|
|
1723
|
+
# Check pipeline permissions
|
|
1724
|
+
az pipelines show --name "MyPipeline" --project "MyProject" --query "permissions"
|
|
1725
|
+
|
|
1726
|
+
# View agent capabilities
|
|
1727
|
+
az pipelines agent show --pool-id <pool-id> --agent-id <agent-id>
|
|
1728
|
+
|
|
1729
|
+
# Check recent failures
|
|
1730
|
+
az pipelines runs list --project "MyProject" --result failed --top 10
|
|
1731
|
+
```
|
|
1732
|
+
|
|
1733
|
+
---
|
|
1734
|
+
|
|
1735
|
+
## 8. References
|
|
1736
|
+
|
|
1737
|
+
### Official Documentation
|
|
1738
|
+
- [Azure DevOps Documentation](https://learn.microsoft.com/en-us/azure/devops/)
|
|
1739
|
+
- [Azure CLI Reference](https://learn.microsoft.com/en-us/cli/azure/)
|
|
1740
|
+
- [Azure DevOps CLI](https://learn.microsoft.com/en-us/azure/devops/cli/)
|
|
1741
|
+
- [YAML Schema Reference](https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/)
|
|
1742
|
+
- [Bicep Documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/)
|
|
1743
|
+
- [Terraform Azure Provider](https://registry.terraform.io/providers/hashicorp/azurerm/)
|
|
1744
|
+
|
|
1745
|
+
### Task References
|
|
1746
|
+
- [Azure Pipelines Tasks](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/)
|
|
1747
|
+
- [Marketplace Extensions](https://marketplace.visualstudio.com/search?term=devops&target=AzureDevOps&category=All%20categories)
|
|
1748
|
+
|
|
1749
|
+
### Best Practices
|
|
1750
|
+
- [Azure DevOps Best Practices](https://learn.microsoft.com/en-us/azure/devops/boards/best-practices-github)
|
|
1751
|
+
- [Security Best Practices](https://learn.microsoft.com/en-us/azure/devops/organizations/security/security-best-practices)
|
|
1752
|
+
- [Pipeline Security](https://learn.microsoft.com/en-us/azure/devops/pipelines/security/secure-pipelines)
|
|
1753
|
+
|
|
1754
|
+
### Tools
|
|
1755
|
+
- [Azure DevOps Demo Generator](https://azuredevopsdemogenerator.azurewebsites.net/)
|
|
1756
|
+
- [YAML Pipeline Designer](https://azuredevopsdemogenerator.azurewebsites.net/)
|
|
1757
|
+
- [Bicep Playground](https://bicepdemo.z22.web.core.windows.net/)
|