Skip to content

Instantly share code, notes, and snippets.

@michaelmaillot
Last active April 16, 2022 12:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelmaillot/12e44f58ffb22cef9c887616952db510 to your computer and use it in GitHub Desktop.
Save michaelmaillot/12e44f58ffb22cef9c887616952db510 to your computer and use it in GitHub Desktop.
Build & Deploy SPFx package in multiple environments (Azure DevOps & PnP PowerShell)
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
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 ]
jobs:
- template: ../jobs/deploy-spfx-pnp-powershell.yml
parameters:
target_environment: 'PRD'
variable_group_name: ${{ parameters.variable_group_prd }}
# No triggers here, which makes the pipeline not callable except from another pipeline
parameters:
- name: include_tests
type: boolean
default: true
jobs:
- job: build
pool:
vmImage: 'ubuntu-latest'
demands:
- npm
- node.js
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm
steps:
- checkout: self
- task: gittools.usegitversion.gitversion-task.UseGitVersion@5
displayName: 'GitVersion'
inputs:
versionSpec: '5.6.x'
- task: NodeTool@0
displayName: 'Use Node 14.x'
inputs:
versionSpec: 14.x
checkLatest: true
- script: npm ci
displayName: 'npm ci'
- task: Gulp@0
displayName: 'Bundle project'
inputs:
targets: bundle
arguments: '--ship'
- ${{ if eq(parameters.include_tests, true) }}:
- script: npm test
displayName: 'npm test'
- task: Gulp@0
displayName: 'Package Solution'
inputs:
targets: 'package-solution'
arguments: '--ship'
- task: CopyFiles@2
displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)'
inputs:
Contents: |
sharepoint/**/*.sppkg
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
# 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
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_uat) -ClientId $(aad_app_id) -Tenant $(aad_tenant_id) -CertificatePath "$(AppCertificate.secureFilePath)" -CertificatePassword $securePassword
Add-PnPApp -Path "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" -Scope Site -Publish -Overwrite
displayName: Upload & deploy SharePoint package
- ${{ 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
@michaelmaillot
Copy link
Author

Abstract

This sample provides a ready-to-use Azure DevOps pipeline that will build an SPFx package, then deploy it on an existing site (in a site collection app catalog) dedicated to test out new features / bug fixes. Then the pipeline will be deployed on the tenant app catalog (or a production site collection app catalog). This sample will use PnP PowerShell in order to connect to SharePoint Online and deploy the SPFx package. Below the different files description:

  • azure-pipelines.yml: the main pipeline that will be triggered as the initiator
  • build-deploy-spfx-pnp-powershell.yml: a global CI / CD template, that will call subsequently
    • build-spfx.yml: CI pipeline
    • deploy-spfx-pnp-powershell.yml: CD pipeline

Configuration

SharePoint sites

For this sample, we'll need two sites:

  • one for testing stage, for which the site collection app catalog will have to be enabled before running the pipeline
  • one 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)

The app will have to be already installed on both UAT & PRD sites.

Solution

The SPFx solution can be ordered like this:

│   .gitignore
│   .npmignore
│   .yo-rc.json
│   azure-pipelines.yml  --------> main pipeline
│   gulpfile.js
│   package-lock.json
│   package.json
│   README.md
│   tsconfig.json
│   tslint.json
│
├───.vscode
│
├───config
│
├───pipelines
│   ├───jobs
│   │       build-spfx.yml  --------> CI pipeline
│   │       deploy-spfx-pnp-powershell.yml  --------> CD pipeline
│   │
│   └───stages
│           build-deploy-spfx-pnp-powershell.yml  --------> global CI / CD template
│
├───src
│   │   index.ts
│   │
│   └───webparts
│       └───helloWorld
│           │   HelloWorldWebPart.manifest.json
│           │   HelloWorldWebPart.ts
│           │
│           ├───components
│           │       HelloWorld.module.scss
│           │       HelloWorld.module.scss.ts
│           │       HelloWorld.tsx
│           │       IHelloWorldProps.ts
│           │
│           └───loc
│                   en-us.js
│                   mystrings.d.ts
│
├───teams

Environments

The environments will be necessary for approving a pipeline before being run. We'll have to add two environments:

  • UAT (testing)
  • PRD (production)

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:

  • contoso-UAT (testing)
  • contoso-PRD (production)

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:

Name UAT PRD Secret value? Definition
aad_app_id YES YES NO The Azure AD application ID with which the authentication to SharePoint will be done
aad_app_password YES YES YES The certificate password necessary for the authentication
aad_tenant_id YES YES NO ID or domain (for example "contoso.onmicrosoft.com") of the tenant from which accounts should be able to authenticate
app_catalog_scope NO YES NO Production app catalog scope (can be "tenant" or "site collection")
site_url_uat YES NO NO SharePoint UAT site URL where the SPFx package will be deployed
app_catalog_site_url NO YES NO Production app catalog site URL (can be a tenant or a site collection one)
site_url_prd NO YES NO SharePoint PRD site URL where the SPFx package will be deployed

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:

  • Certificates & secrets
    • Add a certificate file (.pem, crt or .cer), for which we'll have the thumbprint info and encoded file or .pfx file (in that case, the PnP.SharePoint.AppOnly.pfx one)
  • API permission
    • Add SharePoint application permission "Sites.FullControl.All"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment