Skip to content

Commit a89baa0

Browse files
authored
Add Azure Functions CI/CD Pipeline Template
This YAML file provides a complete CI/CD pipeline template for Azure Function Apps, including build and deployment stages, environment parameters, and deployment methods.
1 parent b042a57 commit a89baa0

1 file changed

Lines changed: 325 additions & 0 deletions

File tree

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
# Example
2+
# Azure DevOps CI/CD Pipeline Template for Azure Functions
3+
# This template provides a complete CI/CD workflow for building and deploying Azure Function Apps
4+
# It includes both build and deployment stages with support for deployment slots and multi-environment deployments
5+
6+
parameters:
7+
# Build Configuration (Optional)
8+
# Specifies whether to build in Debug or Release mode
9+
buildConfiguration: 'Release'
10+
11+
# Branch Name (Required)
12+
# The Git branch to build from (e.g., 'main', 'develop', 'feature/xyz')
13+
branchName: ''
14+
15+
# Artifact Name (Optional)
16+
# Name for the resulting build artifact
17+
artifactName: 'Setup'
18+
19+
# NuGet Feed (Required)
20+
# Your Azure DevOps NuGet feed name (e.g., 'MyOrganization/MyFeed')
21+
nugetFeed: ''
22+
23+
# .NET SDK Version (Optional)
24+
# The version of .NET SDK to use for building
25+
dotnetVersion: '6.0.x'
26+
27+
# Azure Subscription (Required for deployment)
28+
# The name of your Azure DevOps service connection to Azure
29+
azureSubscription: ''
30+
31+
# Function App Name (Required for deployment)
32+
# The name of your Azure Function App resource
33+
functionAppName: ''
34+
35+
# Resource Group Name (Required for deployment)
36+
# The name of the Azure resource group containing your Function App
37+
resourceGroupName: ''
38+
39+
# Deploy to Slot (Optional)
40+
# Whether to use deployment slots for zero-downtime deployments
41+
deployToSlot: false
42+
43+
# Slot Name (Optional, required if deployToSlot is true)
44+
# Name of the deployment slot if using slots
45+
slotName: 'staging'
46+
47+
# Environment (Optional)
48+
# Target environment name (dev/test/prod)
49+
environment: 'dev'
50+
51+
# Swap After Deployment (Optional)
52+
# Whether to swap slots after successful deployment
53+
swapAfterDeployment: true
54+
55+
# App Settings (Optional)
56+
# Array of application settings to configure (key-value pairs)
57+
appSettings: []
58+
59+
# Deployment Method (Optional)
60+
# The method used to deploy the Function App
61+
# Valid options:
62+
# - 'auto': Lets Azure decide the best deployment method based on the app type and configuration.
63+
# - 'zipDeploy': Basic ZIP deployment that extracts files to the wwwroot folder.
64+
# - 'zipDeployWithRestartAppSetting': Sets WEBSITE_RUN_FROM_PACKAGE=1 and deploys as a ZIP package (run-from-package).
65+
# - 'runFromPackage': Sets WEBSITE_RUN_FROM_PACKAGE to a URL of your package.
66+
# - 'webDeploy': Uses MSDeploy (Web Deploy) protocol for deployment.
67+
# For writable deployments prefer 'zipDeploy' or 'webDeploy'.
68+
deploymentMethod: 'zipDeploy'
69+
70+
# Writable Deployment
71+
# true => deploy files to wwwroot (writable approach). Pre-deploy step will remove WEBSITE_RUN_FROM_PACKAGE.
72+
# false => keep mounted/package behavior (run-from-package). Use 'zipDeployWithRestartAppSetting' or 'runFromPackage'.
73+
writableDeployment: true
74+
75+
stages:
76+
#################################################
77+
# BUILD STAGE
78+
# Compiles the code and creates deployment packages
79+
#################################################
80+
- stage: Build
81+
displayName: Build ${{ parameters.artifactName }} for ${{ parameters.branchName }}
82+
jobs:
83+
- job: Build
84+
displayName: Build Function App
85+
steps:
86+
# Step 1: Checkout the correct branch
87+
# This step fetches the source code from the repository
88+
- checkout: self
89+
clean: true # Remove any previous files
90+
persistCredentials: true # Keep git credentials for potential later git operations
91+
fetchDepth: 0 # Fetch full history
92+
displayName: Checkout ${{ parameters.branchName }}
93+
94+
# Step 2: Ensure branch synchronization
95+
# This ensures we're working with the latest code from the specified branch
96+
# It fetches all remote branches and resets the local branch to match the remote
97+
- script: |
98+
git fetch --all
99+
git clean -fd
100+
git reset --hard origin/${{ parameters.branchName }}
101+
displayName: Synchronize with ${{ parameters.branchName }}
102+
103+
# Step 3: Install .NET Core SDK
104+
# Ensures the correct .NET SDK version is available for building
105+
- task: UseDotNet@2
106+
inputs:
107+
packageType: 'sdk'
108+
version: '${{ parameters.dotnetVersion }}'
109+
displayName: Install .NET SDK Version ${{ parameters.dotnetVersion }}
110+
111+
# Step 4: Restore NuGet Packages
112+
- task: NuGetCommand@2
113+
inputs:
114+
command: 'restore'
115+
restoreSolution: '**/*.sln'
116+
feedsToUse: 'select'
117+
vstsFeed: '${{ parameters.nugetFeed }}'
118+
displayName: Restore NuGet Packages
119+
120+
# Step 5: Build the Solution
121+
- task: DotNetCoreCLI@2
122+
displayName: Publish Function App
123+
inputs:
124+
command: 'publish'
125+
projects: '**/*.csproj'
126+
arguments: '--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory)/publish'
127+
publishWebProjects: false # Ensures we publish all projects, not just web projects
128+
zipAfterPublish: false # Don't zip during publish, we'll do that in next step for more control
129+
130+
# Step 6: Archive Published Artifacts
131+
- task: ArchiveFiles@2
132+
inputs:
133+
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/publish'
134+
includeRootFolder: false # Don't include the publish folder itself in the archive
135+
archiveType: 'zip' # Create a ZIP archive
136+
archiveFile: '$(Build.ArtifactStagingDirectory)/archives/${{ parameters.artifactName }}-${{ parameters.environment }}-${{ parameters.branchName }}.zip'
137+
replaceExistingArchive: true # Overwrite any existing archive with the same name
138+
displayName: Archive Function App Package
139+
140+
# Step 7: Publish Archived Build Artifacts
141+
- task: PublishBuildArtifacts@1
142+
inputs:
143+
PathtoPublish: '$(Build.ArtifactStagingDirectory)/archives'
144+
ArtifactName: 'archives' # Name of the artifact feed in Azure DevOps
145+
publishLocation: 'Container' # Publish to the pipeline's artifact storage
146+
displayName: Publish Function App Artifacts
147+
148+
#################################################
149+
# DEPLOYMENT STAGE
150+
# Deploys the built artifacts to Azure Function App
151+
# This stage will only run if the required deployment parameters are provided
152+
#################################################
153+
- stage: Deploy
154+
displayName: Deploy to ${{ parameters.environment }} Environment
155+
dependsOn: Build # Only run after Build stage completes successfully
156+
condition: and(succeeded(), ne('${{ parameters.azureSubscription }}', ''), ne('${{ parameters.functionAppName }}', ''), ne('${{ parameters.resourceGroupName }}', ''))
157+
jobs:
158+
- deployment: DeployFunctionApp
159+
displayName: Deploy Function App
160+
# Create an environment in Azure DevOps for tracking deployments
161+
environment: ${{ parameters.environment }}-${{ parameters.functionAppName }}
162+
strategy:
163+
# Define a standard deployment strategy
164+
runOnce:
165+
deploy:
166+
steps:
167+
# Step 1: Download the artifacts from the build stage
168+
# This retrieves the ZIP package created in the Build stage
169+
# The deployment job automatically downloads artifacts to $(Pipeline.Workspace)
170+
- download: current
171+
artifact: archives
172+
displayName: Download Build Artifacts
173+
174+
# --- PRE-DEPLOY: Prepare app for writable deployment ---
175+
# If writableDeployment is true we remove the WEBSITE_RUN_FROM_PACKAGE app setting (recommended)
176+
# This converts the app from mounted-package (run-from-package) to writable wwwroot.
177+
${{ if eq(parameters.writableDeployment, true) }}:
178+
- task: AzureCLI@2
179+
displayName: 'Prepare app for writable deployment: unset WEBSITE_RUN_FROM_PACKAGE'
180+
inputs:
181+
azureSubscription: '${{ parameters.azureSubscription }}'
182+
scriptType: 'bash'
183+
scriptLocation: 'inlineScript'
184+
inlineScript: |
185+
set -e
186+
APP_NAME='${{ parameters.functionAppName }}'
187+
RG='${{ parameters.resourceGroupName }}'
188+
# If using slot, include slot argument
189+
SLOT_ARG=''
190+
if [ '${{ parameters.deployToSlot }}' = 'true' ]; then
191+
# Note: azure CLI slot argument uses --slot <name> (no quotes)
192+
SLOT_ARG="--slot ${{ parameters.slotName }}"
193+
fi
194+
195+
# Option A (default): delete the app setting key (recommended to fully remove run-from-package)
196+
echo "Deleting WEBSITE_RUN_FROM_PACKAGE app setting (if present)..."
197+
az webapp config appsettings delete --name "$APP_NAME" --resource-group "$RG" --setting-names WEBSITE_RUN_FROM_PACKAGE $SLOT_ARG || true
198+
199+
# Optional: display current setting (for diagnostics)
200+
echo "Current WEBSITE_RUN_FROM_PACKAGE value (if any):"
201+
az webapp config appsettings list --name "$APP_NAME" --resource-group "$RG" $SLOT_ARG --query "[?name=='WEBSITE_RUN_FROM_PACKAGE']" -o table || true
202+
203+
echo "App prepared for writable deployment."
204+
205+
# --- DEPLOY PACKAGE ---
206+
# This deploys the ZIP package to the Azure Function App using the specified deployment method.
207+
- task: AzureFunctionApp@1
208+
inputs:
209+
azureSubscription: '${{ parameters.azureSubscription }}' # Azure service connection
210+
appType: 'functionApp' # Specifies this is a Function App
211+
appName: '${{ parameters.functionAppName }}' # Name of the Function App in Azure
212+
# Configure slot deployment if enabled
213+
${{ if eq(parameters.deployToSlot, true) }}:
214+
deployToSlotOrASE: true # Deploy to a slot
215+
resourceGroupName: '${{ parameters.resourceGroupName }}' # Resource group containing the Function App
216+
slotName: '${{ parameters.slotName }}' # Name of the slot to deploy to
217+
${{ if ne(parameters.deployToSlot, true) }}:
218+
deployToSlotOrASE: false # Deploy directly to production
219+
package: '$(Pipeline.Workspace)/archives/${{ parameters.artifactName }}-${{ parameters.environment }}-${{ parameters.branchName }}.zip' # Path to the ZIP package
220+
deploymentMethod: '${{ parameters.deploymentMethod }}' # Use the specified deployment method
221+
displayName: Deploy to Azure Function App ${{ parameters.functionAppName }} using ${{ parameters.deploymentMethod }}
222+
223+
# Step 3: Configure Application Settings
224+
# This sets up environment variables and app settings for the Function App
225+
# Only runs if appSettings parameter is provided
226+
- task: AzureAppServiceSettings@1
227+
condition: and(succeeded(), gt(length('${{ parameters.appSettings }}'), 0))
228+
inputs:
229+
azureSubscription: '${{ parameters.azureSubscription }}'
230+
appName: '${{ parameters.functionAppName }}'
231+
resourceGroupName: '${{ parameters.resourceGroupName }}'
232+
${{ if eq(parameters.deployToSlot, true) }}:
233+
slotName: '${{ parameters.slotName }}'
234+
appSettings: '${{ parameters.appSettings }}'
235+
displayName: Configure Application Settings
236+
237+
# Step 4: Swap Slots (if enabled)
238+
# This swaps the staging slot with production for zero-downtime deployments
239+
# Only runs if deployToSlot and swapAfterDeployment are both true
240+
- task: AzureAppServiceManage@0
241+
condition: and(succeeded(), eq(parameters.deployToSlot, true), eq(parameters.swapAfterDeployment, true))
242+
inputs:
243+
azureSubscription: '${{ parameters.azureSubscription }}'
244+
Action: 'Swap Slots'
245+
WebAppName: '${{ parameters.functionAppName }}'
246+
ResourceGroupName: '${{ parameters.resourceGroupName }}'
247+
SourceSlot: '${{ parameters.slotName }}'
248+
displayName: Swap Deployment Slots
249+
250+
# --- POST-DEPLOY: Restart the app if using writableDeployment (good to clear state) ---
251+
${{ if eq(parameters.writableDeployment, true) }}:
252+
- task: AzureCLI@2
253+
displayName: 'Restart Function App (writable deployment)'
254+
inputs:
255+
azureSubscription: '${{ parameters.azureSubscription }}'
256+
scriptType: 'bash'
257+
scriptLocation: 'inlineScript'
258+
inlineScript: |
259+
APP_NAME='${{ parameters.functionAppName }}'
260+
RG='${{ parameters.resourceGroupName }}'
261+
SLOT_ARG=''
262+
if [ '${{ parameters.deployToSlot }}' = 'true' ]; then
263+
SLOT_ARG="--slot ${{ parameters.slotName }}"
264+
fi
265+
266+
echo "Restarting $APP_NAME..."
267+
az webapp restart --name "$APP_NAME" --resource-group "$RG" $SLOT_ARG || true
268+
echo "Restart completed."
269+
270+
# Step 5: Verify Deployment
271+
# This checks if the deployment was successful
272+
- script: |
273+
echo "Deployment to ${{ parameters.functionAppName }} completed successfully"
274+
echo "Environment: ${{ parameters.environment }}"
275+
echo "Deployment Method: ${{ parameters.deploymentMethod }}"
276+
if [ '${{ parameters.deployToSlot }}' = 'true' ]; then
277+
echo "Deployed to slot: ${{ parameters.slotName }}"
278+
fi
279+
echo "Function App URL: https://${{ parameters.functionAppName }}${{ if eq(parameters.deployToSlot, true) }}-${{ parameters.slotName }}${{ end }}.azurewebsites.net"
280+
displayName: Verify Deployment
281+
282+
#################################################
283+
# OPTIONAL: SMOKE TEST STAGE
284+
# Runs basic tests against the deployed Function App
285+
# This stage will only run if the deployment stage succeeds
286+
#################################################
287+
- stage: SmokeTest
288+
displayName: Run Smoke Tests
289+
dependsOn: Deploy
290+
condition: and(succeeded(), ne('${{ parameters.azureSubscription }}', ''), ne('${{ parameters.functionAppName }}', ''))
291+
jobs:
292+
- job: TestFunctionApp
293+
displayName: Test Function App Endpoints
294+
steps:
295+
# Run basic health check tests against the deployed Function App
296+
# This ensures the deployment is functioning correctly
297+
- task: PowerShell@2
298+
inputs:
299+
targetType: 'inline'
300+
script: |
301+
# Define the base URL for the Function App (slot-aware)
302+
$baseUrl = "https://${{ parameters.functionAppName }}.azurewebsites.net"
303+
if ('${{ parameters.deployToSlot }}' -eq 'true') {
304+
$baseUrl = "https://${{ parameters.functionAppName }}-${{ parameters.slotName }}.azurewebsites.net"
305+
}
306+
307+
Write-Host "Running health check against $baseUrl"
308+
309+
try {
310+
# You can replace this with actual endpoint tests specific to your Function App
311+
$response = Invoke-WebRequest -Uri $baseUrl -Method Get -TimeoutSec 30 -ErrorAction Stop
312+
Write-Host "Health check status code: $($response.StatusCode)"
313+
314+
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) {
315+
Write-Host "##vso[task.complete result=Succeeded;]Health check passed"
316+
} else {
317+
Write-Warning "Health check returned unexpected status code: $($response.StatusCode)"
318+
}
319+
} catch {
320+
Write-Warning "Health check failed: $_"
321+
}
322+
failOnStderr: false
323+
pwsh: true
324+
displayName: Run Function App Health Check
325+
continueOnError: true # Continue even if health check fails

0 commit comments

Comments
 (0)