Power Platform custom connectors enable using your API in Power Apps or Power Automate flows. When the API which you plan to integrate with your app is a stable and finished product, then creating a custom connector is a one-time task to do. However, when the API is a part of a developed solution or is under active development then keeping the connector in sync with the API is a cumbersome task.
If you are already familiar with DevOps principles then “Continous Integration (CI)” and “Continous Deployment (CD)” terms should come to your mind. What we try to achieve here is to get the latest definition of the API after its update, and prepare the custom connector (CI). After that, push the changes to a Power Platform environment (CD).
Building the deployment pipeline
How to do this? In this article, I use Azure DevOps pipelines and Power Platform Connectors CLI (paconn) to achieve this. The process should be similar for other build automation software like Jenkins or GitHub Actions.
Create the custom connector in the Power Automate or Power Apps portal
Wait, we are talking about automation, and then I need to create something manually? Yes, you can create the connector from the scratch using CLI. However, making it in the UI is much more simple. The automation process will take care of updating the connector, and that is the most important for us.
Follow the official guide to create the custom connector.
After saving the connector, copy the ID from the URL. Look for a value similar to shared_test-5f4540e4010cf8ad28-5fb7527efc5ed37b29
in between /connections/available/custom/
and /edit/
. This value is URL encoded. Please remember to decode it before future usage.
Installing Power Platform Connectors CLI
Installation is very easy. The CLI is a Python app available in PIP.
pip install paconn
How to run it in Azure DevOps?
- script: pip install paconn
displayName: Ensure PACONN
Authentication
This is the tricky part. At the moment of writing this post, paconn
does not support userless authentication flows. So we need to help it a little by creating a simple python script that obtains access token to Azure Service Management using a user/password combination (Resource Owner Password Credential).
Currently, there is no way to get the access token as an app (Client Credentials) which is why the script uses a service account for authentication. Make sure the account has got an Environment Maker role in the target Power Platform environment.
You can use your own Azure AD app registration for the deployment by passing “Client ID” parameter to the script. If not provided the default app of CLI is used. Basicaly, this is the same as in Azure CLI 😉 If you want to use your app, make sure it has got permissions to
https://management.azure.com/user_impersonation
scope.
# authenticate-user.py
import argparse
import os
import adal
from msrestazure.azure_active_directory import AADTokenCredentials
from paconn.authentication.tokenmanager import TokenManager
from paconn.settings.settings import Settings
from paconn.common.util import get_config_dir
parser = argparse.ArgumentParser(
description='Login to Azure Management using ROPC.')
parser.add_argument('--user', action='store', type=str, required=True)
parser.add_argument('--password', action='store', type=str, required=True)
parser.add_argument('--clientid', action='store', type=str)
parser.add_argument('--tenant', action='store', type=str)
args = parser.parse_args()
os.makedirs(get_config_dir(), exist_ok=True)
# Pass nones as it is not needed here
settings = Settings(
connector_id=None,
environment=None,
api_properties=None,
api_definition=None,
icon=None,
script=None,
powerapps_url=None,
powerapps_api_version=None
)
tenant = args.tenant or settings.tenant
client_id = args.clientid or settings.client_id
auth_context = adal.AuthenticationContext(
authority=settings.authority_url + tenant,
api_version=None
)
token = auth_context.acquire_token_with_username_password(
resource=settings.resource,
username=args.user,
password=args.password,
client_id=client_id
)
credentials = AADTokenCredentials(
token=token,
client_id=client_id)
tokenmanager = TokenManager()
tokenmanager.write(credentials.token)
Of course, you have to run it in the pipeline.
- task: PythonScript@0
displayName: Login to Azure Management with ROPC
inputs:
scriptSource: 'filePath'
scriptPath: '$(Pipeline.Workspace)/drop/connector/authenticate-user.py'
arguments: '--user "$(pp_connector_user)" --password "$(pp_connector_password)" --tenant "$(az_ad_tenant_id)"'
Connector deployment
Finally, it is time to deploy the connector. I used PowerShell, but you can use any scripting language. The steps are:
- Download the connector definition from the Power Platform environment
- Fetch the latest Open API definition of your API (only ver. 2 - Swagger 2.0 is supported currently)
- Update the connector with a new API definition
- Clean up the stored credentials
# update-connector.ps1
param (
[string]$ApiDefinitionUrl, # something like "https://webappname.azurewebsites.net/swagger/v1/swagger.json"
[string]$EnvId, # Power Platform environment ID
[string]$ConnectorId # The value copied from the URL after you created the connector
)
Write-Host "1/4 Getting connector definition"
paconn.exe download -e $EnvId -c $ConnectorId
Set-Location -Path ".\$ConnectorId"
Write-Host "2/4 Updating swagger file"
# Add a few retries here, the app needs some time to warm up straight after the deployment.
Invoke-RestMethod -Method Get -Uri $ApiDefinitionUrl -OutFile "apiDefinition.swagger.json" -MaximumRetryCount 5 -TimeoutSec 30 -RetryIntervalSec 15
Write-Host "3/4 Updating connector"
paconn.exe update -s .\settings.json
Write-Host "4/4 Cleaning creadentials"
Remove-Item "$env:USERPROFILE\.paconn" -Recurse -Force
Let’s run it!
- task: PowerShell@2
displayName: Update connector
inputs:
filePath: '$(Pipeline.Workspace)/drop/connector/update-connector.ps1'
arguments: '-ApiDefinitionUrl $(az_api_swagger_url) -EnvId "$(pp_env_id)" -ConnectorId "$(pp_connector_id)"'
workingDirectory: '$(Pipeline.Workspace)/drop/connector'
Putting it all together
Now it is time to see what the final pipeline looks like.
# pipeline.yml
# Main API and custom connector CICD pipeline file
trigger:
- develop
- master
variables:
- group: pipeline-variables
stages:
- stage: build
displayName: Build Stage
variables:
- name: project_path
value: $(Build.SourcesDirectory)/api/
- name: connector_path
value: $(Build.SourcesDirectory)/connector/
- name: build_configuration
value: Release
- name: build_platform
value: Any CPU
- name: runtime
value: linux-x64
- name: vmName
value: ubuntu-latest
jobs:
- job: build_api
displayName: Build API and Connector
pool:
vmImage: '$(vmName)'
steps:
# Here are omitted tasks for building the API package
- task: CopyFiles@2
inputs:
SourceFolder: '$(connector_path)'
Contents: '**'
TargetFolder: '$(Build.ArtifactStagingDirectory)/connector'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
- deployment: deployment_api
displayName: Deploy API
pool:
vmImage: 'ubuntu-latest'
environment: env-name
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- template: api-deployment.yml
parameters:
az_subscription: $(az_subcription_test)
- deployment: deployment_connector
displayName: Deploy PA Connector
pool:
vmImage: 'windows-latest'
environment: env-name
dependsOn: deployment_api
condition: succeeded()
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- template: connector-deployment.yml
# connector-deployment.yml
steps:
- task: UsePythonVersion@0
displayName: Use Python 3
inputs:
versionSpec: '3.x'
addToPath: true
architecture: 'x64'
- script: pip install paconn
displayName: Ensure PACONN
- task: PythonScript@0
displayName: Login to Azure Management with ROPC
inputs:
scriptSource: 'filePath'
scriptPath: '$(Pipeline.Workspace)/drop/connector/authenticate-user.py'
arguments: '--user "$(pp_connector_user)" --password "$(pp_connector_password)" --tenant "$(az_ad_tenant_id)"'
- task: PowerShell@2
displayName: Update connector
inputs:
filePath: '$(Pipeline.Workspace)/drop/connector/update-connector.ps1'
arguments: '-ApiDefinitionUrl $(az_api_swagger_url) -EnvId "$(pp_env_id)" -ConnectorId "$(pp_connector_id)"'
workingDirectory: '$(Pipeline.Workspace)/drop/connector'
Summary
Automating a custom connector deployment allows a development team to work more efficiently. Every new API endpoint is ready to use in your Power App or Power Automate just a few minutes later after pushed changes. There is a list of key things to remember:
- Make sure the service account has got an Environment Maker role in the target Power Platform environment
- Store the credentials securely e.g., variables group in Azure DevOps and make them “secret” type.
- You can use the Azure Pipelines Teams connector to notify the team about a new connector version.
- To make the changes available in a canvas app you need to remove and add again the connectorW