|
| 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 |
0 commit comments