We will use at least four environments within this guide:
- “development” per developer to build your bot,
- “build” to generate a build artifact,
- “test” to test your solution as a managed solution and
- “production” to run the bot as a managed solution.
A GitHub repository to store your solution and all your workflow files.
Please consider a governance solution like “Landing Zones for Power Platform” to create and manage your environments (Role-based Access Control [RBAC], apply policies, including data loss prevention, allowing connectors, or denying some), primarily if you work with fusion teams of pro and low-code developers. “Landing Zones for Power Platform” is Microsoft Reference architecture to govern Power Platform Deployments.
[1] We will export the solution from your build environment to a new branch of your GitHub repository with the first workflow and a manual trigger. After inspecting the changes on GitHub, we will create a pull request and merge the pull request.
[2] After merging a second workflow, we automatically build a managed solution within your build environment and import the managed solution into your test environment.
After end-to-end testing, we create a new release on GitHub, and
[3] the third workflow will automatically import the solution into your production environment.
To create a service principal account, we need to set up an App Registration in Azure Active Directory.
In the Azure portal, select “Azure Active Directory” in the left pane and select “App registrations”, and click on “New registration”.
On the “Register an application” page, enter your application’s registration information:
Upon creation of the application registration, please note and save the “Directory (tenant) ID” and the “Application (client) ID” of the application.
On the navigation panel of the Overview page, select “API permissions”.
Choose “+ Add a permission”, and in the “Microsoft APIs” tab, choose “Dynamics CRM”.
In the “Request API permissions” form, select “Delegated permissions”, check “user_impersonation”, and then choose “Add permissions”.
Back in the “Request API permissions” form, choose “PowerApps Runtime Service”, select “Delegated permissions”, check “user_impersonation”, and then choose “Add permissions” again.
For the third time in the “Request API permissions” form, choose “APIs my organization uses”. Search for “PowerApps-Advisor” using the search field and select “PowerApps-Advisor” in the results list. Select “Delegated permissions”, check “Analysis.All” rights, and then choose “Add permissions” for the last time.
Next, proceed to create a client secret. In the navigation panel, select “Certificates & secrets”:
Below “Client secrets”, select “+ New client secret”.
In the form, enter a description and select “Add”. Record the secret string; you will not be able to view the secret again once you leave the form.
To deploy solutions as part of a CI/CD pipeline, an “Application user” needs access to the environment.
An “Application user” represents an unlicensed user that is authenticated using the application registration completed in the previous steps.
With the “App registration” and the “Application User”, we can use “GitHub Actions” to manage our environments. Yes, environments as we need several to work safely and professionally. Please register this Application User in every environment you deploy (dev, staging, testing, and production).
A solution is a kind of “application container”. In this “box”, you will find everything related to your project. Within the solution, you will find all your bots, flows, connector references, and other stuff a developer produced.
Create four yml-files in the .github/workflow
directory of your repository.
please update CLIENT_ID and TENANT_ID with your id´s and exchange the value for solution_name. Or store them as a secret in GitHub and integrate the secret here like the “PowerPlatformSPN” secret.
export-branch-solution.yml
name: export-and-branch-solution
name: export-and-branch-solution
# Export solution from DEV environment
# unpack it and prepare, commit, and push a git branch with the changes
on:
workflow_dispatch:
inputs:
# Change this value
solution_name:
description: 'name of the solution to worked on from Power Platform'
required: true
default: DemoEnvironment
#Do Not change these values
solution_exported_folder:
description: 'folder name for staging the exported solution *do not change*'
required: true
default: out/exported/
solution_folder:
description: 'staging the unpacked solution folder before check-in *do not change*'
required: true
default: out/solutions/
solution_target_folder:
description: 'folder name to be created and checked in *do not change*'
required: true
default: solutions/
DEV_ENVIRONMENT_URL:
description: 'Development environment url.'
type: string
required: true
env:
#edit your values here
CLIENT_ID: 'your_client_id'
TENANT_ID: 'your_tenant_id'
jobs:
export-from-dev:
runs-on: windows-latest
# or you can say runs-on: ubuntu-latest
env:
RUNNER_DEBUG: 1
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: who-am-i action
uses: microsoft/powerplatform-actions/[email protected]
with:
environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
app-id: ${{env.CLIENT_ID}}
client-secret: ${{ secrets.PowerPlatformSPN }}
tenant-id: ${{env.TENANT_ID}}
- name: export-solution action
uses: microsoft/powerplatform-actions/[email protected]
with:
environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
app-id: ${{env.CLIENT_ID}}
client-secret: ${{ secrets.PowerPlatformSPN }}
tenant-id: ${{env.TENANT_ID}}
solution-name: ${{ github.event.inputs.solution_name }}
solution-output-file: ${{ github.event.inputs.solution_exported_folder}}/${{ github.event.inputs.solution_name }}.zip
- name: unpack-solution action
uses: microsoft/powerplatform-actions/[email protected]
with:
solution-file: ${{ github.event.inputs.solution_exported_folder}}/${{ github.event.inputs.solution_name }}.zip
solution-folder: ${{ github.event.inputs.solution_folder}}/${{ github.event.inputs.solution_name }}
solution-type: 'Unmanaged'
overwrite-files: true
- name: branch-solution, prepare it for a PullRequest
uses: microsoft/powerplatform-actions/branch-solution@v0
with:
solution-folder: ${{ github.event.inputs.solution_folder}}/${{ github.event.inputs.solution_name }}
solution-target-folder: ${{ github.event.inputs.solution_target_folder}}/${{ github.event.inputs.solution_name }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
allow-empty-commit: true
To start this manual flow, go to “Action”, select the flow, click “Run workflow” and insert your values into the fields.
reusable basic flow
release-solution-to-xxx-reusable.yml
name: release-solution-to-xxx-reusable
# Reusable workflow
# convert solution to managed (using a build PowerPlatform environment for the conversion)
# upload the solution to the GitHub artifacts and deploy to the xxx environment
on:
workflow_call:
inputs:
#Do Not change these values
#Values are set by the caller
#caller sample: release-action-call.ymnl
solution_name:
description: 'The solution name.'
type: string
default: DemoEnvironment
solution_shipping_folder:
description: 'folder name for staging the exported solution *do not change*'
type: string
default: out/ship/
solution_outbound_folder:
description: 'staging the unpacked solution folder before check-in *do not change*'
type: string
default: out/solutions/
solution_source_folder:
description: 'folder name to be created and checked in *do not change*'
type: string
default: solutions/
solution_release_folder:
description: 'folder where the released binaries are going to be hosted *do not change*'
type: string
default: out/release
BUILD_ENVIRONMENT_URL:
description: 'Build environment url.'
type: string
required: true
TARGET_ENVIRONMENT_URL:
description: 'target environment url.'
type: string
required: true
CLIENT_ID:
description: 'The client id'
type: string
required: true
TENANT_ID:
description: 'The tenant id'
type: string
required: true
secrets:
envSecret:
description: 'The secret value for authentication using SPN'
required: true
jobs:
convert-to-managed:
runs-on: windows-latest
# or you can say runs-on: ubuntu-latest
env:
RUNNER_DEBUG: 1
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Pack solution
uses: microsoft/powerplatform-actions/[email protected]
with:
solution-folder: ${{ inputs.solution_source_folder}}/${{ inputs.solution_name }}
solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
solution-type: Unmanaged
- name: Import solution as unmanaged to build env
uses: microsoft/powerplatform-actions/[email protected]
with:
environment-url: ${{inputs.BUILD_ENVIRONMENT_URL}}
app-id: ${{inputs.CLIENT_ID}}
client-secret: ${{ secrets.envSecret }}
tenant-id: ${{inputs.TENANT_ID}}
solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
force-overwrite: true
publish-changes: true
- name: Export solution as managed
uses: microsoft/powerplatform-actions/[email protected]
with:
environment-url: ${{inputs.BUILD_ENVIRONMENT_URL}}
app-id: ${{inputs.CLIENT_ID}}
client-secret: ${{ secrets.envSecret }}
tenant-id: ${{inputs.TENANT_ID}}
solution-name: ${{ inputs.solution_name }}
managed: true
solution-output-file: ${{ inputs.solution_shipping_folder}}/${{ inputs.solution_name }}.zip
- name: Upload the ready to ship solution to GH artifact store
uses: actions/upload-artifact@v2
with:
name: managedSolutions
path: ${{ inputs.solution_shipping_folder}}/${{ inputs.solution_name }}.zip
release-to-target:
needs: [ convert-to-managed ]
runs-on: windows-latest
env:
RUNNER_DEBUG: 1
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Fetch the ready to ship solution from GH artifact store
uses: actions/download-artifact@v2
with:
name: managedSolutions
path: ${{ inputs.solution_release_folder}}
- name: Import solution to target env
uses: microsoft/powerplatform-actions/[email protected]
with:
environment-url: ${{inputs.TARGET_ENVIRONMENT_URL}}
app-id: ${{inputs.CLIENT_ID}}
client-secret: ${{ secrets.envSecret }}
tenant-id: ${{inputs.TENANT_ID}}
solution-file: ${{ inputs.solution_release_folder}}/${{ inputs.solution_name }}.zip
force-overwrite: true
Create a starter flow for “deploy to test” and “deploy to production”
please update CLIENT_ID, TENANT_ID, BUILD_ENVIRONMENT_URL and TARGET_ENVIRONMENT_URL with your id´s and exchange the value for solution_name. Or store them as a secret in GitHub and integrate it here like the “PowerPlatformSPN” secret.
release-to-test.yml
name: Release action to test
# Call the reusable workflow release-solution-to-xxx-reusable.yml
# Release your solution to staging.
on:
pull_request:
types:
- closed
workflow_dispatch:
jobs:
Release-solution-staging:
uses: ./.github/workflows/release-solution-to-xxx-reusable.yml
with:
#You can specify the solution name here
solution_name: DemoEnvironment
#Update your values here
BUILD_ENVIRONMENT_URL: 'yourvalue'
TARGET_ENVIRONMENT_URL: 'yourvalue'
CLIENT_ID: 'yourvalue'
TENANT_ID: 'yourvalue'
secrets:
envSecret: ${{ secrets.PowerPlatformSPN }}
release-to-production.yml
name: Release action production
# Call the reusable workflow release-solution-to-xxx-reusable.yml
# Release your solution to staging.
on:
release:
types: [published]
workflow_dispatch:
jobs:
Release-solution-staging:
uses: ./.github/workflows/release-solution-to-xxx-reusable.yml
with:
#You can specify the solution name here
solution_name: DemoEnvironment
#Update your values here
BUILD_ENVIRONMENT_URL: 'yourvalue'
TARGET_ENVIRONMENT_URL: 'yourvalue'
CLIENT_ID: 'yourvalue'
TENANT_ID: 'yourvalue'
secrets:
envSecret: ${{ secrets.PowerPlatformSPN }}
Both actions are triggered automatically (merge or release) and can triggered manually (workflow_dispatch). For a manual run, go to “Action”, select the flow, click on “Run workflow” and insert your values into the fields
deploy-development.yml
name: deploy development environment
# Reusable workflow
# deploy solution to a development environment
on:
workflow_dispatch:
inputs:
#Do Not change these values
solution_name:
description: 'The solution name.'
type: string
default: DemoEnvironment
solution_outbound_folder:
description: 'staging the unpacked solution folder before check-in *do not change*'
type: string
default: out/solutions/
solution_source_folder:
description: 'folder name to be created and checked in *do not change*'
type: string
default: solutions/
DEV_ENVIRONMENT_URL:
description: 'Development environment url.'
type: string
required: true
CLIENT_ID:
description: 'The client id'
type: string
required: true
TENANT_ID:
description: 'The tenant id'
type: string
required: true
jobs:
convert-to-unmanaged:
runs-on: windows-latest
# or you can say runs-on: ubuntu-latest
env:
RUNNER_DEBUG: 1
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Pack solution
uses: microsoft/powerplatform-actions/[email protected]
with:
solution-folder: ${{ inputs.solution_source_folder}}/${{ inputs.solution_name }}
solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
solution-type: Unmanaged
- name: who-am-i action
uses: microsoft/powerplatform-actions/[email protected]
with:
environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
app-id: ${{inputs.CLIENT_ID}}
client-secret: ${{ secrets.PowerPlatformSPN }}
tenant-id: ${{inputs.TENANT_ID}}
- name: Import solution as unmanaged to dev env
uses: microsoft/powerplatform-actions/[email protected]
with:
environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
app-id: ${{inputs.CLIENT_ID}}
client-secret: ${{ secrets.PowerPlatformSPN }}
tenant-id: ${{inputs.TENANT_ID}}
solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
force-overwrite: true
publish-changes: true
To start this manual flow, go to “Action”, select the flow, click “Run workflow” and insert your values into the fields.
Please find sample scripts in my GitHub repository ALM-for-PVA
Landing Zones for Power Platform — a Reference Architecture
Power Platform GitHub Actions — ALM for Developers