Framework/BugLog/BugMetaInfoProvider.ps1
Set-StrictMode -Version Latest class BugMetaInfoProvider { hidden static [PSObject] $BuildSTDetails; hidden static [PSObject] $ReleaseSTDetails; hidden static [PSObject] $ServiceDetails; hidden [InvocationInfo] $InvocationContext hidden [PSObject] $ControlSettingsBugLog hidden static [bool] $CheckBuildSTFileOnServer = $true; hidden static [bool] $CheckReleaseSTFileOnServer = $true; hidden static [bool] $CheckServiceTreeFileOnServer = $true; BugMetaInfoProvider() { } hidden static Initialize() { [BugMetaInfoProvider]::CheckBuildSTFileOnServer = $true; [BugMetaInfoProvider]::CheckReleaseSTFileOnServer = $true; [BugMetaInfoProvider]::CheckServiceTreeFileOnServer = $true; } hidden [string] GetAssignee([SVTEventContext[]] $ControlResult, [InvocationInfo] $InvocationContext, $controlSettingsBugLog) { $this.ControlSettingsBugLog = $controlSettingsBugLog; #flag to check if pluggable bug logging interface (service tree) $isBugLogCustomFlow = $false; if ([Helpers]::CheckMember($this.ControlSettingsBugLog, "BugAssigneeAndPathCustomFlow", $null)) { $isBugLogCustomFlow = $this.ControlSettingsBugLog.BugAssigneeAndPathCustomFlow; } if ($isBugLogCustomFlow) { $ResourceType = $ControlResult.ResourceContext.ResourceTypeName $ResourceName = $ControlResult.ResourceContext.ResourceName $organizationName = $ControlResult.SubscriptionContext.SubscriptionName; $this.InvocationContext = $InvocationContext; switch -regex ($ResourceType) { #get the last run svc pipeline based on pipeline get assignee, else fallback 'ServiceConnection' { try { $projectId = ($ControlResult.ResourceContext.ResourceId -split "project/")[-1].Split('/')[0] $apiURL = "https://{0}.visualstudio.com/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1" -f $($organizationName) $sourcePageUrl = "https://{0}.visualstudio.com/{1}/_settings/adminservices" -f $($organizationName), $ControlResult.ResourceContext.ResourceGroupName; $inputbody = "{'contributionIds':['ms.vss-serviceEndpoints-web.service-endpoints-details-data-provider'],'dataProviderContext':{'properties':{'serviceEndpointId':'$($ControlResult.ResourceContext.ResourceDetails.id)','projectId':'$($projectId)','sourcePage':{'url':'$($sourcePageUrl)','routeId':'ms.vss-admin-web.project-admin-hub-route','routeValues':{'project':'$($ControlResult.ResourceContext.ResourceGroupName)','adminPivot':'adminservices','controller':'ContributedPage','action':'Execute'}}}}}" | ConvertFrom-Json $responseObj = [WebRequestHelper]::InvokePostWebRequest($apiURL, $inputbody); if ([Helpers]::CheckMember($responseObj, "dataProviders") -and $responseObj.dataProviders."ms.vss-serviceEndpoints-web.service-endpoints-details-data-provider") { $serviceConnEndPointDetail = $responseObj.dataProviders."ms.vss-serviceEndpoints-web.service-endpoints-details-data-provider" if ($serviceConnEndPointDetail -and [Helpers]::CheckMember($serviceConnEndPointDetail, "serviceEndpointExecutionHistory") ) { if ([Helpers]::CheckMember($serviceConnEndPointDetail.serviceEndpointExecutionHistory[0].data, "planType") -and $serviceConnEndPointDetail.serviceEndpointExecutionHistory[0].data.planType -eq "Build") { $definitionId = $serviceConnEndPointDetail.serviceEndpointExecutionHistory[0].data.definition.id; return $this.CalculateAssigneeBuild($ControlResult, $definitionId); } elseif ([Helpers]::CheckMember($serviceConnEndPointDetail.serviceEndpointExecutionHistory[0].data, "planType") -and $serviceConnEndPointDetail.serviceEndpointExecutionHistory[0].data.planType -eq "Release") { $definitionId = $serviceConnEndPointDetail.serviceEndpointExecutionHistory[0].data.definition.id; return $this.CalculateAssigneeRelease($ControlResult, $definitionId); } else { return $this.GetAssigneeFallback($ControlResult) } } else { return $this.GetAssigneeFallback($ControlResult, $organizationName) } } else { return $this.GetAssigneeFallback($ControlResult, $organizationName) } } catch { return ""; } } #get last run agent pool pipeline base on that get assignee else fallback option 'AgentPool' { $apiurl = "https://dev.azure.com/{0}/_apis/distributedtask/pools?poolName={1}&api-version=5.1" -f $organizationName, $ResourceName try { $projectId = ($ControlResult.ResourceContext.ResourceId -split "project/")[-1].Split('/')[0] $agtPoolId = ($ControlResult.ResourceContext.ResourceId -split "agentpool/")[-1] $agentPoolsURL = "https://{0}.visualstudio.com/{1}/_settings/agentqueues?queueId={2}&__rt=fps&__ver=2" -f $($ControlResult.SubscriptionContext.SubscriptionName), $projectId, $agtPoolId $agentPool = [WebRequestHelper]::InvokeGetWebRequest($agentPoolsURL); if (([Helpers]::CheckMember($agentPool[0], "fps.dataProviders.data") ) -and ($agentPool[0].fps.dataProviders.data."ms.vss-build-web.agent-jobs-data-provider")) { $agentPoolJobs = $agentPool[0].fps.dataProviders.data."ms.vss-build-web.agent-jobs-data-provider".jobs | Where-Object { $_.scopeId -eq $projectId }; #If agent pool has been queued at least once if (($agentPoolJobs | Measure-Object).Count -gt 0) { if ([Helpers]::CheckMember($agentPoolJobs[0], "planType") -and $agentPoolJobs[0].planType -eq "Build") { $definitionId = $agentPoolJobs[0].definition.id; return $this.CalculateAssigneeBuild($ControlResult, $definitionId); } elseif ([Helpers]::CheckMember($agentPoolJobs[0], "planType") -and $agentPoolJobs[0].planType -eq "Release") { $definitionId = $agentPoolJobs[0].definition.id; return $this.CalculateAssigneeRelease($ControlResult, $definitionId); } else { return $this.GetAssigneeFallback($ControlResult) } } else { return $this.GetAssigneeFallback($ControlResult) } } } catch { return ""; } } #assign to the person who recently triggered the build pipeline, or if the pipeline is empty assign it to the creator 'Build' { $definitionId = $ControlResult.ResourceContext.ResourceDetails.id; return $this.CalculateAssigneeBuild($ControlResult, $definitionId); } #assign to the person who recently triggered the release pipeline, or if the pipeline is empty assign it to the creator 'Release' { $definitionId = ($ControlResult.ResourceContext.ResourceId -split "release/")[-1]; return $this.CalculateAssigneeRelease($ControlResult, $definitionId) } #assign to the person running the scan, as to reach at this point of code, it is ensured the user is PCA/PA and only they or other PCA #PA members can fix the control 'Organization' { return [ContextHelper]::GetCurrentSessionUser(); } 'Project' { return [ContextHelper]::GetCurrentSessionUser(); } } } else { return $this.GetAssigneeFallback($ControlResult); } return ""; } hidden [string] CalculateAssigneeBuild([SVTEventContext[]] $ControlResult, $buildId) { $buildSTDataFileName ="BuildSTData.json"; try { #If file is not cached then load from server if (![BugMetaInfoProvider]::BuildSTDetails) { if([Helpers]::CheckMember($this.ControlSettingsBugLog, "BuildSTData")) { $buildSTDataFileName = $this.ControlSettingsBugLog.BuildSTData; } if ([BugMetaInfoProvider]::CheckBuildSTFileOnServer) { [BugMetaInfoProvider]::BuildSTDetails = [ConfigurationManager]::LoadServerConfigFile($buildSTDataFileName); } } if([BugMetaInfoProvider]::BuildSTDetails -and [Helpers]::CheckMember([BugMetaInfoProvider]::BuildSTDetails, "Data")) { $buildSTData = [BugMetaInfoProvider]::BuildSTDetails.Data | Where-Object { $_.buildDefinitionID -eq $buildId -and $_.projectName -eq $ControlResult[0].ResourceContext.ResourceGroupName}; if ($buildSTData) { $assignee = $this.GetDataFromServiceTree($buildSTData.serviceID); if ($assignee) { return $assignee; } else { return $this.GetAssigneeFallback($ControlResult) } } else { return $this.GetAssigneeFallback($ControlResult) } } #if no triggers found assign to the creator else { return $this.GetAssigneeFallback($ControlResult) } } catch { if ($_.Exception.Message -like "Unable to find the specified file*") { [BugMetaInfoProvider]::CheckBuildSTFileOnServer = $false; Write-Host "Could not find build service tree data file [$($buildSTDataFileName)]." -ForegroundColor Yellow } return $this.GetAssigneeFallback($ControlResult); } } hidden [string] CalculateAssigneeRelease([SVTEventContext[]] $ControlResult, $relDefId) { $releaseSTDataFileName ="ReleaseSTData.json"; try { if (![BugMetaInfoProvider]::ReleaseSTDetails) { if([Helpers]::CheckMember($this.ControlSettingsBugLog, "ReleaseSTData")) { $releaseSTDataFileName = $this.ControlSettingsBugLog.ReleaseSTData; } if ([BugMetaInfoProvider]::CheckReleaseSTFileOnServer) { [BugMetaInfoProvider]::ReleaseSTDetails = [ConfigurationManager]::LoadServerConfigFile($releaseSTDataFileName); } [BugMetaInfoProvider]::ReleaseSTDetails = [ConfigurationManager]::LoadServerConfigFile($releaseSTDataFileName) } if([BugMetaInfoProvider]::ReleaseSTDetails -and [Helpers]::CheckMember([BugMetaInfoProvider]::ReleaseSTDetails, "Data")) { $releaseSTData = [BugMetaInfoProvider]::ReleaseSTDetails.Data | Where-Object { $_.releaseDefinitionID -eq $relDefId -and $_.projectName -eq $ControlResult[0].ResourceContext.ResourceGroupName}; if ($releaseSTData) { $assignee = $this.GetDataFromServiceTree($releaseSTData.serviceID); if ($assignee) { return $assignee; } else { return $this.GetAssigneeFallback($ControlResult) } } else { return $this.GetAssigneeFallback($ControlResult) } } #if no triggers found then fallback option else { return $this.GetAssigneeFallback($ControlResult) } } catch { if ($_.Exception.Message -like "Unable to find the specified file*") { [BugMetaInfoProvider]::CheckReleaseSTFileOnServer = $false; Write-Host "Could not find release service tree data file [$($releaseSTDataFileName)]." -ForegroundColor Yellow } return $this.GetAssigneeFallback($ControlResult) } } hidden [string] GetAssigneeFallback([SVTEventContext[]] $ControlResult) { $ResourceType = $ControlResult.ResourceContext.ResourceTypeName $ResourceName = $ControlResult.ResourceContext.ResourceName $organizationName = $ControlResult.SubscriptionContext.SubscriptionName; switch -regex ($ResourceType) { #assign to the creator of service connection 'ServiceConnection' { return $ControlResult.ResourceContext.ResourceDetails.createdBy.uniqueName } #assign to the creator of agent pool 'AgentPool' { $apiurl = "https://dev.azure.com/{0}/_apis/distributedtask/pools?poolName={1}&api-version=5.1" -f $organizationName, $ResourceName try { $response = [WebRequestHelper]::InvokeGetWebRequest($apiurl) return $response.createdBy.uniqueName } catch { return ""; } } #assign to the person who recently triggered the build pipeline, or if the pipeline is empty assign it to the creator 'Build' { $definitionId = $ControlResult.ResourceContext.ResourceDetails.id; try { $apiurl = "https://dev.azure.com/{0}/{1}/_apis/build/builds?definitions={2}&api-version=5.1" -f $organizationName, $ControlResult.ResourceContext.ResourceGroupName , $definitionId; $response = [WebRequestHelper]::InvokeGetWebRequest($apiurl) #check for recent trigger if ([Helpers]::CheckMember($response, "requestedBy")) { return $response[0].requestedBy.uniqueName } #if no triggers found assign to the creator else { $apiurl = "https://dev.azure.com/{0}/{1}/_apis/build/definitions/{2}?api-version=5.1" -f $organizationName, $ControlResult.ResourceContext.ResourceGroupName , $definitionId; $response = [WebRequestHelper]::InvokeGetWebRequest($apiurl) return $response.authoredBy.uniqueName } } catch { return ""; } } #assign to the person who recently triggered the release pipeline, or if the pipeline is empty assign it to the creator 'Release' { $definitionId = ($ControlResult.ResourceContext.ResourceId -split "release/")[-1]; try { $apiurl = "https://vsrm.dev.azure.com/{0}/{1}/_apis/release/releases?definitionId={2}&api-version=5.1" -f $organizationName, $ControlResult.ResourceContext.ResourceGroupName , $definitionId; $response = [WebRequestHelper]::InvokeGetWebRequest($apiurl) #check for recent trigger if ([Helpers]::CheckMember($response, "modifiedBy")) { return $response[0].modifiedBy.uniqueName } #if no triggers found assign to the creator else { $apiurl = "https://vsrm.dev.azure.com/{0}/{1}/_apis/release/definitions/{2}?&api-version=5.1" -f $organizationName, $ControlResult.ResourceContext.ResourceGroupName , $definitionId; $response = [WebRequestHelper]::InvokeGetWebRequest($apiurl) return $response.createdBy.uniqueName } } catch { return ""; } } #assign to the person running the scan, as to reach at this point of code, it is ensured the user is PCA/PA and only they or other PCA #PA members can fix the control 'Organization' { return [ContextHelper]::GetCurrentSessionUser(); } 'Project' { return [ContextHelper]::GetCurrentSessionUser(); } } return ""; } hidden [string] GetDataFromServiceTree($serviceId) { $serviceDataFileName ="ServiceTreeData.json"; try { if (![BugMetaInfoProvider]::ServiceDetails) { if([Helpers]::CheckMember($this.ControlSettingsBugLog, "ServiceTreeData")) { $serviceDataFileName = $this.ControlSettingsBugLog.ServiceTreeData; } if ([BugMetaInfoProvider]::CheckServiceTreeFileOnServer) { [BugMetaInfoProvider]::ServiceDetails = [ConfigurationManager]::LoadServerConfigFile($serviceDataFileName); } } if ([BugMetaInfoProvider]::ServiceDetails -and [Helpers]::CheckMember([BugMetaInfoProvider]::ServiceDetails, "Data")) { $serviceTree = [BugMetaInfoProvider]::ServiceDetails.Data | Where-Object { $_.serviceID -eq $serviceId }; if ($serviceTree) { [BugLogPathManager]::AreaPath = $serviceTree.areaPath.Replace("\", "\\"); $domainNameForAssignee = "" if([Helpers]::CheckMember($this.ControlSettingsBugLog, "DomainName")) { $domainNameForAssignee = $this.ControlSettingsBugLog.DomainName; } return $serviceTree.devOwner.Split(";")[0] + "@"+ $domainNameForAssignee } else { return ""; } } else { return ""; } } catch { if ($_.Exception.Message -like "Unable to find the specified file*") { [BugMetaInfoProvider]::CheckServiceTreeFileOnServer = $false; Write-Host "Could not find service tree data file [$($serviceDataFileName)]." -ForegroundColor Yellow } return ""; } } } |