diff --git a/.docs/Add-VSTeamPool.md b/.docs/Add-VSTeamPool.md new file mode 100644 index 000000000..0d8f2c8a0 --- /dev/null +++ b/.docs/Add-VSTeamPool.md @@ -0,0 +1,86 @@ + + +# Add-VSTeamPool + +## SYNOPSIS + + + +## SYNTAX + +## DESCRIPTION + + + +## EXAMPLES + +### Example 1 + +```powershell +Add-VSTeamPool -Name "TestPool" -Description "Test Description" -AutoProvision -AutoAuthorize -NoAutoUpdates +``` + +It's creating an agent pool with the name "TestPool" that is auto provisioning to every project and also pre-authorized to be used with every pipeline. Agent software is not updated automatically. + +## PARAMETERS + +### -Name + +Name of the pool to create. + +```yaml +Type: string +Required: True +``` + +### -Description + +Description of the pool to create. + +```yaml +Type: string +Required: False +``` + +### -AutoProvision + +Auto-provision this agent pool in new projects. + +```yaml +Type: string +Required: True +``` + +### -AutoAuthorize + +Grant access permission to all pipelines. + +```yaml +Type: string +Required: True +``` + +### -NoAutoUpdates + +Turn off automatic updates of agents in the pool. Default is turned on. + +```yaml +Type: string +Required: True +``` + +## INPUTS + +## OUTPUTS + +### System.Object + +## NOTES + +## RELATED LINKS + +[Remove-VSTeamPool](Remove-VSTeamPool.md) +[Update-VSTeamPool](Update-VSTeamPool.md) +[Get-VSTeamPool](Get-VSTeamPool.md) + + diff --git a/.docs/Get-VSTeamPool.md b/.docs/Get-VSTeamPool.md index 17c5c4c59..c15ee198b 100644 --- a/.docs/Get-VSTeamPool.md +++ b/.docs/Get-VSTeamPool.md @@ -42,4 +42,8 @@ Accept pipeline input: true (ByPropertyName) ## RELATED LINKS - +[Remove-VSTeamAccount](Remove-VSTeamAccount.md) +[Update-VSTeamAccount](Update-VSTeamAccount.md) +[Add-VSTeamAccount](Add-VSTeamAccount.md) + + \ No newline at end of file diff --git a/.docs/Remove-VSTeamPool.md b/.docs/Remove-VSTeamPool.md new file mode 100644 index 000000000..65db5bb42 --- /dev/null +++ b/.docs/Remove-VSTeamPool.md @@ -0,0 +1,44 @@ + + +# Remove-VSTeamPool + +## SYNOPSIS + + + +## SYNTAX + +## DESCRIPTION + + + +## EXAMPLES + +## PARAMETERS + +### -Id + +Id of the pool to delete. + +```yaml +Type: int +Parameter Sets: ByID +Aliases: PoolID +Required: True +Accept pipeline input: true (ByPropertyName) +``` + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS + +[Add-VSTeamPool](Add-VSTeamPool.md) +[Update-VSTeamPool](Update-VSTeamPool.md) +[Get-VSTeamPool](Get-VSTeamPool.md) + + + \ No newline at end of file diff --git a/.docs/Update-VSTeamPool.md b/.docs/Update-VSTeamPool.md new file mode 100644 index 000000000..a8ef0add3 --- /dev/null +++ b/.docs/Update-VSTeamPool.md @@ -0,0 +1,90 @@ + + +# Update-VSTeamPool + +## SYNOPSIS + + + +## SYNTAX + +## DESCRIPTION + + + +## EXAMPLES + +### Example 1 + +```powershell +Update-VSTeamPool -Id 13 -Name "UpdatedTestPoolName" -Description "Test Description" -AutoProvision -NoAutoUpdates +``` +Updates the pool with id 13 with the name new "UpdatedTestPoolName" that is auto-provisioning to every project and agent software is not updating automatically. + +## PARAMETERS + +### -Id + +Id of the pool to return. + +```yaml +Type: int +Parameter Sets: ByID +Aliases: PoolID +Required: True +Accept pipeline input: true (ByPropertyName) +``` + +### -Name + +Name of the pool to update. + +```yaml +Type: string +Required: True +``` + +### -Description + +Description of the pool to update. + +```yaml +Type: string +Required: False +``` + +### -AutoProvision + +Auto-provision this agent pool in new projects. + +```yaml +Type: string +Required: True +``` + +### -NoAutoUpdates + +Turn off automatic updates of agents in the pool. Default is turned on. + +```yaml +Type: string +Required: True +``` + +## INPUTS + +### System.String + +## OUTPUTS + +### System.Object + +## NOTES + +## RELATED LINKS + +[Remove-VSTeamPool](Remove-VSTeamPool.md) +[Add-VSTeamPool](Add-VSTeamPool.md) +[Get-VSTeamPool](Get-VSTeamPool.md) + + diff --git a/.docs/synopsis/Add-VSTeamPool.md b/.docs/synopsis/Add-VSTeamPool.md new file mode 100644 index 000000000..b8ffc3351 --- /dev/null +++ b/.docs/synopsis/Add-VSTeamPool.md @@ -0,0 +1 @@ +Adds a new agent pool. \ No newline at end of file diff --git a/.docs/synopsis/Remove-VSTeamPool.md b/.docs/synopsis/Remove-VSTeamPool.md new file mode 100644 index 000000000..7b09db8ef --- /dev/null +++ b/.docs/synopsis/Remove-VSTeamPool.md @@ -0,0 +1 @@ +Removes the agent pool. \ No newline at end of file diff --git a/.docs/synopsis/Update-VSTeamPool.md b/.docs/synopsis/Update-VSTeamPool.md new file mode 100644 index 000000000..b2baa8966 --- /dev/null +++ b/.docs/synopsis/Update-VSTeamPool.md @@ -0,0 +1 @@ +Updates an agent pool. \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 6430bb7c5..96570f910 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -56,5 +56,6 @@ ], "powershell.pester.useLegacyCodeLens": false, "powershell.pester.outputVerbosity": "Detailed", - "powershell.codeFormatting.addWhitespaceAroundPipe": true + "powershell.codeFormatting.addWhitespaceAroundPipe": true, + "gitdoc.enabled": false } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dc71d594..820874d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 7.3.0 + +Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/384) from [Sebastian Schütze](https://github.com/SebastianSchuetze) which included the following: + +- Add-VSTeamPool, Remove-VSTeamPool and Update-VSTeampool for handling agent pools on Azure DevOps + ## 7.2.0 Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/371) and (https://github.com/DarqueWarrior/vsteam/pull/389) from [Sebastian Schütze](https://github.com/SebastianSchuetze) which included the following: @@ -25,6 +31,7 @@ Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/350) from [Da Also added Clear-VSTeamDefaultProjectCount and Set-VSTeamDefaultProjectCount to control the default number of projects returned for tab completion and validation. By default only 100 projects are returned and the 100 returned is nondeterministic. But calling Set-VSTeamDefaultProjectCount you can increase the number of projects returned. + ## 7.1.2 Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/366) from [Jhoneill](https://github.com/jhoneill) which included the following: diff --git a/Source/Classes/Attribute/TimeZonesValidateAttribute.cs b/Source/Classes/Attribute/TimeZonesValidateAttribute.cs new file mode 100644 index 000000000..f6aff6321 --- /dev/null +++ b/Source/Classes/Attribute/TimeZonesValidateAttribute.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace vsteam_lib +{ + public class TimeZoneValidateAttribute : BaseValidateArgumentsAttribute + { + internal override IEnumerable GetValues() => TimeZones.GetTimeZoneIds(); + } + +} diff --git a/Source/Classes/Completer/BaseCompleter.cs b/Source/Classes/Completer/BaseCompleter.cs index 1e756d127..aac4317f3 100644 --- a/Source/Classes/Completer/BaseCompleter.cs +++ b/Source/Classes/Completer/BaseCompleter.cs @@ -11,40 +11,71 @@ namespace vsteam_lib { - public abstract class BaseCompleter : IArgumentCompleter - { - protected readonly IPowerShell _powerShell; - - /// - /// This constructor is used during unit testings - /// - /// fake instance of IPowerShell used for testing - protected BaseCompleter(IPowerShell powerShell) { this._powerShell = powerShell; } - - /// - /// This constructor is used when running in a PowerShell session. It cannot be - /// loaded in a unit test. - /// - [ExcludeFromCodeCoverage] - protected BaseCompleter() : this(new PowerShellWrapper(RunspaceMode.CurrentRunspace)) { } - - public abstract IEnumerable CompleteArgument(string commandName, - string parameterName, - string wordToComplete, - CommandAst commandAst, - IDictionary fakeBoundParameters); - - protected static void SelectValues(string wordToComplete, IEnumerable words, List values) - { - foreach (var word in words) - { - if (string.IsNullOrEmpty(wordToComplete) || word.StartsWith(wordToComplete, true, CultureInfo.InvariantCulture)) + public abstract class BaseCompleter : IArgumentCompleter + { + protected readonly IPowerShell _powerShell; + + /// + /// This constructor is used during unit testings + /// + /// fake instance of IPowerShell used for testing + protected BaseCompleter(IPowerShell powerShell) { this._powerShell = powerShell; } + + /// + /// This constructor is used when running in a PowerShell session. It cannot be + /// loaded in a unit test. + /// + [ExcludeFromCodeCoverage] + protected BaseCompleter() : this(new PowerShellWrapper(RunspaceMode.CurrentRunspace)) { } + + public abstract IEnumerable CompleteArgument(string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters); + + protected static void SelectValues(string wordToComplete, IEnumerable words, List values) + { + foreach (var word in words) + { + if (string.IsNullOrEmpty(wordToComplete) || word.StartsWith(wordToComplete, true, CultureInfo.InvariantCulture)) + { + values.Add(GetCompletionResult(word)); + } + } + } + + + private static CompletionResult GetCompletionResult(string word) + { + return GetCompletionResult(word,word,CompletionResultType.Text); + } + + private static CompletionResult GetCompletionResult(string word, string wordLabel, CompletionResultType resultType) + { + // Only wrap in single quotes if they have a space. This makes it easier + // to use on macOs + return (new CompletionResult(word.Contains(" ") ? $"'{word}'" : word, wordLabel, resultType, wordLabel + ": " + word)); + + } + + protected static void SelectValues(string wordToComplete, + Dictionary words, + List values) + { + foreach (var word in words) { - // Only wrap in single quotes if they have a space. This makes it easier - // to use on macOs - values.Add(new CompletionResult(word.Contains(" ") ? $"'{word}'" : word)); + var keyToSearch = word.Key.ToLower(); + var valueToSearch = word.Value.ToLower(); + + if (string.IsNullOrEmpty(wordToComplete) || + keyToSearch.Contains(wordToComplete) || + valueToSearch.Contains(wordToComplete) + ) + { + values.Add(GetCompletionResult(word.Key, word.Value, CompletionResultType.Text)); + } } - } - } - } + } + } } diff --git a/Source/Classes/Completer/TimeZoneCompleter.cs b/Source/Classes/Completer/TimeZoneCompleter.cs new file mode 100644 index 000000000..ab4920a9d --- /dev/null +++ b/Source/Classes/Completer/TimeZoneCompleter.cs @@ -0,0 +1,38 @@ +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using System.Management.Automation.Abstractions; +using System.Management.Automation.Language; + +namespace vsteam_lib +{ + public class TimeZoneCompleter : BaseCompleter + { + /// + /// This constructor is used when running in a PowerShell session. It cannot be + /// loaded in a unit test. + /// + [ExcludeFromCodeCoverage] + public TimeZoneCompleter() : base() { } + + + public TimeZoneCompleter(IPowerShell powerShell) : base(powerShell) { } + + public override IEnumerable CompleteArgument(string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) + { + var values = new List(); + var timeZones = TimeZones.GetTimeZones(); + + SelectValues(wordToComplete,timeZones,values); + + return values; + } + + } +} diff --git a/Source/Classes/GlobalSuppressions.cs b/Source/Classes/GlobalSuppressions.cs index 67e372edf..5a20f3b15 100644 --- a/Source/Classes/GlobalSuppressions.cs +++ b/Source/Classes/GlobalSuppressions.cs @@ -8,3 +8,4 @@ [assembly: SuppressMessage("Performance", "CA1813:Avoid unsealed attributes", Justification = "I need this file unsealed so my test project can derive from it to gain access to the protected method.", Scope = "type", Target = "~T:vsteam_lib.ProcessTemplateValidateAttribute")] [assembly: SuppressMessage("Performance", "CA1813:Avoid unsealed attributes", Justification = "I need this file unsealed so my test project can derive from it to gain access to the protected method.", Scope = "type", Target = "~T:vsteam_lib.ProjectValidateAttribute")] [assembly: SuppressMessage("Performance", "CA1813:Avoid unsealed attributes", Justification = "I need this file unsealed so my test project can derive from it to gain access to the protected method.", Scope = "type", Target = "~T:vsteam_lib.WorkItemTypeValidateAttribute")] +[assembly: SuppressMessage("Performance", "CA1813:Avoid unsealed attributes", Justification = "I need this file unsealed so my test project can derive from it to gain access to the protected method.", Scope = "type", Target = "~T:vsteam_lib.TimeZoneValidateAttribute")] diff --git a/Source/Classes/Provider/AgentPoolMaintenanceDays.cs b/Source/Classes/Provider/AgentPoolMaintenanceDays.cs new file mode 100644 index 000000000..7179963f6 --- /dev/null +++ b/Source/Classes/Provider/AgentPoolMaintenanceDays.cs @@ -0,0 +1,16 @@ +using System; + +namespace vsteam_lib +{ + [Flags] + public enum AgentPoolMaintenanceDays + { + Monday = 1, + Tuesday = 2, + Wednesday = 4, + Thursday = 8, + Friday = 16, + Saturday = 32, + Sunday = 64 + } +} diff --git a/Source/Classes/TimeZones.cs b/Source/Classes/TimeZones.cs new file mode 100644 index 000000000..9ff671660 --- /dev/null +++ b/Source/Classes/TimeZones.cs @@ -0,0 +1,165 @@ +using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; + +namespace vsteam_lib +{ + public static class TimeZones + { + + public static Dictionary GetTimeZones() + { + + var timeZones = new Dictionary(){ + {"Dateline Standard Time", "(UTC-12:00) International Date Line West"}, + {"UTC-11", "(UTC-11:00) Coordinated Universal Time-11"}, + {"Aleutian Standard Time", "(UTC-10:00) Aleutian Islands"}, + {"Hawaiian Standard Time","(UTC-10:00) Hawaii"}, + {"Marquesas Standard Time","(UTC-09:30) Marquesas Islands"}, + {"Alaskan Standard Time","(UTC-09:00) Alaska"}, + {"UTC-09","(UTC-09:00) Coordinated Universal Time-09"}, + {"Pacific Standard Time (Mexico)","(UTC-08:00) Baja California"}, + {"UTC-08","(UTC-08:00) Coordinated Universal Time-08"}, + {"Pacific Standard Time","(UTC-08:00) Pacific Time (US \u0026 Canada)"}, + {"US Mountain Standard Time","(UTC-07:00) Arizona"}, + {"Mountain Standard Time (Mexico)","(UTC-07:00) Chihuahua, La Paz, Mazatlan"}, + {"Mountain Standard Time","(UTC-07:00) Mountain Time (US \u0026 Canada)"}, + {"Yukon Standard Time","(UTC-07:00) Yukon"}, + {"Central America Standard Time","(UTC-06:00) Central America"}, + {"Central Standard Time","(UTC-06:00) Central Time (US \u0026 Canada)"}, + {"Easter Island Standard Time","(UTC-06:00) Easter Island"}, + {"Central Standard Time (Mexico)","(UTC-06:00) Guadalajara, Mexico City, Monterrey"}, + {"Canada Central Standard Time","(UTC-06:00) Saskatchewan"}, + {"SA Pacific Standard Time","(UTC-05:00) Bogota, Lima, Quito, Rio Branco"}, + {"Eastern Standard Time (Mexico)","(UTC-05:00) Chetumal"}, + {"Eastern Standard Time","(UTC-05:00) Eastern Time (US \u0026 Canada)"}, + {"Haiti Standard Time","(UTC-05:00) Haiti"}, + {"Cuba Standard Time","(UTC-05:00) Havana"}, + {"US Eastern Standard Time","(UTC-05:00) Indiana (East)"}, + {"Turks And Caicos Standard Time","(UTC-05:00) Turks and Caicos"}, + {"Paraguay Standard Time","(UTC-04:00) Asuncion"}, + {"Atlantic Standard Time","(UTC-04:00) Atlantic Time (Canada)"}, + {"Venezuela Standard Time","(UTC-04:00) Caracas"}, + {"Central Brazilian Standard Time","(UTC-04:00) Cuiaba"}, + {"SA Western Standard Time","(UTC-04:00) Georgetown, La Paz, Manaus, San Juan"}, + {"Pacific SA Standard Time","(UTC-04:00) Santiago"}, + {"Newfoundland Standard Time","(UTC-03:30) Newfoundland"}, + {"Tocantins Standard Time","(UTC-03:00) Araguaina"}, + {"E. South America Standard Time","(UTC-03:00) Brasilia"}, + {"SA Eastern Standard Time","(UTC-03:00) Cayenne, Fortaleza"}, + {"Argentina Standard Time","(UTC-03:00) City of Buenos Aires"}, + {"Greenland Standard Time","(UTC-03:00) Greenland"}, + {"Montevideo Standard Time","(UTC-03:00) Montevideo"}, + {"Magallanes Standard Time","(UTC-03:00) Punta Arenas"}, + {"Saint Pierre Standard Time","(UTC-03:00) Saint Pierre and Miquelon"}, + {"Bahia Standard Time","(UTC-03:00) Salvador"}, + {"UTC-02","(UTC-02:00) Coordinated Universal Time-02"}, + {"Mid-Atlantic Standard Time","(UTC-02:00) Mid-Atlantic - Old"}, + {"Azores Standard Time","(UTC-01:00) Azores"}, + {"Cape Verde Standard Time","(UTC-01:00) Cabo Verde Is."}, + {"UTC","(UTC) Coordinated Universal Time"}, + {"GMT Standard Time","(UTC+00:00) Dublin, Edinburgh, Lisbon, London"}, + {"Greenwich Standard Time","(UTC+00:00) Monrovia, Reykjavik"}, + {"Sao Tome Standard Time","(UTC+00:00) Sao Tome"}, + {"Morocco Standard Time","(UTC+01:00) Casablanca"}, + {"W. Europe Standard Time","(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, + {"Central Europe Standard Time","(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague"}, + {"Romance Standard Time","(UTC+01:00) Brussels, Copenhagen, Madrid, Paris"}, + {"Central European Standard Time","(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb"}, + {"W. Central Africa Standard Time","(UTC+01:00) West Central Africa"}, + {"Jordan Standard Time","(UTC+02:00) Amman"}, + {"GTB Standard Time","(UTC+02:00) Athens, Bucharest"}, + {"Middle East Standard Time","(UTC+02:00) Beirut"}, + {"Egypt Standard Time","(UTC+02:00) Cairo"}, + {"E. Europe Standard Time","(UTC+02:00) Chisinau"}, + {"Syria Standard Time","(UTC+02:00) Damascus"}, + {"West Bank Standard Time","(UTC+02:00) Gaza, Hebron"}, + {"South Africa Standard Time","(UTC+02:00) Harare, Pretoria"}, + {"FLE Standard Time","(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius"}, + {"Israel Standard Time","(UTC+02:00) Jerusalem"}, + {"Kaliningrad Standard Time","(UTC+02:00) Kaliningrad"}, + {"Sudan Standard Time","(UTC+02:00) Khartoum"}, + {"Libya Standard Time","(UTC+02:00) Tripoli"}, + {"Namibia Standard Time","(UTC+02:00) Windhoek"}, + {"Arabic Standard Time","(UTC+03:00) Baghdad"}, + {"Turkey Standard Time","(UTC+03:00) Istanbul"}, + {"Arab Standard Time","(UTC+03:00) Kuwait, Riyadh"}, + {"Belarus Standard Time","(UTC+03:00) Minsk"}, + {"Russian Standard Time","(UTC+03:00) Moscow, St. Petersburg"}, + {"E. Africa Standard Time","(UTC+03:00) Nairobi"}, + {"Iran Standard Time","(UTC+03:30) Tehran"}, + {"Arabian Standard Time","(UTC+04:00) Abu Dhabi, Muscat"}, + {"Astrakhan Standard Time","(UTC+04:00) Astrakhan, Ulyanovsk"}, + {"Azerbaijan Standard Time","(UTC+04:00) Baku"}, + {"Russia Time Zone 3","(UTC+04:00) Izhevsk, Samara"}, + {"Mauritius Standard Time","(UTC+04:00) Port Louis"}, + {"Saratov Standard Time","(UTC+04:00) Saratov"}, + {"Georgian Standard Time","(UTC+04:00) Tbilisi"}, + {"Volgograd Standard Time","(UTC+04:00) Volgograd"}, + {"Caucasus Standard Time","(UTC+04:00) Yerevan"}, + {"Afghanistan Standard Time","(UTC+04:30) Kabul"}, + {"West Asia Standard Time","(UTC+05:00) Ashgabat, Tashkent"}, + {"Ekaterinburg Standard Time","(UTC+05:00) Ekaterinburg"}, + {"Pakistan Standard Time","(UTC+05:00) Islamabad, Karachi"}, + {"Qyzylorda Standard Time","(UTC+05:00) Qyzylorda"}, + {"India Standard Time","(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi"}, + {"Sri Lanka Standard Time","(UTC+05:30) Sri Jayawardenepura"}, + {"Nepal Standard Time","(UTC+05:45) Kathmandu"}, + {"Central Asia Standard Time","(UTC+06:00) Astana"}, + {"Bangladesh Standard Time","(UTC+06:00) Dhaka"}, + {"Omsk Standard Time","(UTC+06:00) Omsk"}, + {"Myanmar Standard Time","(UTC+06:30) Yangon (Rangoon)"}, + {"SE Asia Standard Time","(UTC+07:00) Bangkok, Hanoi, Jakarta"}, + {"Altai Standard Time","(UTC+07:00) Barnaul, Gorno-Altaysk"}, + {"W. Mongolia Standard Time","(UTC+07:00) Hovd"}, + {"North Asia Standard Time","(UTC+07:00) Krasnoyarsk"}, + {"N. Central Asia Standard Time","(UTC+07:00) Novosibirsk"}, + {"Tomsk Standard Time","(UTC+07:00) Tomsk"}, + {"China Standard Time","(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi"}, + {"North Asia East Standard Time","(UTC+08:00) Irkutsk"}, + {"Singapore Standard Time","(UTC+08:00) Kuala Lumpur, Singapore"}, + {"W. Australia Standard Time","(UTC+08:00) Perth"}, + {"Taipei Standard Time","(UTC+08:00) Taipei"}, + {"Ulaanbaatar Standard Time","(UTC+08:00) Ulaanbaatar"}, + {"Aus Central W. Standard Time","(UTC+08:45) Eucla"}, + {"Transbaikal Standard Time","(UTC+09:00) Chita"}, + {"Tokyo Standard Time","(UTC+09:00) Osaka, Sapporo, Tokyo"}, + {"North Korea Standard Time","(UTC+09:00) Pyongyang"}, + {"Korea Standard Time","(UTC+09:00) Seoul"}, + {"Yakutsk Standard Time","(UTC+09:00) Yakutsk"}, + {"Cen. Australia Standard Time","(UTC+09:30) Adelaide"}, + {"AUS Central Standard Time","(UTC+09:30) Darwin"}, + {"E. Australia Standard Time","(UTC+10:00) Brisbane"}, + {"AUS Eastern Standard Time","(UTC+10:00) Canberra, Melbourne, Sydney"}, + {"West Pacific Standard Time","(UTC+10:00) Guam, Port Moresby"}, + {"Tasmania Standard Time","(UTC+10:00) Hobart"}, + {"Vladivostok Standard Time","(UTC+10:00) Vladivostok"}, + {"Lord Howe Standard Time","(UTC+10:30) Lord Howe Island"}, + {"Bougainville Standard Time","(UTC+11:00) Bougainville Island"}, + {"Russia Time Zone 10","(UTC+11:00) Chokurdakh"}, + {"Magadan Standard Time","(UTC+11:00) Magadan"}, + {"Norfolk Standard Time","(UTC+11:00) Norfolk Island"}, + {"Sakhalin Standard Time","(UTC+11:00) Sakhalin"}, + {"Central Pacific Standard Time","(UTC+11:00) Solomon Is., New Caledonia"}, + {"Russia Time Zone 11","(UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky"}, + {"New Zealand Standard Time","(UTC+12:00) Auckland, Wellington"}, + {"UTC+12","(UTC+12:00) Coordinated Universal Time+12"}, + {"Fiji Standard Time","(UTC+12:00) Fiji"}, + {"Kamchatka Standard Time","(UTC+12:00) Petropavlovsk-Kamchatsky - Old"}, + {"Chatham Islands Standard Time","(UTC+12:45) Chatham Islands"}, + {"UTC+13","(UTC+13:00) Coordinated Universal Time+13"}, + {"Tonga Standard Time","(UTC+13:00) Nuku\u0027alofa"}, + {"Samoa Standard Time","(UTC+13:00) Samoa"}, + {"Line Islands Standard Time","(UTC+14:00) Kiritimati Island"} + }; + + return timeZones; + + } + + internal static IEnumerable GetTimeZoneIds() + { + return TimeZones.GetTimeZones().Keys.ToList(); + } + } +} \ No newline at end of file diff --git a/Source/Private/applyTypes.ps1 b/Source/Private/applyTypes.ps1 index 0a8eddfc8..96f30cbd5 100644 --- a/Source/Private/applyTypes.ps1 +++ b/Source/Private/applyTypes.ps1 @@ -182,3 +182,9 @@ function _applyTypesToBuildTimelineResultType { } } } + +function _applyTypesToAgentPoolMaintenance { + [CmdletBinding()] + param($item) + $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.AgentPoolMaintenance') +} \ No newline at end of file diff --git a/Source/Public/Add-VSTeamPool.ps1 b/Source/Public/Add-VSTeamPool.ps1 new file mode 100644 index 000000000..fe5685ae8 --- /dev/null +++ b/Source/Public/Add-VSTeamPool.ps1 @@ -0,0 +1,46 @@ +function Add-VSTeamPool { + [CmdletBinding( + HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamPool')] + param( + [Parameter(Mandatory = $true, Position = 1)] + [string] $Name, + + [Parameter(Mandatory = $false)] + [string] $Description, + + [Parameter(Mandatory = $false)] + [switch] $AutoProvision, + + [Parameter(Mandatory = $false)] + [switch] $AutoAuthorize, + + [Parameter(Mandatory = $false)] + [switch] $NoAutoUpdates + ) + + process { + + $body = @{ + name = $Name + autoProvision = $AutoProvision.IsPresent + autoUpdate = !$NoAutoUpdates.IsPresent + properties = @{ + "System.AutoAuthorize" = $AutoAuthorize.IsPresent + } + } + + $bodyAsJson = $body | ConvertTo-Json -Compress + + $resp = _callAPI -Method Post -NoProject -Area distributedtask -Resource pools -Version $(_getApiVersion DistributedTask) -Body $bodyAsJson + + $pool = [vsteam_lib.AgentPool]::new($resp) + + if ($resp -and $Description) { + $descriptionAsJson = $Description | ConvertTo-Json -Compress + $null = _callAPI -Method Put -NoProject -Area distributedtask -Resource pools -Id "$($pool.id)/poolmetadata" -Version $(_getApiVersion DistributedTask) -Body $descriptionAsJson + } + + Write-Output $pool + + } +} \ No newline at end of file diff --git a/Source/Public/Get-VSTeamAgentPoolMaintenance.ps1 b/Source/Public/Get-VSTeamAgentPoolMaintenance.ps1 new file mode 100644 index 000000000..fee65dd87 --- /dev/null +++ b/Source/Public/Get-VSTeamAgentPoolMaintenance.ps1 @@ -0,0 +1,22 @@ +function Get-VSTeamAgentPoolMaintenance { + [CmdletBinding( + HelpUri = 'https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Get-VSTeamAgentPoolMaintenance')] + param( + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1)] + [Alias('PoolID')] + [int] $Id + ) + + process { + + $resp = _callAPI -Method Get -NoProject -Area distributedtask -Resource pools -Id "$Id/maintenancedefinitions" -Version $(_getApiVersion DistributedTask) + + if ($resp -and $resp.count -gt 0) { + foreach ($schedule in $resp.value) { + _applyTypesToAgentPoolMaintenance -item $schedule + } + } + + Write-Output $resp.value + } +} \ No newline at end of file diff --git a/Source/Public/Remove-VSTeamPool.ps1 b/Source/Public/Remove-VSTeamPool.ps1 new file mode 100644 index 000000000..3f9b8903f --- /dev/null +++ b/Source/Public/Remove-VSTeamPool.ps1 @@ -0,0 +1,16 @@ +function Remove-VSTeamPool { + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium", + HelpUri = 'https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Remove-VSTeamPool')] + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1)] + [Alias('PoolID')] + [int] $Id + ) + + process { + if ($force -or $pscmdlet.ShouldProcess($Id, "Remove Pool")) { + $null = _callAPI -Method Delete -NoProject -Area distributedtask -Resource pools -Id $id -Version $(_getApiVersion DistributedTask) + } + } +} \ No newline at end of file diff --git a/Source/Public/Set-VSTeamAgentPoolMaintenance.ps1 b/Source/Public/Set-VSTeamAgentPoolMaintenance.ps1 new file mode 100644 index 000000000..3771ee282 --- /dev/null +++ b/Source/Public/Set-VSTeamAgentPoolMaintenance.ps1 @@ -0,0 +1,103 @@ +function Set-VSTeamAgentPoolMaintenance { + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium", DefaultParameterSetName = "Disabled", + HelpUri = 'https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Set-VSTeamAgentPoolMaintenance')] + param( + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1)] + [Alias('PoolID')] + [int] $Id, + + [Parameter(Mandatory = $false, ParameterSetName = "Disabled")] + [switch] $Disable, + + [ValidateRange(0, [int]::MaxValue)] + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [int] $JobTimeoutInMinutes, + + [ValidateRange(0, 100)] + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [int] $MaxConcurrentAgentsPercentage, + + [ValidateRange(0, [int]::MaxValue)] + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [int] $NumberOfHistoryRecordsToKeep, + + [ValidateRange(0, [int]::MaxValue)] + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [int] $WorkingDirectoryExpirationInDays, + + [ValidateRange(0, 23)] + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [int] $StartHours, + + [ValidateRange(0, 59)] + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [int] $StartMinutes, + + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [vsteam_lib.TimeZoneValidateAttribute()] + [ArgumentCompleter([vsteam_lib.TimeZoneCompleter])] + [string] $TimeZoneId, + + [Parameter(Mandatory = $true, ParameterSetName = "Enabled")] + [vsteam_lib.AgentPoolMaintenanceDays] $WeekDaysToBuild + ) + + process { + + if ($force -or $pscmdlet.ShouldProcess($Id, "Set Pool Maintenance")) { + + $resp = _callAPI -Method Get -NoProject -Area distributedtask -Resource pools -Id "$Id/maintenancedefinitions" -Version $(_getApiVersion DistributedTask) + $hasSchedule = $resp.count -gt 0 + + if ($Disable.IsPresent -and $false -eq $hasSchedule) { + Throw "Cannot deactivate. No Maintenance Schedule existing!" + } + + $body = $null + if ($Disable.IsPresent) { + $body = $resp.value[0] + $body.PSObject.Properties.Remove('pool') + $body.enabled = $false + }else { + $body = @{ + id = 0 + enabled = $true + jobTimeoutInMinutes = $JobTimeoutInMinutes + maxConcurrentAgentsPercentage = $MaxConcurrentAgentsPercentage + retentionPolicy = @{ + numberOfHistoryRecordsToKeep = $NumberOfHistoryRecordsToKeep + } + options = @{ + workingDirectoryExpirationInDays = $WorkingDirectoryExpirationInDays + } + scheduleSetting = @{ + scheduleJobId = (New-Guid).ToString() + startHours = $StartHours + startMinutes = $StartMinutes + daysToBuild = ([int]$WeekDaysToBuild) + timeZoneId = $TimeZoneId + } + } + } + + $param = @{ Id = ""; Method = ""} + if ($hasSchedule) { + #reuse existing schedule id + $body.scheduleSetting.scheduleJobId = $resp.value[0].scheduleSetting.scheduleJobId + $param.Id = "$Id/maintenancedefinitions/$($resp.value[0].id)" + $param.Method = "Put" + }else { + $param.Id = "$Id/maintenancedefinitions" + $param.Method = "Post" + } + + $bodyAsJson = $body | ConvertTo-Json -Compress -Depth 50 + $updateResp = _callAPI -NoProject -Area distributedtask -Resource pools -Body $bodyAsJson -Version $(_getApiVersion DistributedTask) @param + _applyTypesToAgentPoolMaintenance -item $updateResp + + Write-Output updateResp + } + } + + +} \ No newline at end of file diff --git a/Source/Public/Update-VSTeamPool.ps1 b/Source/Public/Update-VSTeamPool.ps1 new file mode 100644 index 000000000..0e8fa1beb --- /dev/null +++ b/Source/Public/Update-VSTeamPool.ps1 @@ -0,0 +1,49 @@ +function Update-VSTeamPool { + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium", + HelpUri = 'https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Update-VSTeamPool')] + param( + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1)] + [Alias('PoolID')] + [int] $Id, + + [Parameter(Mandatory = $false)] + [string] $Name, + + [Parameter(Mandatory = $false)] + [string] $Description, + + [Parameter(Mandatory = $false)] + [switch] $AutoProvision, + + [Parameter(Mandatory = $false)] + [switch] $NoAutoUpdates + ) + + process { + + if ($force -or $pscmdlet.ShouldProcess($Id, "Update Pool")) { + $body = @{ + autoProvision = $AutoProvision.IsPresent + autoUpdate = !$NoAutoUpdates.IsPresent + } + + if ($Name) { + $body.name = $Name + } + + $bodyAsJson = $body | ConvertTo-Json -Compress + + $resp = _callAPI -Method Patch -NoProject -Area distributedtask -Resource pools -Version $(_getApiVersion DistributedTask) -Id $Id -Body $bodyAsJson + + $pool = [vsteam_lib.AgentPool]::new($resp) + + if ($resp -and $Description) { + $descriptionAsJson = $Description | ConvertTo-Json + $null = _callAPI -Method Put -NoProject -Area distributedtask -Resource pools -Id "$($pool.id)/poolmetadata" -Version $(_getApiVersion DistributedTask) -Body $descriptionAsJson + } + + Write-Output $pool + + } + } +} \ No newline at end of file diff --git a/Source/VSTeam.psd1 b/Source/VSTeam.psd1 index 2bd6cd5c6..e8a4f0c89 100644 --- a/Source/VSTeam.psd1 +++ b/Source/VSTeam.psd1 @@ -12,7 +12,7 @@ RootModule = 'VSTeam.psm1' # Version number of this module. - ModuleVersion = '7.2.0' + ModuleVersion = '7.3.0' # Supported PSEditions CompatiblePSEditions = @('Core', 'Desktop') diff --git a/Source/formats/vsteam_lib.AgentPoolMaintenance.TableView.ps1xml b/Source/formats/vsteam_lib.AgentPoolMaintenance.TableView.ps1xml new file mode 100644 index 000000000..0be0c9e5d --- /dev/null +++ b/Source/formats/vsteam_lib.AgentPoolMaintenance.TableView.ps1xml @@ -0,0 +1,71 @@ + + + + + vsteam_lib.AgentPoolMaintenance.TableView + + vsteam_lib.AgentPoolMaintenance + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + enabled + + + jobTimeoutInMinutes + + + maxConcurrentAgentsPercentage + + + $_.retentionPolicy.numberOfHistoryRecordsToKeep + + + $_.scheduleSetting.startHours + + + $_.scheduleSetting.startMinutes + + + $_.scheduleSetting.timeZoneId + + + + + + + + diff --git a/Tests/SampleFiles/agentPoolMaintenanceMultipleResult.json b/Tests/SampleFiles/agentPoolMaintenanceMultipleResult.json new file mode 100644 index 000000000..5c7f92f29 --- /dev/null +++ b/Tests/SampleFiles/agentPoolMaintenanceMultipleResult.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "value": [{ + "options": { + "workingDirectoryExpirationInDays": 30 + }, + "retentionPolicy": { + "numberOfHistoryRecordsToKeep": 10 + }, + "scheduleSetting": { + "scheduleJobId": "a69f552a-bc2f-4b85-af66-66f7e8657060", + "timeZoneId": "UTC", + "startHours": 1, + "startMinutes": 0, + "daysToBuild": 28 + }, + "id": 4, + "pool": { + "id": 14, + "isHosted": false, + "poolType": 1, + "size": 0, + "isLegacy": null, + "options": null + }, + "enabled": true, + "jobTimeoutInMinutes": 60, + "maxConcurrentAgentsPercentage": 26 + }] +} \ No newline at end of file diff --git a/Tests/SampleFiles/agentPoolMaintenanceSingleResult.json b/Tests/SampleFiles/agentPoolMaintenanceSingleResult.json new file mode 100644 index 000000000..ede11cf84 --- /dev/null +++ b/Tests/SampleFiles/agentPoolMaintenanceSingleResult.json @@ -0,0 +1,27 @@ +{ + "options": { + "workingDirectoryExpirationInDays": 30 + }, + "retentionPolicy": { + "numberOfHistoryRecordsToKeep": 10 + }, + "scheduleSetting": { + "scheduleJobId": "a69f552a-bc2f-4b85-af66-66f7e8657060", + "timeZoneId": "UTC", + "startHours": 1, + "startMinutes": 0, + "daysToBuild": 28 + }, + "id": 4, + "pool": { + "id": 14, + "isHosted": false, + "poolType": 1, + "size": 0, + "isLegacy": null, + "options": null + }, + "enabled": true, + "jobTimeoutInMinutes": 60, + "maxConcurrentAgentsPercentage": 26 +} \ No newline at end of file diff --git a/Tests/SampleFiles/poolSingleResult.json b/Tests/SampleFiles/poolSingleResult.json new file mode 100644 index 000000000..a2f029352 --- /dev/null +++ b/Tests/SampleFiles/poolSingleResult.json @@ -0,0 +1,48 @@ +{ + "Id": 13, + "PoolId": 13, + "Count": 0, + "IsHosted": false, + "ProjectName": null, + "InternalObject": { + "properties": { + "System.AutoAuthorize": "@{$type=System.Boolean; $value=False}" + }, + "createdOn": "2021-03-07T20:06:15.037Z", + "autoProvision": false, + "autoUpdate": true, + "autoSize": true, + "targetSize": null, + "agentCloudId": null, + "createdBy": { + "displayName": "Sebastian Schütze", + "url": "https://spsprodweu2.vssps.visualstudio.com/A53c67424-6037-4f44-83bd-90b9dfc7d35d/_apis/Identities/da4833c6-8d06-6cc1-ada5-162ecac0b242", + "_links": "@{avatar=}", + "id": "da4833c6-8d06-6cc1-ada5-162ecac0b242", + "uniqueName": "Sebastian.S.Schuetze@deutschebahn.com", + "imageUrl": "https://dev.azure.com/SebastianSSchuetze/_apis/GraphProfile/MemberAvatars/aad.ZGE0ODMzYzYtOGQwNi03Y2MxLWFkYTUtMTYyZWNhYzBiMjQy", + "descriptor": "aad.ZGE0ODMzYzYtOGQwNi03Y2MxLWFkYTUtMTYyZWNhYzBiMjQy" + }, + "owner": { + "displayName": "Sebastian Schütze", + "url": "https://spsprodweu2.vssps.visualstudio.com/A53c67424-6037-4f44-83bd-90b9dfc7d35d/_apis/Identities/da4833c6-8d06-6cc1-ada5-162ecac0b242", + "_links": "@{avatar=}", + "id": "da4833c6-8d06-6cc1-ada5-162ecac0b242", + "uniqueName": "Sebastian.S.Schuetze@deutschebahn.com", + "imageUrl": "https://dev.azure.com/SebastianSSchuetze/_apis/GraphProfile/MemberAvatars/aad.ZGE0ODMzYzYtOGQwNi03Y2MxLWFkYTUtMTYyZWNhYzBiMjQy", + "descriptor": "aad.ZGE0ODMzYzYtOGQwNi03Y2MxLWFkYTUtMTYyZWNhYzBiMjQy" + }, + "id": 13, + "scope": "e92af8b7-7289-4e25-97c7-7970a2c953a0", + "name": "tutu2", + "isHosted": false, + "poolType": "automation", + "size": 0, + "isLegacy": false, + "options": "none" + }, + "DisplayMode": "d-----", + "Command": "Get-VSTeamAgent", + "TypeName": "vsteam_lib.Provider.Agent", + "Name": "tutu2" +} \ No newline at end of file diff --git a/Tests/function/tests/Add-VSTeamPool.Tests.ps1 b/Tests/function/tests/Add-VSTeamPool.Tests.ps1 new file mode 100644 index 000000000..1e7ec5b53 --- /dev/null +++ b/Tests/function/tests/Add-VSTeamPool.Tests.ps1 @@ -0,0 +1,109 @@ +Set-StrictMode -Version Latest + +Describe 'VSTeamPool' { + BeforeAll { + . "$PSScriptRoot\_testInitialize.ps1" $PSCommandPath + + ## Arrange + Mock _getInstance { return 'https://dev.azure.com/test' } + Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'DistributedTask' } + + $singleResult = Open-SampleFile 'poolSingleResult.json' + + } + + Context 'Add-VSTeamPool with parameters' { + BeforeAll { + Mock Invoke-RestMethod { + return $singleResult } -ParameterFilter { $Method -eq 'Post' } + Mock Invoke-RestMethod { + return $singleResult } -ParameterFilter { $Method -eq 'Put' } + } + + it 'with all parameters should be called' { + ## Act + Add-VSTeamPool -Name "TestPool" -Description "Test Description" -AutoProvision -AutoAuthorize -NoAutoUpdates + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '*"System.AutoAuthorize":true*' -and + $Body -like '*"autoProvision":true*' -and + $Body -like '*"autoUpdate":false*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools?api-version=$(_getApiVersion DistributedTask)" + } + + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Put' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)/poolmetadata?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with all parameters without description should be called' { + ## Act + Add-VSTeamPool -Name "TestPool" -AutoProvision -AutoAuthorize -NoAutoUpdates + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '*"System.AutoAuthorize":true*' -and + $Body -like '*"autoProvision":true*' -and + $Body -like '*"autoUpdate":false*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools?api-version=$(_getApiVersion DistributedTask)" + } + + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 0 -ParameterFilter { + $Method -eq 'Put' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)/poolmetadata?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with all parameters without description,NoAutoUpdates should be called' { + ## Act + Add-VSTeamPool -Name "TestPool" -AutoProvision -AutoAuthorize + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '*"System.AutoAuthorize":true*' -and + $Body -like '*"autoProvision":true*' -and + $Body -like '*"autoUpdate":true*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with all parameters without description,NoAutoUpdates,AutoAuthorize should be called' { + ## Act + Add-VSTeamPool -Name "TestPool" -AutoProvision + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '*"System.AutoAuthorize":false*' -and + $Body -like '*"autoProvision":true*' -and + $Body -like '*"autoUpdate":true*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with all parameters without description,NoAutoUpdates,AutoAuthorize,AutoProvision should be called' { + ## Act + Add-VSTeamPool -Name "TestPool" + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '*"System.AutoAuthorize":false*' -and + $Body -like '*"autoProvision":false*' -and + $Body -like '*"autoUpdate":true*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools?api-version=$(_getApiVersion DistributedTask)" + } + } + + } +} \ No newline at end of file diff --git a/Tests/function/tests/Get-VSTeamAgentPoolMaintenance.Tests.ps1 b/Tests/function/tests/Get-VSTeamAgentPoolMaintenance.Tests.ps1 new file mode 100644 index 000000000..65b866b0c --- /dev/null +++ b/Tests/function/tests/Get-VSTeamAgentPoolMaintenance.Tests.ps1 @@ -0,0 +1,30 @@ +Set-StrictMode -Version Latest + +Describe 'VSTeamPool' { + BeforeAll { + . "$PSScriptRoot\_testInitialize.ps1" $PSCommandPath + + ## Arrange + Mock _getInstance { return 'https://dev.azure.com/test' } + Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'DistributedTask' } + + $multipleResults = Open-SampleFile 'agentPoolMaintenanceMultipleResult.json' + } + + Context 'Get-VSTeamAgentPoolMaintenance with parameters' { + BeforeAll { + Mock Invoke-RestMethod { return $multipleResults } + } + + it 'should return schedule' { + ## Act + Get-VSTeamAgentPoolMaintenance -Id 14 + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Get' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/14/maintenancedefinitions?api-version=$(_getApiVersion DistributedTask)" + } + } + } +} \ No newline at end of file diff --git a/Tests/function/tests/Remove-VSTeamPool.Tests.ps1 b/Tests/function/tests/Remove-VSTeamPool.Tests.ps1 new file mode 100644 index 000000000..e1ea2a54b --- /dev/null +++ b/Tests/function/tests/Remove-VSTeamPool.Tests.ps1 @@ -0,0 +1,31 @@ +Set-StrictMode -Version Latest + +Describe 'VSTeamPool' { + BeforeAll { + Import-Module SHiPS + + . "$PSScriptRoot\_testInitialize.ps1" $PSCommandPath + + ## Arrange + Mock _getInstance { return 'https://dev.azure.com/test' } + Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'DistributedTask' } + + } + + Context 'Remove-VSTeamPool with parameters' { + BeforeAll { + Mock Invoke-RestMethod { Write-Host $ return $null } + } + + it 'with ID should be called' { + ## Act + Remove-VSTeamPool -Id 5 + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Delete' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/5?api-version=$(_getApiVersion DistributedTask)" + } + } + } +} \ No newline at end of file diff --git a/Tests/function/tests/Set-VSTeamAgentPoolMaintenance.Tests.ps1 b/Tests/function/tests/Set-VSTeamAgentPoolMaintenance.Tests.ps1 new file mode 100644 index 000000000..18c42427d --- /dev/null +++ b/Tests/function/tests/Set-VSTeamAgentPoolMaintenance.Tests.ps1 @@ -0,0 +1,197 @@ +Set-StrictMode -Version Latest + +Describe 'VSTeamPool' { + BeforeAll { + . "$PSScriptRoot\_testInitialize.ps1" $PSCommandPath + + ## Arrange + Mock _getInstance { return 'https://dev.azure.com/test' } + Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'DistributedTask' } + + $multipleResults = Open-SampleFile 'agentPoolMaintenanceMultipleResult.json' + $singleResult = Open-SampleFile 'agentPoolMaintenanceSingleResult.json' + + $testParam = @{ + Id = 14 + JobTimeoutInMinutes = 5 + MaxConcurrentAgentsPercentage = 25 + NumberOfHistoryRecordsToKeep = 6 + WorkingDirectoryExpirationInDays = 7 + StartHours = 3 + StartMinutes = 30 + WeekDaysToBuild = @("Monday"; "Friday") + TimeZoneId = "UTC" + } + } + + Context 'Set-VSTeamAgentPoolMaintenance with existing Schedule' { + BeforeAll { + Mock Invoke-RestMethod { + return $multipleResults } -ParameterFilter { $Method -eq 'Get' } + Mock Invoke-RestMethod { + return $singleResult } -ParameterFilter { $Method -eq 'Put' } + } + + it 'with disabled setting should be called' { + ## Act + Set-VSTeamAgentPoolMaintenance -Id 14 -Disable + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Get' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/14/maintenancedefinitions?api-version=$(_getApiVersion DistributedTask)" + } + + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Put' -and + $Body -like '*"enabled":false*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/14/maintenancedefinitions/4?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with enabled setting should be called' { + ## Act + Set-VSTeamAgentPoolMaintenance @testParam + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Get' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/14/maintenancedefinitions?api-version=$(_getApiVersion DistributedTask)" + } + + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Put' -and + $Body -like '*"enabled":true*' -and + $Body -like '*"workingDirectoryExpirationInDays":7*' -and + $Body -like '*"numberOfHistoryRecordsToKeep":6*' -and + $Body -like '*"jobTimeoutInMinutes":5*' -and + $Body -like '*"maxConcurrentAgentsPercentage":25*' -and + $Body -like '*"timeZoneId":"UTC"*' -and + $Body -like '*"startHours":3*' -and + $Body -like '*"startMinutes":30*' -and + $Body -like '*"daysToBuild":17*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/14/maintenancedefinitions/4?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with enabled setting and more than 100% should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.MaxConcurrentAgentsPercentage = 101 + { Set-VSTeamAgentPoolMaintenance @localParam } | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'MaxConcurrentAgentsPercentage'. The 101 argument is greater than the maximum allowed range of 100. Supply an argument that is less than or equal to 100 and then try the command again." + } + + it 'with enabled setting and StartMinutes more than 59 should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.StartMinutes = 60 + { Set-VSTeamAgentPoolMaintenance @localParam } | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'StartMinutes'. The 60 argument is greater than the maximum allowed range of 59. Supply an argument that is less than or equal to 59 and then try the command again." + } + + it 'with enabled setting and StartHours more than 23 should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.StartHours = 24 + { Set-VSTeamAgentPoolMaintenance @localParam} | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'StartHours'. The 24 argument is greater than the maximum allowed range of 23. Supply an argument that is less than or equal to 23 and then try the command again." + } + + it 'with enabled setting and wrong time zone id should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.TimeZoneId = "NoTimeZone" + { Set-VSTeamAgentPoolMaintenance @localParam} | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'TimeZoneId'. 'NoTimeZone' is invalid" + } + + it 'with enabled setting and wrong week days should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.WeekDaysToBuild = @("Mo","Tue") + { Set-VSTeamAgentPoolMaintenance @localParam } | Should -Throw + } + + } + + Context 'Set-VSTeamAgentPoolMaintenance without existing Schedule' { + BeforeAll { + Mock Invoke-RestMethod { + return @{ count = 0; value = @() } } -ParameterFilter { $Method -eq 'Get' } + Mock Invoke-RestMethod { + return $singleResult } -ParameterFilter { $Method -eq 'Post' } + } + + it 'with disabled setting should throw' -Tag "Throws" { + ## Act + { + Set-VSTeamAgentPoolMaintenance -Id 14 -Disable + } | Should -Throw + } + + it 'with enabled setting should be called' { + ## Act + Set-VSTeamAgentPoolMaintenance @testParam + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Get' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/14/maintenancedefinitions?api-version=$(_getApiVersion DistributedTask)" + } + + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '*"enabled":true*' -and + $Body -like '*"workingDirectoryExpirationInDays":7*' -and + $Body -like '*"numberOfHistoryRecordsToKeep":6*' -and + $Body -like '*"jobTimeoutInMinutes":5*' -and + $Body -like '*"maxConcurrentAgentsPercentage":25*' -and + $Body -like '*"timeZoneId":"UTC"*' -and + $Body -like '*"startHours":3*' -and + $Body -like '*"startMinutes":30*' -and + $Body -like '*"daysToBuild":17*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/14/maintenancedefinitions?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with enabled setting and more than 100% should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.MaxConcurrentAgentsPercentage = 101 + { Set-VSTeamAgentPoolMaintenance @localParam } | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'MaxConcurrentAgentsPercentage'. The 101 argument is greater than the maximum allowed range of 100. Supply an argument that is less than or equal to 100 and then try the command again." + } + + it 'with enabled setting and StartMinutes more than 59 should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.StartMinutes = 60 + { Set-VSTeamAgentPoolMaintenance @localParam } | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'StartMinutes'. The 60 argument is greater than the maximum allowed range of 59. Supply an argument that is less than or equal to 59 and then try the command again." + } + + it 'with enabled setting and StartHours more than 23 should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.StartHours = 24 + { Set-VSTeamAgentPoolMaintenance @localParam} | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'StartHours'. The 24 argument is greater than the maximum allowed range of 23. Supply an argument that is less than or equal to 23 and then try the command again." + } + + it 'with enabled setting and wrong time zone id should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.TimeZoneId = "NoTimeZone" + { Set-VSTeamAgentPoolMaintenance @localParam} | ` + Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'TimeZoneId'. 'NoTimeZone' is invalid" + } + + it 'with enabled setting and wrong week days should throw' { + ## Act + $localParam = $testParam.Clone() + $localParam.WeekDaysToBuild = @("Mo","Tue") + { Set-VSTeamAgentPoolMaintenance @localParam } | Should -Throw + } + } +} \ No newline at end of file diff --git a/Tests/function/tests/Update-VSTeamPool.Tests.ps1 b/Tests/function/tests/Update-VSTeamPool.Tests.ps1 new file mode 100644 index 000000000..8ce7c02a9 --- /dev/null +++ b/Tests/function/tests/Update-VSTeamPool.Tests.ps1 @@ -0,0 +1,103 @@ +Set-StrictMode -Version Latest + +Describe 'VSTeamPool' { + BeforeAll { + . "$PSScriptRoot\_testInitialize.ps1" $PSCommandPath + + ## Arrange + Mock _getInstance { return 'https://dev.azure.com/test' } + Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'DistributedTask' } + + $singleResult = Open-SampleFile 'poolSingleResult.json' + + } + + Context 'Update-VSTeamPool with parameters' { + BeforeAll { + Mock Invoke-RestMethod { + return $singleResult } -ParameterFilter { $Method -eq 'Patch' } + Mock Invoke-RestMethod { + return $singleResult } -ParameterFilter { $Method -eq 'Put' } + } + + it 'with all parameters should be called' { + ## Act + Update-VSTeamPool -Id $singleResult.Id -Name "TestPool" -Description "Test Description" -AutoProvision -NoAutoUpdates + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Patch' -and + $Body -like '*"autoProvision":true*' -and + $Body -like '*"autoUpdate":false*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)?api-version=$(_getApiVersion DistributedTask)" + } + + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Put' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)/poolmetadata?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with all parameters without description should be called' { + ## Act + Update-VSTeamPool -Id $singleResult.Id -Name "TestPool" -AutoProvision -NoAutoUpdates + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Patch' -and + $Body -like '*"autoProvision":true*' -and + $Body -like '*"autoUpdate":false*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)?api-version=$(_getApiVersion DistributedTask)" + } + + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 0 -ParameterFilter { + $Method -eq 'Put' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)/poolmetadata?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with all parameters without description,NoAutoUpdates should be called' { + ## Act + Update-VSTeamPool -Id $singleResult.Id -Name "TestPool" -AutoProvision + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Patch' -and + $Body -like '*"autoProvision":true*' -and + $Body -like '*"autoUpdate":true*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with all parameters without description,NoAutoUpdates,AutoProvision should be called' { + ## Act + Update-VSTeamPool -Id $singleResult.Id -Name "TestPool" + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Patch' -and + $Body -like '*"autoProvision":false*' -and + $Body -like '*"autoUpdate":true*' -and + $Body -like '*"name":"TestPool"*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)?api-version=$(_getApiVersion DistributedTask)" + } + } + + it 'with only Id should be called' { + ## Act + Update-VSTeamPool -Id $singleResult.Id + + ## Assert + Should -Invoke Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Patch' -and + $Body -like '*"autoProvision":false*' -and + $Body -like '*"autoUpdate":true*' -and + $Uri -eq "https://dev.azure.com/test/_apis/distributedtask/pools/$($singleResult.Id)?api-version=$(_getApiVersion DistributedTask)" + } + } + + } +} \ No newline at end of file diff --git a/Tests/library/Attribute/TimeZoneValidateAttributeTests.cs b/Tests/library/Attribute/TimeZoneValidateAttributeTests.cs new file mode 100644 index 000000000..cb9d3fe98 --- /dev/null +++ b/Tests/library/Attribute/TimeZoneValidateAttributeTests.cs @@ -0,0 +1,61 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Management.Automation; + +namespace vsteam_lib.Test +{ + /// + /// This test derives from ProcessTemplateValidateAttribute to gain access + /// to the protected method we need to test. + /// + [TestClass] + [ExcludeFromCodeCoverage] + public sealed class TimeZoneValidateAttributeTests : TimeZoneValidateAttribute + { + private readonly List _timeZoneIds = new List() { + {"Alaskan Standard Time"}, + {"UTC-09"}, + {"Pacific Standard Time (Mexico)"}, + {"UTC-08"}, + {"Pacific Standard Time"}, + {"US Mountain Standard Time"} + }; + + [TestMethod] + public void TimeZoneValidateAttribute_Invalid_Value_Throws() + { + // Arrange + + // Act + + // Assert + Assert.ThrowsException(() => this.Validate("Test", null)); + } + + [TestMethod] + public void TimeZoneValidateAttribute_Valid_Value_Does_Not_Throw() + { + // Arrange + + // Act + foreach (var id in _timeZoneIds) + { + this.Validate(id, null); + } + + // Assert + } + + [TestMethod] + public void TimeZoneValidateAttribute_Null_Value_Does_Not_Throw() + { + // Arrange + + // Act + this.Validate(null, null); + + // Assert + } + } +} diff --git a/Tests/library/Completer/TimeZoneCompleterTests.cs b/Tests/library/Completer/TimeZoneCompleterTests.cs new file mode 100644 index 000000000..864ce5eaa --- /dev/null +++ b/Tests/library/Completer/TimeZoneCompleterTests.cs @@ -0,0 +1,46 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace vsteam_lib.Test +{ + [TestClass] + [ExcludeFromCodeCoverage] + public class TimeZoneCompleterTests + { + private readonly Dictionary _timeZones = new Dictionary() { + {"'W. Europe Standard Time'","(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"}, + {"'Central Europe Standard Time'","(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague"}, + {"'Middle East Standard Time'","(UTC+02:00) Beirut"}, + {"'Belarus Standard Time'","(UTC+03:00) Minsk"}, + {"'China Standard Time'","(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi"}, + {"'AUS Eastern Standard Time'","(UTC+10:00) Canberra, Melbourne, Sydney"} + }; + + [TestMethod] + public void TimeZoneCompleter_Exercise() + { + // Arrange + var ps = BaseTests.PrepPowerShell(); + + var target = new TimeZoneCompleter(ps); + var fakeBoundParameters = new Dictionary(); + + // Act + var actual = target.CompleteArgument(string.Empty, string.Empty, "be", null, fakeBoundParameters); + + // Assert + Assert.AreEqual(_timeZones.Count, actual.Count()); + var e = actual.GetEnumerator(); + foreach (var timeZone in _timeZones) + { + e.MoveNext(); + Assert.AreEqual(timeZone.Key, e.Current.CompletionText, timeZone.Key); + Assert.AreEqual(timeZone.Value, e.Current.ListItemText, timeZone.Value); + } + } + } +} diff --git a/config.json b/config.json index a71a43b2e..17643213e 100644 --- a/config.json +++ b/config.json @@ -102,6 +102,7 @@ "vsteam_lib.Process.ListView.ps1xml", "vsteam_lib.AgentPool.TableView.ps1xml", "vsteam_lib.Provider.AgentPool.TableView.ps1xml", + "vsteam_lib.AgentPoolMaintenance.TableView.ps1xml", "vsteam_lib.Extension.TableView.ps1xml", "vsteam_lib.Provider.Extension.TableView.ps1xml", "vsteam_lib.PSDrive.Default.TableView.ps1xml",