Add Devops Release Item scripts (#1264)
- Add set of helpers to work with devops work items - Add script to create devops release package items - Update SemVer to support version type Co-authored-by: Wes Haggard <Wes.Haggard@microsoft.com>
This commit is contained in:
parent
bce390946b
commit
e697a7939b
805
eng/common/scripts/Helpers/DevOps-WorkItem-Helpers.ps1
Normal file
805
eng/common/scripts/Helpers/DevOps-WorkItem-Helpers.ps1
Normal file
@ -0,0 +1,805 @@
|
||||
|
||||
$ReleaseDevOpsOrgParameters = @("--organization", "https://dev.azure.com/azure-sdk")
|
||||
$ReleaseDevOpsCommonParameters = $ReleaseDevOpsOrgParameters + @("--output", "json")
|
||||
$ReleaseDevOpsCommonParametersWithProject = $ReleaseDevOpsCommonParameters + @("--project", "Release")
|
||||
|
||||
function Invoke-AzBoardsCmd($subCmd, $parameters, $output = $true)
|
||||
{
|
||||
$azCmdStr = "az boards ${subCmd} $($parameters -join ' ')"
|
||||
if ($output) {
|
||||
Write-Host $azCmdStr
|
||||
}
|
||||
return Invoke-Expression "$azCmdStr" | ConvertFrom-Json -AsHashTable
|
||||
}
|
||||
|
||||
function LoginToAzureDevops([string]$devop_pat)
|
||||
{
|
||||
if (!$devop_pat) {
|
||||
return
|
||||
}
|
||||
$azCmdStr = "'$devops_pat' | az devops login $($ReleaseDevOpsOrgParameters -join ' ')"
|
||||
Invoke-Expression $azCmdStr
|
||||
}
|
||||
|
||||
function BuildHashKeyNoNull()
|
||||
{
|
||||
$filterNulls = $args | Where-Object { $_ }
|
||||
# if we had any nulls then return null
|
||||
if (!$filterNulls -or $args.Count -ne $filterNulls.Count) {
|
||||
return $null
|
||||
}
|
||||
return BuildHashKey $args
|
||||
}
|
||||
|
||||
function BuildHashKey()
|
||||
{
|
||||
# if no args or the first arg is null return null
|
||||
if ($args.Count -lt 1 -or !$args[0]) {
|
||||
return $null
|
||||
}
|
||||
|
||||
# exclude null values
|
||||
$keys = $args | Where-Object { $_ }
|
||||
return $keys -join "|"
|
||||
}
|
||||
|
||||
$parentWorkItems = @{}
|
||||
function FindParentWorkItem($serviceName, $packageDisplayName, $outputCommand = $true)
|
||||
{
|
||||
$key = BuildHashKey $serviceName $packageDisplayName
|
||||
if ($key -and $parentWorkItems.ContainsKey($key)) {
|
||||
return $parentWorkItems[$key]
|
||||
}
|
||||
|
||||
if ($serviceName) {
|
||||
$serviceCondition = "[ServiceName] = '${serviceName}'"
|
||||
if ($packageDisplayName) {
|
||||
$serviceCondition += " AND [PackageDisplayName] = '${packageDisplayName}'"
|
||||
}
|
||||
else {
|
||||
$serviceCondition += " AND [PackageDisplayName] = ''"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$serviceCondition = "[ServiceName] <> ''"
|
||||
}
|
||||
|
||||
$parameters = $ReleaseDevOpsCommonParametersWithProject
|
||||
$parameters += "--wiql"
|
||||
$parameters += "`"SELECT [ID], [ServiceName], [PackageDisplayName], [Parent] FROM WorkItems WHERE [Work Item Type] = 'Epic' AND ${serviceCondition}`""
|
||||
|
||||
$workItems = Invoke-AzBoardsCmd "query" $parameters $outputCommand
|
||||
|
||||
foreach ($wi in $workItems) {
|
||||
$localKey = BuildHashKey $wi.fields["Custom.ServiceName"] $wi.fields["Custom.PackageDisplayName"]
|
||||
if (!$localKey) { continue }
|
||||
if ($parentWorkItems.ContainsKey($localKey)) {
|
||||
Write-Warning "Already found parent [$($parentWorkItems[$localKey].id)] with key [$localKey], using that one instead of [$($wi.id)]."
|
||||
}
|
||||
else {
|
||||
Write-Verbose "[$($wi.id)]$localKey - Cached"
|
||||
$parentWorkItems[$localKey] = $wi
|
||||
}
|
||||
}
|
||||
|
||||
if ($key -and $parentWorkItems.ContainsKey($key)) {
|
||||
return $parentWorkItems[$key]
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
$packageWorkItems = @{}
|
||||
$packageWorkItemWithoutKeyFields = @{}
|
||||
|
||||
function FindLatestPackageWorkItem($lang, $packageName, $outputCommand = $true)
|
||||
{
|
||||
# Cache all the versions of this package and language work items
|
||||
$null = FindPackageWorkItem $lang $packageName -includeClosed $true -outputCommand $outputCommand
|
||||
|
||||
$latestWI = $null
|
||||
foreach ($wi in $packageWorkItems.Values)
|
||||
{
|
||||
if ($wi.fields["Custom.Language"] -ne $lang) { continue }
|
||||
if ($wi.fields["Custom.Package"] -ne $packageName) { continue }
|
||||
|
||||
if (!$latestWI) {
|
||||
$latestWI = $wi
|
||||
continue
|
||||
}
|
||||
|
||||
# Note this only does string sorting which is enough for our current usages
|
||||
# if we need absolute sorting at some point we would need to parse these versions
|
||||
if ($wi.fields["Custom.PackageVersionMajorMinor"] -gt $latestWI.fields["Custom.PackageVersionMajorMinor"]) {
|
||||
$latestWI = $wi
|
||||
}
|
||||
}
|
||||
return $latestWI
|
||||
}
|
||||
|
||||
function FindPackageWorkItem($lang, $packageName, $version, $outputCommand = $true, $includeClosed = $false)
|
||||
{
|
||||
$key = BuildHashKeyNoNull $lang $packageName $version
|
||||
if ($key -and $packageWorkItems.ContainsKey($key)) {
|
||||
return $packageWorkItems[$key]
|
||||
}
|
||||
|
||||
$fields = @()
|
||||
$fields += "ID"
|
||||
$fields += "State"
|
||||
$fields += "System.AssignedTo"
|
||||
$fields += "Microsoft.VSTS.Common.StateChangeDate"
|
||||
$fields += "Parent"
|
||||
$fields += "Language"
|
||||
$fields += "Package"
|
||||
$fields += "PackageDisplayName"
|
||||
$fields += "Title"
|
||||
$fields += "PackageType"
|
||||
$fields += "PackageTypeNewLibrary"
|
||||
$fields += "PackageVersionMajorMinor"
|
||||
$fields += "PackageRepoPath"
|
||||
$fields += "ServiceName"
|
||||
$fields += "Planned Packages"
|
||||
$fields += "Shipped Packages"
|
||||
$fields += "PackageBetaVersions"
|
||||
$fields += "PackageGAVersion"
|
||||
$fields += "PackagePatchVersions"
|
||||
|
||||
$fieldList = ($fields | ForEach-Object { "[$_]"}) -join ", "
|
||||
$query = "SELECT ${fieldList} FROM WorkItems WHERE [Work Item Type] = 'Package'"
|
||||
|
||||
if (!$includeClosed -and !$lang) {
|
||||
$query += " AND [State] <> 'No Active Development'"
|
||||
}
|
||||
if ($lang) {
|
||||
$query += " AND [Language] = '${lang}'"
|
||||
}
|
||||
if ($packageName) {
|
||||
$query += " AND [Package] = '${packageName}'"
|
||||
}
|
||||
if ($version) {
|
||||
$query += " AND [PackageVersionMajorMinor] = '${version}'"
|
||||
}
|
||||
$parameters = $ReleaseDevOpsCommonParametersWithProject
|
||||
$parameters += "--wiql", "`"${query}`""
|
||||
|
||||
$workItems = Invoke-AzBoardsCmd "query" $parameters $outputCommand
|
||||
|
||||
foreach ($wi in $workItems)
|
||||
{
|
||||
$localKey = BuildHashKeyNoNull $wi.fields["Custom.Language"] $wi.fields["Custom.Package"] $wi.fields["Custom.PackageVersionMajorMinor"]
|
||||
if (!$localKey) {
|
||||
$packageWorkItemWithoutKeyFields[$wi.id] = $wi
|
||||
Write-Host "Skipping package [$($wi.id)]$($wi.fields['System.Title']) which is missing required fields language, package, or version."
|
||||
continue
|
||||
}
|
||||
if ($packageWorkItems.ContainsKey($localKey)) {
|
||||
Write-Warning "Already found package [$($packageWorkItems[$localKey].id)] with key [$localKey], using that one instead of [$($wi.id)]."
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Caching package [$($wi.id)] for [$localKey]"
|
||||
$packageWorkItems[$localKey] = $wi
|
||||
}
|
||||
}
|
||||
|
||||
if ($key -and $packageWorkItems.ContainsKey($key)) {
|
||||
return $packageWorkItems[$key]
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function InitializeWorkItemCache($outputCommand = $true, $includeClosed = $false)
|
||||
{
|
||||
# Pass null to cache all service parents
|
||||
$null = FindParentWorkItem -serviceName $null -packageDisplayName $null -outputCommand $outputCommand
|
||||
|
||||
# Pass null to cache all the package items
|
||||
$null = FindPackageWorkItem -lang $null -packageName $null -version $null -outputCommand $outputCommand -includeClosed $includeClosed
|
||||
}
|
||||
|
||||
function GetCachedPackageWorkItems()
|
||||
{
|
||||
return $packageWorkItems.Values
|
||||
}
|
||||
|
||||
function UpdateWorkItemParent($childWorkItem, $parentWorkItem, $outputCommand = $true)
|
||||
{
|
||||
$childId = $childWorkItem.id
|
||||
$existingParentId = $childWorkItem.fields["System.Parent"]
|
||||
$newParentId = $parentWorkItem.id
|
||||
|
||||
if ($existingParentId -eq $newParentId) {
|
||||
return
|
||||
}
|
||||
|
||||
CreateWorkItemParent $childId $newParentId $existingParentId -outputCommand $outputCommand
|
||||
$childWorkItem.fields["System.Parent"] = $newParentId
|
||||
}
|
||||
|
||||
function CreateWorkItemParent($id, $parentId, $oldParentId, $outputCommand = $true)
|
||||
{
|
||||
# Have to remove old parent first if you want to add a new parent.
|
||||
if ($oldParentId)
|
||||
{
|
||||
$parameters = $ReleaseDevOpsCommonParameters
|
||||
$parameters += "--yes"
|
||||
$parameters += "--id", $id
|
||||
$parameters += "--relation-type", "parent"
|
||||
$parameters += "--target-id", $oldParentId
|
||||
|
||||
Invoke-AzBoardsCmd "work-item relation remove" $parameters $outputCommand | Out-Null
|
||||
}
|
||||
|
||||
$parameters = $ReleaseDevOpsCommonParameters
|
||||
$parameters += "--id", $id
|
||||
$parameters += "--relation-type", "parent"
|
||||
$parameters += "--target-id", $parentId
|
||||
|
||||
Invoke-AzBoardsCmd "work-item relation add" $parameters $outputCommand | Out-Null
|
||||
}
|
||||
function CreateWorkItem($title, $type, $iteration, $area, $fields, $assignedTo, $parentId, $outputCommand = $true)
|
||||
{
|
||||
$parameters = $ReleaseDevOpsCommonParametersWithProject
|
||||
$parameters += "--title", "`"${title}`""
|
||||
$parameters += "--type", "`"${type}`""
|
||||
$parameters += "--iteration", "`"${iteration}`""
|
||||
$parameters += "--area", "`"${area}`""
|
||||
if ($assignedTo) {
|
||||
$parameters += "--assigned-to", "`"${assignedTo}`""
|
||||
}
|
||||
if ($fields) {
|
||||
$parameters += "--fields"
|
||||
$parameters += $fields
|
||||
}
|
||||
|
||||
$workItem = Invoke-AzBoardsCmd "work-item create" $parameters $outputCommand
|
||||
|
||||
if ($parentId) {
|
||||
$parameters = $ReleaseDevOpsCommonParameters
|
||||
$parameters += "--id", $workItem.id
|
||||
$parameters += "--relation-type", "parent"
|
||||
$parameters += "--target-id", $parentId
|
||||
|
||||
Invoke-AzBoardsCmd "work-item relation add" $parameters $outputCommand | Out-Null
|
||||
}
|
||||
|
||||
return $workItem
|
||||
}
|
||||
|
||||
function ResetWorkItemState($workItem, $resetState = $null, $outputCommand = $true)
|
||||
{
|
||||
if (!$resetState -or $resetState -eq "New") {
|
||||
$resetState = "Next Release Unknown"
|
||||
}
|
||||
if ($workItem.fields["System.State"] -ne $resetState)
|
||||
{
|
||||
Write-Verbose "Resetting state for [$($workItem.id)] from '$($workItem.fields['System.State'])' to '$resetState'"
|
||||
return UpdateWorkItem $workItem.id -state $resetState -outputCommand $outputCommand
|
||||
}
|
||||
return $workItem
|
||||
}
|
||||
|
||||
function UpdateWorkItem($id, $fields, $title, $state, $assignedTo, $outputCommand = $true)
|
||||
{
|
||||
$parameters = $ReleaseDevOpsCommonParameters
|
||||
$parameters += "--id", $id
|
||||
if ($title) {
|
||||
$parameters += "--title", "`"${title}`""
|
||||
}
|
||||
if ($state) {
|
||||
$parameters += "--state", "`"${state}`""
|
||||
}
|
||||
if ($assignedTo) {
|
||||
$parameters += "--assigned-to", "`"${assignedTo}`""
|
||||
}
|
||||
if ($fields) {
|
||||
$parameters += "--fields"
|
||||
$parameters += $fields
|
||||
}
|
||||
|
||||
return Invoke-AzBoardsCmd "work-item update" $parameters $outputCommand
|
||||
}
|
||||
|
||||
function UpdatePackageWorkItemReleaseState($id, $state, $releaseType, $outputCommand = $true)
|
||||
{
|
||||
$fields = "`"Custom.ReleaseType=${releaseType}`""
|
||||
return UpdateWorkItem -id $id -state $state -fields $fields -outputCommand $outputCommand
|
||||
}
|
||||
|
||||
function CreateOrUpdatePackageWorkItem($lang, $pkg, $verMajorMinor, $existingItem, $assignedTo = $null, $outputCommand = $true)
|
||||
{
|
||||
if (!$lang -or !$pkg -or !$verMajorMinor) {
|
||||
Write-Host "Cannot create or update because one of lang, pkg or verMajorMinor aren't set. [$lang|$($pkg.Package)|$verMajorMinor]"
|
||||
return
|
||||
}
|
||||
$pkgName = $pkg.Package
|
||||
$pkgDisplayName = $pkg.DisplayName
|
||||
$pkgType = $pkg.Type
|
||||
$pkgNewLibrary = $pkg.New
|
||||
$pkgRepoPath = $pkg.RepoPath
|
||||
$serviceName = $pkg.ServiceName
|
||||
$title = $lang + " - " + $pkg.DisplayName + " - " + $verMajorMinor
|
||||
|
||||
$fields = @()
|
||||
$fields += "`"Language=${lang}`""
|
||||
$fields += "`"Package=${pkgName}`""
|
||||
$fields += "`"PackageDisplayName=${pkgDisplayName}`""
|
||||
$fields += "`"PackageType=${pkgType}`""
|
||||
$fields += "`"PackageTypeNewLibrary=${pkgNewLibrary}`""
|
||||
$fields += "`"PackageVersionMajorMinor=${verMajorMinor}`""
|
||||
$fields += "`"ServiceName=${serviceName}`""
|
||||
$fields += "`"PackageRepoPath=${pkgRepoPath}`""
|
||||
|
||||
if ($existingItem)
|
||||
{
|
||||
$changedField = $null
|
||||
|
||||
if ($lang -ne $existingItem.fields["Custom.Language"]) { $changedField = "Custom.Language" }
|
||||
if ($pkgName -ne $existingItem.fields["Custom.Package"]) { $changedField = "Custom.Package" }
|
||||
if ($verMajorMinor -ne $existingItem.fields["Custom.PackageVersionMajorMinor"]) { $changedField = "Custom.PackageVersionMajorMinor" }
|
||||
if ($pkgDisplayName -ne $existingItem.fields["Custom.PackageDisplayName"]) { $changedField = "Custom.PackageDisplayName" }
|
||||
if ($pkgType -ne $existingItem.fields["Custom.PackageType"]) { $changedField = "Custom.PackageType" }
|
||||
if ($pkgNewLibrary -ne $existingItem.fields["Custom.PackageTypeNewLibrary"]) { $changedField = "Custom.PackageTypeNewLibrary" }
|
||||
if ($pkgRepoPath -ne $existingItem.fields["Custom.PackageRepoPath"]) { $changedField = "Custom.PackageRepoPath" }
|
||||
if ($serviceName -ne $existingItem.fields["Custom.ServiceName"]) { $changedField = "Custom.ServiceName" }
|
||||
if ($title -ne $existingItem.fields["System.Title"]) { $changedField = "System.Title" }
|
||||
|
||||
if ($changedField) {
|
||||
Write-Host "At least field $changedField ($($existingItem.fields[$field])) changed so updating."
|
||||
}
|
||||
|
||||
$beforeState = $existingItem.fields["System.State"]
|
||||
|
||||
if ($changedField) {
|
||||
# Need to set to New to be able to update
|
||||
$existingItem = UpdateWorkItem -id $existingItem.id -fields $fields -title $title -state "New" -assignedTo $assignedTo -outputCommand $outputCommand
|
||||
Write-Host "[$($existingItem.id)]$lang - $pkgName($verMajorMinor) - Updated"
|
||||
}
|
||||
$existingItem = ResetWorkItemState $existingItem $beforeState -outputCommand $outputCommand
|
||||
|
||||
$newparentItem = FindOrCreatePackageGroupParent $serviceName $pkgDisplayName -outputCommand $outputCommand
|
||||
UpdateWorkItemParent $existingItem $newParentItem -outputCommand $outputCommand
|
||||
return $existingItem
|
||||
}
|
||||
|
||||
$parentItem = FindOrCreatePackageGroupParent $serviceName $pkgDisplayName -outputCommand $outputCommand
|
||||
$workItem = CreateWorkItem $title "Package" "Release" "Release" $fields $assignedTo $parentItem.id -outputCommand $outputCommand
|
||||
$workItem = ResetWorkItemState $workItem -outputCommand $outputCommand
|
||||
Write-Host "[$($workItem.id)]$lang - $pkgName($verMajorMinor) - Created"
|
||||
return $workItem
|
||||
}
|
||||
|
||||
function FindOrCreatePackageGroupParent($serviceName, $packageDisplayName, $outputCommand = $true)
|
||||
{
|
||||
$existingItem = FindParentWorkItem $serviceName $packageDisplayName -outputCommand $outputCommand
|
||||
if ($existingItem) {
|
||||
$newparentItem = FindOrCreateServiceParent $serviceName -outputCommand $outputCommand
|
||||
UpdateWorkItemParent $existingItem $newParentItem
|
||||
return $existingItem
|
||||
}
|
||||
|
||||
$fields = @()
|
||||
$fields += "`"PackageDisplayName=${packageDisplayName}`""
|
||||
$fields += "`"ServiceName=${serviceName}`""
|
||||
$serviceParentItem = FindOrCreateServiceParent $serviceName -outputCommand $outputCommand
|
||||
$workItem = CreateWorkItem $packageDisplayName "Epic" "Release" "Release" $fields $null $serviceParentItem.id
|
||||
|
||||
$localKey = BuildHashKey $serviceName $packageDisplayName
|
||||
Write-Host "[$($workItem.id)]$localKey - Created Parent"
|
||||
$parentWorkItems[$localKey] = $workItem
|
||||
return $workItem
|
||||
}
|
||||
|
||||
function FindOrCreateServiceParent($serviceName, $outputCommand = $true)
|
||||
{
|
||||
$serviceParent = FindParentWorkItem $serviceName -outputCommand $outputCommand
|
||||
if ($serviceParent) {
|
||||
return $serviceParent
|
||||
}
|
||||
|
||||
$fields = @()
|
||||
$fields += "`"PackageDisplayName=`""
|
||||
$fields += "`"ServiceName=${serviceName}`""
|
||||
$parentId = $null
|
||||
$workItem = CreateWorkItem $serviceName "Epic" "Release" "Release" $fields $null $parentId -outputCommand $outputCommand
|
||||
|
||||
$localKey = BuildHashKey $serviceName
|
||||
Write-Host "[$($workItem.id)]$localKey - Created"
|
||||
$parentWorkItems[$localKey] = $workItem
|
||||
return $workItem
|
||||
}
|
||||
|
||||
function ParseVersionSetFromMDField([string]$field)
|
||||
{
|
||||
$MDTableRegex = "\|\s*(?<t>\S*)\s*\|\s*(?<v>\S*)\s*\|\s*(?<d>\S*)\s*\|"
|
||||
$versionSet = @{}
|
||||
$tableMatches = [Regex]::Matches($field, $MDTableRegex)
|
||||
|
||||
foreach ($match in $tableMatches)
|
||||
{
|
||||
if ($match.Groups["t"].Value -eq "Type" -or $match.Groups["t"].Value -eq "-") {
|
||||
continue
|
||||
}
|
||||
$version = New-Object PSObject -Property @{
|
||||
Type = $match.Groups["t"].Value
|
||||
Version = $match.Groups["v"].Value
|
||||
Date = $match.Groups["d"].Value
|
||||
}
|
||||
if (!$versionSet.ContainsKey($version.Version)) {
|
||||
$versionSet[$version.Version] = $version
|
||||
}
|
||||
}
|
||||
return $versionSet
|
||||
}
|
||||
|
||||
function GetTextVersionFields($versionList, $pkgWorkItem)
|
||||
{
|
||||
$betaVersions = $gaVersions = $patchVersions = ""
|
||||
foreach ($v in $versionList) {
|
||||
$vstr = "$($v.Version),$($v.Date)"
|
||||
if ($v.Type -eq "Beta") {
|
||||
if ($betaVersions.Length + $vstr.Length -lt 255) {
|
||||
if ($betaVersions.Length -gt 0) { $betaVersions += "|" }
|
||||
$betaVersions += $vstr
|
||||
}
|
||||
}
|
||||
elseif ($v.Type -eq "GA") {
|
||||
if ($gaVersions.Length + $vstr.Length -lt 255) {
|
||||
if ($gaVersions.Length -gt 0) { $gaVersions += "|" }
|
||||
$gaVersions += $vstr
|
||||
}
|
||||
}
|
||||
elseif ($v.Type -eq "Patch") {
|
||||
if ($patchVersions.Length + $vstr.Length -lt 255) {
|
||||
if ($patchVersions.Length -gt 0) { $patchVersions += "|" }
|
||||
$patchVersions += $vstr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fieldUpdates = @()
|
||||
if ("$($pkgWorkItem.fields["Custom.PackageBetaVersions"])" -ne $betaVersions)
|
||||
{
|
||||
$fieldUpdates += @"
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/fields/PackageBetaVersions",
|
||||
"value": "$betaVersions"
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
if ("$($pkgWorkItem.fields["Custom.PackageGAVersion"])" -ne $gaVersions)
|
||||
{
|
||||
$fieldUpdates += @"
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/fields/PackageGAVersion",
|
||||
"value": "$gaVersions"
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
if ("$($pkgWorkItem.fields["Custom.PackagePatchVersions"])" -ne $patchVersions)
|
||||
{
|
||||
$fieldUpdates += @"
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/fields/PackagePatchVersions",
|
||||
"value": "$patchVersions"
|
||||
}
|
||||
"@
|
||||
}
|
||||
return ,$fieldUpdates
|
||||
}
|
||||
|
||||
function GetMDVersionValue($versionlist)
|
||||
{
|
||||
$mdVersions = ""
|
||||
$mdFormat = "| {0} | {1} | {2} |`n"
|
||||
$versionlist | ForEach-Object { $mdVersions += ($mdFormat -f $_.Type, $_.Version, $_.Date) }
|
||||
|
||||
$htmlVersions = ""
|
||||
$htmlFormat = @"
|
||||
<tr>
|
||||
<td>{0}</td>
|
||||
<td>{1}</td>
|
||||
<td>{2}</td>
|
||||
</tr>
|
||||
|
||||
"@
|
||||
$versionlist | ForEach-Object { $htmlVersions += ($htmlFormat -f $_.Type, $_.Version, $_.Date) }
|
||||
|
||||
$htmlTemplate = @"
|
||||
<div style='display:none;width:0;height:0;overflow:hidden;position:absolute;font-size:0;' id=__md>| Type | Version | Date |
|
||||
| - | - | - |
|
||||
mdVersions
|
||||
</div><style id=__mdStyle>
|
||||
.rendered-markdown img {
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.rendered-markdown h1, .rendered-markdown h2, .rendered-markdown h3, .rendered-markdown h4, .rendered-markdown h5, .rendered-markdown h6 {
|
||||
color:#007acc;
|
||||
font-weight:400;
|
||||
}
|
||||
|
||||
.rendered-markdown h1 {
|
||||
border-bottom:1px solid #e6e6e6;
|
||||
font-size:26px;
|
||||
font-weight:600;
|
||||
margin-bottom:20px;
|
||||
}
|
||||
|
||||
.rendered-markdown h2 {
|
||||
font-size:18px;
|
||||
border-bottom:1px solid #e6e6e6;
|
||||
font-weight:600;
|
||||
color:#303030;
|
||||
margin-bottom:10px;
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
.rendered-markdown h3 {
|
||||
font-size:16px;
|
||||
font-weight:600;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.rendered-markdown h4 {
|
||||
font-size:14px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.rendered-markdown h5 {
|
||||
font-size:12px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.rendered-markdown h6 {
|
||||
font-size:12px;
|
||||
font-weight:300;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.rendered-markdown.metaitem {
|
||||
font-size:12px;
|
||||
padding-top:15px;
|
||||
}
|
||||
|
||||
.rendered-markdown.metavalue {
|
||||
font-size:12px;
|
||||
padding-left:4px;
|
||||
}
|
||||
|
||||
.rendered-markdown.metavalue>img {
|
||||
height:32px;
|
||||
width:32px;
|
||||
margin-bottom:3px;
|
||||
padding-left:1px;
|
||||
}
|
||||
|
||||
.rendered-markdown li.metavaluelink {
|
||||
list-style-type:disc;
|
||||
list-style-position:inside;
|
||||
}
|
||||
|
||||
.rendered-markdown li.metavalue>a {
|
||||
border:none;
|
||||
padding:0;
|
||||
display:inline;
|
||||
}
|
||||
|
||||
.rendered-markdown li.metavalue>a:hover {
|
||||
background-color:inherit;
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
.rendered-markdown code, .rendered-markdown pre, .rendered-markdown samp {
|
||||
font-family:Monaco,Menlo,Consolas,'Droid Sans Mono','Inconsolata','Courier New',monospace;
|
||||
}
|
||||
|
||||
.rendered-markdown code {
|
||||
color:#333;
|
||||
background-color:#f8f8f8;
|
||||
border:1px solid #ccc;
|
||||
border-radius:3px;
|
||||
padding:2px 4px;
|
||||
font-size:90%;
|
||||
line-height:2;
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
.rendered-markdown pre {
|
||||
color:#333;
|
||||
background-color:#f8f8f8;
|
||||
border:1px solid #ccc;
|
||||
display:block;
|
||||
padding:6px;
|
||||
font-size:13px;
|
||||
word-break:break-all;
|
||||
word-wrap:break-word;
|
||||
}
|
||||
|
||||
.rendered-markdown pre code {
|
||||
padding:0;
|
||||
font-size:inherit;
|
||||
color:inherit;
|
||||
white-space:pre-wrap;
|
||||
background-color:transparent;
|
||||
line-height:1.428571429;
|
||||
border:none;
|
||||
}
|
||||
|
||||
.rendered-markdown.pre-scrollable {
|
||||
max-height:340px;
|
||||
overflow-y:scroll;
|
||||
}
|
||||
|
||||
.rendered-markdown table {
|
||||
border-collapse:collapse;
|
||||
}
|
||||
|
||||
.rendered-markdown table {
|
||||
width:auto;
|
||||
}
|
||||
|
||||
.rendered-markdown table, .rendered-markdown th, .rendered-markdown td {
|
||||
border:1px solid #ccc;
|
||||
padding:4px;
|
||||
}
|
||||
|
||||
.rendered-markdown th {
|
||||
font-weight:bold;
|
||||
background-color:#f8f8f8;
|
||||
}
|
||||
</style><div class=rendered-markdown><table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Version</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>htmlVersions</tbody>
|
||||
</table>
|
||||
</div>
|
||||
"@ -replace "'", '\"'
|
||||
|
||||
return $htmlTemplate.Replace("mdVersions", $mdVersions).Replace("htmlVersions", "`n$htmlVersions");
|
||||
}
|
||||
|
||||
function UpdatePackageVersions($pkgWorkItem, $plannedVersions, $shippedVersions)
|
||||
{
|
||||
# Create the planned and shipped versions, adding the new ones if any
|
||||
$updatePlanned = $false
|
||||
$plannedVersionSet = ParseVersionSetFromMDField $pkgWorkItem.fields["Custom.PlannedPackages"]
|
||||
foreach ($version in $plannedVersions)
|
||||
{
|
||||
if (!$plannedVersionSet.ContainsKey($version.Version))
|
||||
{
|
||||
$plannedVersionSet[$version.Version] = $version
|
||||
$updatePlanned = $true
|
||||
}
|
||||
else
|
||||
{
|
||||
# Lets check to see if someone wanted to update a date
|
||||
$existingVersion = $plannedVersionSet[$version.Version]
|
||||
if ($existingVersion.Date -ne $version.Date) {
|
||||
$existingVersion.Date = $version.Date
|
||||
$updatePlanned = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$updateShipped = $false
|
||||
$shippedVersionSet = ParseVersionSetFromMDField $pkgWorkItem.fields["Custom.ShippedPackages"]
|
||||
foreach ($version in $shippedVersions)
|
||||
{
|
||||
if (!$shippedVersionSet.ContainsKey($version.Version))
|
||||
{
|
||||
$shippedVersionSet[$v.Version] = $version
|
||||
$updateShipped = $true
|
||||
}
|
||||
}
|
||||
|
||||
$versionSet = @{}
|
||||
foreach ($version in $shippedVersionSet.Keys)
|
||||
{
|
||||
if (!$versionSet.ContainsKey($version))
|
||||
{
|
||||
$versionSet[$version] = $shippedVersionSet[$version]
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($version in $plannedVersionSet.Keys)
|
||||
{
|
||||
if (!$versionSet.ContainsKey($version))
|
||||
{
|
||||
$versionSet[$version] = $plannedVersionSet[$version]
|
||||
}
|
||||
else
|
||||
{
|
||||
# Looks like we shipped this version so remove it from the planned set
|
||||
$plannedVersionSet.Remove($version)
|
||||
$updatePlanned = $true
|
||||
}
|
||||
}
|
||||
|
||||
$fieldUpdates = @()
|
||||
if ($updatePlanned)
|
||||
{
|
||||
$plannedPackages = GetMDVersionValue ($plannedVersionSet.Values | Sort-Object Date, Version -Descending)
|
||||
$fieldUpdates += @"
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/fields/Planned Packages",
|
||||
"value": "$plannedPackages"
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
if ($updateShipped)
|
||||
{
|
||||
$newShippedVersions = $shippedVersionSet.Values | Sort-Object Date, Version -Descending
|
||||
$shippedPackages = GetMDVersionValue $newShippedVersions
|
||||
$fieldUpdates += @"
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/fields/Shipped Packages",
|
||||
"value": "$shippedPackages"
|
||||
}
|
||||
"@
|
||||
|
||||
# If we shipped a version after we set "In Release" state then reset the state to "Next Release Unknown"
|
||||
if ($pkgWorkItem.fields["System.State"] -eq "In Release")
|
||||
{
|
||||
$lastShippedDate = [DateTime]$newShippedVersions[0].Date
|
||||
$markedInReleaseDate = ([DateTime]$pkgWorkItem.fields["Microsoft.VSTS.Common.StateChangeDate"])
|
||||
|
||||
# We just shipped so lets set the state to "Next Release Unknown"
|
||||
if ($markedInReleaseDate -le $lastShippedDate)
|
||||
{
|
||||
$fieldUpdates += @'
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/fields/State",
|
||||
"value": "Next Release Unknown"
|
||||
}
|
||||
'@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Full merged version set
|
||||
$versionList = $versionSet.Values | Sort-Object Date, Version -Descending
|
||||
|
||||
$versionFieldUpdates = GetTextVersionFields $versionList $pkgWorkItem
|
||||
if ($versionFieldUpdates.Count -gt 0)
|
||||
{
|
||||
$fieldUpdates += $versionFieldUpdates
|
||||
}
|
||||
|
||||
|
||||
# If no version files to update do nothing
|
||||
if ($fieldUpdates.Count -eq 0) {
|
||||
return
|
||||
}
|
||||
|
||||
$versionsForDebug = ($versionList | Foreach-Object { $_.Version }) -join ","
|
||||
$id = $pkgWorkItem.id
|
||||
$loggingString = "[$($pkgWorkItem.id)]"
|
||||
$loggingString += "$($pkgWorkItem.fields['Custom.Language'])"
|
||||
$loggingString += " - $($pkgWorkItem.fields['Custom.Package'])"
|
||||
$loggingString += "($($pkgWorkItem.fields['Custom.PackageVersionMajorMinor']))"
|
||||
$loggingString += " - Updating versions $versionsForDebug"
|
||||
Write-Host $loggingString
|
||||
|
||||
$body = "[" + ($fieldUpdates -join ',') + "]"
|
||||
|
||||
# Get a temp access token from the logged in az cli user for azure devops resource
|
||||
$jwt_accessToken = (az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query "accessToken" --output tsv)
|
||||
$headers = @{ Authorization = "Bearer $jwt_accessToken" }
|
||||
$response = Invoke-RestMethod -Method PATCH `
|
||||
-Uri "https://dev.azure.com/azure-sdk/_apis/wit/workitems/${id}?api-version=6.0" `
|
||||
-Headers $headers -Body $body -ContentType "application/json-patch+json"
|
||||
}
|
||||
@ -25,6 +25,7 @@ class AzureEngSemanticVersion {
|
||||
[string] $BuildNumber
|
||||
[int] $PrereleaseNumber
|
||||
[bool] $IsPrerelease
|
||||
[string] $VersionType
|
||||
[string] $RawVersion
|
||||
[bool] $IsSemVerFormat
|
||||
[string] $DefaultPrereleaseLabel
|
||||
@ -80,6 +81,10 @@ class AzureEngSemanticVersion {
|
||||
$this.PrereleaseLabel = "zzz"
|
||||
$this.PrereleaseNumber = 99999999
|
||||
$this.IsPrerelease = $false
|
||||
$this.VersionType = "GA"
|
||||
if ($this.Patch -ne 0) {
|
||||
$this.VersionType = "Patch"
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -88,6 +93,7 @@ class AzureEngSemanticVersion {
|
||||
$this.PrereleaseNumber = [int]$matches["prenumber"]
|
||||
$this.PrereleaseNumberSeparator = $matches["prenumsep"]
|
||||
$this.IsPrerelease = $true
|
||||
$this.VersionType = "Beta"
|
||||
|
||||
$this.BuildNumberSeparator = $matches["buildnumsep"]
|
||||
$this.BuildNumber = $matches["buildnumber"]
|
||||
|
||||
107
eng/common/scripts/Update-DevOps-Release-WorkItem.ps1
Normal file
107
eng/common/scripts/Update-DevOps-Release-WorkItem.ps1
Normal file
@ -0,0 +1,107 @@
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$language,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$packageName,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$version,
|
||||
[string]$plannedDate,
|
||||
[string]$serviceName = $null,
|
||||
[string]$packageDisplayName = $null,
|
||||
[string]$packageRepoPath = "NA",
|
||||
[string]$packageType = "client",
|
||||
[string]$packageNewLibrary = "true"
|
||||
)
|
||||
Set-StrictMode -Version 3
|
||||
|
||||
if (!(Get-Command az)) {
|
||||
Write-Host 'You must have the Azure CLI installed: https://aka.ms/azure-cli'
|
||||
exit 1
|
||||
}
|
||||
|
||||
az extension show -n azure-devops > $null
|
||||
if (!$?){
|
||||
Write-Host 'You must have the azure-devops extension run `az extension add --name azure-devops`'
|
||||
exit 1
|
||||
}
|
||||
|
||||
. (Join-Path $PSScriptRoot SemVer.ps1)
|
||||
. (Join-Path $PSScriptRoot Helpers DevOps-WorkItem-Helpers.ps1)
|
||||
|
||||
$parsedNewVersion = [AzureEngSemanticVersion]::new($version)
|
||||
$state = "In Release"
|
||||
$releaseType = $parsedNewVersion.VersionType
|
||||
$versionMajorMinor = "" + $parsedNewVersion.Major + "." + $parsedNewVersion.Minor
|
||||
|
||||
$packageInfo = [PSCustomObject][ordered]@{
|
||||
Package = $packageName
|
||||
DisplayName = $packageDisplayName
|
||||
ServiceName = $serviceName
|
||||
RepoPath = $packageRepoPath
|
||||
Type = $packageType
|
||||
New = $packageNewLibrary
|
||||
};
|
||||
|
||||
if (!$plannedDate) {
|
||||
$plannedDate = Get-Date -Format "MM/dd/yyyy"
|
||||
}
|
||||
|
||||
$plannedVersions = @(
|
||||
[PSCustomObject][ordered]@{
|
||||
Type = $releaseType
|
||||
Version = $version
|
||||
Date = $plannedDate
|
||||
}
|
||||
)
|
||||
|
||||
$workItem = FindPackageWorkItem -lang $language -packageName $packageName -version $versionMajorMinor -includeClosed $true -outputCommand $false
|
||||
|
||||
if (!$workItem) {
|
||||
$latestVersionItem = FindLatestPackageWorkItem -lang $language -packageName $packageName -outputCommand $false
|
||||
$assignedTo = "me"
|
||||
if ($latestVersionItem) {
|
||||
Write-Host "Copying data from latest matching [$($latestVersionItem.id)] with version $($latestVersionItem.fields["Custom.PackageVersionMajorMinor"])"
|
||||
if ($latestVersionItem.fields["System.AssignedTo"]) {
|
||||
$assignedTo = $latestVersionItem.fields["System.AssignedTo"]["uniqueName"]
|
||||
}
|
||||
$packageInfo.DisplayName = $latestVersionItem.fields["Custom.PackageDisplayName"]
|
||||
$packageInfo.ServiceName = $latestVersionItem.fields["Custom.ServiceName"]
|
||||
if (!$packageInfo.RepoPath -and $packageInfo.RepoPath -ne "NA" -and $packageInfo.fields["Custom.PackageRepoPath"]) {
|
||||
$packageInfo.RepoPath = $packageInfo.fields["Custom.PackageRepoPath"]
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Creating a release work item for a package release with the following properties:"
|
||||
Write-Host " Lanuage: $language"
|
||||
Write-Host " Version: $versionMajorMinor"
|
||||
Write-Host " Package: $packageName"
|
||||
Write-Host " AssignedTo: $assignedTo"
|
||||
|
||||
if (!$packageInfo.DisplayName) {
|
||||
Write-Host "We need a package display name to be used in various places and it should be consistent across languages for similar packages."
|
||||
while (($readInput = Read-Host -Prompt "Input the display name") -eq "") { }
|
||||
$packageInfo.DisplayName = $readInput
|
||||
}
|
||||
Write-Host " PackageDisplayName: $($packageInfo.DisplayName)"
|
||||
|
||||
if (!$packageInfo.ServiceName) {
|
||||
Write-Host "We need a package service name to be used in various places and it should be consistent across languages for similar packages."
|
||||
while (($readInput = Read-Host -Prompt "Input the service name") -eq "") { }
|
||||
$packageInfo.ServiceName = $readInput
|
||||
}
|
||||
Write-Host " ServiceName: $($packageInfo.ServiceName)"
|
||||
Write-Host " PackageType: $packageType"
|
||||
|
||||
$workItem = CreateOrUpdatePackageWorkItem -lang $language -pkg $packageInfo -verMajorMinor $versionMajorMinor -assignedTo $assignedTo -outputCommand $false
|
||||
}
|
||||
|
||||
if (!$workItem) {
|
||||
Write-Host "Something failed as we don't have a work-item so exiting."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Marking item [$($workItem.id)]$($workItem.fields['System.Title']) as '$state' for '$releaseType'"
|
||||
$updatedWI = UpdatePackageWorkItemReleaseState -id $workItem.id -state "In Release" -releaseType $releaseType -outputCommand $false
|
||||
UpdatePackageVersions $workItem -plannedVersions $plannedVersions
|
||||
Write-Host "https://dev.azure.com/azure-sdk/Release/_workitems/edit/$($updatedWI.id)/"
|
||||
Loading…
Reference in New Issue
Block a user