From 5a6874704e66948d09306792e5f1315b0241417c Mon Sep 17 00:00:00 2001 From: Max Ewing Date: Fri, 28 Jan 2022 10:35:58 +0000 Subject: [PATCH] ci: provision environments for pipelines (#75) --- azure-pipelines-pull-request.yml | 15 +- azure-pipelines.yml | 25 ++- scripts/New-PowerAppFlowConnection.ps1 | 66 ++++++++ templates/build-and-test-job.yml | 211 ++++++++++++++----------- templates/build-and-test-stages.yml | 105 ++++++++++++ 5 files changed, 307 insertions(+), 115 deletions(-) create mode 100644 scripts/New-PowerAppFlowConnection.ps1 create mode 100644 templates/build-and-test-stages.yml diff --git a/azure-pipelines-pull-request.yml b/azure-pipelines-pull-request.yml index 4a342b4..28ae823 100644 --- a/azure-pipelines-pull-request.yml +++ b/azure-pipelines-pull-request.yml @@ -1,11 +1,16 @@ name: $(GITVERSION_FullSemVer) + trigger: none + pr: - master + pool: - vmImage: 'windows-latest' + vmImage: windows-latest + stages: - - stage: BuildAndTest - displayName: Build and Test - jobs: - - template: templates/build-and-test-job.yml + - template: templates/build-and-test-stages.yml + parameters: + environmentIdentifier: $[ coalesce(variables['System.PullRequest.PullRequestNumber'], variables['Build.BuildId']) ] + environmentDisplayName: Package Deployer Template - PR + environmentDomainName: pdt-pr \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e34fde0..bb984f4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,28 +1,25 @@ name: $(GITVERSION_FullSemVer) + trigger: batch: true branches: include: - master + pr: none + pool: - vmImage: 'windows-latest' -variables: -- name: GitVersion.SemVer - value: '' -- name: solution - value: '**/*.sln' -- name: buildPlatform - value: 'anycpu' -- name: buildConfiguration - value: 'Release' + vmImage: windows-latest + stages: - - stage: BuildAndTest - displayName: Build and Test - jobs: - - template: templates/build-and-test-job.yml + - template: templates/build-and-test-stages.yml + parameters: + environmentIdentifier: $(Build.BuildId) + environmentDisplayName: Package Deployer Template - CI + environmentDomainName: pdt-ci - stage: Publish displayName: Publish + dependsOn: ManualValidation jobs: - job: PublishJob displayName: Publish diff --git a/scripts/New-PowerAppFlowConnection.ps1 b/scripts/New-PowerAppFlowConnection.ps1 new file mode 100644 index 0000000..6be2ed3 --- /dev/null +++ b/scripts/New-PowerAppFlowConnection.ps1 @@ -0,0 +1,66 @@ +# +# New-PowerAppFlowConnection.ps1 +# +[CmdletBinding()] +param ( + [string] + [Parameter(Mandatory = $true)] + $Username, + [securestring] + [Parameter(Mandatory = $true)] + $Password, + [Parameter(Mandatory = $true)] + [string] + $EnvironmentName, + [Parameter(Mandatory = $true)] + [string] + $Region, + [Parameter(Mandatory = $true)] + [string] + $Connector, + [Parameter(Mandatory = $false)] + [hashtable] + $ConnectionParameters = @{}, + [string] + [parameter(Mandatory = $false)] + $DisplayName, + [string] + [Parameter(Mandatory = $false)] + $OutputVariable +) + +$ErrorActionPreference = 'Stop' + +Install-Module -Name Microsoft.PowerApps.Administration.PowerShell -Force -Scope CurrentUser -AllowClobber + +Write-Host "Authenticating as $Username." +Add-PowerAppsAccount -Username $Username -Password $Password + +if (!$ConnectionId) { + $ConnectionId = [Guid]::NewGuid().ToString("N") +} + +$body = @{ + properties = @{ + environment = @{ + name = "$EnvironmentName" + } + connectionParameters = $ConnectionParameters + displayName = $DisplayName + } +} + +Write-Host "Creating $Connector connection with ID $ConnectionId on environment $EnvironmentName with $($ConnectionParameters.connectionString)" + +$result = InvokeApi ` + -Method PUT ` + -Route "https://$Region.api.powerapps.com/providers/Microsoft.PowerApps/apis/$Connector/connections/$($ConnectionId)?api-version=2021-02-01&`$filter=environment eq '$EnvironmentName'" ` + -Body $body ` + -ThrowOnFailure + +Write-Host "Connection $($result.name) created." + +if ($OutputVariable) { + Write-Host "Setting $OutputVariable variable to $($result.name)." + Write-Host "##vso[task.setvariable variable=$OutputVariable]$($result.name)" +} \ No newline at end of file diff --git a/templates/build-and-test-job.yml b/templates/build-and-test-job.yml index 92ad967..c3ef716 100644 --- a/templates/build-and-test-job.yml +++ b/templates/build-and-test-job.yml @@ -1,97 +1,116 @@ -jobs: -- job: BuildAndTestJob - displayName: Build and Test - variables: - - name: GitVersion.SemVer - value: '' - - name: solution - value: '**/*.sln' - - name: buildPlatform - value: 'anycpu' - - name: buildConfiguration - value: 'Release' - - group: Cap Dev - CI - steps: - - - task: gitversion/setup@0 - displayName: Install GitVersion - inputs: - versionSpec: '5.x' - - - task: gitversion/execute@0 - displayName: Execute GitVersion - inputs: - useConfigFile: true - configFilePath: '$(Build.SourcesDirectory)\GitVersion.yml' - updateAssemblyInfo: false - - - pwsh: Write-Host "##vso[task.setvariable variable=SemVer;isOutput=true]$(GitVersion.SemVer)" - name: OutputSemVerTask - displayName: Output SemVer - - - task: DotNetCoreCLI@2 - displayName: Restore NuGet packages - inputs: - command: restore - projects: '**/*.csproj' - - - task: SonarCloudPrepare@1 - displayName: Prepare SonarCloud - inputs: - SonarCloud: 'SonarCloud' - organization: 'capgemini-1' - scannerMode: 'MSBuild' - projectKey: 'Capgemini_xrm-packagedeployer' - projectName: 'xrm-packagedeployer' - projectVersion: '$(GitVersion.SemVer)' - extraProperties: | - sonar.exclusions=**\*.css +parameters: + - name: environmentUrl + displayName: Environment URL + type: string + - name: environmentName + displayName: environmentName + type: string + - name: username + displayName: Username + type: string + - name: password + displayName: Password + type: string - - task: VSBuild@1 - displayName: Build solution - inputs: - solution: '$(solution)' - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - - - task: VSTest@2 - displayName: Run tests - env: - CAPGEMINI_PACKAGE_DEPLOYER_TESTS_URL: $(URL) - CAPGEMINI_PACKAGE_DEPLOYER_TESTS_USERNAME: $(User ADO Integration Username) - CAPGEMINI_PACKAGE_DEPLOYER_TESTS_PASSWORD: $(User ADO Integration Password) - inputs: - codeCoverageEnabled: true - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - testAssemblyVer2: '**\*Tests.dll' - searchFolder: tests - - - task: SonarCloudAnalyze@1 - displayName: Analyse with SonarCloud - - - task: SonarCloudPublish@1 - displayName: Publish SonarCloud results - inputs: - pollingTimeoutSec: '300' - - - task: WhiteSource Bolt@20 - displayName: Detect security and licence issues - inputs: - cwd: '$(Build.SourcesDirectory)' - - - task: DotNetCoreCLI@2 - displayName: Pack NuGet package - inputs: - command: pack - packagesToPack: src\Capgemini.PowerApps.PackageDeployerTemplate\Capgemini.PowerApps.PackageDeployerTemplate.csproj - modifyOutputPath: true - versioningScheme: byEnvVar - versionEnvVar: GitVersion.NuGetVersionV2 - includesymbols: false - buildProperties: Configuration=$(buildConfiguration) - packDirectory: $(Build.ArtifactStagingDirectory)/out - - - publish: $(Build.ArtifactStagingDirectory)/out - displayName: Publish NuGet artifact - artifact: Capgemini.PowerApps.PackageDeployerTemplate +jobs: + - job: BuildAndTestJob + displayName: Build and Test + variables: + - name: GitVersion.SemVer + value: '' + - name: solution + value: '**/*.sln' + - name: buildPlatform + value: 'anycpu' + - name: buildConfiguration + value: 'Release' + steps: + - task: gitversion/setup@0 + displayName: Install GitVersion + inputs: + versionSpec: '5.x' + - task: gitversion/execute@0 + displayName: Execute GitVersion + inputs: + useConfigFile: true + configFilePath: '$(Build.SourcesDirectory)\GitVersion.yml' + updateAssemblyInfo: false + - pwsh: Write-Host "##vso[task.setvariable variable=SemVer;isOutput=true]$(GitVersion.SemVer)" + name: OutputSemVerTask + displayName: Output SemVer + - task: DotNetCoreCLI@2 + displayName: Restore NuGet packages + inputs: + command: restore + projects: '**/*.csproj' + - task: SonarCloudPrepare@1 + displayName: Prepare SonarCloud + inputs: + SonarCloud: 'SonarCloud' + organization: 'capgemini-1' + scannerMode: 'MSBuild' + projectKey: 'Capgemini_xrm-packagedeployer' + projectName: 'xrm-packagedeployer' + projectVersion: '$(GitVersion.SemVer)' + extraProperties: | + sonar.exclusions=**\*.css + - task: VSBuild@1 + displayName: Build solution + inputs: + solution: '$(solution)' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + - task: PowerShell@2 + displayName: Create Approvals connection + inputs: + filePath: $(Build.SourcesDirectory)\scripts\New-PowerAppFlowConnection.ps1 + arguments: > + -Username ${{ parameters.username }} + -Password (ConvertTo-SecureString -String $env:CONNECTIONOWNER_PASSWORD -AsPlainText -Force) + -EnvironmentName ${{ parameters.environmentName }} + -Region unitedkingdom + -Connector shared_approvals + -ConnectionParameters @{ } + -OutputVariable "TestEnvironment.Connection.Approvals" + targetType: filePath + env: + CONNECTIONOWNER_PASSWORD: ${{ parameters.password }} + - task: VSTest@2 + displayName: Run tests + env: + CAPGEMINI_PACKAGE_DEPLOYER_TESTS_URL: ${{ parameters.environmentUrl }} + CAPGEMINI_PACKAGE_DEPLOYER_TESTS_USERNAME: ${{ parameters.username }} + CAPGEMINI_PACKAGE_DEPLOYER_TESTS_PASSWORD: ${{ parameters.password }} + PACKAGEDEPLOYER_SETTINGS_CONNREF_PDT_SHAREDAPPROVALS_D7DCB: $(TestEnvironment.Connection.Approvals) + PACKAGEDEPLOYER_SETTINGS_ENVVAR_PDT_TESTVARIABLE: Any string + PACKAGEDEPLOYER_SETTINGS_CONNBASEURL_pdt_5Fexample-20api: https://anyurl.com + inputs: + codeCoverageEnabled: true + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + testAssemblyVer2: '**\*Tests.dll' + searchFolder: tests + - task: SonarCloudAnalyze@1 + displayName: Analyse with SonarCloud + - task: SonarCloudPublish@1 + displayName: Publish SonarCloud results + inputs: + pollingTimeoutSec: '300' + - task: WhiteSource Bolt@20 + displayName: Detect security and licence issues + inputs: + cwd: '$(Build.SourcesDirectory)' + - task: DotNetCoreCLI@2 + displayName: Pack NuGet package + inputs: + command: pack + packagesToPack: src\Capgemini.PowerApps.PackageDeployerTemplate\Capgemini.PowerApps.PackageDeployerTemplate.csproj + modifyOutputPath: true + versioningScheme: byEnvVar + versionEnvVar: GitVersion.NuGetVersionV2 + includesymbols: false + buildProperties: Configuration=$(buildConfiguration) + packDirectory: $(Build.ArtifactStagingDirectory)/out + - publish: $(Build.ArtifactStagingDirectory)/out + displayName: Publish NuGet artifact + artifact: Capgemini.PowerApps.PackageDeployerTemplate diff --git a/templates/build-and-test-stages.yml b/templates/build-and-test-stages.yml new file mode 100644 index 0000000..d6f1e7d --- /dev/null +++ b/templates/build-and-test-stages.yml @@ -0,0 +1,105 @@ +parameters: + - name: environmentIdentifier + displayName: Environment identifier + type: string + - name: environmentDisplayName + displayName: Environment display name + type: string + - name: environmentDomainName + displayName: Environment domain name + type: string + +stages: + - stage: ProvisionEnvironment + displayName: Provision environment + variables: + - name: Environment.Identifier + value: ${{ parameters.environmentIdentifier }} + - name: Environment.DisplayName + value: ${{ parameters.environmentDisplayName }} $(Environment.Identifier) + - name: TestEnvironment.DomainName + value: ${{ parameters.environmentDomainName }}-$(Environment.Identifier) + - group: Dataverse users + jobs: + - job: ProvisionEnvironmentJob + displayName: Provision environment + steps: + - task: PowerPlatformToolInstaller@0 + displayName: Install Power Platform Build Tools + inputs: + DefaultVersion: true + - task: PowerPlatformCreateEnvironment@0 + displayName: Create environment + inputs: + authenticationType: PowerPlatformSPN + PowerPlatformSPN: Dataverse (placeholder) + DisplayName: $(Environment.DisplayName) + EnvironmentSku: Sandbox + LocationName: unitedkingdom + LanguageName: 1033 + CurrencyName: GBP + DomainName: $(TestEnvironment.DomainName) + - powershell: | + echo "##vso[task.setvariable variable=EnvironmentUrl;isOutput=true]$env:BUILDTOOLS_ENVIRONMENTURL" + echo "##vso[task.setvariable variable=EnvironmentName;isOutput=true]$env:BUILDTOOLS_ENVIRONMENTID" + displayName: Set output variables + name: SetEnvironmentOutputVariables + - stage: BuildAndTest + displayName: Build and Test + dependsOn: ProvisionEnvironment + variables: + - name: BuildTools.EnvironmentUrl + value: $[ stageDependencies.ProvisionEnvironment.ProvisionEnvironmentJob.outputs['SetEnvironmentOutputVariables.EnvironmentUrl'] ] + - name: BuildTools.EnvironmentId + value: $[ stageDependencies.ProvisionEnvironment.ProvisionEnvironmentJob.outputs['SetEnvironmentOutputVariables.EnvironmentName'] ] + - group: Dataverse users + jobs: + - template: build-and-test-job.yml + parameters: + environmentUrl: $(BuildTools.EnvironmentUrl) + environmentName: $(BuildTools.EnvironmentId) + username: $(DataverseUsers.AzureDevOps.Username) + password: $(DataverseUsers.AzureDevOps.Password) + - stage: ManualValidation + displayName: Manual validation + dependsOn: + - ProvisionEnvironment + - BuildAndTest + condition: and(not(canceled()), ne(dependencies.ProvisionEnvironment.outputs['ProvisionEnvironmentJob.SetEnvironmentOutputVariables.EnvironmentUrl'], '')) + variables: + BuildTools.EnvironmentUrl: $[ stageDependencies.ProvisionEnvironment.ProvisionEnvironmentJob.outputs['SetEnvironmentOutputVariables.EnvironmentUrl'] ] + jobs: + - job: ManualValidationJob + displayName: Manual validation + pool: server + timeoutInMinutes: 8640 + steps: + - task: ManualValidation@0 + displayName: Wait for manual validation + timeoutInMinutes: 7200 + inputs: + onTimeout: resume + instructions: Please perform any checks required on $(BuildTools.EnvironmentUrl). + - stage: DeleteEnvironment + displayName: Delete environment + dependsOn: + - ProvisionEnvironment + - ManualValidation + condition: ne(dependencies.ProvisionEnvironment.outputs['ProvisionEnvironmentJob.SetEnvironmentOutputVariables.EnvironmentUrl'], '') + variables: + BuildTools.EnvironmentUrl: $[ stageDependencies.ProvisionEnvironment.ProvisionEnvironmentJob.outputs['SetEnvironmentOutputVariables.EnvironmentUrl'] ] + jobs: + - job: DeleteEnvironmentJob + displayName: Delete environment + steps: + - checkout: none + - task: PowerPlatformToolInstaller@0 + displayName: Install Power Platform Build Tools + inputs: + DefaultVersion: true + - task: PowerPlatformDeleteEnvironment@0 + displayName: Delete environment + continueOnError: true + inputs: + authenticationType: PowerPlatformSPN + PowerPlatformSPN: Dataverse (placeholder) \ No newline at end of file