ACDC Craftsman

It’s EVIDIosa, not Leviosaaaa thoughts on ALM and the way of work.

Claims the badge ACDC Craftsman.🚀

Workflow – Azure DevOps Boards

Using boards to keep track of badges and tasks. For better collaboration, progress and overview.

Scrum

Given the very short length of the project, there has been a need for tight collaborations and check-ins. In a way we have practised a super compressed variation of Scrum, with several daily standups each day to secure progress. In a project like this, it is particularly important to avoid banging your head against the wall for too long time on one task. Regular meetings has made sure that the team has been able to maintain a best possible progress at all times, while also being a platform for contributing with new ideas.

Naming conventions

Tables (Entities)

General

  • Change tracking should be enabled by default for all “business” entities where history might be needed.
    • This is because change tracking is often indispensable for troubleshooting.
    • Change tracking can be disabled for specific fields if necessary, e.g., when automation frequently changes irrelevant fields.

Forms

  • Never modify the standard form. Clone it (save as) and hide the original form from users.
    • Standard forms can be updated by the vendor, which may overwrite changes or disrupt updates.
    • Having the original form for comparison is also useful for troubleshooting.
  • This does not apply to custom tables.
  • Ensure fields like status, status reason, created by/on, modified by/on, and owner are visible on the form.
    • These are useful for both administrators and end-users.

Option Sets

Views

When Should I Use Configuration, Business Rules, Processes, Flows, JavaScript, Plugins, or Other Tools?

  • It is important to choose the right tool for the job.
  • Considering administration, maintenance, and documentation, we prioritize tools in the following order:
    • Standard functionality/configuration
      • Many things can be solved with built-in functionality, e.g., setting a field to read-only doesn’t require a Flow. 😉
    • Business Rules
    • Processes/Workflows
      • Can run synchronously.
    • JavaScript
    • Flows
      • Suitable for querying Dataverse data.
    • Plugins

Solutions

  • Unmanaged in development:
    • We have not yet decided on a solution model but will likely use a base package initially and then move to feature-based solutions.
  • Managed in test and production.

Deployment

  • All changes are deployed via pipelines in DevOps.
  • NO MANUAL STEPS/ADJUSTMENTS IN TEST OR PRODUCTION.
  • All data referenced by automation (e.g., Flow) must be scripted and inserted programmatically (preferably via pipeline) to ensure GUID consistency across environments.

Application Lifecycle Management

Why ALM?

Application Lifecycle Management (ALM) enables structured development, testing, and deployment processes, ensuring quality and reducing risks.

  • It is a consistant way of deploying features
  • You get equal environments
  • Better collaboration
  • History
  • Common way of working
  • The whole team is always up to date with changes in developmen

Overall solution

A diagram showing the overall deployment cycle for our CRM Solution.

Solution Strategy

Using a single solution strategy. All ready changes is added to the solution. Fewer dependencies and less complexity is preferable when working within such a time span, and allows for smoother collaboration. The solution is not too complex still, so it makes sense to gather all components in a single solution. As mentioned earlier, it should be considered over time to move to feature-based solutions.

Service Principles

Service Principals are used for between Azure DevOps each environment to ensure industry standard connection to Dataverse without exposing credentials in the code. These are the names of our Service Connections.

  • hogverse-dev
  • hogverse-validation
  • hogverse-test
  • hogverse-prod

In honor of the great ALM Wizard 👉Benedik Bergmann👈the Service Connections are configured with the new feature of Workload Identity federation for ADO Connection. This eliminates the need for managing and renewing secrets, reducing the risk of pipeline failures due to expired credentials. Credentials are not exposed in code or stored in repositories.

Setup of Workload Identity federation for ADO Connection

  1. App Registration: Registering app registrations in Microsoft Entra ID.
  2. Dataverse: Adding the app registration as an application user in our target Dataverse environment, assigning it System Administrator role.
  3. ADO Service Connection: Creating a service connection in Azure DevOps, linking it to the Dataverse instance using Workload Identity Federation.
  4. Adding Federated Credentials: Configuring the app registration to recognize the ADO service connection by setting up federated credentials with the correct issuer and subject identifier.

