November Happy Hour will be moved to Thursday December 5th.

Azure DevOps build pipeline for CMS 12

Vote:
 

I'm trying to set up a build pipeline (using yaml) for CMS 12 (.NET 5) in Azure DevOps and need some assistance. Does anyone have a working example or a guide for doing this? There are plenty of guides for older CMS versions (.NET Framework) but I'm not able to find any that covers the .NET 5 version.

#266582
Nov 11, 2021 15:07
Vote:
 

You're using the Deployment API correct? As there's a requirement now to do so for the new infrastructure. I should imagine it should just be the same process as before.

FYI there's extensions in Azure DevOps by EpiNova that can help make it easier https://www.epinova.se/en/folg-med/blog/2020/epinova-dxp-deployment-part-1-introduction/ 

#266618
Nov 12, 2021 10:16
Vote:
 

Following on from what Scott added and with the help of Epinova, I put togeather the following for a CICD pipeline, utilises DirectDeploy to Integration and than slot swaps for pre-prod and prod 

# Pipeline to deploy to Integration DXP environment using Azure App Service Deploy task
# -----------------------------

# Set the pipeline build number format
name: $(Build.DefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)

trigger:
  branches:
    include:
      - master
  paths:
    include:
      - src/Content

pool:
  vmImage: 'windows-2019' 

variables:
- name: buildConfiguration
  value: 'Release'
- name: workingDirectory
  value: '$(Build.SourcesDirectory)/src/Content'
- name: sourcePath
  value: '$(Agent.TempDirectory)/SitePackageContent'
- name: artifact.Name
  value: "Optimizely CMS DXP Package"
- name: archiveName
  value: 'optimizely.cms.app.$(Build.BuildId).nupkg'
- group: Optimizely-common-vars

stages:
- stage: CI
  jobs:
  - job: CI
    displayName: 'Build & package'
    steps:    
    - script: dotnet restore ./ProjectName.sln
      displayName: Restore
      workingDirectory: $(workingDirectory)
    
    - script: dotnet build ./ProjectName.sln --configuration $(buildConfiguration)
      displayName: Build
      workingDirectory: $(workingDirectory)
      
    - script: dotnet publish ./ProjectName.Cms --configuration $(buildConfiguration) --output $(sourcePath)
      displayName: Publish Optimizely CMS Artifact
      workingDirectory: $(workingDirectory)

    - task: ArchiveFiles@2
      displayName: "Archive Files"
      inputs:
        rootFolderOrFile: '$(sourcePath)'
        includeRootFolder: false
        archiveType: 'zip'
        archiveFile: '$(Build.ArtifactStagingDirectory)/$(archiveName)'
        replaceExistingArchive: true

    - publish: '$(Build.ArtifactStagingDirectory)/$(archiveName)'
      artifact: $(artifact.Name)
      
    - task: PowerShell@2
      displayName: 'Upload package to DXP'
      env:
        # Map secret variable
        API_SECRET: $(ApiSecret)
      inputs:
        targetType: 'inline'
        script: |
          $env:PSModulePath = "C:\Modules\azurerm_6.13.1;" + $env:PSModulePath
          if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
              Install-Module EpiCloud -Scope CurrentUser -Force
          }
          Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
          
          $packagePath = "$(Build.ArtifactStagingDirectory)/$(archiveName)"
          
          $saslink = Get-EpiDeploymentPackageLocation
          Add-EpiDeploymentPackage -SasUrl $saslink -Path $packagePath

- stage: Optimizely_CMS_Integration 
  jobs:
    # Add a Deployment job. 
    # Artifacts are automatically downloaded in deployment jobs. To disable use -download: none
  - deployment: DeployApp
    displayName: 'Deploy to Integration'
    variables: 
      # Explicitly set the EnvironmentName variable in multi-stage YAML to ensure environment config transform is applied automatically
      Release.EnvironmentName: 'Optimizely_CMS_Integration'
    # Creates an environment if it doesn't exist
    environment: 'Optimizely_CMS_Integration'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: none

          - task: PowerShell@2
            displayName: 'Deploy to Integration'
            env:
              API_SECRET: $(ApiSecret)
            inputs:
              targetType: 'inline'
              script: |
                if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
                    Install-Module EpiCloud -Scope CurrentUser -Force
                }
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                $packageName = "$(archiveName)"
                
                echo "Starting deployment using package $packageName" 
                $params = @{
                  DeploymentPackage = $packageName
                  TargetEnvironment = 'Integration'
                  UseMaintenancePage = $false
                  DirectDeploy = $true
                  Wait = $true
                }
                $deploy = Start-EpiDeployment @params
                
                echo "DeploymentId - $($deploy.id)" 
                echo "##vso[task.setvariable variable=deploymentId;]$($deploy.id)"
