-
-
Save michaelmaillot/ceeca60b9c3f604b431afff7190036e7 to your computer and use it in GitHub Desktop.
Build & Deploy SPFx package in an ephemeral site before production (Azure DevOps & PnP PowerShell)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: main | |
trigger: | |
branches: | |
include: | |
- main | |
stages: | |
- template: pipelines/stages/build-deploy-spfx-pnp-powershell.yml | |
parameters: | |
include_tests: false | |
variable_group_uat: 'contoso-UAT' | |
variable_group_prd: 'contoso-PRD' | |
deploy_uat: true | |
deploy_prd: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
parameters: | |
- name: include_tests | |
type: boolean | |
default: true | |
- name: variable_group_uat | |
type: string | |
- name: variable_group_prd | |
type: string | |
- name: deploy_uat | |
type: boolean | |
default: true | |
- name: deploy_prd | |
type: boolean | |
default: true | |
stages: | |
- stage: 'build' | |
jobs: | |
- template: ../jobs/build-spfx.yml | |
parameters: | |
include_tests: false | |
- ${{ if eq(parameters.deploy_uat, true) }}: | |
- stage: 'deploy_uat' | |
displayName: Deploy on UAT | |
dependsOn: [ build ] | |
jobs: | |
- template: ../jobs/deploy-spfx-pnp-powershell.yml | |
parameters: | |
target_environment: 'UAT' | |
variable_group_name: ${{ parameters.variable_group_uat }} | |
- ${{ if eq(parameters.deploy_prd, true) }}: | |
- stage: 'deploy_prd' | |
displayName: Deploy on PRD | |
dependsOn: [ deploy_uat ] | |
variables: | |
siteToRemove: $[stageDependencies.deploy_uat.deploy.outputs['deploy.CreateSiteCopy.siteCopy']] | |
# This syntax gives us the ephemeral site URL, in order to remove it once the deployment on PRD environment is done | |
# $[stageDependencies.STAGE_NAME.JOB_NAME.VARIABLE_NAME] | |
jobs: | |
- template: ../jobs/deploy-spfx-pnp-powershell.yml | |
parameters: | |
target_environment: 'PRD' | |
variable_group_name: ${{ parameters.variable_group_prd }} | |
uat_site_to_remove: $(siteToRemove) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# No triggers here, which makes the pipeline not callable except from another pipeline | |
parameters: | |
- name: target_environment | |
type: string | |
- name: variable_group_name | |
type: string | |
- name: uat_site_to_remove | |
type: string | |
default: '' | |
jobs: | |
- deployment: deploy | |
displayName: 'Upload & deploy *.sppkg to SharePoint app catalog' | |
pool: | |
vmImage: 'ubuntu-latest' | |
environment: ${{ parameters.target_environment }} | |
variables: | |
- group: ${{ parameters.variable_group_name }} | |
# This is specific to AzDO: it allows to use variables / secrets stored in a "library" | |
# It's very useful with multi-stage pipelines | |
strategy: | |
runOnce: | |
deploy: | |
steps: | |
- download: current | |
artifact: drop | |
patterns: '**/*.sppkg' | |
- pwsh: Install-Module -Name "PnP.PowerShell" -Force | |
displayName: Installing PnP.PowerShell Module | |
- task: DownloadSecureFile@1 | |
inputs: | |
secureFile: PnP.SharePoint.AppOnly.pfx | |
displayName: 'Download authentication certificate' | |
name: AppCertificate | |
# Thanks to the AzDO pipeline files management, we can store secure files | |
# Such as a certificate file | |
- pwsh: | | |
$package = Get-ChildItem -Path $(Pipeline.Workspace)/drop -Recurse -Filter '*.sppkg' | Select Name | Select-Object -First 1 | |
Write-Host "##vso[task.setvariable variable=SpPkgFileName;isOutput=true]$($package.Name)" | |
name: GetSharePointPackage | |
displayName: Get generated *.sppkg filename | |
- ${{ if ne(parameters.target_environment, 'PRD') }}: | |
- pwsh: | | |
$securePassword = ConvertTo-SecureString "$(aad_app_password)" -AsPlainText -Force | |
Connect-PnPOnline -Url $(site_url_prd) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword | |
Get-PnPSiteTemplate -Out $(Pipeline.Workspace)/template.pnp | |
Add-PnPDataRowsToSiteTemplate -Path $(Pipeline.Workspace)/template.pnp -List "$(site_list_prd)" | |
displayName: Connecting to Production site and get site template (with list data) | |
- pwsh: | | |
$securePassword = ConvertTo-SecureString "$(aad_app_password)" -AsPlainText -Force | |
Connect-PnPOnline -Url $(site_url_prd) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword | |
$web = Get-PnPWeb | |
$siteName = "$($web.Title)-${{ parameters.target_environment }}" + [guid]::NewGuid().Guid | |
$uri = [System.Uri]"$(site_url_prd)" | |
$siteUrl = $uri.Scheme + "://" + $uri.Authority + "/sites/" + $siteName | |
$site = New-PnPSite -Type CommunicationSite -Title $siteName -Url $siteUrl -Owner "$(site_copy_owner)" -Wait | |
Write-Host "##vso[task.setvariable variable=siteCopy;isOutput=true]$site" | |
Write-Host $site | |
name: CreateSiteCopy | |
displayName: Create ${{ parameters.target_environment }} site copy | |
- pwsh: | | |
$securePassword = ConvertTo-SecureString "$(aad_app_password)" -AsPlainText -Force | |
Connect-PnPOnline -Url $(CreateSiteCopy.siteCopy) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword | |
Invoke-PnPSiteTemplate -Path $(Pipeline.Workspace)/template.pnp -ClearNavigation | |
displayName: Apply PRD site template to ${{ parameters.target_environment }} site copy | |
- pwsh: | | |
$uploadHasFailed = $false | |
try { | |
$securePassword = ConvertTo-SecureString "$(aad_app_password)" -AsPlainText -Force | |
Connect-PnPOnline -Url $(CreateSiteCopy.siteCopy) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword | |
Add-PnPSiteCollectionAppCatalog | |
$packageId = Add-PnPApp -Path "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" -Scope Site -Publish | |
} | |
catch { | |
Write-Host $_.Exception.Message -ForegroundColor Yellow | |
Write-Host "Retrying by removing and re-enabling the site collection app catalog, then the package (after 30 seconds delay)" | |
$uploadHasFailed = $true | |
} | |
finally { | |
if ($uploadHasFailed -eq $true) { | |
Remove-PnPSiteCollectionAppCatalog -Site $(CreateSiteCopy.siteCopy) | |
Add-PnPSiteCollectionAppCatalog | |
Start-Sleep -Seconds 30 | |
$packageId = Add-PnPApp -Path "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" -Scope Site -Publish -Overwrite | |
} | |
} | |
displayName: Enable Site Collection App Catalog, upload SharePoint package, deploy it and add it to the site | |
# Enabling the site collection app catalog or deploying a solution on it can fail, as the site has just been created | |
# We prevent this failure, by waiting and retrying those actions | |
- pwsh: | | |
$webPartHasFailed = $false | |
if ('$(webpart_name)' -ne '') { | |
try { | |
$securePassword = ConvertTo-SecureString "$(aad_app_password)" -AsPlainText -Force | |
Connect-PnPOnline -Url $(CreateSiteCopy.siteCopy) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword | |
Add-PnPPage -Name "TestSPFx" | |
Add-PnPPageWebPart -Page "TestSPFx" -Component "$(webpart_name)" | |
} | |
catch { | |
Write-Host $_.Exception.Message -ForegroundColor Yellow | |
Write-Host "Retrying the WebPart addition after 30 seconds delay" | |
$webPartHasFailed = $true | |
Start-Sleep -Seconds 30 | |
} | |
finally { | |
if ($webPartHasFailed -eq $true) { | |
Add-PnPPageWebPart -Page "TestSPFx" -Component "$(webpart_name)" | |
} | |
} | |
} | |
displayName: Add an article page and the WebPart (if exists) | |
# Here again, adding a page with the WebPart ont it can fail, as the site has just been created | |
# We prevent this failure, by waiting and retrying those actions | |
- ${{ if eq(parameters.target_environment, 'PRD') }}: | |
- pwsh: | | |
$securePassword = ConvertTo-SecureString "$(aad_app_password)" -AsPlainText -Force | |
Connect-PnPOnline -Url $(site_url_prd) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword | |
Add-PnPApp -Path "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" -Scope $(app_catalog_scope) -Publish -Overwrite | |
displayName: Upload & deploy SharePoint package | |
- ${{ if ne(parameters.uat_site_to_remove, '') }}: | |
- pwsh: | | |
$securePassword = ConvertTo-SecureString "$(aad_app_password)" -AsPlainText -Force | |
Connect-PnPOnline -Url $(site_url_prd) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword | |
Remove-PnPTenantSite -Url ${{ parameters.uat_site_to_remove }} -SkipRecyclebin -Force | |
displayName: Remove site copy |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Abstract
This sample provides a ready-to-use Azure DevOps pipeline that will build an SPFx package, then create an testing site, based on a template from the production site, on which the package will be deployed (in a site collection app catalog) dedicated to test out new features / bug fixes, with a testing page. Then the pipeline will be deployed on the tenant app catalog (or a production site collection app catalog), and finally remove the testing site. This sample will use PnP PowerShell in order to connect to SharePoint Online and deploy the SPFx package, using the Provisioning Engine. Below the different files description:
azure-pipelines.yml
: the main pipeline that will be triggered as the initiatorbuild-deploy-spfx-pnp-powershell.yml
: a global CI / CD template, that will call subsequentlybuild-spfx.yml
: CI pipelinedeploy-spfx-pnp-powershell.yml
: CD pipelineConfiguration
SharePoint site
For this sample, we'll need one site for production stage, this can use a tenant app catalog or a site collection one (in this case, it will have to be enabled first).
For this sample, the production site has to be a Communication one.
The app will have to be already installed on PRD site.
Solution
The SPFx solution can be ordered like this:
Environments
The environments will be necessary for approving a pipeline before being run. We'll have to add two environments:
Environments can be configured here: https://dev.azure.com/[ORGANIZATION]/[PROJECT]/_environment.
Approvers
For each environment, we can add an approver by clicking on the three dots located in the upper right of it, then "Approvals and checks" and select "Approvals".
Variable Groups
Variable groups allow to store specific values that can be called when running a pipeline. We'll have to add two variable groups:
Variable groups can be configured here: https://dev.azure.com/[ORGANIZATION]/[PROJECT]/_library?itemType=VariableGroups.
Below the variables used in the pipelines, depending on the context:
aad_app_id
aad_app_password
aad_tenant_id
site_url_prd
site_list_prd
webpart_name
app_catalog_scope
app_catalog_site_url
Secure files
Especially for AzDO, we will use the Secure files feature to store a PFX file in order to login to SharePoint in Application context. In this example, we'll use one PFX file for both testing and production environments:
PnP.SharePoint.AppOnly.pfx
.Secure files can be configured here: https://dev.azure.com/[ORGANIZATION]/[PROJECT]/_library?itemType=SecureFiles.
Stage permissions
During the pipeline run, both CI and CD parts are considered as "Stages". As the UAT and PRD deployments will have to access to environments, secure files and variable groups, we will have to give permission for those stages once.
For each of those, we will have to permit the pipeline to access to them only on the first run. This will be seen on the pipeline run page, as we'll see that the UAT / PR deployments will be pending for approving permissions. More info here.
Azure AD application
As the authentication will be done in app context (not delegated), to be sure to authenticate correctly, the AAD application will have to be configured with the following info:
PnP.SharePoint.AppOnly.pfx
one)