Entra ID:

Environments

  • DEV The Room of Requirement
    The development environment for Hogverse.
  • Validation – The Chamber of Truth
    The validation environement to check changes before merging to our main branch.
  • TEST: The Restricted Section
    The UAT test environment for Hogverse.
  • PROD: The Great Hall
    The production environment for Hogverse.

Pipelines

Our ALM solution for deploying changes in Power Platform and CRM is the following:
image.png

  • Export
    The Export Pipeline is retrieving the changes from the “DEV The Room of Requirement” environment and creates a pull request.
  • Build
    The Build Pipeline packs the changes in the pull request branch and deployes it to “Validation – The Chamber of Truth” to see if something breaks before mergin it to our main branch. When this completes successfully it creates a Release and can be deployed with the Release Pipeline.
  • Release
    The Release Pipeline deployes the changes to “TEST: The Restricted Section” and “PROD: The Great Hall” after a user has in the team has approved the release, by using Approval and checks in Azure DevOps Pipelines.

Approval and checks for “TEST: The Restricted Section”

Approval and checks for “PROD: The Great Hall”
image.png

Repository Settings:

Settings for the Build Service User.
image.png

Setting and requirements for merging changes to our main branch that goes to production.
image.png

Azure DevOps Pipelines for CRM Solution

Export Pipeline

The Export Pipeline is retrieving the changes from the “DEV The Room of Requirement” environment and creates a pull request. Below is the yml code for export pipeline.

trigger: none

parameters:
  - name: SolutionName
    displayName: Solution to export
    type: string
    default: HogverseBasis
  - name: TypeOfTask
    displayName: "Select task type"
    type: string
    default: "feature"
    values:
      - feature
      - bug
  - name: WorkItems
    displayName: Work Item ID needs to be attached to Automated Pull Request. Multiple work items are space separated.
    type: string
    default:
  - name: Description
    displayName: Pullrequest description
    type: string
    default: "This Pull request is generated automatically through build"

# Define branch name dynamically based on build number and solution name
variables:
  - name: BranchName
    value: "${{ parameters.TypeOfTask }}/${{ parameters.SolutionName }}-$(Build.BuildNumber)"

pool:
  vmImage: "windows-latest"

steps:
  - checkout: self
    persistCredentials: true
  - task: PowerPlatformToolInstaller@2
    displayName: "Power Platform Tool Installer"

  # Create New Branch and Commit Changes
  - script: |
      echo checkout souce branch
      echo git config user.email $(Build.RequestedForEmail)
      echo git config user.name $(Build.RequestedFor)
      git config user.email "$(Build.RequestedForEmail)"
      git config user.name "$(Build.RequestedFor)"
      git fetch origin
      git checkout main
      git checkout -b "${{ variables.BranchName }}" main
      git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push origin "${{ variables.BranchName }}"
      echo git checkout -b "${{ variables.BranchName }}"
      git fetch origin
      git checkout ${{ variables.BranchName }}
    displayName: "Checkout GIT Automated CRM Build Branch"

  - task: PowerPlatformPublishCustomizations@2
    displayName: Publish Customizations
    inputs:
      authenticationType: "PowerPlatformSPN"
      PowerPlatformSPN: "hogverse-dev"
      AsyncOperation: true
      MaxAsyncWaitTime: "60"
  - task: PowerPlatformSetSolutionVersion@2
    displayName: "PowerApps Set Solution Version - ${{ parameters.SolutionName }}"
    inputs:
      authenticationType: "PowerPlatformSPN"
      PowerPlatformSPN: "hogverse-dev"
      SolutionName: "${{ parameters.SolutionName }}"
      SolutionVersionNumber: "$(Build.BuildNumber)"
  - task: PowerPlatformExportSolution@2
    displayName: Export ${{ parameters.SolutionName }} - Unmanaged
    inputs:
      authenticationType: "PowerPlatformSPN"
      PowerPlatformSPN: "hogverse-dev"
      SolutionName: "${{ parameters.SolutionName }}"
      SolutionOutputFile: '$(Build.ArtifactStagingDirectory)\\${{ parameters.SolutionName }}.zip'
      AsyncOperation: true
      MaxAsyncWaitTime: "60"
  - task: PowerPlatformExportSolution@2
    displayName: Export Solution - Managed
    inputs:
      authenticationType: "PowerPlatformSPN"
      PowerPlatformSPN: "hogverse-dev"
      SolutionName: "${{ parameters.SolutionName }}"
      SolutionOutputFile: '$(Build.ArtifactStagingDirectory)\\${{ parameters.SolutionName }}_managed.zip'
      Managed: true
      AsyncOperation: true
      MaxAsyncWaitTime: "60"
  - task: PowerPlatformUnpackSolution@2
    displayName: Unpack unmanaged solution ${{ parameters.SolutionName }}
    inputs:
      SolutionInputFile: '$(Build.ArtifactStagingDirectory)\\${{ parameters.SolutionName }}.zip'
      SolutionTargetFolder: '$(build.sourcesdirectory)\PowerPlatform\Solutions\${{ parameters.SolutionName }}'
      SolutionType: "Both"

  - script: |
      echo git push "${{ variables.BranchName }}"
      git push origin ${{ variables.BranchName }}
      echo git add -all
      git add --all
      echo git commit -m "Solutions committed by build number $(Build.BuildNumber)"
      git commit -m "Solutions committed by build number $(Build.BuildNumber)"
      echo push code to ${{ variables.BranchName }}
      git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push origin ${{ variables.BranchName }}
    displayName: "Commit CRM solutions to Automated CRM Build Branch"

  # Install Azure DevOps extension
  - script: az --version
    displayName: "Show Azure CLI version"

  # Install Azure DevOps Extension
  - script: az extension add -n azure-devops
    displayName: "Install Azure DevOps Extension"

  # Login to Azure DevOps Extension
  - script: echo $(System.AccessToken) | az devops login
    env:
      AZURE_DEVOPS_CLI_PAT: $(System.AccessToken)
    displayName: "Login Azure DevOps Extension"

  # Configure Azure DevOps Extension
  - script: az devops configure --defaults organization=https://dev.azure.com/hogverse project="Platform9¾Hub" --use-git-aliases true
    displayName: "Set default Azure DevOps organization and project"

  - script: |
      az repos pr create --repository "CRM.Solutions" --title "Automated Pull Request for solution HogverseBasis from branch ${{ variables.BranchName }}" --auto-complete false --bypass-policy false --description "${{parameters.Description}}" --detect --source-branch "${{ variables.BranchName }}" --target-branch "main" --work-items ${{parameters.WorkItems}}
    displayName: "Create automated Pull request for merging CRM solutions to master build list and PRs"

Using work items to track the changes for each pull request and releases.

Build Pipeline

The yml code for the Build Pipeline.

trigger:
  branches:
    include:
      - main # Trigger build pipeline on changes to main
pr:
  branches:
    include:
      - "*" # Trigger on all PR branches

pool:
  vmImage: "windows-latest"

steps:
  - script: |
      echo "Validating pull request from source branch: $(System.PullRequest.SourceBranch)"
      echo "Target branch: $(System.PullRequest.TargetBranch)"
    displayName: "Validate Pull Request Source Branch"
  - task: PowerPlatformToolInstaller@2
    displayName: "Power Platform Tool Installer "

  - task: PowerPlatformPackSolution@2
    inputs:
      SolutionSourceFolder: '$(build.sourcesdirectory)\PowerPlatform\Solutions\HogverseBasis'
      SolutionOutputFile: '$(Build.ArtifactStagingDirectory)\Solutions\HogverseBasis.zip'
      SolutionType: "Managed"

  - task: PublishPipelineArtifact@1
    displayName: Publish Artifacts
    inputs:
      targetPath: "$(Build.ArtifactStagingDirectory)"
      artifact: "drop"
      publishLocation: "pipeline"

  - task: PowerPlatformImportSolution@2
    displayName: "Power Platform Import HogverseBasis to Validation"
    inputs:
      authenticationType: PowerPlatformSPN
      PowerPlatformSPN: "hogverse-validation"
      SolutionInputFile: '$(Build.ArtifactStagingDirectory)\Solutions\HogverseBasis.zip'
      StageAndUpgrade: false
      ActivatePlugins: false
      SkipLowerVersion: true

Release Pipeline

The code for the release pipeline.

trigger: none
pr: none

resources:
  pipelines:
    - pipeline: Build # Reference to the build pipeline
      source: Build # Name of the build pipeline to trigger from
      trigger:
        branches:
          include:
            - main

stages:
  - stage: DeployTest
    displayName: "Deploy to Test"
    jobs:
      - deployment: deployTest # Use deployment job for environment reference
        environment: "The Great Hall - Test" # Reference the 'The Great Hall - Test' environment
        pool:
          vmImage: "windows-latest"
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - download: Build # pipeline resource identifier.
                  artifact: drop
                - task: PowerPlatformToolInstaller@2
                  displayName: "Power Platform Tool Installer"
                - task: PowerPlatformImportSolution@2
                  inputs:
                    authenticationType: "PowerPlatformSPN"
                    PowerPlatformSPN: "hogverse-test"
                    SolutionInputFile: '$(Pipeline.Workspace)\Build\drop\Solutions\HogverseBasis.zip'
                    AsyncOperation: true
                    MaxAsyncWaitTime: "60"
                - task: PowerPlatformPublishCustomizations@2
                  inputs:
                    authenticationType: "PowerPlatformSPN"
                    PowerPlatformSPN: "hogverse-test"
                    AsyncOperation: true
                    MaxAsyncWaitTime: "60"
  - stage: DeployProd
    displayName: "Deploy to Production"
    dependsOn: DeployTest # Depends on successful deployment to Test
    condition: succeeded()
    jobs:
      - deployment: deployProd # Use deployment job for environment reference
        environment: "The Restricted Section - Prod" # Reference the 'The Restricted Section - Prod' environment
        pool:
          vmImage: "windows-latest"
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - download: Build # pipeline resource identifier.
                  artifact: drop
                - task: PowerPlatformToolInstaller@2
                  displayName: "Power Platform Tool Installer"
                - task: PowerPlatformImportSolution@2
                  inputs:
                    authenticationType: "PowerPlatformSPN"
                    PowerPlatformSPN: "hogverse-prod"
                    SolutionInputFile: '$(Pipeline.Workspace)\Build\drop\Solutions\FE360Basis.zip'
                    AsyncOperation: true
                    MaxAsyncWaitTime: "60"
                - task: PowerPlatformPublishCustomizations@2
                  inputs:
                    authenticationType: "PowerPlatformSPN"
                    PowerPlatformSPN: "hogverse-prod"
                    AsyncOperation: true
                    MaxAsyncWaitTime: "60"

Issues

We had this problem during the event and unfortunately we did get to run hour pipelines the way we wanted.

And we tried a work around but that ended up with… Self Hosted Agent.

Looks good all the way in the installation aaaaand in the final step was blocked by admin rights at the company…. You need admin rights…

ALM for Fabric

We have implemented deployment pipelines for deploying changes between workspaces so that the reports can be tested and verified before going into production.

Hogverse Deployment Pipelines for deploying items between workspaces.

ALM for Svelte and Front end

The Svelte project is for now hosted in a private Github repository shared between the developers. Each developer creates their own branch for each new feature added. When a feature is ready to commit, a pull request is created and approved from others on the team. On approval, the branches are merged and the feature branch is normally deleted to ensure a clean project at all times.

With more time on our hands, we would have preferred to import the repository to Azure DevOps and created pipelines for Dev, Validation, Test and Prod as for the CRM solution.

Bicep honourable mention and hot tip

The Azure resources used in the project has mostly been created on the fly as part of experimentation, but we would most definitely have created Bicep files for deployment of them for each environment as well. Microsoft MVP 👉Jan Vidar Elven👈 have created a super useful public repository with templates for deploying resources on his Github account: https://github.com/JanVidarElven/workshop-get-started-with-bicep

Leave a Reply