Skip to content

Commit 2da269e

Browse files
authored
Merge pull request #4 from MicrosoftCloudEssentials-LearningHub/cicd-vnet-subnet-scenario2
Add Azure DevOps CI/CD Pipeline Template for Functions
2 parents fd8276a + fd5a1dc commit 2da269e

6 files changed

Lines changed: 303 additions & 10 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Last updated: 2025-08-27
108108

109109
<!-- START BADGE -->
110110
<div align="center">
111-
<img src="https://img.shields.io/badge/Total%20views-1403-limegreen" alt="Total views">
112-
<p>Refresh Date: 2025-09-05</p>
111+
<img src="https://img.shields.io/badge/Total%20views-1334-limegreen" alt="Total views">
112+
<p>Refresh Date: 2025-09-30</p>
113113
</div>
114114
<!-- END BADGE -->

scenario1-high-decay/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Last updated: 2025-08-27
147147

148148
<!-- START BADGE -->
149149
<div align="center">
150-
<img src="https://img.shields.io/badge/Total%20views-1403-limegreen" alt="Total views">
151-
<p>Refresh Date: 2025-09-05</p>
150+
<img src="https://img.shields.io/badge/Total%20views-1334-limegreen" alt="Total views">
151+
<p>Refresh Date: 2025-09-30</p>
152152
</div>
153153
<!-- END BADGE -->

scenario1-high-decay/terraform-infrastructure/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ graph TD;
126126

127127
<!-- START BADGE -->
128128
<div align="center">
129-
<img src="https://img.shields.io/badge/Total%20views-1403-limegreen" alt="Total views">
130-
<p>Refresh Date: 2025-09-05</p>
129+
<img src="https://img.shields.io/badge/Total%20views-1334-limegreen" alt="Total views">
130+
<p>Refresh Date: 2025-09-30</p>
131131
</div>
132132
<!-- END BADGE -->

scenario2-optimized/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Last updated: 2025-09-05
147147

