From 778c6c31695c67604e8942853f94b6b3f4c4669c Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 10:28:31 +0900 Subject: [PATCH 001/160] :sparkles: Implement cmdlet `Get-VariableData` --- src/Cmdlets/VariableData.cs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/Cmdlets/VariableData.cs diff --git a/src/Cmdlets/VariableData.cs b/src/Cmdlets/VariableData.cs new file mode 100644 index 00000000..5cf5a935 --- /dev/null +++ b/src/Cmdlets/VariableData.cs @@ -0,0 +1,35 @@ +using System.Management.Automation; +using AWX.Resources; + +namespace AWX.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "VariableData")] + [OutputType(typeof(Dictionary))] + public class GetVariableDataCommand : APICmdletBase + { + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [ValidateSet(nameof(ResourceType.Inventory), + nameof(ResourceType.Group), + nameof(ResourceType.Host))] + public ResourceType Type { get;set;} + + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 1)] + public ulong Id {get;set;} + + protected override void ProcessRecord() + { + var path = Type switch + { + ResourceType.Inventory => $"{Inventory.PATH}{Id}/variable_data/", + ResourceType.Group => $"{Group.PATH}{Id}/variable_data/", + ResourceType.Host => $"{Host.PATH}{Id}/variable_data/", + _ => throw new ArgumentException($"Unkown Resource Type: {Type}") + }; + var variableData = GetResource>(path); + if (variableData == null) + return; + + WriteObject(variableData, false); + } + } +} From 77f177c94a2df5c30c8917429730b23665a10698 Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 11:11:33 +0900 Subject: [PATCH 002/160] :books: Add document for `Get-VariableData` --- docs/en-US/cmdlets/AWX.psm.md | 4 +- docs/en-US/cmdlets/Get-VariableData.md | 113 +++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 docs/en-US/cmdlets/Get-VariableData.md diff --git a/docs/en-US/cmdlets/AWX.psm.md b/docs/en-US/cmdlets/AWX.psm.md index fccf344a..48915605 100644 --- a/docs/en-US/cmdlets/AWX.psm.md +++ b/docs/en-US/cmdlets/AWX.psm.md @@ -290,6 +290,9 @@ Retrieve (OAuth2) AccessTokens by the ID(s). ### [Get-User](Get-User.md) Retrieve Users by the ID(s). +### [Get-VariableData](Get-VariableData.md) +Retrieve Variable Data + ### [Get-WorkflowApprovalRequest](Get-WorkflowApprovalRequest.md) Retrieve request jobs for WorkflowApproval by ID(s). @@ -358,4 +361,3 @@ Switch to anothor config. ### [Wait-UnifiedJob](Wait-UnifiedJob.md) Wait until jobs are finished. - diff --git a/docs/en-US/cmdlets/Get-VariableData.md b/docs/en-US/cmdlets/Get-VariableData.md new file mode 100644 index 00000000..ab3ba05e --- /dev/null +++ b/docs/en-US/cmdlets/Get-VariableData.md @@ -0,0 +1,113 @@ +--- +external help file: AWX.psm.dll-Help.xml +Module Name: AWX.psm +online version: +schema: 2.0.0 +--- + +# Get-VariableData + +## SYNOPSIS +Retrieve Variable Data + +## SYNTAX + +``` +Get-VariableData [-Type] [-Id] + [] +``` + +## DESCRIPTION +Retrieve Variable Data for Inventory, Group or Host. + +Implements following Rest API: +- `/api/v2/inventories/{id}/variable_data/` +- `/api/v2/groups/{id}/variable_data/` +- `/api/v2/hosts/{id}/variable_data/` + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Get-VariableData -Type Group -Id 1 + +Key Value +--- ----- +ansible_connection ssh +ansible_ssh_private_key_file id_ed25519_ansible +ansible_user ansible +``` + +## PARAMETERS + +### -Id +Datebase ID of the target resource. +Use in conjection with the `-Type` parameter. + +```yaml +Type: UInt64 +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Type +Resource type name of the target. +Use in conjection with the `-Id` parameter. + +```yaml +Type: ResourceType +Parameter Sets: (All) +Aliases: +Accepted values: Inventory, Group, Host + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### AWX.Resources.ResourceType +Input by `Type` property in the pipeline object. + +Acceptable values: +- `Inventory` +- `Group` +- `Host` + +### System.UInt64 +Input by `Id` property in the pipeline object. + +Database ID for the ResourceType + +## OUTPUTS + +### System.Collections.Generic.Dictionary`2[[System.String],[System.Object]] +All variables for the resource. + +## NOTES + +## RELATED LINKS + +[Find-Inventory](Find-Inventory.md) + +[Get-Inventory](Get-Inventory.md) + +[Find-Group](Find-Group.md) + +[Get-Group](Get-Group.md) + +[Find-Host](Find-Host.md) + +[Get-Host](Get-Host.md) From fe7fec2a66e3e9a59bf1aa5453ad7239cc9cd61b Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 11:22:27 +0900 Subject: [PATCH 003/160] :memo: Add implemented API list for `Get-SurveySpec` for https://github.com/teramako/AWX.psm/pull/32 --- docs/en-US/cmdlets/Get-SurveySpec.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en-US/cmdlets/Get-SurveySpec.md b/docs/en-US/cmdlets/Get-SurveySpec.md index 7c73c333..54805681 100644 --- a/docs/en-US/cmdlets/Get-SurveySpec.md +++ b/docs/en-US/cmdlets/Get-SurveySpec.md @@ -19,6 +19,10 @@ Get-SurveySpec [-Type] [-Id] [] ## DESCRIPTION Retrieve Survey specifications for JobTemplate or WorkflowJobTemplate. +Implements following Rest API: +- `/api/v2/job_templates/{id}/survey_spec/` +- `/api/v2/workflow_job_templates/{id}/survey_spec/` + ## EXAMPLES ### Example 1 From 010cae1baac4b5a28029d7c7ca637467beab9f5b Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 11:37:33 +0900 Subject: [PATCH 004/160] :+1::books: Generate markdown documents as LF code on non-Windows --- docs/Make-Doc.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/Make-Doc.ps1 b/docs/Make-Doc.ps1 index ce7939af..3f23ee8a 100644 --- a/docs/Make-Doc.ps1 +++ b/docs/Make-Doc.ps1 @@ -89,6 +89,11 @@ function Update-CommonParameterFromMarkdown { $updateFile = $true } } + + if (-not $IsWindows) { + $newContent = $content -replace "`r?`n", "`n" + } + # Save file if content has changed if ($updateFile) { $newContent | Out-File -Encoding utf8 -FilePath $p @@ -191,6 +196,11 @@ if ($resultFiles.Count -gt 0) { Repair-PlatyPSMarkdown -Path $resultFiles } +$moduleMarkdownFile = Join-Path $OutputFolder "$moduleName.md" +if (-not $IsWindows -and (Test-Path -Path $moduleMarkdownFile -PathType Leaf)) { + $content = (Get-Content -Path $moduleMarkdownFile -Raw).TrimEnd() -replace "`r?`n", "`n" + $content | Out-File -FilePath $moduleMarkdownFile -Encoding utf8NoBOM +} # $externalHelpDirPath = Join-Path $PSScriptRoot ..\out\$Locale $externalHelpDirPath = Join-Path $module.ModuleBase $Locale From d8cbc3cb5b7cf45a166b53b953edb5d5044127c1 Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 12:12:32 +0900 Subject: [PATCH 005/160] :+1: Add data type for "variable_data" of GET method. --- src/Resources/ResourceType.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Resources/ResourceType.cs b/src/Resources/ResourceType.cs index 5e5f9917..ba2664f4 100644 --- a/src/Resources/ResourceType.cs +++ b/src/Resources/ResourceType.cs @@ -256,7 +256,7 @@ public enum ResourceType [ResourceSubPath("tree", Description = "Group Tree for an Inventory")] [ResourceSubPath("update_inventory_sources", Description = "")] [ResourceSubPath("update_inventory_sources", Method = Method.POST, Description = "Update Inventory Sources")] - [ResourceSubPath("variable_data", Description = "Retrieve Inventory Variable Data")] + [ResourceSubPath("variable_data", typeof(Dictionary), Description = "Retrieve Inventory Variable Data")] [ResourceSubPath("variable_data", Method = Method.PUT, Description = "Update Inventory Variable Data")] [ResourceSubPath("variable_data", Method = Method.PATCH, Description = "Update Inventory Variable Data")] Inventory, @@ -313,7 +313,7 @@ public enum ResourceType [ResourceSubPath("job_events", typeof(ResultSet), Description = "List Job Events for a Group")] [ResourceSubPath("job_host_summaries", typeof(ResultSet), Description = "List Job Host Summaries for a Group")] [ResourceSubPath("potential_children", typeof(ResultSet), Description = "List Potential Child Groups for a Group")] - [ResourceSubPath("variable_data", Description = "Retrieve Group Variable Data")] + [ResourceSubPath("variable_data", typeof(Dictionary), Description = "Retrieve Group Variable Data")] [ResourceSubPath("variable_data", Method = Method.PUT, Description = "Update Group Variable Data")] [ResourceSubPath("variable_data", Method = Method.PATCH, Description = "Update Group Variable Data")] Group, @@ -335,7 +335,7 @@ public enum ResourceType [ResourceSubPath("job_events", typeof(ResultSet), Description = "List Job Events for a Host")] [ResourceSubPath("job_host_summaries", typeof(ResultSet), Description = "List Job Host Summaries for a Host")] [ResourceSubPath("smart_inventories", typeof(ResultSet), Description = "List Smart Inventires for a Host")] - [ResourceSubPath("variable_data", Description = "Retrieve Group Variable Data")] + [ResourceSubPath("variable_data", typeof(Dictionary), Description = "Retrieve Group Variable Data")] [ResourceSubPath("variable_data", Method = Method.PUT, Description = "Update Group Variable Data")] [ResourceSubPath("variable_data", Method = Method.PATCH, Description = "Update Group Variable Data")] Host, From 44adcf0bcd9d07f6f7b5b44a9ff43bda1307767b Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 12:57:32 +0900 Subject: [PATCH 006/160] :sparkles: Implement cmdlet `Get-HostFactsCache` --- src/Cmdlets/HostCommand.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Cmdlets/HostCommand.cs b/src/Cmdlets/HostCommand.cs index 05e743d5..645a3aee 100644 --- a/src/Cmdlets/HostCommand.cs +++ b/src/Cmdlets/HostCommand.cs @@ -1,4 +1,5 @@ using AWX.Resources; +using System.Collections; using System.Management.Automation; namespace AWX.Cmdlets @@ -78,5 +79,31 @@ protected override void ProcessRecord() } } } + + [Cmdlet(VerbsCommon.Get, "HostFactsCache")] + [OutputType(typeof(Dictionary))] + public class GetHostFactsCacheCommand : GetCmdletBase + { + protected override void ProcessRecord() + { + if (Type != null && Type != ResourceType.Host) + { + return; + } + foreach (var id in Id) + { + if (!IdSet.Add(id)) + { + // skip already processed + continue; + } + var facts = GetResource>($"{Host.PATH}{id}/ansible_facts/"); + if (facts == null) + return; + + WriteObject(facts, false); + } + } + } } From 2c7c8ac07760b253297760eb6be0b339927862ce Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 13:30:09 +0900 Subject: [PATCH 007/160] :+1: Add data type for "ansible_facts" of GET method. --- src/Resources/ResourceType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resources/ResourceType.cs b/src/Resources/ResourceType.cs index 5e5f9917..2d667cd2 100644 --- a/src/Resources/ResourceType.cs +++ b/src/Resources/ResourceType.cs @@ -328,7 +328,7 @@ public enum ResourceType [ResourceSubPath("ad_hoc_commands", typeof(ResultSet), Description = "List Ad Hoc Commands for a Host")] [ResourceSubPath("ad_hoc_commands", typeof(AdHocCommand), Method = Method.POST, Description = "Create an Ad Hoc Commmand for a Host")] [ResourceSubPath("all_groups", typeof(ResultSet), Description = "List All Groups for a Host")] - [ResourceSubPath("ansible_facts", Description = "Retrieve Ansible Facts for a Host")] + [ResourceSubPath("ansible_facts", typeof(Dictionary), Description = "Retrieve Ansible Facts for a Host")] [ResourceSubPath("groups", typeof(ResultSet), Description = "List Groups for a Host")] [ResourceSubPath("groups", typeof(Group), Method = Method.POST, Description = "Create a Group for a Host")] [ResourceSubPath("inventory_sources", typeof(ResultSet), Description = "List Inventory Sources for a Host")] From 24792f31c3186cbb73cafdc2ef23db899a8eb91b Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 13:31:08 +0900 Subject: [PATCH 008/160] :books: Add document for Get-HostFactsCache --- docs/en-US/cmdlets/AWX.psm.md | 4 +- docs/en-US/cmdlets/Get-HostFactsCache.md | 82 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 docs/en-US/cmdlets/Get-HostFactsCache.md diff --git a/docs/en-US/cmdlets/AWX.psm.md b/docs/en-US/cmdlets/AWX.psm.md index fccf344a..2537343d 100644 --- a/docs/en-US/cmdlets/AWX.psm.md +++ b/docs/en-US/cmdlets/AWX.psm.md @@ -197,6 +197,9 @@ Retrieve Groups by the ID(s). ### [Get-Host](Get-Host.md) Retrieve Hosts by the ID(s). +### [Get-HostFactsCache](Get-HostFactsCache.md) +Retrieve Ansible Facts for a Host. + ### [Get-HostMetric](Get-HostMetric.md) Retrieve HostMetrics by the ID(s). @@ -358,4 +361,3 @@ Switch to anothor config. ### [Wait-UnifiedJob](Wait-UnifiedJob.md) Wait until jobs are finished. - diff --git a/docs/en-US/cmdlets/Get-HostFactsCache.md b/docs/en-US/cmdlets/Get-HostFactsCache.md new file mode 100644 index 00000000..79d4a065 --- /dev/null +++ b/docs/en-US/cmdlets/Get-HostFactsCache.md @@ -0,0 +1,82 @@ +--- +external help file: AWX.psm.dll-Help.xml +Module Name: AWX.psm +online version: +schema: 2.0.0 +--- + +# Get-HostFactsCache + +## SYNOPSIS +Retrieve Ansible Facts for a Host. + +## SYNTAX + +``` +Get-HostFactsCache [-Id] [] +``` + +## DESCRIPTION +Retrieve Ansible Facts for a Host. + +Implements following Rest API: +- `/api/v2/hosts/{id}/ansible_facts/` + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Get-HostFactsCache -Id 1 + +Key Value +--- ----- +ansible_dns {[search, System.Object[]], [options, System.Collections.Generic.Dictionary`2[System.String,System.Object]], [nameservers, System.Object[]]} +ansible_env {[_, /usr/bin/python3], [PWD, /runner/project], [HOME, /root], [PATH, /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin]…} +ansible_lsb {} +ansible_lvm N/A +ansible_fips False +ansible_fqdn awx_1 +module_setup True +ansible_local {} +gather_subset {all} +ansible_domain +ansible_kernel 5.15.153.1-microsoft-standard-WSL2 + +(snip) +``` + +## PARAMETERS + +### -Id +List of Host ID(s). + +```yaml +Type: UInt64[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.UInt64[] +One or more Host ID(s). + +## OUTPUTS + +### System.Collections.Generic.Dictionary`2[[System.String],[System.Object]] +## NOTES + +## RELATED LINKS + +[Find-Host](Find-Host.md) + +[Get-Host](Get-Host.md) From c90d6fa22ef9573ece928d0c586927728cbd4c50 Mon Sep 17 00:00:00 2001 From: teramako Date: Fri, 6 Sep 2024 15:44:28 +0900 Subject: [PATCH 009/160] :recycle: fix format --- src/Cmdlets/HostCommand.cs | 1 - src/Cmdlets/VariableData.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Cmdlets/HostCommand.cs b/src/Cmdlets/HostCommand.cs index 645a3aee..f86fa03c 100644 --- a/src/Cmdlets/HostCommand.cs +++ b/src/Cmdlets/HostCommand.cs @@ -1,5 +1,4 @@ using AWX.Resources; -using System.Collections; using System.Management.Automation; namespace AWX.Cmdlets diff --git a/src/Cmdlets/VariableData.cs b/src/Cmdlets/VariableData.cs index 5cf5a935..891d20b9 100644 --- a/src/Cmdlets/VariableData.cs +++ b/src/Cmdlets/VariableData.cs @@ -11,10 +11,10 @@ public class GetVariableDataCommand : APICmdletBase [ValidateSet(nameof(ResourceType.Inventory), nameof(ResourceType.Group), nameof(ResourceType.Host))] - public ResourceType Type { get;set;} + public ResourceType Type { get; set; } [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 1)] - public ulong Id {get;set;} + public ulong Id { get; set; } protected override void ProcessRecord() { From ab19aac9812ecd13346fb6c2585c1f08273ef59e Mon Sep 17 00:00:00 2001 From: teramako Date: Sun, 8 Sep 2024 21:44:04 +0900 Subject: [PATCH 010/160] :recycle: Rename `JobManager` to `JobProgressManager` --- src/Cmdlets/APIBase.cs | 24 +++++++++++------------ src/Cmdlets/AdHocCommandCommand.cs | 2 +- src/Cmdlets/InventoryUpdateCommand.cs | 4 ++-- src/Cmdlets/JobTemplateCommand.cs | 2 +- src/Cmdlets/ProjectUpdateCommand.cs | 2 +- src/Cmdlets/SystemJobTemplateCommand.cs | 2 +- src/Cmdlets/UnifiedJobCommand.cs | 6 +++--- src/Cmdlets/WorkflowJobTemplateCommand.cs | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Cmdlets/APIBase.cs b/src/Cmdlets/APIBase.cs index f076ca91..cca361df 100644 --- a/src/Cmdlets/APIBase.cs +++ b/src/Cmdlets/APIBase.cs @@ -11,7 +11,7 @@ namespace AWX.Cmdlets { public abstract class InvokeJobBase : APICmdletBase { - protected readonly JobProgressManager JobManager = []; + protected readonly JobProgressManager JobProgressManager = []; private Sleep? _sleep; protected void Sleep(int milliseconds) { @@ -48,35 +48,35 @@ protected void WaitJobs(string activityId, int intervalSeconds, bool suppressJobLog) { - if (JobManager.Count == 0) + if (JobProgressManager.Count == 0) { return; } - JobManager.Start(activityId, intervalSeconds); + JobProgressManager.Start(activityId, intervalSeconds); do { UpdateAllProgressRecordType(ProgressRecordType.Processing); for (var i = 1; i <= intervalSeconds; i++) { Sleep(1000); - JobManager.UpdateProgress(i); - WriteProgress(JobManager.RootProgress); + JobProgressManager.UpdateProgress(i); + WriteProgress(JobProgressManager.RootProgress); } - JobManager.UpdateJob(); + JobProgressManager.UpdateJob(); // Remove Progressbar UpdateAllProgressRecordType(ProgressRecordType.Completed); ShowJobLog(suppressJobLog); - WriteObject(JobManager.CleanCompleted(), true); - } while (JobManager.Count > 0); + WriteObject(JobProgressManager.CleanCompleted(), true); + } while (JobProgressManager.Count > 0); } private void UpdateAllProgressRecordType(ProgressRecordType type) { - JobManager.RootProgress.RecordType = type; - WriteProgress(JobManager.RootProgress); - foreach (var jp in JobManager.GetAll()) + JobProgressManager.RootProgress.RecordType = type; + WriteProgress(JobProgressManager.RootProgress); + foreach (var jp in JobProgressManager.GetAll()) { jp.Progress.RecordType = type; WriteProgress(jp.Progress); @@ -85,7 +85,7 @@ private void UpdateAllProgressRecordType(ProgressRecordType type) protected void ShowJobLog(bool suppressJobLog) { - var jpList = JobManager.GetJobLog(); + var jpList = JobProgressManager.GetJobLog(); foreach (var jp in jpList) { if (jp == null) continue; diff --git a/src/Cmdlets/AdHocCommandCommand.cs b/src/Cmdlets/AdHocCommandCommand.cs index c23f63ca..ee227b9e 100644 --- a/src/Cmdlets/AdHocCommandCommand.cs +++ b/src/Cmdlets/AdHocCommandCommand.cs @@ -164,7 +164,7 @@ protected override void ProcessRecord() return; } WriteVerbose($"Invoke AdHocCommand:{job.Name} => Job:[{job.Id}]"); - JobManager.Add(job); + JobProgressManager.Add(job); } protected override void EndProcessing() { diff --git a/src/Cmdlets/InventoryUpdateCommand.cs b/src/Cmdlets/InventoryUpdateCommand.cs index 66dcb50c..a3d563ec 100644 --- a/src/Cmdlets/InventoryUpdateCommand.cs +++ b/src/Cmdlets/InventoryUpdateCommand.cs @@ -188,7 +188,7 @@ protected override void ProcessRecord() foreach (var job in UpdateInventory(Inventory)) { WriteVerbose($"Update InventorySource:{job.InventorySource} => Job:[{job.Id}]"); - JobManager.Add(job); + JobProgressManager.Add(job); } } @@ -211,7 +211,7 @@ protected override void ProcessRecord() { var job = UpdateInventorySource(Id); WriteVerbose($"Update InventorySource:{job.InventorySource} => Job:[{job.Id}]"); - JobManager.Add(job); + JobProgressManager.Add(job); } catch (RestAPIException) { } } diff --git a/src/Cmdlets/JobTemplateCommand.cs b/src/Cmdlets/JobTemplateCommand.cs index 6b663a2a..0ff636f4 100644 --- a/src/Cmdlets/JobTemplateCommand.cs +++ b/src/Cmdlets/JobTemplateCommand.cs @@ -984,7 +984,7 @@ protected override void ProcessRecord() var launchResult = Launch(Id); if (launchResult != null) { - JobManager.Add(launchResult); + JobProgressManager.Add(launchResult); } } catch (RestAPIException) { } diff --git a/src/Cmdlets/ProjectUpdateCommand.cs b/src/Cmdlets/ProjectUpdateCommand.cs index 079e7f62..8d8f03ee 100644 --- a/src/Cmdlets/ProjectUpdateCommand.cs +++ b/src/Cmdlets/ProjectUpdateCommand.cs @@ -139,7 +139,7 @@ protected override void ProcessRecord() { var job = UpdateProject(Id); WriteVerbose($"Update Project:{Id} => Job:[{job.Id}]"); - JobManager.Add(job); + JobProgressManager.Add(job); } catch (RestAPIException) { } } diff --git a/src/Cmdlets/SystemJobTemplateCommand.cs b/src/Cmdlets/SystemJobTemplateCommand.cs index 94f786b8..6f12ab81 100644 --- a/src/Cmdlets/SystemJobTemplateCommand.cs +++ b/src/Cmdlets/SystemJobTemplateCommand.cs @@ -109,7 +109,7 @@ protected override void ProcessRecord() { var job = Launch(Id); WriteVerbose($"Launch SystemJobTemplate:{Id} => Job:[{job.Id}]"); - JobManager.Add(job); + JobProgressManager.Add(job); } catch (RestAPIException) { } } diff --git a/src/Cmdlets/UnifiedJobCommand.cs b/src/Cmdlets/UnifiedJobCommand.cs index c786f29b..fc862cb9 100644 --- a/src/Cmdlets/UnifiedJobCommand.cs +++ b/src/Cmdlets/UnifiedJobCommand.cs @@ -147,13 +147,13 @@ public class WaitJobCommand : InvokeJobBase protected override void ProcessRecord() { - JobManager.Add(Id, new JobProgress(Id, Type)); + JobProgressManager.Add(Id, new JobProgress(Id, Type)); } protected override void EndProcessing() { - JobManager.UpdateJob(); + JobProgressManager.UpdateJob(); ShowJobLog(SuppressJobLog); - JobManager.CleanCompleted(); + JobProgressManager.CleanCompleted(); WaitJobs("Wait Job", IntervalSeconds, SuppressJobLog); } } diff --git a/src/Cmdlets/WorkflowJobTemplateCommand.cs b/src/Cmdlets/WorkflowJobTemplateCommand.cs index 376d08e8..09d955b2 100644 --- a/src/Cmdlets/WorkflowJobTemplateCommand.cs +++ b/src/Cmdlets/WorkflowJobTemplateCommand.cs @@ -525,7 +525,7 @@ protected override void ProcessRecord() var launchResult = Launch(Id); if (launchResult != null) { - JobManager.Add(launchResult); + JobProgressManager.Add(launchResult); } } catch (RestAPIException) { } From 46c3ca38846f490f7c060a35350d2aae025c8def Mon Sep 17 00:00:00 2001 From: teramako Date: Mon, 9 Sep 2024 22:24:10 +0900 Subject: [PATCH 011/160] :bug: Add `IntervalSeconds` and `SuppressJobLog` parameters to `Invoke-SystemJobTemplate` --- .../en-US/cmdlets/Invoke-SystemJobTemplate.md | 46 +++++++++++++++++-- src/Cmdlets/SystemJobTemplateCommand.cs | 2 + 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/docs/en-US/cmdlets/Invoke-SystemJobTemplate.md b/docs/en-US/cmdlets/Invoke-SystemJobTemplate.md index 8c679c1f..4220b9ed 100644 --- a/docs/en-US/cmdlets/Invoke-SystemJobTemplate.md +++ b/docs/en-US/cmdlets/Invoke-SystemJobTemplate.md @@ -14,14 +14,14 @@ Invoke (launch) a SystemJobTemplate and wait unti the job is finished. ### Id ``` -Invoke-SystemJobTemplate [-Id] [-ExtraVars ] +Invoke-SystemJobTemplate [-IntervalSeconds ] [-SuppressJobLog] [-Id] [-ExtraVars ] [] ``` ### Template ``` -Invoke-SystemJobTemplate [-SystemJobTemplate] [-ExtraVars ] - [] +Invoke-SystemJobTemplate [-IntervalSeconds ] [-SuppressJobLog] [-SystemJobTemplate] + [-ExtraVars ] [] ``` ## DESCRIPTION @@ -101,6 +101,46 @@ Accept pipeline input: True (ByValue) Accept wildcard characters: False ``` +### -IntervalSeconds +Interval to confirm job completion (seconds). +Default is 5 seconds. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 5 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SuppressJobLog +Suppress display job log. + +> [!TIP] +> If you need the job log, use `-InformationVariable` parameter likes following: +> +> PS C:\> Invoke-SystemJobTemplate ... -SuppressJobLog -InformationVariable joblog +> (snip) +> PS C:\> $joblog +> ==> [463] SystemJob +> (snip) + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -SystemJobTemplate SystemJobTempalte object to be launched. diff --git a/src/Cmdlets/SystemJobTemplateCommand.cs b/src/Cmdlets/SystemJobTemplateCommand.cs index 6f12ab81..83da3cd6 100644 --- a/src/Cmdlets/SystemJobTemplateCommand.cs +++ b/src/Cmdlets/SystemJobTemplateCommand.cs @@ -94,9 +94,11 @@ protected SystemJob.Detail Launch(ulong id) [OutputType(typeof(SystemJob))] public class InvokeSystemJobTemplateCommand : LaunchSystemJobTemplateCommandBase { + [Parameter()] [ValidateRange(5, int.MaxValue)] public int IntervalSeconds { get; set; } = 5; + [Parameter()] public SwitchParameter SuppressJobLog { get; set; } protected override void ProcessRecord() From 0ff6c091014984907eed1eeee646f2e77c2897fb Mon Sep 17 00:00:00 2001 From: teramako Date: Sun, 8 Sep 2024 17:33:06 +0900 Subject: [PATCH 012/160] :sparkles: Implement `IResource` interface and `Resource` structure --- src/Resources/ResourceBase.cs | 8 +++++++- src/Resources/SummaryField.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Resources/ResourceBase.cs b/src/Resources/ResourceBase.cs index c8376a9d..faee4293 100644 --- a/src/Resources/ResourceBase.cs +++ b/src/Resources/ResourceBase.cs @@ -1,6 +1,6 @@ namespace AWX.Resources { - public interface IResource + public interface IResource { /// /// Database ID for the resource @@ -10,6 +10,12 @@ public interface IResource /// Data type for the resource /// ResourceType Type { get; } + } + + record struct Resource(ResourceType Type, ulong Id) : IResource; + + public interface IResource : IResource + { string Url { get; } /// /// Data structure with URLs of related resources. diff --git a/src/Resources/SummaryField.cs b/src/Resources/SummaryField.cs index 9db3c198..ea7808bf 100644 --- a/src/Resources/SummaryField.cs +++ b/src/Resources/SummaryField.cs @@ -14,7 +14,7 @@ public sealed override string ToString() return sb.ToString(); } } - public abstract record ResourceSummary(ulong Id, ResourceType Type) : SummaryBase; + public abstract record ResourceSummary(ulong Id, ResourceType Type) : SummaryBase, IResource; [JsonConverter(typeof(Json.CapabilityConverter))] [Flags] From d6a9aeafadcdbdc6c6af7655197b98d0fcb0b7d4 Mon Sep 17 00:00:00 2001 From: teramako Date: Sun, 8 Sep 2024 17:33:45 +0900 Subject: [PATCH 013/160] :sparkles: Implement Resource transformation attributes - `ResourceTransformationAttribute` - `ResourceIdTransformationAttribute` --- src/Cmdlets/ResourceTransformation.cs | 139 ++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/Cmdlets/ResourceTransformation.cs diff --git a/src/Cmdlets/ResourceTransformation.cs b/src/Cmdlets/ResourceTransformation.cs new file mode 100644 index 00000000..398ba9ca --- /dev/null +++ b/src/Cmdlets/ResourceTransformation.cs @@ -0,0 +1,139 @@ +using System.Collections; +using System.Management.Automation; +using AWX.Resources; + +namespace AWX.Cmdlets +{ + class ResourceIdTransformationAttribute : ResourceTransformationAttribute + { + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + switch (inputData) + { + case IList list: + return TransformList(engineIntrinsics, list); + default: + return TransformToId(engineIntrinsics, inputData); + } + } + private IList TransformList(EngineIntrinsics engineIntrinsics, IList list) + { + var arr = new List(); + foreach (var inputItem in list) + { + arr.Add(TransformToId(engineIntrinsics, inputItem)); + } + return arr; + } + private ulong TransformToId(EngineIntrinsics engineIntrinsics, object inputData) + { + if (inputData is PSObject pso) + inputData = pso.BaseObject; + + switch (inputData) + { + case int: + case long: + if (ulong.TryParse($"{inputData}", out var id)) + return id; + throw new ArgumentException(); + case uint: + case ulong: + id = (ulong)inputData; + return id; + } + + var resource = base.Transform(engineIntrinsics, inputData) as IResource; + if (resource != null) + return resource.Id; + + throw new ArgumentException(); + } + } + + class ResourceTransformationAttribute : ArgumentTransformationAttribute + { + public ResourceType[] AcceptableTypes { get; init; } = []; + + private bool Validate(IResource resource) + { + if (resource.Id == 0) return false; + if (AcceptableTypes.Length == 0) + return AcceptableTypes.Any(type => resource.Type == type); + return true; + } + + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + if (inputData is PSObject pso) + { + inputData = pso.BaseObject; + } + return TransformToResource(inputData); + } + protected ResourceType ToResourceType(object? data) + { + switch (data) + { + case ResourceType resType: + return resType; + case string strType: + if (Enum.TryParse(strType, true, out var type)) + return type; + break; + case int intType: + return (ResourceType)intType; + } + throw new ArgumentException($"Could not convert to ResourcType: {data} ({data?.GetType().Name})"); + } + protected ulong ToULong(object? data) + { + switch (data) + { + case int: + case long: + if (ulong.TryParse($"{data}", out var id)) + return id; + break; + case uint: + case ulong: + return (ulong)data; + } + throw new ArgumentException($"Could not convert to ulong: {data} ({data?.GetType().Name})"); + } + protected IResource TransformToResource(object inputData) + { + switch (inputData) + { + case IResource resource: + if (Validate(resource)) + return resource; + + break; + case IDictionary dict: + ResourceType type = ResourceType.None;; + ulong id = 0; + foreach (var key in dict.Keys) + { + var strKey = key as string; + if (strKey == null) continue; + switch (strKey.ToLowerInvariant()) + { + case "type": + type = ToResourceType(dict[key]); + continue; + case "id": + id = ToULong(dict[key]); + continue; + } + } + var res = new Resource(type, id); + if (Validate(res)) + return res; + + break; + } + throw new ArgumentException($"{nameof(inputData)} should be {typeof(IResource)}: {inputData}"); + } + } +} From 92179290e4efe7d9f523ce0dcedfd9bcb45ed30d Mon Sep 17 00:00:00 2001 From: teramako Date: Tue, 10 Sep 2024 17:50:24 +0900 Subject: [PATCH 014/160] Fix to support HTTP status 204 (NoContent) when POST --- src/Cmdlets/APIBase.cs | 4 +- src/Cmdlets/InventoryUpdateCommand.cs | 4 +- src/Cmdlets/InvokeAPICommand.cs | 65 +++++++++++++--------- src/Cmdlets/JobTemplateCommand.cs | 1 + src/Cmdlets/ProjectUpdateCommand.cs | 2 +- src/Cmdlets/SystemJobTemplateCommand.cs | 2 +- src/Cmdlets/WorkflowJobTemplateCommand.cs | 1 + src/RestAPI.cs | 67 +++++++++++++++++------ src/RestAPIResult.cs | 19 ++++++- 9 files changed, 113 insertions(+), 52 deletions(-) diff --git a/src/Cmdlets/APIBase.cs b/src/Cmdlets/APIBase.cs index cca361df..966055ed 100644 --- a/src/Cmdlets/APIBase.cs +++ b/src/Cmdlets/APIBase.cs @@ -505,7 +505,7 @@ protected virtual IEnumerable> GetResultSet(string pat /// /// Return the result if success, otherwise null /// - protected virtual RestAPIResult CreateResource(string pathAndQuery, object? sendData = null) + protected virtual RestAPIPostResult CreateResource(string pathAndQuery, object? sendData = null) where TValue : class { WriteVerboseRequest(pathAndQuery, Method.POST); @@ -513,7 +513,7 @@ protected virtual RestAPIResult CreateResource(string pathAndQue { using var apiTask = RestAPI.PostJsonAsync(pathAndQuery, sendData); apiTask.Wait(); - RestAPIResult result = apiTask.Result; + RestAPIPostResult result = apiTask.Result; WriteVerboseResponse(result.Response); return result; } diff --git a/src/Cmdlets/InventoryUpdateCommand.cs b/src/Cmdlets/InventoryUpdateCommand.cs index a3d563ec..f914d76b 100644 --- a/src/Cmdlets/InventoryUpdateCommand.cs +++ b/src/Cmdlets/InventoryUpdateCommand.cs @@ -111,12 +111,12 @@ protected void CheckCanUpdateInventory(Inventory inventory) protected InventoryUpdateJob.Detail UpdateInventorySource(ulong id) { var apiResult = CreateResource($"{InventorySource.PATH}{id}/update/"); - return apiResult.Contents; + return apiResult.Contents ?? throw new NullReferenceException(); } protected InventoryUpdateJob.Detail[] UpdateInventory(Inventory inventory) { var apiResult = CreateResource($"{Inventory.PATH}{inventory.Id}/update_inventory_sources/"); - return apiResult.Contents; + return apiResult.Contents ?? throw new NullReferenceException(); } protected override void ProcessRecord() { diff --git a/src/Cmdlets/InvokeAPICommand.cs b/src/Cmdlets/InvokeAPICommand.cs index 53f2921a..38471ab5 100644 --- a/src/Cmdlets/InvokeAPICommand.cs +++ b/src/Cmdlets/InvokeAPICommand.cs @@ -52,49 +52,62 @@ protected override void BeginProcessing() pathAndQuery = Path; } } - protected override void ProcessRecord() + private IRestAPIResult InvokeAPI(string pathAndQuery) { - if (string.IsNullOrEmpty(pathAndQuery)) { return; } - WriteVerboseRequest(pathAndQuery, Method); - - Task>? task; - switch (Method) { case Method.GET: - task = RestAPI.GetAsync(pathAndQuery); - break; + { + var task = RestAPI.GetAsync(pathAndQuery); + task.Wait(); + return task.Result; + } case Method.POST: - task = RestAPI.PostJsonAsync(pathAndQuery, SenData); - break; + { + var task = RestAPI.PostJsonAsync(pathAndQuery, SenData); + task.Wait(); + return task.Result; + } case Method.PUT: - if (SenData == null) + if (SenData != null) { - throw new ArgumentNullException(nameof(SenData)); + var task = RestAPI.PutJsonAsync(pathAndQuery, SenData); + task.Wait(); + return task.Result; } - task = RestAPI.PutJsonAsync(pathAndQuery, SenData); - break; + throw new ArgumentNullException(nameof(SenData)); case Method.PATCH: - if (SenData == null) + if (SenData != null) { - throw new ArgumentNullException(nameof(SenData)); + var task = RestAPI.PatchJsonAsync(pathAndQuery, SenData); + task.Wait(); + return task.Result; } - task = RestAPI.PatchJsonAsync(pathAndQuery, SenData); - break; + throw new ArgumentNullException(nameof(SenData)); case Method.DELETE: - task = RestAPI.DeleteAsync(pathAndQuery); - break; + { + var task = RestAPI.DeleteAsync(pathAndQuery); + task.Wait(); + return task.Result; + } case Method.OPTIONS: - task = RestAPI.OptionsJsonAsync(pathAndQuery); - break; + { + var task = RestAPI.OptionsJsonAsync(pathAndQuery); + task.Wait(); + return task.Result; + } default: throw new NotSupportedException(); - } - task.Wait(); - var result = task.Result; + } + protected override void ProcessRecord() + { + if (string.IsNullOrEmpty(pathAndQuery)) { return; } + WriteVerboseRequest(pathAndQuery, Method); + + var result = InvokeAPI(pathAndQuery); WriteVerboseResponse(result.Response); - if (!AsRawString && result.Response.ContentType == "application/json") + if (!AsRawString && result.Response.ContentType == "application/json" && result.Contents != null) { if (APIPath.TryGetTypeFromPath(pathAndQuery, Method, out var type)) { diff --git a/src/Cmdlets/JobTemplateCommand.cs b/src/Cmdlets/JobTemplateCommand.cs index 0ff636f4..6c098279 100644 --- a/src/Cmdlets/JobTemplateCommand.cs +++ b/src/Cmdlets/JobTemplateCommand.cs @@ -950,6 +950,7 @@ Enter the skip tags. Multiple values can be separated by commas(`,`). } var apiResult = CreateResource($"{JobTemplate.PATH}{id}/launch/", sendData); var launchResult = apiResult.Contents; + if (launchResult == null) return null; WriteVerbose($"Launch JobTemplate:{id} => Job:[{launchResult.Id}]"); if (launchResult.IgnoredFields.Count > 0) { diff --git a/src/Cmdlets/ProjectUpdateCommand.cs b/src/Cmdlets/ProjectUpdateCommand.cs index 8d8f03ee..4cc79bfa 100644 --- a/src/Cmdlets/ProjectUpdateCommand.cs +++ b/src/Cmdlets/ProjectUpdateCommand.cs @@ -105,7 +105,7 @@ protected void CheckCanUpdate(ulong projectId) protected ProjectUpdateJob.Detail UpdateProject(ulong projectId) { var apiResult = CreateResource($"{Project.PATH}{projectId}/update/"); - return apiResult.Contents; + return apiResult.Contents ?? throw new NullReferenceException(); } } diff --git a/src/Cmdlets/SystemJobTemplateCommand.cs b/src/Cmdlets/SystemJobTemplateCommand.cs index 83da3cd6..6f5f1c8e 100644 --- a/src/Cmdlets/SystemJobTemplateCommand.cs +++ b/src/Cmdlets/SystemJobTemplateCommand.cs @@ -86,7 +86,7 @@ protected Hashtable CreateSendData() protected SystemJob.Detail Launch(ulong id) { var apiResult = CreateResource($"{SystemJobTemplate.PATH}{id}/launch/", CreateSendData()); - return apiResult.Contents; + return apiResult.Contents ?? throw new NullReferenceException(); } } diff --git a/src/Cmdlets/WorkflowJobTemplateCommand.cs b/src/Cmdlets/WorkflowJobTemplateCommand.cs index 09d955b2..f8209b42 100644 --- a/src/Cmdlets/WorkflowJobTemplateCommand.cs +++ b/src/Cmdlets/WorkflowJobTemplateCommand.cs @@ -491,6 +491,7 @@ Enter the skip tags. Multiple values can be separated by commas(`,`). } var apiResult = CreateResource($"{WorkflowJobTemplate.PATH}{id}/launch/", sendData); var launchResult = apiResult.Contents; + if (launchResult == null) return null; WriteVerbose($"Launch WorkflowJobTemplate:{id} => Job:[{launchResult.Id}]"); if (launchResult.IgnoredFields.Count > 0) { diff --git a/src/RestAPI.cs b/src/RestAPI.cs index 8067214d..9594997e 100644 --- a/src/RestAPI.cs +++ b/src/RestAPI.cs @@ -88,18 +88,46 @@ private static async Task CreateException(HttpResponseMessage } } - private static async Task> HandleResponse(HttpResponseMessage response) + /// + /// Handle HTTP response contents.
+ /// Returns JSON deserialized object if the HTTP status is success code and Content-Type is application/json. + ///
+ /// + /// + /// The contents object or exception is wrapped + /// " + private static async Task> HandleResponse(HttpResponseMessage response) where T : class { - long contentLength = response.Content.Headers.ContentLength ?? 0; var contentType = response.Content.Headers.ContentType?.MediaType ?? string.Empty; if (response.IsSuccessStatusCode) { - if (contentLength == 0) + if (typeof(T) == typeof(string)) { - return new RestAPIResult(response, contents: string.Empty); + long contentLength = response.Content.Headers.ContentLength ?? 0; + T? stringContents = (contentLength == 0 ? string.Empty : await response.Content.ReadAsStringAsync()) as T; + if (stringContents == null) + throw new NullReferenceException(); + return new RestAPIResult(response, stringContents); + } + if (contentType == JsonContentType) + { + try + { + var obj = await response.Content.ReadFromJsonAsync(Json.DeserializeOptions) + ?? throw new RestAPIException("Failed to read JSON. The result is null.", response); + return new RestAPIResult(response, obj); + + } + catch (Exception ex) + { + throw new RestAPIException($"Could not deserialize JSON to {typeof(T)}", response, ex); + } + } + else + { + throw new RestAPIException($"Invalid Content-Type: {contentType}", response); + } - string stringContent = await response.Content.ReadAsStringAsync(); - return new RestAPIResult(response, stringContent); } // Error handling throw await CreateException(response, contentType); @@ -112,24 +140,28 @@ private static async Task> HandleResponse(HttpResponseMess /// /// The contents object or exception is wrapped /// " - private static async Task> HandleResponse(HttpResponseMessage response) where T : class + private static async Task> HandlePostResponse(HttpResponseMessage response) where T : class { - if (typeof(T) == typeof(string)) - { - var stringTask = await HandleResponse(response) as RestAPIResult - ?? throw new RestAPIException($"Could not retrieve contents", response); - return stringTask; - } var contentType = response.Content.Headers.ContentType?.MediaType ?? string.Empty; if (response.IsSuccessStatusCode) { - if (contentType == JsonContentType) + long contentLength = response.Content.Headers.ContentLength ?? 0; + if (typeof(T) == typeof(string)) + { + string stringContents = (contentLength == 0 ? string.Empty : await response.Content.ReadAsStringAsync()); + return new RestAPIPostResult(response, stringContents as T); + } + else if (contentLength == 0 || response.StatusCode == HttpStatusCode.NoContent) + { + return new RestAPIPostResult(response); + } + else if (contentType == JsonContentType) { try { var obj = await response.Content.ReadFromJsonAsync(Json.DeserializeOptions) ?? throw new RestAPIException("Failed to read JSON. The result is null.", response); - return new RestAPIResult(response, obj); + return new RestAPIPostResult(response, obj); } catch (Exception ex) @@ -140,7 +172,6 @@ private static async Task> HandleResponse(HttpResponseMessag else { throw new RestAPIException($"Invalid Content-Type: {contentType}", response); - } } // Error handling @@ -281,11 +312,11 @@ private static StringContent GetStringContent(object? data) /// /// /// - public static async Task> PostJsonAsync(string path, object? data) where T : class + public static async Task> PostJsonAsync(string path, object? data) where T : class { using var jsonContent = GetStringContent(data); using HttpResponseMessage response = await Client.PostAsync(path, jsonContent); - return await HandleResponse(response); + return await HandlePostResponse(response); } /// /// Request PUT to AWX diff --git a/src/RestAPIResult.cs b/src/RestAPIResult.cs index 274c454f..531cbf1b 100644 --- a/src/RestAPIResult.cs +++ b/src/RestAPIResult.cs @@ -41,7 +41,7 @@ public interface IRestAPIResult /// /// Json deserialized object or contents string. /// - TValue Contents { get; } + TValue? Contents { get; } /// /// HTTP response /// @@ -61,4 +61,19 @@ public override string ToString() return $"{typeof(T).Name} ({Response.StatusCode:d} {Response.ReasonPhrase}: {Response.Method} {Response.RequestUri?.PathAndQuery})"; } } -} \ No newline at end of file + + /// + /// The contents of . + /// The response contents may be null especially HTTP status code is 204 (NotContent). + /// + /// + public class RestAPIPostResult(HttpResponseMessage response, T? contents = default(T)) : IRestAPIResult + { + public T? Contents { get; } = contents; + public RestAPIResponse Response { get; } = new RestAPIResponse(response); + public override string ToString() + { + return $"{typeof(T).Name} ({Response.StatusCode:d} {Response.ReasonPhrase}: {Response.Method} {Response.RequestUri?.PathAndQuery})"; + } + } +} From 6376104409327c2f0805141627c84c7ab42cadfa Mon Sep 17 00:00:00 2001 From: teramako Date: Tue, 10 Sep 2024 12:01:37 +0900 Subject: [PATCH 015/160] :sparkles: Implement cmdlet `New-Label` --- docs/en-US/cmdlets/AWX.psm.md | 3 + docs/en-US/cmdlets/New-Label.md | 112 ++++++++++++++++++++++++++++++++ src/Cmdlets/LabelCommand.cs | 32 +++++++++ 3 files changed, 147 insertions(+) create mode 100644 docs/en-US/cmdlets/New-Label.md diff --git a/docs/en-US/cmdlets/AWX.psm.md b/docs/en-US/cmdlets/AWX.psm.md index 5c0b9b4e..c1e67186 100644 --- a/docs/en-US/cmdlets/AWX.psm.md +++ b/docs/en-US/cmdlets/AWX.psm.md @@ -338,6 +338,9 @@ Invoke (update) a WorkflowJobTemplate and wait until the job is finished. ### [New-ApiConfig](New-ApiConfig.md) Create config file that should be used by this module. +### [New-Label](New-Label.md) +Create a Label. + ### [Start-AdHocCommand](Start-AdHocCommand.md) Invoke (launch) an AdHocCommand. diff --git a/docs/en-US/cmdlets/New-Label.md b/docs/en-US/cmdlets/New-Label.md new file mode 100644 index 00000000..1eeaefdd --- /dev/null +++ b/docs/en-US/cmdlets/New-Label.md @@ -0,0 +1,112 @@ +--- +external help file: AWX.psm.dll-Help.xml +Module Name: AWX.psm +online version: +schema: 2.0.0 +--- + +# New-Label + +## SYNOPSIS +Create a Label. + +## SYNTAX + +``` +New-Label -Name -Organization [-WhatIf] [-Confirm] + [] +``` + +## DESCRIPTION +Create a Label. + +Implements following Rest API: +- `/api/v2/labels/` (POST) + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> New-Label -Name TestLabel -Organization 1 +``` + +Create a Label named "TestLabel" in Organization ID 1. + +## PARAMETERS + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Name +Label name + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Organization +Organization ID. + +```yaml +Type: UInt64 +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String +Label name. + +## OUTPUTS + +### AWX.Resources.Label +New created Label object. + +## NOTES + +## RELATED LINKS diff --git a/src/Cmdlets/LabelCommand.cs b/src/Cmdlets/LabelCommand.cs index e5ad098a..66642e49 100644 --- a/src/Cmdlets/LabelCommand.cs +++ b/src/Cmdlets/LabelCommand.cs @@ -80,4 +80,36 @@ protected override void ProcessRecord() } } } + + [Cmdlet(VerbsCommon.New, "Label", SupportsShouldProcess = true)] + [OutputType(typeof(Label))] + public class NewLabelCommand : APICmdletBase + { + [Parameter(Mandatory = true, ValueFromPipeline = true)] + public string Name { get; set; } = string.Empty; + + [Parameter(Mandatory = true)] + [ValidateRange(1, ulong.MaxValue)] + public ulong Organization { get; set; } + + protected override void ProcessRecord() + { + var sendData = new Dictionary() + { + { "name", Name }, + { "organization", Organization }, + }; + + var newDescription = string.Join(", ", sendData.Select(kv => $"{kv.Key} = {kv.Value}")); + if (ShouldProcess($"{{ {newDescription} }}")) + { + try + { + var apiResult = CreateResource