Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more

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.