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:
Azure SDK Bot 2021-01-05 15:51:23 -08:00 committed by GitHub
parent bce390946b
commit e697a7939b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 918 additions and 0 deletions

View 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"
}

View File

@ -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"]

View 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)/"