diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/WASP/Private/Find-PackageInWishlist.ps1 b/WASP/Private/Find-PackageInWishlist.ps1 new file mode 100644 index 0000000..c965c77 --- /dev/null +++ b/WASP/Private/Find-PackageInWishlist.ps1 @@ -0,0 +1,47 @@ +function Find-PackageInWishlist { + <# + .SYNOPSIS + Takes a package name and searches for it in the wishlist. Returns true if found, false otherwise. + .DESCRIPTION + Find a package in the wishlist by its name. + .NOTES + FileName: Find-PackageInWishlist.ps1 + Author: Julian Bopp + Contact: its-wcs-ma@unibas.ch + Created: 2025-10-16 + Version: 1.0.0 + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)][string] $packageName + ) + + begin { + $config = Read-ConfigFile + + $GitRepo = $config.Application.PackagesWishlist + $GitFile = $GitRepo.Substring($GitRepo.LastIndexOf("/") + 1, $GitRepo.Length - $GitRepo.LastIndexOf("/") - 1) + $GitFolderName = $GitFile.Replace(".git", "") + $PackagesWishlistPath = Join-Path -Path $config.Application.BaseDirectory -ChildPath $GitFolderName + $wishlistPath = Join-Path -Path $PackagesWishlistPath -ChildPath "wishlist.txt" + $wishlistContent = Get-Content -Path $wishlistPath | Where-Object { $_ -notlike "#*" } + } + + process { + $foundInWishlist = $false + foreach ($line in $wishlistContent) { + $line = $line -replace "@.*", "" + if ($line -eq $packageName) { + $foundInWishlist = $true + } + } + if (!$foundInWishlist) { + return $false + } else { + return $true + } + } + + end { + } +} \ No newline at end of file diff --git a/WASP/Private/Flag-JiraTicket.ps1 b/WASP/Private/Flag-JiraTicket.ps1 new file mode 100644 index 0000000..ec7c57f --- /dev/null +++ b/WASP/Private/Flag-JiraTicket.ps1 @@ -0,0 +1,93 @@ +function Flag-JiraTicket { + <# + .SYNOPSIS + Flags a Jira Ticket + .DESCRIPTION + Flags or unflags and optionally comments a Jira Ticket with the given parameters + .NOTES + FileName: Flag-JiraTicket.ps1 + Author: Julian Bopp + Contact: its-wcs-ma@unibas.ch + Created: 2025-08-21 + Version: 1.0.0 + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)][string] $issueKey, + [Parameter(Mandatory = $false)][string] $comment, + [Parameter(Mandatory = $false)][bool] $unflag + ) + + begin { + $config = Read-ConfigFile + $jiraBaseUrl = $config.Application.JiraBaseUrl + $jiraUser = $config.Application.JiraUser + $jiraPassword = $config.Application.JiraPassword + $projectKey = $config.Application.ProjectKey + $issueType = $config.Application.IssueType + } + + process { + $url = "$($jiraBaseUrl)/rest/api/2/issue/$issueKey" + + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("${jiraUser}:${jiraPassword}"))) + + $header = @{ + "Authorization" = "Basic $base64AuthInfo" + "Content-Type" = "application/json" + } + + # Create comment if provided + if ($comment) { + $flagComment = "(flag) " + $comment + if ($unflag) { + $flagComment = "(flagoff) " + $comment + } + } + # Create the JSON payload + $value = "Impediment" + + if ($unflag) { + $value = $null + } + + $body = @{ + fields = @{ + # This field is used for the flagging + customfield_10000 = @( + @{ value = $value } + ) + + } + } | ConvertTo-Json -Depth 3 + + if ($comment) { + + # Check if the comment already exists + $existingComment = Search-JiraComment -issueKey $issueKey -searchString $flagComment + + if ($existingComment) { + Write-Log -Message "Comment already exists on Jira ticket $issueKey. Skipping adding comment and flag." -Severity 2 + return + } + New-JiraComment -issueKey $issueKey -comment $flagComment + } + + Write-Log -Message "Flagging jira ticket $issueKey" -Severity 1 + $response = Invoke-WebRequest -Uri $url -Method Put -Headers $header -Body $body + + if ($response.StatusCode -eq 204) { + Write-Log -Message "StatusCode: $($response.StatusCode)" -Severity 0 + if ($unflag) { + Write-Log -Message "Jira ticket successfully unflagged" -Severity -0 + } else { + Write-Log -Message "Jira ticket successfully flagged" -Severity -0 + } + } else { + Write-Log -Message "Failed to flag/unflag jira ticket! StatusCode: $($response.StatusCode): $($response.StatusDescription)" -Severity 3 + } + } + + end { + } +} diff --git a/WASP/Private/Get-JiraIssueKeyFromName.ps1 b/WASP/Private/Get-JiraIssueKeyFromName.ps1 new file mode 100644 index 0000000..08f1786 --- /dev/null +++ b/WASP/Private/Get-JiraIssueKeyFromName.ps1 @@ -0,0 +1,61 @@ +function Get-JiraIssueKeyFromName { + <# + .SYNOPSIS + Retrieves a Jira issue key from an issue name (summary). + .DESCRIPTION + Uses Jira REST API search to find an issue by its summary/title + and returns the issue key (e.g., PROJ-123). + .NOTES + FileName: Get-JiraIssueKeyFromName.ps1 + Author: Julian Bopp + Contact: its-wcs-ma@unibas.ch + Created: 2025-09-09 + Version: 1.0.0 + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)][string] $issueName + ) + + begin { + $config = Read-ConfigFile + $jiraBaseUrl = $config.Application.JiraBaseUrl + $jiraUser = $config.Application.JiraUser + $jiraPassword = $config.Application.JiraPassword + $projectKey = $config.Application.ProjectKey + } + + process { + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("${jiraUser}:${jiraPassword}"))) + + $header = @{ + "Authorization" = "Basic $base64AuthInfo" + "Content-Type" = "application/json" + } + + # JQL to search by issue summary + $jql = "project = $projectKey AND summary ~ `"$issueName`"" + $searchUrl = "$jiraBaseUrl/rest/api/2/search?jql=$([System.Web.HttpUtility]::UrlEncode($jql))" + + Write-Log -Message "Searching for Jira issue with summary: $issueName" -Severity 1 + $response = Invoke-RestMethod -Uri $searchUrl -Method Get -Headers $header + + if ($response.issues.Count -eq 0) { + Write-Log -Message "No Jira issue found with summary: $issueName" -Severity 2 + return $null + } + + # Return all matching issue keys + $issueKeys = $response.issues | ForEach-Object { $_.key } + + # if multiple issues found, log a warning + if ($issueKeys.Count -gt 1) { + Write-Log -Message "Multiple Jira issues found with summary: $issueName. Returning all matching keys." -Severity 2 + } + + Write-Log -Message "Found Jira issues: $($issueKeys -join ', ')" -Severity 0 + return $issueKeys + } + + end { } +} diff --git a/WASP/Private/Invoke-JiraObserver.ps1 b/WASP/Private/Invoke-JiraObserver.ps1 index 818604c..5584a7a 100644 --- a/WASP/Private/Invoke-JiraObserver.ps1 +++ b/WASP/Private/Invoke-JiraObserver.ps1 @@ -29,9 +29,14 @@ function Invoke-JiraObserver { $GitBranchDEV = $Config.Application.GitBranchDEV $GitBranchTEST = $Config.Application.GitBranchTEST $GitBranchPROD = $Config.Application.GitBranchPROD + + $GitRepo = $config.Application.PackagesWishlist + $GitFile = $GitRepo.Substring($GitRepo.LastIndexOf("/") + 1, $GitRepo.Length - $GitRepo.LastIndexOf("/") - 1) } process { + $nameAndVersionSeparator = '@' + # Die neueste Jira State file wird eingelesen als Hashtable Write-Log -Message "Reading latest Jira state file" -Severity 0 $jiraStateFileContent, $JiraStateFile = Read-JiraStateFile @@ -132,6 +137,16 @@ function Invoke-JiraObserver { $UpdateJiraStateFile = $true # jedes Issue, welches vom Stand im neusten JiraState-File abweicht wird einzeln durchgegangen foreach($key in $IssuesCompareState.keys) { + $packageName, $packageVersion, $re = $key.split($nameAndVersionSeparator) + $foundInWishlist = Find-PackageInWishlist -PackageName $packageName + if (!$foundInWishlist) { + # Paket nicht in der Wishlist, Ticket wird auf Jira geflagged und kommentiert. + Write-Log "Skip handling of $key - deactivated in wishlist." -Severity 2 + $IssueKey = Get-JiraIssueKeyFromName -issueName "$packageName$nameAndVersionSeparator$packageVersion" + Flag-JiraTicket -issueKey $IssueKey -comment "Package $packageName is deactivated in the wishlist." + continue + } + # Ermittlung des Dev-Branches anhand des Paket Namens (mit Eventualität des Repackaging branches) $DevBranchPrefix = "$GitBranchDEV$key" $DevBranch = Get-DevBranch -RemoteBranches $RemoteBranches -DevBranchPrefix $DevBranchPrefix @@ -196,4 +211,3 @@ function Invoke-JiraObserver { } } } - \ No newline at end of file diff --git a/WASP/Private/New-JiraComment.ps1 b/WASP/Private/New-JiraComment.ps1 new file mode 100644 index 0000000..997e455 --- /dev/null +++ b/WASP/Private/New-JiraComment.ps1 @@ -0,0 +1,58 @@ +function New-JiraComment { + <# + .SYNOPSIS + Adds a comment to a Jira Ticket + .DESCRIPTION + Adds a comment to a Jira Ticket + .NOTES + FileName: New-JiraComment.ps1 + Author: Julian Bopp + Contact: its-wcs-ma@unibas.ch + Created: 2025-08-21 + Version: 1.0.0 + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)][string] $issueKey, + [Parameter(Mandatory = $true)][string] $comment + ) + + begin { + $config = Read-ConfigFile + $jiraBaseUrl = $config.Application.JiraBaseUrl + $jiraUser = $config.Application.JiraUser + $jiraPassword = $config.Application.JiraPassword + $projectKey = $config.Application.ProjectKey + $issueType = $config.Application.IssueType # Story + } + + process { + $url = "$($jiraBaseUrl)/rest/api/2/issue/$issueKey/comment" + + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("${jiraUser}:${jiraPassword}"))) + + $header = @{ + "Authorization" = "Basic $base64AuthInfo" + "Content-Type" = "application/json" + } + + # Create the JSON payload for the new comment + $body = @{ + body = $comment + } | ConvertTo-Json -Depth 3 + + Write-Log -Message "Commenting jira ticket $issueKey with comment: `"$($comment)`"" -Severity 1 + + $response = Invoke-WebRequest -Uri $url -Method Post -Headers $header -Body $body + + if ($response.StatusCode -eq 201) { + Write-Log -Message "StatusCode: $($response.StatusCode)" -Severity 0 + Write-Log -Message "Jira ticket successfully commented." -Severity 0 + } else { + Write-Log -Message "Failed to comment jira ticket! StatusCode: $($response.StatusCode): $($response.StatusDescription)" -Severity 3 + } + } + + end { + } +} \ No newline at end of file diff --git a/WASP/Private/Search-JiraComment.ps1 b/WASP/Private/Search-JiraComment.ps1 new file mode 100644 index 0000000..1914df3 --- /dev/null +++ b/WASP/Private/Search-JiraComment.ps1 @@ -0,0 +1,66 @@ +function Search-JiraComment { + <# + .SYNOPSIS + Searches for a specific comment in a Jira Ticket + .DESCRIPTION + Retrieves all comments from the specified Jira ticket and searches for a comment containing the given search string. + .NOTES + FileName: Search-JiraComment.ps1 + Author: Julian Bopp + Contact: its-wcs-ma@unibas.ch + Created: 2025-11-04 + Version: 1.0.0 + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)][string] $issueKey, + [Parameter(Mandatory = $true)][string] $searchString + ) + + begin { + $config = Read-ConfigFile + $jiraBaseUrl = $config.Application.JiraBaseUrl + $jiraUser = $config.Application.JiraUser + $jiraPassword = $config.Application.JiraPassword + + $base64AuthInfo = [Convert]::ToBase64String( + [Text.Encoding]::ASCII.GetBytes("${jiraUser}:${jiraPassword}") + ) + $headers = @{ + "Authorization" = "Basic $base64AuthInfo" + "Content-Type" = "application/json" + } + + $url = "$jiraBaseUrl/rest/api/2/issue/$issueKey/comment" + } + + process { + Write-Log -Message "Searching Jira ticket $issueKey for comment containing '$searchString'" -Severity 1 + + try { + $response = Invoke-WebRequest -Uri $url -Method Get -Headers $headers -UseBasicParsing + $comments = ($response.Content | ConvertFrom-Json).comments + + if (-not $comments) { + Write-Log -Message "No comments found for Jira ticket $issueKey" -Severity 2 + return $null + } + + $matched = $comments | Where-Object { $_.body -match [Regex]::Escape($searchString) } + + if ($matched) { + Write-Log -Message "Found $($matched.Count) matching comment(s) in $issueKey" -Severity 0 + return $matched | Select-Object id, author, created, body + } else { + Write-Log -Message "No comment matching '$searchString' in $issueKey" -Severity 2 + return $null + } + } + catch { + Write-Log -Message "Failed to retrieve comments for Jira ticket $(issueKey): $($_.Exception.Message)" -Severity 3 + return $null + } + } + + end { } +} diff --git a/WASP/Private/Search-NewPackages.ps1 b/WASP/Private/Search-NewPackages.ps1 index 76f6912..a8620c5 100644 --- a/WASP/Private/Search-NewPackages.ps1 +++ b/WASP/Private/Search-NewPackages.ps1 @@ -23,21 +23,16 @@ function Search-NewPackages { $Manual ) begin { - $config = Read-ConfigFile - $GitRepo = $config.Application.PackagesWishlist - $GitFile = $GitRepo.Substring($GitRepo.LastIndexOf("/") + 1, $GitRepo.Length - $GitRepo.LastIndexOf("/") - 1) - $GitFolderName = $GitFile.Replace(".git", "") - $PackagesWishlistPath = Join-Path -Path $config.Application.BaseDirectory -ChildPath $GitFolderName - $wishlistPath = Join-Path -Path $PackagesWishlistPath -ChildPath "wishlist.txt" } process { - $wishlistContent = Get-Content -Path $wishlistPath | Select-String -Pattern "#" -NotMatch | ForEach-Object {$_ -replace "@.*", ""} foreach ($package in $Packages) { # Check if the package is in (not deactivated) wishlist. Only scan for packages that are relevant - if (!(($package.Name) -in $wishlistContent)) { + $packageName = $package.Name + $foundInWishlist = Find-PackageInWishlist -PackageName $packageName + if (!($foundInWishlist)) { continue } else { if ($Manual) { diff --git a/WASP/Private/Start-PackageDistribution.ps1 b/WASP/Private/Start-PackageDistribution.ps1 index f9dbc78..2317410 100644 --- a/WASP/Private/Start-PackageDistribution.ps1 +++ b/WASP/Private/Start-PackageDistribution.ps1 @@ -24,12 +24,6 @@ function Start-PackageDistribution() { $PackageGalleryPath = Join-Path -Path $config.Application.BaseDirectory -ChildPath $GitFolderName $OldWorkingDir = $PWD.Path #? - $GitRepo = $config.Application.PackagesWishlist - $GitFile = $GitRepo.Substring($GitRepo.LastIndexOf("/") + 1, $GitRepo.Length - $GitRepo.LastIndexOf("/") - 1) - $WishlistFolderName = $GitFile.Replace(".git", "") - $PackagesWishlistPath = Join-Path -Path $config.Application.BaseDirectory -ChildPath $WishlistFolderName - $wishlistPath = Join-Path -Path $PackagesWishlistPath -ChildPath "wishlist.txt" - $tmpdir = $env:TEMP $GitHubOrganisation = $config.Application.GitHubOrganisation } @@ -47,9 +41,6 @@ function Start-PackageDistribution() { Write-Log $remoteBranch } - - $wishlist = Get-Content -Path $wishlistPath | Where-Object { $_ -notlike "#*" } - $nameAndVersionSeparator = '@' $num_remoteBranches = $remoteBranches.Count $num_branch = 1 @@ -70,15 +61,9 @@ function Start-PackageDistribution() { } } - $foundInWishlist = $false - foreach ($line in $wishlist) { - $line = $line -replace "@.*", "" - if ($line -eq $packageName) { - $foundInWishlist = $true - } - } + $foundInWishlist = Find-PackageInWishlist -packageName $packageName if (!$foundInWishlist) { - Write-Log "Skip $packageName - deactivated in wishlist." -Severity 1 + Write-Log "Skip $packageName - deactivated in wishlist." -Severity 2 continue } $packageRootPath = Join-Path $PackageGalleryPath (Join-Path $packageName $packageVersion) @@ -91,6 +76,23 @@ function Start-PackageDistribution() { Write-Log ("Skip $packageName@$PackageVersion - No tools/ folder.") -Severity 3 continue } + + # Check if the only changes in the branch are changes to the package in question + # If other files were changed, skip the branch + $allowedPath = "$packageName/$packageVersion" + $base = git -C $PackageGalleryPath merge-base prod $branch + $changedFiles = git -C $PackageGalleryPath diff --name-only --merge-base $base + $invalidFiles = $changedFiles | Where-Object { $_ -notlike "$allowedPath/*" } + + if ($invalidFiles) { + Write-Log "Skip $packageName@$PackageVersion - Other files than $allowedPath were changed:" -Severity 2 + foreach ($file in $invalidFiles) { + Write-Log " - $file" -Severity 2 + } + $jiraKey = Get-JiraIssueKeyFromName -issueName "$packageName@$packageVersion" + Flag-JiraTicket -issueKey $jiraKey -comment "Please only change files in $allowedPath. Other changes were detected: `n$($invalidFiles -join "`n")`nPlease create a separate branch/PR for these changes." + continue + } # Check if the package, that has a dev branch is still in Development in JIRA or if it has been approved for Testing. # Only run the package distribution for packages that are in Development. $process = Test-JiraIssueForTesting -packageName $packageName -packageVersion $packageVersion @@ -198,15 +200,9 @@ function Start-PackageDistribution() { foreach ($package in $packagesList) { - $foundInWishlist = $false - foreach ($line in $wishlist) { - $line = $line -replace "@.*", "" - if ($line -eq $package) { - $foundInWishlist = $true - } - } + $foundInWishlist = Find-PackageInWishlist -packageName $package if (!$foundInWishlist) { - Write-Log "Skip Package $package`: deactivated in wishlist." -Severity 1 + Write-Log "Skip Package $package`: deactivated in wishlist." -Severity 2 continue } $packagePath = Join-Path $PackageGalleryPath $package @@ -221,10 +217,23 @@ function Start-PackageDistribution() { $FileDate = (Get-ChildItem -Path $packageRootPath | Where-Object { $_.FullName -match "\.nupkg" }).LastWriteTime # check if package is being repackaged - if ($repackagingBranches -match "$package@$FullVersion") { + if ($repackagingBranches -match "$package@$Version") { + Write-Log "Package $package with version $version is in repackaging." + $repackagingBranch = $repackagingBranches | Where-Object { $_ -like "dev/$package@$Version@*" } | Select-Object -First 1 + $latestPullRequest = Test-PullRequest -Branch $repackagingBranch + $prBranch = $latestPullRequest.Branch + $prState = $latestPullRequest.Details.state + $prMergedTime = $LatestPullRequest.Details.merged_at # only push it to test if the jira issue is in test if ($chocolateyDestinationServer -eq $config.Application.ChocoServerTEST) { if (Test-IssueStatus $package $version 'Testing') { + + if ($prBranch -eq "test" -and $prState -eq "open") { + Write-Log "PR from $repackagingBranch to test is still open. Skip pushing package to testing." + continue + } + + if (-Not (Test-ExistsOnRepo -PackageName $FullID -PackageVersion $FullVersion -Repository $Repo -FileCreationDate $FileDate)) { Write-Log "Pushing Package $FullID with version $FullVersion to $chocolateyDestinationServer." -Severity 1 Send-NupkgToServer $packageRootPath $chocolateyDestinationServer @@ -236,6 +245,26 @@ function Start-PackageDistribution() { continue } } + if ($chocolateyDestinationServer -eq $config.Application.ChocoServerPROD) { + if (Test-IssueStatus $package $version 'Production') { + + if ($prBranch -eq "prod" -and $prState -eq "open") { + Write-Log "PR from $repackagingBranch to prod is still open. Skip pushing package to production." + continue + } + + + if (-Not (Test-ExistsOnRepo -PackageName $FullID -PackageVersion $FullVersion -Repository $Repo -FileCreationDate $FileDate)) { + Write-Log "Pushing Package $FullID with version $FullVersion to $chocolateyDestinationServer." -Severity 1 + Send-NupkgToServer $packageRootPath $chocolateyDestinationServer + } + continue + } + else { + Write-Log "Package $package with version $version is in repackaging and its jira task is not in production." -Severity 1 + continue + } + } } # if package is in PROD, check if it exists in DEV as well --> make sure that if a nupkg is faulty on dev and gets deleted on dev server, it is pushed there again # goal is to be sure that the same nupkg exists on all three servers diff --git a/WASP/Private/Test-ExistPackageVersion.ps1 b/WASP/Private/Test-ExistPackageVersion.ps1 index 03dcd4a..77651c3 100644 --- a/WASP/Private/Test-ExistPackageVersion.ps1 +++ b/WASP/Private/Test-ExistPackageVersion.ps1 @@ -7,7 +7,7 @@ function Test-ExistPackageVersion { The folder is defined by $packageName/$version URL will look like this: https://api.github.com/repos/wasp-its/zzz-test-package-gallery/contents/git.install/2.37.0?ref=dev/git.install@2.37.0 .NOTES - FileName: Format-VersionString.ps1 + FileName: Test-ExistPackageVersion.ps1 Author: Kevin Schaefer, Maximilian Burgert Contact: its-wcs-ma@unibas.ch Created: 2020-30-01 diff --git a/WASP/Private/Write-JiraStateFile.ps1 b/WASP/Private/Write-JiraStateFile.ps1 index 217fcf1..9f3904b 100644 --- a/WASP/Private/Write-JiraStateFile.ps1 +++ b/WASP/Private/Write-JiraStateFile.ps1 @@ -5,7 +5,7 @@ function Write-JiraStateFile () { .Description Read the lates jira state file .Notes - FileName: Read-JiraStateFile.ps1 + FileName: Write-JiraStateFile.ps1 Author: Tim Keller Contact: tim.keller@unibas.ch Created: 2024-07-04 diff --git a/WASP/Private/Write-Log.ps1 b/WASP/Private/Write-Log.ps1 index 7f1c351..6e2da53 100644 --- a/WASP/Private/Write-Log.ps1 +++ b/WASP/Private/Write-Log.ps1 @@ -89,7 +89,7 @@ function Write-Log { $LogFiles | Sort-Object CreationTime | Select-Object -First 1 | Remove-Item } elseif ($numLogFiles -gt $MaxLogFiles) { - Get-ChildItem $LogPath | Sort-Object CreationTime | Select-Object -First ($numLogFiles - $MaxLogFiles + 1) | Remove-Item + $LogFiles | Sort-Object CreationTime | Select-Object -First ($numLogFiles - $MaxLogFiles + 1) | Remove-Item } $null = New-Item $LogFilePath -type file }