diff --git a/adapters/powershell/PowerShell_adapter.dsc.resource.json b/adapters/powershell/PowerShell_adapter.dsc.resource.json index 49d233078..954191224 100644 --- a/adapters/powershell/PowerShell_adapter.dsc.resource.json +++ b/adapters/powershell/PowerShell_adapter.dsc.resource.json @@ -7,6 +7,7 @@ "tags": [ "PowerShell" ], + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "adapter": { "list": { "executable": "pwsh", diff --git a/adapters/powershell/powershell.dsc.resource.json b/adapters/powershell/powershell.dsc.resource.json index 397649f4a..23cd6246a 100644 --- a/adapters/powershell/powershell.dsc.resource.json +++ b/adapters/powershell/powershell.dsc.resource.json @@ -8,6 +8,7 @@ "tags": [ "PowerShell" ], + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "adapter": { "list": { "executable": "pwsh", diff --git a/data.build.json b/data.build.json index bd4e18e01..bd36e3f5d 100644 --- a/data.build.json +++ b/data.build.json @@ -29,6 +29,8 @@ "runcommandonset", "sshdconfig", "sshd_config.dsc.resource.json", + "sshd-subsystem.dsc.resource.json", + "sshd-subsystemList.dsc.resource.json", "y2j" ], "macOS": [ @@ -56,6 +58,8 @@ "runcommandonset", "sshdconfig", "sshd_config.dsc.resource.json", + "sshd-subsystem.dsc.resource.json", + "sshd-subsystemList.dsc.resource.json", "y2j" ], "Windows": [ @@ -92,6 +96,8 @@ "sshdconfig.exe", "sshd-windows.dsc.resource.json", "sshd_config.dsc.resource.json", + "sshd-subsystem.dsc.resource.json", + "sshd-subsystemList.dsc.resource.json", "windowspowershell.dsc.resource.json", "windowsupdate.dsc.resource.json", "wu_dsc.exe", diff --git a/dsc/src/mcp/list_dsc_resources.rs b/dsc/src/mcp/list_dsc_resources.rs index 7b53d7d05..0047d0e58 100644 --- a/dsc/src/mcp/list_dsc_resources.rs +++ b/dsc/src/mcp/list_dsc_resources.rs @@ -12,7 +12,6 @@ use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrap use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use tokio::task; #[derive(Serialize, JsonSchema)] @@ -63,7 +62,7 @@ impl McpServer { }, None => None, }; - let mut resources = BTreeMap::::new(); + let mut resources = Vec::::new(); for resource in dsc.list_available(&DiscoveryKind::Resource, &TypeNameFilter::default(), adapter_filter, ProgressFormat::None) { if let Resource(resource) = resource { let summary = ResourceSummary { @@ -72,10 +71,10 @@ impl McpServer { description: resource.description.clone(), require_adapter: resource.require_adapter, }; - resources.insert(resource.type_name.clone(), summary); + resources.push(summary); } } - Ok(ResourceListResult { resources: resources.into_values().collect() }) + Ok(ResourceListResult { resources }) }).await.map_err(|e| McpError::internal_error(e.to_string(), None))??; Ok(Json(result)) diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index ab51b52d0..aa40c24d0 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -104,8 +104,8 @@ Describe 'Tests for MCP server' { $response = Send-McpRequest -request $mcpRequest $response.id | Should -BeGreaterOrEqual 3 - $resources = dsc resource list | ConvertFrom-Json -Depth 20 | Select-Object type, kind, description -Unique - $response.result.structuredContent.resources.Count | Should -Be $resources.Count + $resources = dsc resource list | ConvertFrom-Json -Depth 20 | Select-Object type, kind, description + $response.result.structuredContent.resources.Count | Should -Be $resources.Count -Because "MCP:`n$($response.result.structuredContent.resources | Format-Table | Out-String)`nDSC:`n$($resources | Format-Table | Out-String)" for ($i = 0; $i -lt $resources.Count; $i++) { ($response.result.structuredContent.resources[$i].psobject.properties | Measure-Object).Count | Should -BeGreaterOrEqual 3 $response.result.structuredContent.resources[$i].type | Should -BeExactly $resources[$i].type -Because ($response.result.structuredContent | ConvertTo-Json -Depth 20 | Out-String) diff --git a/dsc/tests/dsc_resource_list.tests.ps1 b/dsc/tests/dsc_resource_list.tests.ps1 index 88d70ec9f..267c72ec5 100644 --- a/dsc/tests/dsc_resource_list.tests.ps1 +++ b/dsc/tests/dsc_resource_list.tests.ps1 @@ -38,20 +38,28 @@ Describe 'Tests for listing resources' { ) { param($tags, $description, $expectedCount, $expectedType) - if ($tags -and $description) { - $resources = dsc resource list --tags $tags --description $description | ConvertFrom-Json - } - elseif ($tags) { - $resources = dsc resource list --tags $tags | ConvertFrom-Json - } - else { - $resources = dsc resource list --description $description | ConvertFrom-Json - } + $oldPath = $env:DSC_RESOURCE_PATH + try { + # Need to restrict the search as more resources are being added like from PS7 + $env:DSC_RESOURCE_PATH = Split-Path (Get-Command dsc).Source -Parent - $LASTEXITCODE | Should -Be 0 - $resources.Count | Should -Be $expectedCount - if ($expectedCount -gt 0) { - $resources.type | Should -BeExactly $expectedType + if ($tags -and $description) { + $resources = dsc resource list --tags $tags --description $description | ConvertFrom-Json + } + elseif ($tags) { + $resources = dsc resource list --tags $tags | ConvertFrom-Json + } + else { + $resources = dsc resource list --description $description | ConvertFrom-Json + } + + $LASTEXITCODE | Should -Be 0 + $resources.Count | Should -Be $expectedCount + if ($expectedCount -gt 0) { + $resources.type | Should -BeExactly $expectedType + } + } finally { + $env:DSC_RESOURCE_PATH = $oldPath } } @@ -111,4 +119,46 @@ Describe 'Tests for listing resources' { } $foundWideLine | Should -BeTrue } + + It 'No duplicates based on type name and version are returned' { + $resource_manifest = @' +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.DSC.Debug/EchoDupe", + "version": "1.2.3", + "description": "A duplicate resource for testing", + "get": { + "executable": "dscecho", + "args": [ + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dscecho" + } + } +} +'@ + $manifestPath = Join-Path $TestDrive "echoDupeManifest.dsc.resource.json" + $manifestDupePath = Join-Path $TestDrive "echoDupeManifestDuplicate.dsc.resource.json" + Set-Content -Path $manifestPath -Value $resource_manifest + Set-Content -Path $manifestDupePath -Value $resource_manifest + + $oldPath = $env:DSC_RESOURCE_PATH + try { + $env:DSC_RESOURCE_PATH = $TestDrive + [System.IO.Path]::PathSeparator + $env:PATH + $resources = dsc resource list | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $resourceGroups = $resources | Group-Object -Property type, version + foreach ($group in $resourceGroups) { + $group.Count | Should -Be 1 -Because ($resources | ConvertTo-Json -Depth 20) + } + } finally { + $env:DSC_RESOURCE_PATH = $oldPath + } + } } diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json index 9096e4fa1..5323dc614 100644 --- a/extensions/powershell/powershell.dsc.extension.json +++ b/extensions/powershell/powershell.dsc.extension.json @@ -3,6 +3,7 @@ "type": "Microsoft.PowerShell/Discover", "version": "0.1.0", "description": "Discovers DSC resources packaged in PowerShell 7 modules.", + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "discover": { "executable": "pwsh", "args": [ diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index 6446d9996..58565ae40 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -68,7 +68,7 @@ impl Discovery { Box::new(command_discovery::CommandDiscovery::new(progress_format)), ]; - let mut resources: Vec = Vec::new(); + let mut resources: BTreeMap = BTreeMap::new(); for mut discovery_type in discovery_types { @@ -81,8 +81,16 @@ impl Discovery { }; for (_resource_name, found_resources) in discovered_resources { - for resource in found_resources { - resources.push(resource.clone()); + for manifest in found_resources { + let key = match &manifest { + ImportedManifest::Resource(resource) => { + format!("{}@{}", resource.type_name.to_lowercase(), resource.version) + }, + ImportedManifest::Extension(extension) => { + format!("{}@{}", extension.type_name.to_lowercase(), extension.version) + } + }; + resources.insert(key, manifest); } }; @@ -91,7 +99,7 @@ impl Discovery { } } - resources + resources.into_values().collect::>() } pub fn get_extensions(&mut self, capability: &Capability) -> Vec { diff --git a/resources/PSScript/psscript.dsc.resource.json b/resources/PSScript/psscript.dsc.resource.json index fda07988d..c189434a1 100644 --- a/resources/PSScript/psscript.dsc.resource.json +++ b/resources/PSScript/psscript.dsc.resource.json @@ -3,6 +3,7 @@ "type": "Microsoft.DSC.Transitional/PowerShellScript", "description": "Enable running PowerShell 7 scripts inline", "version": "0.1.0", + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "get": { "executable": "pwsh", "args": [ diff --git a/resources/sshdconfig/sshd-subsystem.dsc.resource.json b/resources/sshdconfig/sshd-subsystem.dsc.resource.json index 32b9923c5..c11850730 100644 --- a/resources/sshdconfig/sshd-subsystem.dsc.resource.json +++ b/resources/sshdconfig/sshd-subsystem.dsc.resource.json @@ -3,6 +3,7 @@ "type": "Microsoft.OpenSSH.SSHD/Subsystem", "description": "Manage SSH Server Subsystem keyword (single entry)", "version": "0.1.0", + "condition": "[not(equals(tryWhich('sshd'), null()))]", "get": { "executable": "sshdconfig", "args": [ diff --git a/resources/sshdconfig/sshd-subsystemList.dsc.resource.json b/resources/sshdconfig/sshd-subsystemList.dsc.resource.json index 1d64fb9c9..6fe9a6c9a 100644 --- a/resources/sshdconfig/sshd-subsystemList.dsc.resource.json +++ b/resources/sshdconfig/sshd-subsystemList.dsc.resource.json @@ -3,6 +3,7 @@ "type": "Microsoft.OpenSSH.SSHD/SubsystemList", "description": "Manage SSH Server Subsystem keyword (accepts multiple entries)", "version": "0.1.0", + "condition": "[not(equals(tryWhich('sshd'), null()))]", "get": { "executable": "sshdconfig", "args": [ diff --git a/resources/sshdconfig/sshd-windows.dsc.resource.json b/resources/sshdconfig/sshd-windows.dsc.resource.json index 6a92d0a12..814b9d1df 100644 --- a/resources/sshdconfig/sshd-windows.dsc.resource.json +++ b/resources/sshdconfig/sshd-windows.dsc.resource.json @@ -5,6 +5,7 @@ "tags": [ "Windows" ], + "condition": "[not(equals(tryWhich('sshd'), null()))]", "version": "0.1.0", "get": { "executable": "sshdconfig", diff --git a/resources/sshdconfig/sshd_config.dsc.resource.json b/resources/sshdconfig/sshd_config.dsc.resource.json index aafa95d6f..223f4f05c 100644 --- a/resources/sshdconfig/sshd_config.dsc.resource.json +++ b/resources/sshdconfig/sshd_config.dsc.resource.json @@ -2,6 +2,7 @@ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Microsoft.OpenSSH.SSHD/sshd_config", "description": "Manage SSH Server Configuration", + "condition": "[not(equals(tryWhich('sshd'), null()))]", "version": "0.1.0", "get": { "executable": "sshdconfig",