From 558caa593b2fc32722994634639ad2cc0b066e8d Mon Sep 17 00:00:00 2001 From: Pim Simons <32359437+pim-simons@users.noreply.github.com> Date: Wed, 10 May 2023 10:44:19 +0200 Subject: [PATCH] feat: script for resubmitting failed Logic App instances (#385) Co-authored-by: Pim Simons --- .../powershell/azure-logic-apps.md | 33 ++++ .../Arcus.Scripting.LogicApps.psd1 | Bin 8092 -> 8168 bytes .../Arcus.Scripting.LogicApps.psm1 | 41 ++++- .../Arcus.Scripting.LogicApps.pssproj | 1 + .../Scripts/Resubmit-FailedAzLogicAppRuns.ps1 | 50 ++++++ .../Arcus.Scripting.LogicApps.tests.ps1 | 111 ++++++++++++ .../Arcus.Scripting.LogicApps.tests.ps1 | 169 +++++++++++++++++- 7 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 src/Arcus.Scripting.LogicApps/Scripts/Resubmit-FailedAzLogicAppRuns.ps1 diff --git a/docs/preview/03-Features/powershell/azure-logic-apps.md b/docs/preview/03-Features/powershell/azure-logic-apps.md index 85570483..cdf7344d 100644 --- a/docs/preview/03-Features/powershell/azure-logic-apps.md +++ b/docs/preview/03-Features/powershell/azure-logic-apps.md @@ -33,6 +33,39 @@ PS> Cancel-AzLogicAppRuns ` # Successfully cancelled all running instances for the Azure Logic App 'rcv-shopping-order-sftp' in resource group 'rg-common-dev' ``` +## Resubmitting failed instances for an Azure Logic App + +Use this script to re-run a failed Azure Logic App run. + +| Parameter | Mandatory | Description | +| ------------------- | --------- | ---------------------------------------------------------------------------------------------------------- | +| `ResourceGroupName` | yes | The resource group containing the Azure Logic App. | +| `LogicAppName` | yes | The name of the Azure Logic App to be disabled. | +| `StartTime` | yes | The start time in UTC for retrieving the failed instances. | +| `EndTime` | no | The end time in UTC for retrieving the failed instances, if not supplied it will use the current datetime. | + +**Example** + +Taking an example in which a specific Azure Logic App (`"rcv-shopping-order-sftp"`) needs to have all its failed runs resubmitted from 2023-05-01 00:00:00. + +```powershell +PS> Resubmit-FailedAzLogicAppRuns ` +-ResourceGroupName "rg-common-dev" ` +-LogicAppName "rcv-shopping-order-sftp" ` +-StartTime "2023-05-01 00:00:00" +# Successfully resubmitted all failed instances for the Azure Logic App 'rcv-shopping-order-sftp' in resource group 'rg-common-dev' from '2023-05-01 00:00:00' +``` + +Taking an example in which a specific Azure Logic App (`"rcv-shopping-order-sftp"`) needs to have all its failed runs resubmitted from 2023-05-01 00:00:00 until 2023-05-01 10:00:00. + +```powershell +PS> Resubmit-FailedAzLogicAppRuns ` +-ResourceGroupName "rg-common-dev" ` +-LogicAppName "rcv-shopping-order-sftp" ` +-StartTime "2023-05-01 00:00:00" ` +-EndTime "2023-05-01 10:00:00" +# Successfully resubmitted all failed instances for the Azure Logic App 'rcv-shopping-order-sftp' in resource group 'rg-common-dev' from '2023-05-01 00:00:00' and until '2023-05-01 10:00:00' +``` ## Disable an Azure Logic App diff --git a/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.psd1 b/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.psd1 index 911cd4df54d486891fb9d434a110a533a0c29126..95a27c1238a96751ab0618aba7809e8eee22f70f 100644 GIT binary patch delta 42 vcmbPZ|H6L54*}UAhE#@PhEj$khFpeBh7tx{1~-O8AS(wbp0fF*z-2xF5&sMV delta 20 ccmaE1KgWK<4}r;5f+~|=2?}kl63pWR0Ar*GR{#J2 diff --git a/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.psm1 b/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.psm1 index e88f6564..cdbf30ed 100644 --- a/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.psm1 +++ b/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.psm1 @@ -1,9 +1,9 @@ <# .Synopsis - Cancel all running instances of a specific Logic App. + Cancel all running instances of a specific Azure Logic App. .Description - Cancel all running instances of a specific Logic App. + Cancel all running instances of a specific Azure Logic App. .Parameter ResourceGroupName The resource group containing the Azure Logic App. @@ -23,6 +23,43 @@ function Cancel-AzLogicAppRuns { Export-ModuleMember -Function Cancel-AzLogicAppRuns +<# + .Synopsis + Resubmit all failed instances of a specific Azure Logic App. + + .Description + Resubmit all failed instances of a specific Azure Logic App within a specified start and end time. + + .Parameter ResourceGroupName + The resource group containing the Azure Logic App. + + .Parameter LogicAppName + The name of the Azure Logic App. + + .Parameter StartTime + The start time of the failed instances of the Azure Logic App. + + .Parameter EndTime + The end time of the failed instances of the Azure Logic App. + +#> +function Resubmit-FailedAzLogicAppRuns { + param( + [Parameter(Mandatory = $true)][string] $ResourceGroupName = $(throw "Name of the resource group is required"), + [Parameter(Mandatory = $true)][string] $LogicAppName = $(throw "Name of the logic app is required"), + [Parameter(Mandatory = $true)][datetime] $StartTime = $(throw "Start time is required"), + [Parameter(Mandatory = $false)][datetime] $EndTime + ) + + if ($EndTime) { + . $PSScriptRoot\Scripts\Resubmit-FailedAzLogicAppRuns.ps1 -ResourceGroupName $ResourceGroupName -LogicAppName $LogicAppName -StartTime $StartTime -EndTime $EndTime + } else { + . $PSScriptRoot\Scripts\Resubmit-FailedAzLogicAppRuns.ps1 -ResourceGroupName $ResourceGroupName -LogicAppName $LogicAppName -StartTime $StartTime + } +} + +Export-ModuleMember -Function Resubmit-FailedAzLogicAppRuns + <# .Synopsis Disable a specific Logic App. diff --git a/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.pssproj b/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.pssproj index 1724d462..65d2e31e 100644 --- a/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.pssproj +++ b/src/Arcus.Scripting.LogicApps/Arcus.Scripting.LogicApps.pssproj @@ -31,6 +31,7 @@ + diff --git a/src/Arcus.Scripting.LogicApps/Scripts/Resubmit-FailedAzLogicAppRuns.ps1 b/src/Arcus.Scripting.LogicApps/Scripts/Resubmit-FailedAzLogicAppRuns.ps1 new file mode 100644 index 00000000..5118abe1 --- /dev/null +++ b/src/Arcus.Scripting.LogicApps/Scripts/Resubmit-FailedAzLogicAppRuns.ps1 @@ -0,0 +1,50 @@ +param( + [Parameter(Mandatory = $true)][string] $ResourceGroupName = $(throw "Name of the resource group is required"), + [Parameter(Mandatory = $true)][string] $LogicAppName = $(throw "Name of the logic app is required"), + [Parameter(Mandatory = $true)][datetime] $StartTime = $(throw "Start time is required"), + [Parameter(Mandatory = $false)][datetime] $EndTime +) + +try{ + if ($EndTime) { + $runs = Get-AzLogicAppRunHistory -ResourceGroupName $ResourceGroupName -Name $LogicAppName | + Where-Object {$_.Status -eq 'Failed' -and $_.StartTime -ge $StartTime -and $_.EndTime -le $EndTime} + } else { + $runs = Get-AzLogicAppRunHistory -ResourceGroupName $ResourceGroupName -Name $LogicAppName | + Where-Object {$_.Status -eq 'Failed' -and $_.StartTime -ge $StartTime} + } + + $token = Get-AzCachedAccessToken + $accessToken = $token.AccessToken + $subscriptionId = $token.SubscriptionId + + foreach ($run in $runs) { + $triggerName = $run.Trigger.Name + $runId = $run.Name + $resubmitUrl = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Logic/workflows/$LogicAppName/triggers/$triggerName/histories/$runId/resubmit?api-version=2016-06-01" + + $params = @{ + Method = 'Post' + Headers = @{ + 'authorization'="Bearer $accessToken" + } + URI = $resubmitUrl + } + + $web = Invoke-WebRequest @params -ErrorAction Stop + + Write-Verbose "Resubmitted run $runId for the Azure Logic App '$LogicAppName' in resource group '$ResourceGroupName'" + } + + if ($EndTime) { + Write-Host "Successfully resubmitted all failed instances for the Azure Logic App '$LogicAppName' in resource group '$ResourceGroupName' from '$StartTime' and until '$EndTime'" -ForegroundColor Green + } else { + Write-Host "Successfully resubmitted all failed instances for the Azure Logic App '$LogicAppName' in resource group '$ResourceGroupName' from '$StartTime'" -ForegroundColor Green + } +} catch { + if ($EndTime) { + throw "Failed to resubmit all failed instances for the Azure Logic App '$LogicAppName' in resource group '$ResourceGroupName' from '$StartTime' and until '$EndTime'. Details: $($_.Exception.Message)" + } else { + throw "Failed to resubmit all failed instances for the Azure Logic App '$LogicAppName' in resource group '$ResourceGroupName' from '$StartTime'. Details: $($_.Exception.Message)" + } +} \ No newline at end of file diff --git a/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.LogicApps.tests.ps1 b/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.LogicApps.tests.ps1 index c05c9011..b7868410 100644 --- a/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.LogicApps.tests.ps1 +++ b/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.LogicApps.tests.ps1 @@ -227,5 +227,116 @@ InModuleScope Arcus.Scripting.LogicApps { } } } + Context "Resubmit Failed Logic Apps runs" { + It "Resubmit all failed instances for a Logic App"{ + # Arrange + $resourceGroupName = $config.Arcus.ResourceGroupName + $logicAppName = Create-AzLogicAppName + $workflowDefinition = '{ + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "actions": { + "Terminate": { + "inputs": { + "runStatus": "Failed" + }, + "runAfter": {}, + "type": "Terminate" + } + }, + "outputs": {}, + "parameters": {}, + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Minute", + "interval": 1 + }, + "type": "recurrence" + } + }, + "contentVersion": "1.0.0.0" + }' + + $startTime = [datetime]::Now.ToUniversalTime() + + New-AzLogicApp ` + -ResourceGroupName $resourceGroupName ` + -Location westeurope ` + -Name $logicAppName ` + -Definition $workflowDefinition ` + -State Enabled + + Start-Sleep -Seconds 5 + + try { + # Act + Resubmit-FailedAzLogicAppRuns -ResourceGroupName $resourceGroupName -LogicAppName $logicAppName -StartTime $startTime + + # Assert + $runs = Get-AzLogicAppRunHistory -ResourceGroupName $resourceGroupName -Name $logicAppName | + Where-Object {$_.StartTime -ge $startTime} | measure + + $runs.Count | Should -BeGreaterThan 0 + + } finally { + Remove-AzLogicApp -ResourceGroupName $resourceGroupName -Name $logicAppName -Force + } + } + It "Resubmit all failed instances for a Logic App with specifying an EndTime"{ + # Arrange + $resourceGroupName = $config.Arcus.ResourceGroupName + $logicAppName = Create-AzLogicAppName + $workflowDefinition = '{ + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "actions": { + "Terminate": { + "inputs": { + "runStatus": "Failed" + }, + "runAfter": {}, + "type": "Terminate" + } + }, + "outputs": {}, + "parameters": {}, + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Minute", + "interval": 1 + }, + "type": "recurrence" + } + }, + "contentVersion": "1.0.0.0" + }' + + $startTime = [datetime]::Now.ToUniversalTime() + $endTime = [datetime]::Now.AddDays(1).ToUniversalTime() + + New-AzLogicApp ` + -ResourceGroupName $resourceGroupName ` + -Location westeurope ` + -Name $logicAppName ` + -Definition $workflowDefinition ` + -State Enabled + + Start-Sleep -Seconds 5 + + try { + # Act + Resubmit-FailedAzLogicAppRuns -ResourceGroupName $resourceGroupName -LogicAppName $logicAppName -StartTime $startTime -EndTime $endTime + + # Assert + $runs = Get-AzLogicAppRunHistory -ResourceGroupName $resourceGroupName -Name $logicAppName | + Where-Object {$_.StartTime -ge $startTime} | measure + + $runs.Count | Should -BeGreaterThan 0 + + } finally { + Remove-AzLogicApp -ResourceGroupName $resourceGroupName -Name $logicAppName -Force + } + } + } } } \ No newline at end of file diff --git a/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.LogicApps.tests.ps1 b/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.LogicApps.tests.ps1 index ad5eae49..040b5f3f 100644 --- a/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.LogicApps.tests.ps1 +++ b/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.LogicApps.tests.ps1 @@ -286,7 +286,7 @@ InModuleScope Arcus.Scripting.LogicApps { } } Context "Cancel Logic Apps runs" { - It "Cancelling all runs from Logic App history should succeed" { + It "Cancelling all runs from Logic App history should succeed" { # Arrange $resourceGroupName = "codit-arcus-scripting" $logicAppName = "arc-dev-we-rcv-unknown-http" @@ -311,7 +311,7 @@ InModuleScope Arcus.Scripting.LogicApps { Assert-MockCalled Get-AzLogicAppRunHistory -Scope It -Times 1 Assert-MockCalled Stop-AzLogicAppRun -Scope It -Times 1 } - It "Cancelling all runs should fail when retrieving Logic App history fails" { + It "Cancelling all runs should fail when retrieving Logic App history fails" { # Arrange $resourceGroupName = "codit-arcus-scripting" $logicAppName = "arc-dev-we-rcv-unknown-http" @@ -332,5 +332,170 @@ InModuleScope Arcus.Scripting.LogicApps { Assert-MockCalled Stop-AzLogicAppRun -Scope It -Times 0 } } + Context "Resubmitting failed Logic Apps runs" { + It "Resubmitting a single failed run from Logic App history should succeed" { + # Arrange + $resourceGroupName = "codit-arcus-scripting" + $logicAppName = "arc-dev-we-rcv-unknown-http" + $startTime = '2023-01-01 00:00:00' + + Mock Get-AzCachedAccessToken -MockWith { + return @{ + SubscriptionId = "123456" + AccessToken = "accessToken" + } + } + + Mock Get-AzLogicAppRunHistory -MockWith { + return @{ + Name = "test" + Status = "Failed" + StartTime = "2023-01-01 01:00:00" + } + } + + Mock Invoke-WebRequest -MockWith { + return $null + } + + # Act + { Resubmit-FailedAzLogicAppRuns -ResourceGroupName $resourceGroupName -LogicAppName $logicAppName -StartTime $startTime } | + Should -Not -Throw + + # Assert + Assert-VerifiableMock + Assert-MockCalled Get-AzCachedAccessToken -Times 1 + Assert-MockCalled Get-AzLogicAppRunHistory -Scope It -Times 1 + Assert-MockCalled Invoke-WebRequest -Scope It -Times 1 + } + It "Resubmitting a single failed run from Logic App history with specifying an EndTime should succeed" { + # Arrange + $resourceGroupName = "codit-arcus-scripting" + $logicAppName = "arc-dev-we-rcv-unknown-http" + $startTime = '2023-01-01 00:00:00' + $endTime = '2023-02-01 00:00:00' + + Mock Get-AzCachedAccessToken -MockWith { + return @{ + SubscriptionId = "123456" + AccessToken = "accessToken" + } + } + + Mock Get-AzLogicAppRunHistory -MockWith { + return @{ + Name = "test" + Status = "Failed" + StartTime = "2023-01-01 01:00:00" + } + } + + Mock Invoke-WebRequest -MockWith { + return $null + } + + # Act + { Resubmit-FailedAzLogicAppRuns -ResourceGroupName $resourceGroupName -LogicAppName $logicAppName -StartTime $startTime -EndTime $endTime } | + Should -Not -Throw + + # Assert + Assert-VerifiableMock + Assert-MockCalled Get-AzCachedAccessToken -Times 1 + Assert-MockCalled Get-AzLogicAppRunHistory -Scope It -Times 1 + Assert-MockCalled Invoke-WebRequest -Scope It -Times 1 + } + It "Resubmitting multiple failed runs from Logic App history should succeed" { + # Arrange + $resourceGroupName = "codit-arcus-scripting" + $logicAppName = "arc-dev-we-rcv-unknown-http" + $startTime = '2023-01-01 00:00:00' + + Mock Get-AzCachedAccessToken -MockWith { + return @{ + SubscriptionId = "123456" + AccessToken = "accessToken" + } + } + + + $logicAppRunHistory = @([pscustomobject]@{Name="Test1";Status="Failed";StartTime="2023-01-01 01:00:00"}, + [pscustomobject]@{Name="Test2";Status="Failed";StartTime="2023-01-01 01:00:00"}) + + Mock Get-AzLogicAppRunHistory -MockWith { + return $logicAppRunHistory + } + + Mock Invoke-WebRequest -MockWith { + return $null + } + + # Act + { Resubmit-FailedAzLogicAppRuns -ResourceGroupName $resourceGroupName -LogicAppName $logicAppName -StartTime $startTime } | + Should -Not -Throw + + # Assert + Assert-VerifiableMock + Assert-MockCalled Get-AzCachedAccessToken -Times 1 + Assert-MockCalled Get-AzLogicAppRunHistory -Scope It -Times 1 + Assert-MockCalled Invoke-WebRequest -Scope It -Times 2 + } + It "Resubmitting multiple failed runs from Logic App history with specifying an EndTime should succeed" { + # Arrange + $resourceGroupName = "codit-arcus-scripting" + $logicAppName = "arc-dev-we-rcv-unknown-http" + $startTime = '2023-01-01 00:00:00' + $endTime = '2023-02-01 00:00:00' + + Mock Get-AzCachedAccessToken -MockWith { + return @{ + SubscriptionId = "123456" + AccessToken = "accessToken" + } + } + + + $logicAppRunHistory = @([pscustomobject]@{Name="Test1";Status="Failed";StartTime="2023-01-01 01:00:00"}, + [pscustomobject]@{Name="Test2";Status="Failed";StartTime="2023-01-01 01:00:00"}) + + Mock Get-AzLogicAppRunHistory -MockWith { + return $logicAppRunHistory + } + + Mock Invoke-WebRequest -MockWith { + return $null + } + + # Act + { Resubmit-FailedAzLogicAppRuns -ResourceGroupName $resourceGroupName -LogicAppName $logicAppName -StartTime $startTime -EndTime $endTime } | + Should -Not -Throw + + # Assert + Assert-VerifiableMock + Assert-MockCalled Get-AzCachedAccessToken -Times 1 + Assert-MockCalled Get-AzLogicAppRunHistory -Scope It -Times 1 + Assert-MockCalled Invoke-WebRequest -Scope It -Times 2 + } + It "Resubmitting failed runs should fail when retrieving Logic App history fails" { + # Arrange + $resourceGroupName = "codit-arcus-scripting" + $logicAppName = "arc-dev-we-rcv-unknown-http" + $startTime = '01/01/2023 00:00:00' + + Mock Get-AzLogicAppRunHistory { throw 'some error' } + + Mock Invoke-WebRequest -MockWith { + return $null + } + + # Act + { Resubmit-FailedAzLogicAppRuns -ResourceGroupName $resourceGroupName -LogicAppName $logicAppName -StartTime $startTime } | + Should -Throw -ExpectedMessage "Failed to resubmit all failed instances for the Azure Logic App '$LogicAppName' in resource group '$ResourceGroupName' from '$startTime'. Details: some error" + + # Assert + Assert-VerifiableMock + Assert-MockCalled Get-AzLogicAppRunHistory -Scope It -Times 1 + Assert-MockCalled Invoke-WebRequest -Scope It -Times 0 + } + } } } \ No newline at end of file