Skip to content

Commit c5c4a75

Browse files
pranemsPrashant Rane (ALLYIS INC)Copilot
authored
Support single authentication method selection and validation metadata (#171)
* Support single authentication method selection for SCIM onboarding Adds an explicit choice between OAuth client credentials and Federated identity for each SCIM onboarding validation run. The onboarding agent confirms the selected method and its connection parameters, and the Logic App runs only the corresponding authentication test. Requires the selected test to pass, preserves existing semantic validation requirements, and includes the selected method in final results. Direct SCIM bearer usage and separately configured Entra provisioning credentials remain unchanged. Updates shared parameters, authentication result aggregation, onboarding instructions, setup documentation, and the validation overview. Adds commented local regression tests with execution documentation and checks version consistency against VERSION rather than a hard-coded release. Bumps the Standard Logic App version to 14.0. Validation: all four local regression tests pass from both the worktree root and the tests directory. An OAuth - selected run, completed with overall success, 20 successes, no failures or warnings, and five skips: four explicitly excluded Group tests and unselected federation. And successful added test successfully exercised the FederatedIdentity-Selected case.. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Include bundle version and validation timing in SCIM results Use VERSION as the single source for the deployed LogicAppVersion. Populate the runtime parameter during PowerShell and agent-led deployment, leave the source template version blank, and include the deployed version in the Orchestrator's native final results. Capture validation start and completion timestamps once in existing Orchestrator actions. Include their UTC values, Unix epoch seconds and elapsed whole seconds in final results. These are validation milestones, not the exact Azure run-history start and end timestamps. Validate version preparation and deployed read-back, reject parameter-skip deployments that could leave stale version metadata, and update setup, agent and test documentation. Add offline deployment and timing regression coverage without changing authentication or provisioning behavior. Validation: six local regression tests pass. Agent parameter-generation, JSON and whitespace checks pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Prashant Rane (ALLYIS INC) <v-prasrane@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8bdf02e commit c5c4a75

11 files changed

Lines changed: 627 additions & 81 deletions

‎Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Deploy-LogicAppWorkflows.ps1‎

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
.DESCRIPTION
66
This script reads *_Workflow.json files from a specified directory and creates/updates
77
workflows in an Azure Logic App Standard resource using the Azure REST API (via az CLI).
8-
It also optionally deploys a parameters.json file for workflow parameters.
8+
It also deploys parameters.json, deriving LogicAppVersion from VERSION in WorkflowsPath.
9+
The source parameters file is not modified. VERSION must come from the same source bundle.
910
1011
The script performs pre-deployment validation of all files before making any API calls,
1112
ensuring no partial deployments occur if any file is invalid.
@@ -26,6 +27,10 @@
2627
(Optional) Path to a parameters JSON file to deploy as the app-level parameters.json.
2728
If not specified, the script will look for *_Parameters.json in the WorkflowsPath.
2829
30+
.PARAMETER SkipParameters
31+
Retained for command-line compatibility, but rejected for versioned bundles.
32+
Deploy parameters with workflows so final results cannot report a stale version.
33+
2934
.EXAMPLE
3035
.\Deploy-LogicAppWorkflows.ps1 -SubscriptionId "xxxx" -ResourceGroup "myRG" -LogicAppName "myLogicApp"
3136
@@ -34,7 +39,7 @@
3439
3540
.EXAMPLE
3641
.\Deploy-LogicAppWorkflows.ps1 -SubscriptionId "xxxx" -ResourceGroup "myRG" -LogicAppName "myLogicApp" -SkipParameters
37-
Deploys only workflow files without updating the parameters.json.
42+
Rejected for versioned bundles: parameters must be deployed with the workflow version.
3843
3944
.NOTES
4045
Prerequisites:
@@ -268,7 +273,8 @@ function Deploy-VfsFile {
268273
[string]$RG,
269274
[string]$AppName,
270275
[string]$RemotePath,
271-
[string]$LocalFilePath
276+
[string]$LocalFilePath,
277+
[string]$Content
272278
)
273279

274280
# ARM-proxied VFS endpoint — routes through management.azure.com instead of SCM directly
@@ -284,7 +290,7 @@ function Deploy-VfsFile {
284290
}
285291

286292
# Read the raw file content — preserves exact bytes including Logic Apps expressions
287-
$bodyContent = Get-Content -Path $LocalFilePath -Raw -Encoding UTF8
293+
$bodyContent = if ($PSBoundParameters.ContainsKey('Content')) { $Content } else { Get-Content -Path $LocalFilePath -Raw -Encoding UTF8 }
288294

289295
# Use Invoke-RestMethod to send raw content without JSON re-serialization
290296
$headers = @{
@@ -295,6 +301,13 @@ function Deploy-VfsFile {
295301

296302
$result = Invoke-RestMethod -Method PUT -Uri $vfsUri -Headers $headers -Body $bodyContent -ContentType "application/json"
297303

304+
if ($RemotePath -eq "parameters.json") {
305+
$readback = Invoke-RestMethod -Method GET -Uri $vfsUri -Headers $headers
306+
if ($readback.LogicAppVersion.value -cne $script:BundleVersion) {
307+
throw "Deployed LogicAppVersion does not match VERSION. Do not start the workflows."
308+
}
309+
}
310+
298311
return $true
299312
}
300313
catch {
@@ -351,6 +364,23 @@ function Deploy-Workflow {
351364
}
352365
}
353366

367+
function Get-VersionedParametersContent {
368+
param(
369+
[string]$ParametersFilePath,
370+
[string]$BundleVersion
371+
)
372+
373+
$parameters = Get-Content -LiteralPath $ParametersFilePath -Raw -Encoding UTF8 | ConvertFrom-Json
374+
if ($null -eq $parameters -or $parameters.LogicAppVersion.type -cne 'String') {
375+
throw "Parameters must define LogicAppVersion with type String."
376+
}
377+
if ([string]::IsNullOrWhiteSpace($BundleVersion)) {
378+
throw "VERSION must contain a nonempty bundle version."
379+
}
380+
$parameters.LogicAppVersion | Add-Member -NotePropertyName value -NotePropertyValue $BundleVersion.Trim() -Force
381+
return $parameters | ConvertTo-Json -Depth 100
382+
}
383+
354384
function Deploy-Parameters {
355385
<#
356386
.SYNOPSIS
@@ -366,7 +396,7 @@ function Deploy-Parameters {
366396
Write-LogMessage "Deploying parameters file: $ParametersFilePath" -Level Info
367397

368398
try {
369-
$paramsContent = Get-Content -Path $ParametersFilePath -Raw -Encoding UTF8
399+
$paramsContent = Get-VersionedParametersContent -ParametersFilePath $ParametersFilePath -BundleVersion $script:BundleVersion
370400
$paramsObj = $paramsContent | ConvertFrom-Json
371401

372402
# Validate it's a valid JSON object
@@ -382,9 +412,9 @@ function Deploy-Parameters {
382412

383413
try {
384414
# Upload parameters.json to root via ARM VFS
385-
Deploy-VfsFile -SubId $SubId -RG $RG -AppName $AppName `
415+
$null = Deploy-VfsFile -SubId $SubId -RG $RG -AppName $AppName `
386416
-RemotePath "parameters.json" `
387-
-LocalFilePath $ParametersFilePath
417+
-Content $paramsContent
388418

389419
Write-LogMessage " Parameters file deployed successfully." -Level Success
390420
return $true
@@ -412,6 +442,23 @@ if (-not $WorkflowsPath) {
412442
exit 1
413443
}
414444

445+
try {
446+
if ($SkipParameters) {
447+
throw "-SkipParameters is not supported for versioned bundles. Deploy parameters with the workflows to avoid stale version metadata."
448+
}
449+
if ($ParametersFile -and -not (Test-Path -LiteralPath $ParametersFile -PathType Leaf)) {
450+
throw "Parameters file does not exist: $ParametersFile"
451+
}
452+
$script:BundleVersion = (Get-Content -LiteralPath (Join-Path $WorkflowsPath 'VERSION') -Raw -Encoding UTF8).Trim()
453+
if ([string]::IsNullOrWhiteSpace($script:BundleVersion)) {
454+
throw "VERSION must contain a nonempty bundle version."
455+
}
456+
}
457+
catch {
458+
Write-LogMessage "Version preparation failed: $_" -Level Error
459+
exit 1
460+
}
461+
415462
Write-LogMessage "Configuration:" -Level Info
416463
Write-Host " Subscription ID : $SubscriptionId"
417464
Write-Host " Resource Group : $ResourceGroup"
@@ -482,6 +529,12 @@ if ($SkipParameters) {
482529
}
483530
elseif ($ParametersFile -and (Test-Path $ParametersFile)) {
484531
$pErrors = Test-ParametersFile -FilePath $ParametersFile -FileName (Split-Path $ParametersFile -Leaf)
532+
try {
533+
$null = Get-VersionedParametersContent -ParametersFilePath $ParametersFile -BundleVersion $script:BundleVersion
534+
}
535+
catch {
536+
$pErrors += "Version preparation failed: $_"
537+
}
485538
if ($pErrors.Count -gt 0) {
486539
$validationFailed = $true
487540
foreach ($err in $pErrors) {
@@ -494,8 +547,18 @@ elseif ($ParametersFile -and (Test-Path $ParametersFile)) {
494547
}
495548
else {
496549
$autoParamFiles = Get-ParametersFiles -Path $WorkflowsPath
550+
if ($autoParamFiles.Count -eq 0) {
551+
Write-LogMessage "A parameters file is required to deploy the bundle version." -Level Error
552+
$validationFailed = $true
553+
}
497554
foreach ($pf in $autoParamFiles) {
498555
$pErrors = Test-ParametersFile -FilePath $pf.FullName -FileName $pf.Name
556+
try {
557+
$null = Get-VersionedParametersContent -ParametersFilePath $pf.FullName -BundleVersion $script:BundleVersion
558+
}
559+
catch {
560+
$pErrors += "Version preparation failed: $_"
561+
}
499562
if ($pErrors.Count -gt 0) {
500563
$validationFailed = $true
501564
foreach ($err in $pErrors) {
@@ -535,12 +598,20 @@ else {
535598
foreach ($pf in $paramFiles) {
536599
Write-LogMessage " Found: $($pf.Name)" -Level Info
537600
$paramsDeployed = Deploy-Parameters -SubId $SubscriptionId -RG $ResourceGroup -AppName $LogicAppName -ParametersFilePath $pf.FullName
601+
if (-not $paramsDeployed) {
602+
Write-LogMessage "Parameters deployment or version read-back failed. No workflows will be uploaded." -Level Error
603+
exit 1
604+
}
538605
}
539606
}
540607
else {
541608
Write-LogMessage "Step 6: No parameters file found. Skipping parameters deployment." -Level Info
542609
}
543610
}
611+
if (-not $paramsDeployed) {
612+
Write-LogMessage "Parameters deployment or version read-back failed. No workflows will be uploaded." -Level Error
613+
exit 1
614+
}
544615
Write-Host ""
545616

546617
# --- Step 7: Deploy workflows ---

‎Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{
2+
"selectedAuthenticationMethod": {
3+
"type": "String",
4+
"value": "",
5+
"metadata": {
6+
"description": "ISV-selected authentication test for this run: OAuth2ClientCredentials or FederatedIdentity. Direct semantic tests still use scimBearerToken."
7+
}
8+
},
29
"EnabledTests": {
310
"allowedValues": [
411
"All",
@@ -36,10 +43,10 @@
3643
},
3744
"LogicAppVersion": {
3845
"metadata": {
39-
"description": "Version of the Logic App for tracking and deployment purposes"
46+
"description": "Populated during deployment from VERSION in the same source bundle."
4047
},
4148
"type": "String",
42-
"value": "10.0"
49+
"value": ""
4350
},
4451
"attributeTransforms": {
4552
"metadata": {

‎Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"workflowId": "@workflow().id",
2424
"workflowName": "@workflow().name",
2525
"workflowRunName": "@workflow().run.name",
26+
"validationStartedAtUtc": "@utcNow()",
2627
"workflowIdSplit": "@split(workflow().id, '/')",
2728
"workflowIdSplitLength": "@length(split(workflow().id, '/'))"
2829
}
@@ -504,9 +505,10 @@
504505
"GroupTests_Workflow": "@outputs('Compose_GroupTests_RunLink')",
505506
"SCIMTests_Workflow": "@outputs('Compose_SCIMTests_RunLink')"
506507
},
507-
"overallLogicAppResult": "@if(or(not(equals(coalesce(body('Get_UserTests_Output_Content')?['CreateUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_UserTests_Output_Content')?['UpdateUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_UserTests_Output_Content')?['DisableUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isManagerAttributeSupported'],false),not(equals(coalesce(body('Get_UserTests_Output_Content')?['ManagerTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_UserTests_Output_Content')?['RestoreUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMNullUpdateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['CreateGroupTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['UpdateGroupTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['AddMemberTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['RemoveMemberTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SchemaDiscoverabilityTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserCreateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupCreateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserUpdateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupUpdateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserPaginationTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_UserTests_Output_Content')?['PODUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),equals(actions('Call_Initialization_Workflow')?['status'],'Failed'),equals(coalesce(body('Poll_UserTests_Status')?['properties']?['status'],''),'Failed'),equals(coalesce(body('Poll_GroupTests_Status')?['properties']?['status'],''),'Failed'),or(equals(coalesce(body('Get_SCIMTests_Output_Content')?['authenticationModelResult'],'FAILED'),'FAILED'),and(coalesce(body('Call_Initialization_Workflow')?['isManagerAttributeSupported'],false),contains(string(coalesce(body('Call_Initialization_Workflow')?['targetDirectoryUserAttributes'],json('[]'))),'manager'),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))))),'Failed',if(equals(coalesce(body('Poll_SCIMTests_Status')?['properties']?['status'],''),'Failed'),'Failed','Succeeded'))",
508+
"overallLogicAppResult": "@if(or(not(equals(coalesce(body('Get_UserTests_Output_Content')?['CreateUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_UserTests_Output_Content')?['UpdateUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_UserTests_Output_Content')?['DisableUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isManagerAttributeSupported'],false),not(equals(coalesce(body('Get_UserTests_Output_Content')?['ManagerTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_UserTests_Output_Content')?['RestoreUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMNullUpdateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['CreateGroupTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['UpdateGroupTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['AddMemberTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['RemoveMemberTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SchemaDiscoverabilityTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserCreateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupCreateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserUpdateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupUpdateTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserPaginationTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_UserTests_Output_Content')?['PODUserTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false),not(equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))),equals(actions('Call_Initialization_Workflow')?['status'],'Failed'),equals(coalesce(body('Poll_UserTests_Status')?['properties']?['status'],''),'Failed'),equals(coalesce(body('Poll_GroupTests_Status')?['properties']?['status'],''),'Failed'),or(not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['authenticationModelResult'],'FAILED'),'PASSED')),and(coalesce(body('Call_Initialization_Workflow')?['isManagerAttributeSupported'],false),contains(string(coalesce(body('Call_Initialization_Workflow')?['targetDirectoryUserAttributes'],json('[]'))),'manager'),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['overallResult'],'SKIPPED'),'PASSED'))))),'Failed',if(equals(coalesce(body('Poll_SCIMTests_Status')?['properties']?['status'],''),'Failed'),'Failed','Succeeded'))",
508509
"optionalSuccessTestsResult": "@if(or(not(equals(coalesce(body('Get_UserTests_Output_Content')?['DeleteUserTestOutputs']?['overallResult'], 'SKIPPED'), 'PASSED')), and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'], false), not(equals(coalesce(body('Get_GroupTests_Output_Content')?['DeleteGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'PASSED'))), and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'], false), not(equals(coalesce(body('Get_GroupTests_Output_Content')?['RestoreGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'PASSED'))), and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'], false), not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupPaginationTestOutputs']?['overallResult'], 'SKIPPED'), 'PASSED')))), 'WARNING', 'PASSED')",
509-
"authenticationModelResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['authenticationModelResult'], 'FAILED')"
510+
"authenticationModelResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['authenticationModelResult'], 'FAILED')",
511+
"validationCompletedAtUtc": "@utcNow()"
510512
}
511513
},
512514
"Final_TestResults": {
@@ -751,7 +753,14 @@
751753
"SCIMTests_Workflow": "@outputs('Compose_SCIMTests_RunLink')"
752754
},
753755
"authenticationModelResult": "@outputs('Evaluate_Test_Results')?['authenticationModelResult']",
754-
"overallResult": "@outputs('Evaluate_Test_Results')?['overallLogicAppResult']"
756+
"overallResult": "@outputs('Evaluate_Test_Results')?['overallLogicAppResult']",
757+
"selectedAuthenticationMethod": "@parameters('selectedAuthenticationMethod')",
758+
"LogicAppVersion": "@parameters('LogicAppVersion')",
759+
"validationStartedAtUtc": "@outputs('Compose_Debug_WorkflowObject')?['validationStartedAtUtc']",
760+
"validationStartedAtEpochSeconds": "@div(sub(ticks(outputs('Compose_Debug_WorkflowObject')?['validationStartedAtUtc']), ticks('1970-01-01T00:00:00Z')), 10000000)",
761+
"validationCompletedAtUtc": "@outputs('Evaluate_Test_Results')?['validationCompletedAtUtc']",
762+
"validationCompletedAtEpochSeconds": "@div(sub(ticks(outputs('Evaluate_Test_Results')?['validationCompletedAtUtc']), ticks('1970-01-01T00:00:00Z')), 10000000)",
763+
"validationDurationSeconds": "@div(sub(ticks(outputs('Evaluate_Test_Results')?['validationCompletedAtUtc']), ticks(outputs('Compose_Debug_WorkflowObject')?['validationStartedAtUtc'])), 10000000)"
755764
}
756765
},
757766
"Terminate_With_Test_Status": {

0 commit comments

Comments
 (0)