148148
<!-- START BADGE -->
149149
<div align="center">
150-
<img src="https://img.shields.io/badge/Total%20views-865-limegreen" alt="Total views">
151-
<p>Refresh Date: 2025-09-05</p>
150+
<img src="https://img.shields.io/badge/Total%20views-1334-limegreen" alt="Total views">
151+
<p>Refresh Date: 2025-09-30</p>
152152
</div>
153153
<!-- END BADGE -->
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# Azure DevOps CI/CD Pipeline Template for Azure Functions
2+
# This template provides a complete CI/CD workflow for building and deploying Azure Function Apps
3+
# It includes both build and deployment stages with support for deployment slots and multi-environment deployments
4+
5+
parameters:
6+
# Build Configuration (Optional)
7+
# Specifies whether to build in Debug or Release mode
8+
buildConfiguration: 'Release'
9+
10+
# Branch Name (Required)
11+
# The Git branch to build from (e.g., 'main', 'develop', 'feature/xyz')
12+
branchName: ''
13+
14+
# Artifact Name (Optional)
15+
# Name for the resulting build artifact
16+
artifactName: 'Setup'
17+
18+
# NuGet Feed (Required)
19+
# Your Azure DevOps NuGet feed name (e.g., 'MyOrganization/MyFeed')
20+
nugetFeed: ''
21+
22+
# .NET SDK Version (Optional)
23+
# The version of .NET SDK to use for building
24+
dotnetVersion: '6.0.x'
25+
26+
# Azure Subscription (Required for deployment)
27+
# The name of your Azure DevOps service connection to Azure
28+
azureSubscription: ''
29+
30+
# Function App Name (Required for deployment)
31+
# The name of your Azure Function App resource
32+
functionAppName: ''
33+
34+
# Resource Group Name (Required for deployment)
35+
# The name of the Azure resource group containing your Function App
36+
resourceGroupName: ''
37+
38+
# Deploy to Slot (Optional)
39+
# Whether to use deployment slots for zero-downtime deployments
40+
deployToSlot: false
41+
42+
# Slot Name (Optional, required if deployToSlot is true)
43+
# Name of the deployment slot if using slots
44+
slotName: 'staging'
45+
46+
# Environment (Optional)
47+
# Target environment name (dev/test/prod)
48+
environment: 'dev'
49+
50+
# Swap After Deployment (Optional)
51+
# Whether to swap slots after successful deployment
52+
swapAfterDeployment: true
53+
54+
# App Settings (Optional)
55+
# Array of application settings to configure (key-value pairs)
56+
appSettings: []
57+
58+
# Deployment Method (Optional)
59+
# The method used to deploy the Function App
60+
# Valid options:
61+
# - 'auto': Lets Azure decide the best deployment method based on the app type and configuration.
62+
# Use this if you're unsure which method is best for your scenario.
63+
#
64+
# - 'zipDeploy': Basic ZIP deployment that extracts files to the wwwroot folder.
65+
# USE CASE: Simple deployments where you need to update only the application files.
66+
# LIMITATIONS: Doesn't set any app settings, may cause cold start delays as files are extracted.
67+
#
68+
# - 'zipDeployWithRestartAppSetting': RECOMMENDED FOR PRODUCTION.
69+
# Sets WEBSITE_RUN_FROM_PACKAGE=1 and deploys as a ZIP package.
70+
# USE CASE: Production deployments where you need read-only operation and optimal performance.
71+
# BENEFITS:
72+
# - Makes wwwroot folder read-only (prevents in-portal editing)
73+
# - Runs directly from the package (no file extraction, reducing temp storage usage)
74+
# - Improves cold start performance
75+
# - Simplifies rollback (just switch app setting back to previous package)
76+
#
77+
# - 'runFromPackage': Sets WEBSITE_RUN_FROM_PACKAGE to a URL of your package.
78+
# USE CASE: When your package is stored in Azure Blob Storage or other external location.
79+
# BENEFITS: Separates code deployment from activation, enabling blue/green deployments.
80+
#
81+
# - 'webDeploy': Uses MSDeploy (Web Deploy) protocol for deployment.
82+
# USE CASE: Complex deployments with parameters, specific configurations, or transforms.
83+
# LIMITATIONS: Slower than ZIP deployments and more complex to configure.
84+
deploymentMethod: 'zipDeployWithRestartAppSetting'
85+
86+
stages:
87+
#################################################
88+
# BUILD STAGE
89+
# Compiles the code and creates deployment packages
90+
#################################################
91+
- stage: Build
92+
displayName: Build ${{parameters.artifactName}} for ${{parameters.branchName}}
93+
jobs:
94+
- job: Build
95+
displayName: Build Function App
96+
steps:
97+
# Step 1: Checkout the correct branch
98+
# This step fetches the source code from the repository
99+
- checkout: self
100+
clean: true # Remove any previous files
101+
persistCredentials: true # Keep git credentials for potential later git operations
102+
fetchDepth: 0 # Fetch full history
103+
displayName: Checkout ${{ parameters.branchName }}
104+
105+
# Step 2: Ensure branch synchronization
106+
# This ensures we're working with the latest code from the specified branch
107+
# It fetches all remote branches and resets the local branch to match the remote
108+
- script: |
109+
git fetch --all
110+
git clean -fd
111+
git reset --hard origin/${{ parameters.branchName }}
112+
displayName: Synchronize with ${{ parameters.branchName }}
113+
114+
# Step 3: Install .NET Core SDK
115+
# Ensures the correct .NET SDK version is available for building
116+
# This allows you to standardize the SDK version across different build agents
117+
- task: UseDotNet@2
118+
inputs:
119+
packageType: 'sdk'
120+
version: '${{ parameters.dotnetVersion }}'
121+
displayName: Install .NET SDK Version ${{ parameters.dotnetVersion }}
122+
123+
# Step 4: Restore NuGet Packages
124+
# Downloads and installs all NuGet dependencies required by the projects
125+
# This helps ensure all required libraries are available for the build
126+
- task: NuGetCommand@2
127+
inputs:
128+
command: 'restore'
129+
restoreSolution: '**/*.sln'
130+
feedsToUse: 'select'
131+
vstsFeed: '${{ parameters.nugetFeed }}'
132+
displayName: Restore NuGet Packages
133+
134+
# Step 5: Build the Solution
135+
# Compiles and publishes the projects to a directory
136+
# We use zipAfterPublish: false to maintain control over the archive process
137+
# This step creates the compiled application that will be deployed to Azure
138+
- task: DotNetCoreCLI@2
139+
displayName: Publish Function App
140+
inputs:
141+
command: 'publish'
142+
projects: '**/*.csproj'
143+
arguments: '--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory)/publish'
144+
publishWebProjects: false # Ensures we publish all projects, not just web projects
145+
zipAfterPublish: false # Don't zip during publish, we'll do that in next step for more control
146+
147+
# Step 6: Archive Published Artifacts
148+
# Creates a single ZIP file from all published files
149+
# This is important for Azure Functions as they require a specific deployment package format
150+
# The resulting ZIP file includes all files needed to run the Function App in Azure
151+
- task: ArchiveFiles@2
152+
inputs:
153+
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/publish'
154+
includeRootFolder: false # Don't include the publish folder itself in the archive
155+
archiveType: 'zip' # Create a ZIP archive
156+
archiveFile: '$(Build.ArtifactStagingDirectory)/archives/${{ parameters.artifactName }}-${{ parameters.environment }}-${{ parameters.branchName }}.zip'
157+
replaceExistingArchive: true # Overwrite any existing archive with the same name
158+
displayName: Archive Function App Package
159+
160+
# Step 7: Publish Archived Build Artifacts
161+
# Makes the ZIP archive available for download or use in subsequent pipeline stages
162+
# This allows the deployment stage to access the build artifacts
163+
- task: PublishBuildArtifacts@1
164+
inputs:
165+
PathtoPublish: '$(Build.ArtifactStagingDirectory)/archives'
166+
ArtifactName: 'archives' # Name of the artifact feed in Azure DevOps
167+
publishLocation: 'Container' # Publish to the pipeline's artifact storage
168+
displayName: Publish Function App Artifacts
169+
170+
#################################################
171+
# DEPLOYMENT STAGE
172+
# Deploys the built artifacts to Azure Function App
173+
# This stage will only run if the required deployment parameters are provided
174+
#################################################
175+
- stage: Deploy
176+
displayName: Deploy to ${{ parameters.environment }} Environment
177+
dependsOn: Build # Only run after Build stage completes successfully
178+
condition: and(succeeded(), ne('${{ parameters.azureSubscription }}', ''), ne('${{ parameters.functionAppName }}', ''), ne('${{ parameters.resourceGroupName }}', ''))
179+
jobs:
180+
- deployment: DeployFunctionApp
181+
displayName: Deploy Function App
182+
# Create an environment in Azure DevOps for tracking deployments
183+
environment: ${{ parameters.environment }}-${{ parameters.functionAppName }}
184+
strategy:
185+
# Define a standard deployment strategy
186+
runOnce:
187+
deploy:
188+
steps:
189+
# Step 1: Download the artifacts from the build stage
190+
# This retrieves the ZIP package created in the Build stage
191+
# The deployment job automatically downloads artifacts to $(Pipeline.Workspace)
192+
- download: current
193+
artifact: archives
194+
displayName: Download Build Artifacts
195+
196+
# Step 2: Deploy to Azure Function App
197+
# This deploys the ZIP package to the Azure Function App using the specified deployment method
198+
# If using slots, it deploys to the staging slot first
199+
# The deployment is done using the Azure Functions deployment API
200+
- task: AzureFunctionApp@1
201+
inputs:
202+
azureSubscription: '${{ parameters.azureSubscription }}' # Azure service connection
203+
appType: 'functionApp' # Specifies this is a Function App
204+
appName: '${{ parameters.functionAppName }}' # Name of the Function App in Azure
205+
# Configure slot deployment if enabled
206+
${{ if eq(parameters.deployToSlot, true) }}:
207+
deployToSlotOrASE: true # Deploy to a slot
208+
resourceGroupName: '${{ parameters.resourceGroupName }}' # Resource group containing the Function App
209+
slotName: '${{ parameters.slotName }}' # Name of the slot to deploy to
210+
${{ if ne(parameters.deployToSlot, true) }}:
211+
deployToSlotOrASE: false # Deploy directly to production
212+
package: '$(Pipeline.Workspace)/archives/${{ parameters.artifactName }}-${{ parameters.environment }}-${{ parameters.branchName }}.zip' # Path to the ZIP package
213+
deploymentMethod: '${{ parameters.deploymentMethod }}' # Use the specified deployment method
214+
displayName: Deploy to Azure Function App ${{ parameters.functionAppName }} using ${{ parameters.deploymentMethod }}
215+
216+
# Step 3: Configure Application Settings
217+
# This sets up environment variables and app settings for the Function App
218+
# Only runs if appSettings parameter is provided
219+
- task: AzureAppServiceSettings@1
220+
condition: and(succeeded(), gt(length('${{ parameters.appSettings }}'), 0))
221+
inputs:
222+
azureSubscription: '${{ parameters.azureSubscription }}'
223+
appName: '${{ parameters.functionAppName }}'
224+
resourceGroupName: '${{ parameters.resourceGroupName }}'
225+
${{ if eq(parameters.deployToSlot, true) }}:
226+
slotName: '${{ parameters.slotName }}'
227+
appSettings: '${{ parameters.appSettings }}'
228+
displayName: Configure Application Settings
229+
230+
# Step 4: Swap Slots (if enabled)
231+
# This swaps the staging slot with production for zero-downtime deployments
232+
# Only runs if deployToSlot and swapAfterDeployment are both true
233+
- task: AzureAppServiceManage@0
234+
condition: and(succeeded(), eq(parameters.deployToSlot, true), eq(parameters.swapAfterDeployment, true))
235+
inputs:
236+
azureSubscription: '${{ parameters.azureSubscription }}'
237+
Action: 'Swap Slots'
238+
WebAppName: '${{ parameters.functionAppName }}'
239+
ResourceGroupName: '${{ parameters.resourceGroupName }}'
240+
SourceSlot: '${{ parameters.slotName }}'
241+
displayName: Swap Deployment Slots
242+
243+
# Step 5: Verify Deployment
244+
# This checks if the deployment was successful
245+
# It's a simple echo command, but could be extended to perform actual health checks
246+
- script: |
247+
echo "Deployment to ${{ parameters.functionAppName }} completed successfully"
248+
echo "Environment: ${{ parameters.environment }}"
249+
echo "Deployment Method: ${{ parameters.deploymentMethod }}"
250+
echo "Function App URL: https://${{ parameters.functionAppName }}${{ if eq(parameters.deployToSlot, true) }}${{ if ne(parameters.swapAfterDeployment, true) }}-${{ parameters.slotName }}${{ end }}${{ end }}.azurewebsites.net"
251+
displayName: Verify Deployment
252+
253+
#################################################
254+
# OPTIONAL: SMOKE TEST STAGE
255+
# Runs basic tests against the deployed Function App
256+
# This stage will only run if the deployment stage succeeds
257+
#################################################
258+
- stage: SmokeTest
259+
displayName: Run Smoke Tests
260+
dependsOn: Deploy
261+
condition: and(succeeded(), ne('${{ parameters.azureSubscription }}', ''), ne('${{ parameters.functionAppName }}', ''))
262+
jobs:
263+
- job: TestFunctionApp
264+
displayName: Test Function App Endpoints
265+
steps:
266+
# Run basic health check tests against the deployed Function App
267+
# This ensures the deployment is functioning correctly
268+
- task: PowerShell@2
269+
inputs:
270+
targetType: 'inline'
271+
script: |
272+
# Define the base URL for the Function App
273+
$baseUrl = "https://${{ parameters.functionAppName }}.azurewebsites.net"
274+
275+
Write-Host "Running health check against $baseUrl"
276+
277+
try {
278+
# You can replace this with actual endpoint tests specific to your Function App
279+
$response = Invoke-WebRequest -Uri "$baseUrl" -Method Get -TimeoutSec 30 -ErrorAction Stop
280+
Write-Host "Health check status code: $($response.StatusCode)"
281+
282+
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) {
283+
Write-Host "##vso[task.complete result=Succeeded;]Health check passed"
284+
} else {
285+
Write-Warning "Health check returned unexpected status code: $($response.StatusCode)"
286+
}
287+
} catch {
288+
Write-Warning "Health check failed: $_"
289+
}
290+
failOnStderr: false
291+
pwsh: true
292+
displayName: Run Function App Health Check
293+
continueOnError: true # Continue even if health check fails

scenario2-optimized/terraform-infrastructure/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ graph TD;
126126

127127
<!-- START BADGE -->
128128
<div align="center">
129-
<img src="https://img.shields.io/badge/Total%20views-1403-limegreen" alt="Total views">
130-
<p>Refresh Date: 2025-09-05</p>
129+
<img src="https://img.shields.io/badge/Total%20views-1334-limegreen" alt="Total views">
130+
<p>Refresh Date: 2025-09-30</p>
131131
</div>
132132
<!-- END BADGE -->

0 commit comments

Comments
 (0)