Last active
March 4, 2025 20:19
-
-
Save rleap-m/253257fbe051a67151e72aa8b4e45333 to your computer and use it in GitHub Desktop.
PowerShell wrapper which runs containerized TestRail Manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function trman { | |
<# | |
.SYNOPSIS | |
Runs (containerized) TestRail Manager CLI using credentials defined by env vars | |
.DESCRIPTION | |
This tool interacts with both the TestRail Server and the Jenkins Server to faciliate quickly creating | |
test runs (TestRail) which are categorized under a milestone and job templates (Jenkins) which with | |
only some minor edits in the UI can then be executed. The tool accepts credentials via environment variables | |
for TestRail (TESTRAIL_USER, TESTRAIL_PASSWORD) and Jenkins (JENKINS_USER, JENKINS_PASSWORD). | |
.NOTES | |
Named non-mandatory params don't work as expected with ValueFromRemainingArguments so avoiding named parms | |
.EXAMPLE | |
trman --version | |
Displays the version of the TestRail Manager command line tool | |
.EXAMPLE | |
trman tool tab-to-yaml --file-name ./patch_2025-01B/permutations.tsv ` | |
--parent-milestone-id '472' ` | |
--set-a 'rhel/7.9,rhel/8.10' ` | |
--set-b 'rhel/9.4,windows/2022' ` | |
--set-c 'ubuntu/20.04' ` | |
--output-file ./patch_2025-01B/milestone.yaml | |
Given a proper permutations file (from ver X of MCR/MKE/MSR to ver Y of MCR/MKE/MSR), this command produces a (child) | |
milestone (round) YAML file. Certain test runs (MCR+MKE+MSR) only work with specific OS platforms (cgroup v2 support | |
needed for ex) and therefore this command accommodates "platform sets" (--set-a, b, c) allowing the user to define | |
here which platforms are associated with which product test runs. | |
.EXAMPLE | |
trman milestone create-from-yaml --file ./patch_2024-11B/milestone.yaml | |
Creates a child milestone (test round) and test plans on the TestRail server based on the YAML file provided | |
.EXAMPLE | |
trman milestone yaml 484 | Out-File -FilePath ./patch_2024-11B/milestone_484.yaml | |
Outputs YAML representing the milestone (allowing you to edit it locally) as it exists on the TestRail server | |
.EXAMPLE | |
trman milestone update --file ./patch_2024-11B/milestone_484.yaml | |
Takes the (presumably edited) milestone YAML file and updates the changes accordingly on the TestRail server | |
.EXAMPLE | |
trman milestone show 484 | |
Displays a tree diagram of the milestone (given a valid milestone ID) provided, including rounds and test runs | |
.EXAMPLE | |
trman tool create-template-job 161459 | |
Creates a job in Jenkins w/out the platforms populated so you can quickly review it with the Web UI, make edits, and execute | |
To obtain a job id to supply the above command use the 'trman milestone show' command and select the appropriate run: | |
... | |
├- plan.id: 161459 | |
| .name: 01: Upgrade Patch Ver. default 2.9 23.0.16/3.7.17/2.9.22 → 23.0.16/3.7.18-rc1/2.9.22 | |
... | |
.EXAMPLE | |
trman milestone clone 484 | |
Clones the round (child milestone). This is useful when updated container product builds have been released | |
(e.g. tp1 -> tp2). When this occurs (new product builds released) you will need to: | |
1. Conclude any runs in the existing test round | |
2. Clone the round (child milestone) | |
3. Download the cloned milestone (as YAML) | |
4. Edit it with the new product set (also update the name) | |
5. Sync it back to TestRail | |
6. Create the template job(s) listed in the milestone in Jenkins and begin executing the jobs when ready | |
.EXAMPLE | |
trman report milestone 486 | Tee-Object -Path ./patch_2025-01/report-milestone-486.md | |
Creates a markdown report of the milestone (given a valid milestone ID) provided, including rounds and test runs. The contents | |
of this markdown file can then be copy/pasted directly into the Confluence report page for the milestone. | |
.LINK | |
https://mirantis.jira.com/wiki/x/A4CwWQE | |
#> | |
[cmdletbinding()] | |
param ( | |
[Parameter(ValueFromRemainingArguments = $true)] | |
[string[]] $Args | |
) | |
for ($i = 0; $i -lt $Args.Count; $i++) { | |
Write-Debug "${i}: $($Args[$i])" | |
} | |
if ($env:TESTRAIL_MANAGER_IMAGE) { | |
[string] $TestRailMgrImage = $env:TESTRAIL_MANAGER_IMAGE | |
} | |
else { | |
[string] $TestRailMgrImage = 'msr.ci.mirantis.com/ekitagawa/testrailmanager:0.0.7' | |
} | |
Write-Debug "Using TestRail container image [$TestRailMgrImage]" | |
if ($Args.Contains('--help')) { | |
Write-Verbose "Running docker container..." | |
if ($DebugPreference -ne 'SilentlyContinue') { | |
Write-Verbose "Running container with debug env flag" | |
docker container run --rm -e TESTRAIL_LOG='DEBUG' $TestRailMgrImage --help | |
} | |
else { | |
docker container run --rm $TestRailMgrImage --help | |
} | |
Write-Verbose "Running docker container complete." | |
} | |
else { | |
if (-not($env:TESTRAIL_USER)) { | |
Write-Warning "TestRail user env var (TESTRAIL_USER) not defined." | |
} | |
if (-not($env:TESTRAIL_PASSWORD)) { | |
Write-Warning "TestRail password env var (TESTRAIL_PASSWORD) not defined." | |
} | |
if (($Args[0] -eq 'tool') -and ($Args[1] -eq 'create-template-job')) { | |
if (-not($env:JENKINS_USER)) { | |
Write-Warning "Jenkins user env var (JENKINS_USER) not defined." | |
} | |
if (-not($env:JENKINS_PASSWORD)) { | |
Write-Warning "Jenkins password env var (JENKINS_PASSWORD) not defined." | |
} | |
} | |
if (($Args[0] -eq 'tool') -and ($Args[1] -eq 'tab-to-yaml')) { | |
if ($Args.Contains('--file-name')) { | |
[int] $permutationsIndex = $Args.IndexOf('--file-name') + 1 | |
[string] $permutationsPath = $Args[$permutationsIndex] | |
$Args[$permutationsIndex] = "/data/permutations.tsv" # modify the path to that of the file in the container | |
if (Test-Path -Path $permutationsPath -PathType Leaf) { | |
if ($Args.Contains('--output-file')) { | |
[int] $outputFileIndex = $Args.IndexOf('--output-file') + 1 | |
[string] $outputFileHostPath = $Args[$outputFileIndex] | |
[string] $milestoneFile = Split-Path -Path $outputFileHostPath -Leaf | |
$Args[$outputFileIndex] = $milestoneFile # trman CLI expects a filename (not a path to a file) | |
if ($milestoneFile) { | |
Write-Verbose "Running docker container with a volume to access permuations file [$permutationsPath]." | |
Write-Debug 'docker container run -d `' | |
Write-Debug " -v `"$($permutationsPath):$($Args[$permutationsIndex])`" ``" | |
if ($DebugPreference -ne 'SilentlyContinue') { | |
Write-Debug " -e TESTRAIL_LOG='DEBUG'" | |
} | |
Write-Debug " -e TESTRAIL_USER=`$env:TESTRAIL_USER ``" | |
Write-Debug " -e TESTRAIL_PASSWORD=`$env:TESTRAIL_PASSWORD ``" | |
Write-Debug " -e JENKINS_USER=`$env:JENKINS_USER ``" | |
Write-Debug " -e JENKINS_PASSWORD=`$env:JENKINS_PASSWORD ``" | |
Write-Debug " $TestRailMgrImage $Args" | |
if ($DebugPreference -ne 'SilentlyContinue') { | |
$containerId = docker container run -d ` | |
-v "$($permutationsPath):$($Args[$permutationsIndex])" ` | |
-e TESTRAIL_LOG='DEBUG' ` | |
-e TESTRAIL_USER=$env:TESTRAIL_USER ` | |
-e TESTRAIL_PASSWORD=$env:TESTRAIL_PASSWORD ` | |
-e JENKINS_USER=$env:JENKINS_USER ` | |
-e JENKINS_PASSWORD=$env:JENKINS_PASSWORD ` | |
$TestRailMgrImage $Args | |
} | |
else { | |
$containerId = docker container run -d ` | |
-v "$($permutationsPath):$($Args[$permutationsIndex])" ` | |
-e TESTRAIL_USER=$env:TESTRAIL_USER ` | |
-e TESTRAIL_PASSWORD=$env:TESTRAIL_PASSWORD ` | |
-e JENKINS_USER=$env:JENKINS_USER ` | |
-e JENKINS_PASSWORD=$env:JENKINS_PASSWORD ` | |
$TestRailMgrImage $Args | |
} | |
Write-Verbose "Running docker container complete." | |
docker wait $containerId | Out-Null | |
docker container cp "$($containerId):/app/$milestoneFile" $outputFileHostPath | |
if (-not(Test-Path -Path $outputFileHostPath -PathType Leaf)) { | |
Write-Warning "Milestone file [$outputFileHostPath] not generated." | |
} | |
else { | |
Write-Verbose "Removing container with ID [$containerId]." | |
docker container rm $containerId | Out-Null | |
Write-Verbose "Removing container with ID [$containerId] complete." | |
} | |
} | |
else { | |
Write-Warning "Milestone output file not provided!" | |
} | |
} | |
else { | |
Write-Warning "Option '--output-file' not provided." | |
} | |
} | |
else { | |
Write-Warning "Permutations file [$permutationsPath] missing!" | |
} | |
} | |
else { | |
Write-Warning "Option '--file-name' not provided." | |
} | |
} | |
elseif (($Args[0] -eq 'milestone') -and (($Args[1] -eq 'create-from-yaml') -or ($Args[1] -eq 'update'))) { | |
if ($Args.Contains('--file')) { | |
[int] $milestoneIndex = $Args.IndexOf('--file') + 1 | |
[string] $milestonePath = $Args[$milestoneIndex] | |
if (Test-Path -Path $milestonePath -PathType Leaf) { | |
Write-Verbose "Running docker container with a volume to access to milestone file [$milestonePath]." | |
$Args[$milestoneIndex] = Join-Path '/data' -ChildPath (Split-Path -Path $milestonePath -Leaf) # modify the path to that of the file in the container | |
Write-Debug 'docker container run --rm `' | |
Write-Debug " -v `"$($milestonePath):$($Args[$milestoneIndex])`" ``" | |
if ($DebugPreference -ne 'SilentlyContinue') { | |
Write-Debug " -e TESTRAIL_LOG='DEBUG'" | |
} | |
Write-Debug " -e TESTRAIL_USER=`$env:TESTRAIL_USER ``" | |
Write-Debug " -e TESTRAIL_PASSWORD=`$env:TESTRAIL_PASSWORD ``" | |
Write-Debug " -e JENKINS_USER=`$env:JENKINS_USER ``" | |
Write-Debug " -e JENKINS_PASSWORD=`$env:JENKINS_PASSWORD ``" | |
Write-Debug " $TestRailMgrImage $Args" | |
if ($DebugPreference -ne 'SilentlyContinue') { | |
docker container run --rm ` | |
-v "$($milestonePath):$($Args[$milestoneIndex])" ` | |
-e TESTRAIL_LOG='DEBUG' ` | |
-e TESTRAIL_USER=$env:TESTRAIL_USER ` | |
-e TESTRAIL_PASSWORD=$env:TESTRAIL_PASSWORD ` | |
-e JENKINS_USER=$env:JENKINS_USER ` | |
-e JENKINS_PASSWORD=$env:JENKINS_PASSWORD ` | |
$TestRailMgrImage $Args | |
} | |
else { | |
docker container run --rm ` | |
-v "$($milestonePath):$($Args[$milestoneIndex])" ` | |
-e TESTRAIL_USER=$env:TESTRAIL_USER ` | |
-e TESTRAIL_PASSWORD=$env:TESTRAIL_PASSWORD ` | |
-e JENKINS_USER=$env:JENKINS_USER ` | |
-e JENKINS_PASSWORD=$env:JENKINS_PASSWORD ` | |
$TestRailMgrImage $Args | |
} | |
Write-Verbose "Running docker container complete." | |
} | |
else { | |
Write-Warning "Milestone file [$milestonePath] does not exist!" | |
} | |
} | |
else { | |
Write-Warning "Option '--file' not provided." | |
} | |
} | |
else { | |
Write-Verbose "Running docker container..." | |
if ($DebugPreference -ne 'SilentlyContinue') { | |
docker container run --rm ` | |
-e TESTRAIL_LOG='DEBUG' ` | |
-e TESTRAIL_USER=$env:TESTRAIL_USER ` | |
-e TESTRAIL_PASSWORD=$env:TESTRAIL_PASSWORD ` | |
-e JENKINS_USER=$env:JENKINS_USER ` | |
-e JENKINS_PASSWORD=$env:JENKINS_PASSWORD ` | |
$TestRailMgrImage $Args | |
} | |
else { | |
docker container run --rm ` | |
-e TESTRAIL_USER=$env:TESTRAIL_USER ` | |
-e TESTRAIL_PASSWORD=$env:TESTRAIL_PASSWORD ` | |
-e JENKINS_USER=$env:JENKINS_USER ` | |
-e JENKINS_PASSWORD=$env:JENKINS_PASSWORD ` | |
$TestRailMgrImage $Args | |
} | |
Write-Verbose "Running docker container complete." | |
} | |
} | |
} | |
function Get-TestRailTestRun { | |
<# | |
.SYNOPSIS | |
Gets the TestRail test run(s) belonging to a milestone | |
.PARAMETER MilestoneId | |
ID of a milestone | |
.PARAMETER TestRunId | |
ID of a Test Run (plan.id) within the milestone | |
.EXAMPLE | |
Get-TestRailTestRun -MilestoneId 469 | |
Gets all the test runs within the milestone | |
.EXAMPLE | |
Get-TestRailTestRun -MilestoneId 469 -TestRunId 161002 | |
Gets the specified test run belonging to the milestone | |
.EXAMPLE | |
Get-TestRailTestRun -MilestoneId 469 | Where-Object {(!$_.crossv) -and (!$_.cncf)} | | |
Select-Object -Property * -ExcludeProperty name,os,crossv,cncf | Format-Table -Wrap | |
Gets the test runs which are not cross version upgrades or CNCF Conformance tests | |
.EXAMPLE | |
$propBag = @('id','shortname','swarmo','crossv','MCRVerBase','MCRVerUp','MKEVerBase','MKEVerUp','MSRVerBase','MSRVerUp') | |
Get-TestRailTestRun -MilestoneId 469 | Select-Object -Property $propBag | Format-Table | |
Gets the 'upgrade from' and 'upgrade to' properties of all the Mirantis products for each test run | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true)] | |
[int16] $MilestoneId, | |
[Parameter(Mandatory = $false)] | |
[int[]] $TestRunId | |
) | |
$installed = Try { $null = Get-command -Name yj -ErrorAction Stop; $true } catch { $false } | |
if (-not($installed)) { | |
Write-Warning "Required utility [yj] not installed. See https://github.com/sclevine/yj for installation details." | |
return | |
} | |
$milestone = trman milestone yaml $MilestoneId | yj | ConvertFrom-Json | |
if ($milestone) { | |
if ($TestRunId) { | |
$milestone.test_plans = $milestone.test_plans | Where-Object { $_.id -in $TestRunId } | |
if ($null -eq $milestone.test_plans) { | |
Write-Warning "Unable to obtain a test run with ID(s) [$($TestRunId -join ',')] for milestone ID [$MilestoneId]" | |
} | |
} | |
foreach ($testPlan in $milestone.test_plans) { | |
Clear-Variable -Name 'mode','sut','type','script','osList','sutFrom','sutTo','fromMcr','fromMke','fromMsr','toMcr','toMke','toMsr' -ErrorAction SilentlyContinue | |
Clear-Variable -Name 'fromMcrVer','fromMcrChannel','toMcrVer','toMcrChannel','fromMkeVer','fromMkeRepo','toMkeVer','toMkeRepo' -ErrorAction SilentlyContinue | |
$mode,$sut,$type,$script,$osList = $testPlan.description -split [System.Environment]::NewLine | |
$sutFrom,$sutTo = $sut.Substring('SUT:'.Length).Trim() -split ' ' | |
$fromMcr,$fromMke,$fromMsr = $sutFrom -split ',' | |
$toMcr,$toMke,$toMsr = $sutTo -split ',' | |
if ($fromMcr) { | |
if ($fromMcr -match '\d') { | |
$fromMcrVer = $fromMcr.Substring($fromMcr.IndexOf($Matches[0])) | |
$fromMcrChannel = $fromMcr.Substring(0,$fromMcr.IndexOf($Matches[0])-1) | |
} | |
} | |
if ($toMcr) { | |
if ($toMcr -match '\d') { | |
$toMcrVer = $toMcr.Substring($toMcr.IndexOf($Matches[0])) | |
$toMcrChannel = $toMcr.Substring(0,$toMcr.IndexOf($Matches[0])-1) | |
} | |
} | |
if ($fromMke) { | |
$fromMkeVer = $fromMke.Substring($fromMke.LastIndexOf('/')+1) | |
$fromMkeRepo = $fromMke.Substring(0,$fromMke.LastIndexOf('/')) | |
} | |
if ($toMke) { | |
$toMkeVer = $toMke.Substring($toMke.LastIndexOf('/')+1) | |
$toMkeRepo = $toMke.Substring(0,$toMke.LastIndexOf('/')) | |
} | |
if ($fromMsr) { | |
$fromMsrVer = $fromMsr.Substring($fromMsr.LastIndexOf('/')+1) | |
$fromMsrRepo = $fromMsr.Substring(0,$fromMsr.LastIndexOf('/')) | |
} | |
if ($toMsr) { | |
$toMsrVer = $toMsr.Substring($toMsr.LastIndexOf('/')+1) | |
$toMsrRepo = $toMsr.Substring(0,$toMsr.LastIndexOf('/')) | |
} | |
[PSCustomObject]@{ | |
parentid = $MilestoneId | |
id = $testPlan.id | |
name = $testPlan.Name | |
shortname = $testPlan.Name.Substring(0,15).trim() + '...' | |
mode = $mode.Substring('Mode:'.Length).Trim() | |
swarmo = $testPlan.Name.Contains('swarm-only') | |
crossv = $testPlan.Name.Contains('Cross') | |
cncf = $testPlan.Name.Contains('CNCF') | |
testcase = $script.Substring('SCRIPT:'.Length).Trim() | |
MCRVerBase = $fromMcrVer | |
MCRChanBase = $fromMcrChannel | |
MCRVerUp = $toMcrVer | |
MCRChanUp = $toMcrChannel | |
MKEVerBase = $fromMkeVer | |
MKERepoBase = $fromMkeRepo | |
MKEVerUp = $toMkeVer | |
MKERepoUp = $toMkeRepo | |
MSRVerBase = $fromMsrVer | |
MSRRepoBase = $fromMsrRepo | |
MSRVerUp = $toMsrVer | |
MSRRepoUp = $toMsrRepo | |
OS = @($osList.Substring('OS:'.Length).Trim() -split ',') | |
} | |
} | |
} | |
else { | |
Write-Warning "Unable to obtain milestone data for milestone ID [$MilestoneId]" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment