# Azure DevOps CI Pipeline ## 🔹 CI/CD Overview ✅ **CI (Continuous Integration)** - Responsible for **Restore**, **Build**, **Test**, and **Package** - Generates **deployable artifacts (`.zip`, `.dll`, `dist/`)** ✅ **CD (Continuous Deployment)** - Responsible for **Deploying** applications to **Azure Web App** or **Azure Static Web Apps** - Uses **Azure DevOps Pipelines** to automatically download and deploy applications from **artifacts** --- ## 🔹 CI/CD Stages | **Stage** | **Task** | **Description** | |------------------|-----------------|-----------| | **Restore** | `dotnet restore` / `npm install` | Restores NuGet/npm dependencies | | **Build** | `dotnet build` / `npm run build` | Compiles source code | | **Test** | `dotnet test` / `npm test` | Runs unit tests| | **Code Analysis** | `SonarQube` / `ESLint` | Analyzes code quality | | **Publish** | `Publish Artifacts` | Generates .zip / .dll for deployment | --- ## 🔹 What is an Azure DevOps Agent? ### Agent Definition - **An agent is a virtual machine (VM) or container that executes Azure DevOps CI/CD Pipelines.** - **It downloads source code and runs `dotnet build`, `npm install`, `NuGet restore`**, etc. ### Agent offer by Microsoft Azure DevOps - **`ubuntu-latest`** (Linux) - **`windows-latest`** (Windows) - **`macos-latest`** (Mac) ### Self-hosted Agent - Can be installed on **On-Premises Servers** or **Docker Containers** - Suitable for **private network deployments** --- ## 🔹 SDK & NuGet ### When to Use `UseDotNet@2` & `NodeTool@0`? - **When the agent does not have `.NET SDK` or `Node.js` pre-installed** - **`ubuntu-latest` & `windows-latest`agents include .NET SDK by default** - **`ubuntu-latest`** **includes Node.js by default** - Required for executing `dotnet build`, `dotnet publish`, `npm install`, `npm run build`, etc. #### .NET Framework (NuGetToolInstaller@1) - Required for restoring package.config or *.sln ```yaml! - task: NuGetToolInstaller@1 displayName: 'Install NuGet' inputs: versionSpec: '5.4.0' - task: NuGetCommand@2 displayName: 'Restore NuGet Packages' inputs: command: 'restore' restoreSolution: 'MyProject.sln' ``` #### .NET Core (.NET 5 ⬆) (DotNetCoreCLI@2) - `dotnet restore` automatically restores NuGet packages ```yaml! - task: DotNetCoreCLI@2 displayName: 'Restore NuGet Packages' inputs: command: 'restore' projects: '**/*.csproj' ``` - Private NuGet Source - Manually install NuGet for private repositories like Nexus ```yaml! - task: NuGetCommand@2 displayName: 'Restore NuGet Packages from Nexus' inputs: command: 'restore' restoreSolution: 'MyProject.sln' feedsToUse: 'select' vstsFeed: 'https://nexus.example.com/repository/nuget-hosted' ``` #### Vite + React - Use --legacy-peer-deps to avoid version conflicts in peer dependencies ```yaml! - script: | npm install displayName: 'Install Dependencies' ``` ## 🔹 Build Stage ### .NET Framework - **VSBuild@1** (Windows Only) ```yaml! - task: VSBuild@1 displayName: 'Build .NET Framework Solution' inputs: solution: '**/*.sln' msbuildArgs: '/p:Configuration=Release' platform: 'Any CPU' configuration: 'Release' ``` ### .NET Core 5+ - **DotNetCoreCLI@2** - Supports `Windows/Linux/Mac` ```yaml! - task: DotNetCoreCLI@2 displayName: 'Build .NET Core Application' inputs: command: 'build' arguments: '--configuration Release' ``` ### Vite + React ```yaml! - script: | npm run build displayName: 'Build Vite React App' ``` ## 🔹 Publish Stage ### .NET Framework - **VSBuild@1** **(MSBuild + WebDeploy for IIS)** ```yaml! - task: VSBuild@1 displayName: 'Build .NET Framework Solution' inputs: solution: '**/*.sln' msbuildArgs: '/p:DeployOnBuild=true /p:PublishProfile=WebDeploy' platform: 'Any CPU' configuration: 'Release' ``` ### .NET Core #### Azure App Service / Linux - **DotNetCoreCLI@2** (dotnet publish) -> **PublishBuildArtifacts@1** ```yaml! - task: DotNetCoreCLI@2 displayName: 'Publish .NET Core API' inputs: command: 'publish' publishWebProjects: true arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)' zipAfterPublish: true # Save drop to Artifact - task: PublishBuildArtifacts@1 displayName: 'Store Published Artifacts' inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container' ``` #### Azure Kubernetes ### Vite + React - Between stages, files are not automatically preserved. Save them in an **artifact** ```yaml! - task: CopyFiles@2 inputs: SourceFolder: 'dist' TargetFolder: '$(Build.ArtifactStagingDirectory)' displayName: 'Copy Built Files to Staging Directory' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'dist' displayName: 'Publish Build Artifacts' ``` ## 🔹 Deployment Stage ### IIS (.NET Framework) ```yaml! - task: VSBuild@1 displayName: 'Build .NET Framework Solution' inputs: solution: '**/*.sln' msbuildArgs: '/p:DeployOnBuild=true /p:PublishProfile=WebDeploy' platform: 'Any CPU' configuration: 'Release' ``` ### Azure Web App (.NET Core) #### Web App Service - Use **AzureWebApp@1** to deploy - Support dotnet publish deploy by .zip ```yaml! - task: AzureWebApp@1 displayName: 'Deploy .NET Core API' inputs: azureSubscription: 'Azure Service Connection' appName: 'app-service-name' package: '$(Build.ArtifactStagingDirectory)' ``` ### Azure Static Web App (Vite + React) #### Static Web App Service - Use **AzureStaticWebApp@0** to deploy ```yaml! - stage: Deploy jobs: - deployment: Deploy environment: 'Production' strategy: runOnce: deploy: steps: # Download to Pipeline.WorkSpace directory - task: DownloadBuildArtifacts@0 inputs: artifactName: 'dist' downloadPath: '$(Pipeline.Workspace)' displayName: 'Download dist Artifact' - task: AzureStaticWebApp@0 inputs: app_location: 'dist' # Path where we find the source code skip_app_build: true # no need for npm build & install skip_api_build: true # without api output_location: '' # Path where the exit is located azure_static_web_apps_api_token: $(SWA_DEPLOYMENT_TOKEN) #部署權杖 ``` ## 🔹 Common Issues & Fixes ### 🔍Issue : `There was an error exporting the HTTPS developer certificate to a file. Please create the target directory before exporting.` #### ❓ Problem : .NET 8 does not automatically create the certificate folder, causing HTTPS certificate export to fail. #### ✅ Fixed : Manually create the folder before exporting the certificate. ```yaml! - script: | mkdir -p ~/.aspnet/https # Linux displayName: 'Fix HTTPS Developer Certificate' ``` ### 🔍Issue : `AzureStaticWebApp@0 App Directory Location: 'dist' is invalid. Could not detect this directory.` #### ❓ Problem : The dist artifact is downloaded to `/home/vsts/work/1`, but AzureStaticWebApp@0 searches in `/home/vsts/work/1/s`. #### ✅ Fixed : Move the dist folder to the correct location. ```yaml! - script: | mv $(Pipeline.Workspace)/dist $(Pipeline.Workspace)/s/dist displayName: 'Move dist to Source Directory' ```