- stage: Optimizely_CMS_PreProduction 
  jobs:
    # Add a Deployment job. 
    # Artifacts are automatically downloaded in deployment jobs. To disable use -download: none
  - deployment: DeployApp
    displayName: 'Deploy to Preproduction' 
    # Creates an environment if it doesn’t exist
    environment: 'Optimizely_CMS_PreProduction'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: none

          - task: PowerShell@2
            displayName: 'Deploy to slot'
            env:
              API_SECRET: $(ApiSecret)
            inputs:
              targetType: 'inline'
              script: |
                if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
                    Install-Module EpiCloud -Scope CurrentUser -Force
                }
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                $packageName = "$(archiveName)"
                
                echo "Starting deployment using package $packageName" 
                $params = @{
                  DeploymentPackage = $packageName
                  TargetEnvironment = 'Preproduction'
                  UseMaintenancePage = $(UseMaintenancePage)
                  Wait = $true
                }
                $deploy = Start-EpiDeployment @params
                
                echo "DeploymentId - $($deploy.id)" 
                echo "##vso[task.setvariable variable=deploymentId;]$($deploy.id)"
          - task: PowerShell@2
            displayName: 'Validate slot'
            env:
              API_SECRET: $(ApiSecret)
            inputs:
              targetType: 'inline'
              script: |
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                
                $deploy = Get-EpiDeployment -Id $env:DeploymentId
                if ($deploy.status -eq 'AwaitingVerification'){
                  $response = Invoke-WebRequest -Uri $deploy.validationLinks[0]
                  if ($response.StatusCode -ne 200){
                    echo "Smoke test of slot url failed"
                    exit 1
                  }
                }
          - task: PowerShell@2
            displayName: 'Reset'
            env:
              API_SECRET: $(ApiSecret)
            condition: failed()
            inputs:
              targetType: 'inline'
              script: |
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                
                Reset-EpiDeployment -Id $env:DeploymentId -Wait
          - task: PowerShell@2
            displayName: 'Complete deployment'
            env:
              API_SECRET: $(ApiSecret)
            inputs:
              targetType: 'inline'
              script: |
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                
                Complete-EpiDeployment -Id $env:DeploymentId -Wait
          
          # Run performance tests

- stage: Optimizely_CMS_Production 
  jobs:
    # Add a Deployment job. 
    # Artifacts are automatically downloaded in deployment jobs. To disable use -download: none
  - deployment: DeployApp
    displayName: 'Deploy to Production'  
    # Creates an environment if it doesn’t exist
    environment: 'Optimizely_CMS_Production'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: none

          - task: PowerShell@2
            displayName: 'Deploy to slot'
            env:
              API_SECRET: $(ApiSecret)
            inputs:
              targetType: 'inline'
              script: |
                if (-not (Get-Module -Name EpiCloud -ListAvailable)) {
                    Install-Module EpiCloud -Scope CurrentUser -Force
                }
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                $packageName = "$(archiveName)"
                
                echo "Starting deployment using package $packageName" 
                $params = @{
                  DeploymentPackage = $packageName
                  TargetEnvironment = 'Production'
                  UseMaintenancePage = $(UseMaintenancePage)
                  Wait = $true
                }
                $deploy = Start-EpiDeployment @params
                
                echo "DeploymentId - $($deploy.id)" 
                echo "##vso[task.setvariable variable=deploymentId;]$($deploy.id)"
          - task: PowerShell@2
            displayName: 'Validate slot'
            env:
              API_SECRET: $(ApiSecret)
            inputs:
              targetType: 'inline'
              script: |
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                
                $deploy = Get-EpiDeployment -Id $env:DeploymentId
                if ($deploy.status -eq 'AwaitingVerification'){
                  $response = Invoke-WebRequest -Uri $deploy.validationLinks[0]
                  if ($response.StatusCode -ne 200){
                    echo "Smoke test of slot url failed"
                    exit 1
                  }
                }            
          - task: PowerShell@2
            displayName: 'Reset'
            env:
              API_SECRET: $(ApiSecret)
            condition: failed()
            inputs:
              targetType: 'inline'
              script: |
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                
                Reset-EpiDeployment -Id $env:DeploymentId -Wait

          - task: PowerShell@2
            displayName: 'Complete deployment'
            env:
              API_SECRET: $(ApiSecret)
            inputs:
              targetType: 'inline'
              script: |
                Connect-EpiCloud -ClientKey $(ApiKey) -ClientSecret $env:API_SECRET -ProjectId $(ProjectId)
                
                Complete-EpiDeployment -Id $env:DeploymentId -Wait

And the following variables 

Project Structure 

#267186
Edited, Nov 22, 2021 18:08
Scott Reed - Nov 22, 2021 18:10
Nice, this would be an excellent topic for a Blog post so people can find it in the future
Minesh Shah (Netcel) - Nov 22, 2021 18:19
Thanks Scott, although felt I would be covering a lot of what others have already posted i.e. Epinova and Ron Rangaiya, concepts are identical just takes a bit of trial and error to get working.
markus.bergendahl - Nov 24, 2021 14:30
Thank you Minesh for a great example! The pipeline works fine except that the folder structure of my published code package is wrong. Could you please post a screenshot of the folder structure for your project?
Minesh Shah (Netcel) - Nov 24, 2021 14:39
Apologies marks I made a change to the Paths/Include section to exclude the Content folder and I did not do this every where else, It should have been

paths:
include:
- src/Content

Edited the original now
Vote:
 

Epinova have an updated blog post for CMS 12.
https://www.epinova.no/folg-med/blogg/2021/epinova-dxp-deployment-extension-for-azure-devops--version-2/

#267232
Nov 23, 2021 9:34
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.