Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ switch ($Operation) {
@{
type = "Test/TestCase"
kind = 'resource'
version = '1'
version = '1.0.0'
capabilities = @('get', 'set', 'test', 'export')
path = $PSScriptRoot
directory = Split-Path $PSScriptRoot
Expand Down
18 changes: 9 additions & 9 deletions adapters/powershell/Tests/powershellgroup.resource.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ Describe 'PowerShell adapter resource tests' {
$srcPath = Join-Path $PSScriptRoot 'TestClassResource'
$pathRoot1 = Join-Path $TestDrive 'A'
$pathRoot2 = Join-Path $TestDrive 'B'
$path1 = Join-Path $pathRoot1 'TestClassResource' '1.0'
$path2 = Join-Path $pathRoot1 'TestClassResource' '1.1'
$path3 = Join-Path $pathRoot2 'TestClassResource' '2.0'
$path1 = Join-Path $pathRoot1 'TestClassResource' '1.0.0'
$path2 = Join-Path $pathRoot1 'TestClassResource' '1.1.0'
$path3 = Join-Path $pathRoot2 'TestClassResource' '2.0.0'
$path4 = Join-Path $pathRoot2 'TestClassResource' '2.0.1'

New-Item -ItemType Directory -Force -Path $path1 | Out-Null
Expand All @@ -212,11 +212,11 @@ Describe 'PowerShell adapter resource tests' {
$files | Copy-Item -Destination $path4

$filePath = Join-Path $path1 'TestClassResource.psd1'
(Get-Content -Raw $filePath).Replace("ModuleVersion = `'0.0.1`'", "ModuleVersion = `'1.0`'") | Set-Content $filePath
(Get-Content -Raw $filePath).Replace("ModuleVersion = `'0.0.1`'", "ModuleVersion = `'1.0.0`'") | Set-Content $filePath
$filePath = Join-Path $path2 'TestClassResource.psd1'
(Get-Content -Raw $filePath).Replace("ModuleVersion = `'0.0.1`'", "ModuleVersion = `'1.1`'") | Set-Content $filePath
(Get-Content -Raw $filePath).Replace("ModuleVersion = `'0.0.1`'", "ModuleVersion = `'1.1.0`'") | Set-Content $filePath
$filePath = Join-Path $path3 'TestClassResource.psd1'
(Get-Content -Raw $filePath).Replace("ModuleVersion = `'0.0.1`'", "ModuleVersion = `'2.0`'") | Set-Content $filePath
(Get-Content -Raw $filePath).Replace("ModuleVersion = `'0.0.1`'", "ModuleVersion = `'2.0.0`'") | Set-Content $filePath
$filePath = Join-Path $path4 'TestClassResource.psd1'
(Get-Content -Raw $filePath).Replace("ModuleVersion = `'0.0.1`'", "ModuleVersion = `'2.0.1`'") | Set-Content $filePath

Expand Down Expand Up @@ -377,15 +377,15 @@ Describe 'PowerShell adapter resource tests' {
}

It 'Specifying version works' {
$out = dsc resource get -r TestClassResource/TestClassResource --version 0.0.1 | ConvertFrom-Json
$out = dsc resource get -r TestClassResource/TestClassResource --version '=0.0.1' | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$out.actualState.Ensure | Should -BeExactly 'Present'
}

It 'Specifying a non-existent version returns an error' {
$null = dsc resource get -r TestClassResource/TestClassResource --version 0.0.2 2> $TestDrive/error.log
$null = dsc resource get -r TestClassResource/TestClassResource --version '=0.0.2' 2> $TestDrive/error.log
$LASTEXITCODE | Should -Be 7
(Get-Content -Raw -Path $TestDrive/error.log) | Should -BeLike '*Resource not found: TestClassResource/TestClassResource 0.0.2*' -Because (Get-Content -Raw -Path $TestDrive/error.log)
(Get-Content -Raw -Path $TestDrive/error.log) | Should -BeLike '*Resource not found: TestClassResource/TestClassResource =0.0.2*' -Because (Get-Content -Raw -Path $TestDrive/error.log)
}

It 'Can process SecureString property' {
Expand Down
19 changes: 16 additions & 3 deletions adapters/powershell/psDscAdapter/psDscAdapter.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,26 @@ function Add-AstMembers {
}
}

function ConvertTo-SemanticVersionString {
[cmdletbinding()]
param([version]$version)

$patch = if ($version.Build -ne -1) { $version.Build } else { 0 }

"{0}.{1}.{2}" -f @(
$version.Major,
$version.Minor,
$patch
)
}

function FindAndParseResourceDefinitions {
[CmdletBinding(HelpUri = '')]
param(
[Parameter(Mandatory = $true)]
[string]$filePath,
[Parameter(Mandatory = $true)]
[string]$moduleVersion
[version]$moduleVersion
)

if (-not (Test-Path $filePath)) {
Expand Down Expand Up @@ -134,7 +147,7 @@ function FindAndParseResourceDefinitions {
#TODO: ModuleName, Version and ParentPath should be taken from psd1 contents
$DscResourceInfo.ModuleName = [System.IO.Path]::GetFileNameWithoutExtension($filePath)
$DscResourceInfo.ParentPath = [System.IO.Path]::GetDirectoryName($filePath)
$DscResourceInfo.Version = $moduleVersion
$DscResourceInfo.Version = ConvertTo-SemanticVersionString $moduleVersion

$DscResourceInfo.Properties = [System.Collections.Generic.List[DscResourcePropertyInfo]]::new()
$DscResourceInfo.Capabilities = GetClassBasedCapabilities $typeDefinitionAst.Members
Expand Down Expand Up @@ -206,7 +219,7 @@ function LoadPowerShellClassResourcesFromModule {
$scriptPath = $moduleInfo.Path;
}

$version = if ($moduleInfo.Version) { $moduleInfo.Version.ToString() } else { '0.0.0' }
$version = if ($moduleInfo.Version) { $moduleInfo.Version } else { [version]'0.0.0' }
$Resources = FindAndParseResourceDefinitions $scriptPath $version

if ($moduleInfo.NestedModules) {
Expand Down
22 changes: 20 additions & 2 deletions adapters/powershell/psDscAdapter/win_psDscAdapter.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ if ($PSVersionTable.PSVersion.Major -gt 5) {
}
}

function ConvertTo-SemanticVersionString {
[cmdletbinding()]
param([version]$version)

$patch = if ($version.Build -ne -1) { $version.Build } else { 0 }

"{0}.{1}.{2}" -f @(
$version.Major,
$version.Minor,
$patch
)
}

<# public function Invoke-DscCacheRefresh
.SYNOPSIS
This function caches the results of the Get-DscResource call to optimize performance.
Expand Down Expand Up @@ -187,7 +200,12 @@ function Invoke-DscCacheRefresh {
$DscResourceInfo = [DscResourceInfo]::new()
$dscResource.PSObject.Properties | ForEach-Object -Process {
if ($null -ne $_.Value) {
$DscResourceInfo.$($_.Name) = $_.Value
# Handle version specially to munge as semantic version
if ($_.Name -eq 'Version') {
$DscResourceInfo.$($_.Name) = ConvertTo-SemanticVersionString $_.Value
} else {
$DscResourceInfo.$($_.Name) = $_.Value
}
} else {
$DscResourceInfo.$($_.Name) = ''
}
Expand All @@ -212,7 +230,7 @@ function Invoke-DscCacheRefresh {
# workaround: populate module version from psmoduleinfo if available
if ($moduleInfo = $Modules | Where-Object { $_.Name -eq $moduleName }) {
$moduleInfo = $moduleInfo | Sort-Object -Property Version -Descending | Select-Object -First 1
$DscResourceInfo.Version = $moduleInfo.Version.ToString()
$DscResourceInfo.Version = ConvertTo-SemanticVersionString $moduleInfo.Version
}
}

Expand Down
4 changes: 2 additions & 2 deletions adapters/wmi/wmi.resource.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ switch ($Operation) {
'List' {
$clases = Get-CimClass

foreach ($r in $clases) {
$version_string = ""
foreach ($r in $clases) {`
$version_string = "1.0.0" # WMI resources don't have a version, default to 1.0.0
$author_string = ""
$description = ""

Expand Down
33 changes: 18 additions & 15 deletions dsc/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::{Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
use dsc_lib::dscresources::command_resource::TraceLevel;
use dsc_lib::progress::ProgressFormat;
use dsc_lib::types::{FullyQualifiedTypeName, ResourceVersionReq, TypeNameFilter};
use rust_i18n::t;
use serde::Deserialize;

Expand Down Expand Up @@ -178,7 +179,8 @@ pub enum ExtensionSubCommand {
#[clap(name = "list", about = t!("args.listExtensionAbout").to_string())]
List {
/// Optional extension name to filter the list
extension_name: Option<String>,
#[clap(default_value_t)]
extension_name: TypeNameFilter,
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
output_format: Option<ListOutputFormat>,
},
Expand All @@ -200,10 +202,11 @@ pub enum ResourceSubCommand {
#[clap(name = "list", about = t!("args.listAbout").to_string())]
List {
/// Optional resource name to filter the list
resource_name: Option<String>,
#[clap(default_value_t)]
resource_name: TypeNameFilter,
/// Optional adapter filter to apply to the list of resources
#[clap(short = 'a', long = "adapter", help = t!("args.adapter").to_string())]
adapter_name: Option<String>,
adapter_name: Option<TypeNameFilter>,
#[clap(short, long, help = t!("args.description").to_string())]
description: Option<String>,
#[clap(short, long, help = t!("args.tags").to_string())]
Expand All @@ -216,9 +219,9 @@ pub enum ResourceSubCommand {
#[clap(short, long, help = t!("args.getAll").to_string())]
all: bool,
#[clap(short, long, help = t!("args.resource").to_string())]
resource: String,
resource: FullyQualifiedTypeName,
#[clap(short, long, help = t!("args.version").to_string())]
version: Option<String>,
version: Option<ResourceVersionReq>,
#[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")]
input: Option<String>,
#[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")]
Expand All @@ -229,9 +232,9 @@ pub enum ResourceSubCommand {
#[clap(name = "set", about = "Invoke the set operation to a resource", arg_required_else_help = true)]
Set {
#[clap(short, long, help = t!("args.resource").to_string())]
resource: String,
resource: FullyQualifiedTypeName,
#[clap(short, long, help = t!("args.version").to_string())]
version: Option<String>,
version: Option<ResourceVersionReq>,
#[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")]
input: Option<String>,
#[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")]
Expand All @@ -244,9 +247,9 @@ pub enum ResourceSubCommand {
#[clap(name = "test", about = "Invoke the test operation to a resource", arg_required_else_help = true)]
Test {
#[clap(short, long, help = t!("args.resource").to_string())]
resource: String,
resource: FullyQualifiedTypeName,
#[clap(short, long, help = t!("args.version").to_string())]
version: Option<String>,
version: Option<ResourceVersionReq>,
#[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")]
input: Option<String>,
#[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")]
Expand All @@ -257,9 +260,9 @@ pub enum ResourceSubCommand {
#[clap(name = "delete", about = "Invoke the delete operation to a resource", arg_required_else_help = true)]
Delete {
#[clap(short, long, help = t!("args.resource").to_string())]
resource: String,
resource: FullyQualifiedTypeName,
#[clap(short, long, help = t!("args.version").to_string())]
version: Option<String>,
version: Option<ResourceVersionReq>,
#[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")]
input: Option<String>,
#[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")]
Expand All @@ -272,18 +275,18 @@ pub enum ResourceSubCommand {
#[clap(name = "schema", about = "Get the JSON schema for a resource", arg_required_else_help = true)]
Schema {
#[clap(short, long, help = t!("args.resource").to_string())]
resource: String,
resource: FullyQualifiedTypeName,
#[clap(short, long, help = t!("args.version").to_string())]
version: Option<String>,
version: Option<ResourceVersionReq>,
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
output_format: Option<OutputFormat>,
},
#[clap(name = "export", about = "Retrieve all resource instances", arg_required_else_help = true)]
Export {
#[clap(short, long, help = t!("args.resource").to_string())]
resource: String,
resource: FullyQualifiedTypeName,
#[clap(short, long, help = t!("args.version").to_string())]
version: Option<String>,
version: Option<ResourceVersionReq>,
#[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")]
input: Option<String>,
#[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")]
Expand Down
2 changes: 1 addition & 1 deletion dsc/src/mcp/invoke_dsc_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl McpServer {
pub async fn invoke_dsc_resource(&self, Parameters(InvokeDscResourceRequest { operation, resource_type, properties_json }): Parameters<InvokeDscResourceRequest>) -> Result<Json<InvokeDscResourceResponse>, McpError> {
let result = task::spawn_blocking(move || {
let mut dsc = DscManager::new();
let Some(resource) = dsc.find_resource(&DiscoveryFilter::new(&resource_type, None, None)).unwrap_or(None) else {
let Some(resource) = dsc.find_resource(&DiscoveryFilter::new_for_resource(&resource_type, None, None)).unwrap_or(None) else {
return Err(McpError::invalid_request(t!("mcp.invoke_dsc_resource.resourceNotFound", resource = resource_type), None));
};
match operation {
Expand Down
16 changes: 8 additions & 8 deletions dsc/src/mcp/list_dsc_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use dsc_lib::{
DscManager, discovery::{
command_discovery::ImportedManifest::Resource,
discovery_trait::{DiscoveryFilter, DiscoveryKind},
}, dscresources::resource_manifest::Kind, progress::ProgressFormat, types::FullyQualifiedTypeName
}, dscresources::resource_manifest::Kind, progress::ProgressFormat, types::{FullyQualifiedTypeName, TypeNameFilter}
};
use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters};
use rust_i18n::t;
Expand All @@ -32,7 +32,7 @@ pub struct ResourceSummary {
#[derive(Deserialize, JsonSchema)]
pub struct ListResourcesRequest {
#[schemars(description = "Filter adapted resources to only those requiring the specified adapter type. If not specified, all non-adapted resources are returned.")]
pub adapter: Option<String>,
pub adapter: Option<FullyQualifiedTypeName>,
}

#[tool_router(router = list_dsc_resources_router, vis = "pub")]
Expand All @@ -52,27 +52,27 @@ impl McpServer {
let mut dsc = DscManager::new();
let adapter_filter = match adapter {
Some(adapter) => {
if let Some(resource) = dsc.find_resource(&DiscoveryFilter::new(&adapter, None, None)).unwrap_or(None) {
if let Some(resource) = dsc.find_resource(&DiscoveryFilter::new_for_resource(&adapter, None, None)).unwrap_or(None) {
if resource.kind != Kind::Adapter {
return Err(McpError::invalid_params(t!("mcp.list_dsc_resources.resourceNotAdapter", adapter = adapter), None));
}
adapter
Some(&TypeNameFilter::Literal(resource.type_name.clone()))
} else {
return Err(McpError::invalid_params(t!("mcp.list_dsc_resources.adapterNotFound", adapter = adapter), None));
}
},
None => String::new(),
None => None,
};
let mut resources = BTreeMap::<String, ResourceSummary>::new();
for resource in dsc.list_available(&DiscoveryKind::Resource, "*", &adapter_filter, ProgressFormat::None) {
let mut resources = BTreeMap::<FullyQualifiedTypeName, ResourceSummary>::new();
for resource in dsc.list_available(&DiscoveryKind::Resource, &TypeNameFilter::default(), adapter_filter, ProgressFormat::None) {
if let Resource(resource) = resource {
let summary = ResourceSummary {
r#type: resource.type_name.clone(),
kind: resource.kind.clone(),
description: resource.description.clone(),
require_adapter: resource.require_adapter,
};
resources.insert(resource.type_name.to_lowercase(), summary);
resources.insert(resource.type_name.clone(), summary);
}
}
Ok(ResourceListResult { resources: resources.into_values().collect() })
Expand Down
8 changes: 4 additions & 4 deletions dsc/src/mcp/show_dsc_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use dsc_lib::{
dscresources::{
dscresource::{Capability, Invoke},
resource_manifest::Kind
}, types::FullyQualifiedTypeName,
}, types::{FullyQualifiedTypeName, ResourceVersion},
};
use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters};
use rust_i18n::t;
Expand All @@ -25,7 +25,7 @@ pub struct DscResource {
/// The kind of resource.
pub kind: Kind,
/// The version of the resource.
pub version: String,
pub version: ResourceVersion,
/// The capabilities of the resource.
pub capabilities: Vec<Capability>,
/// The description of the resource.
Expand All @@ -41,7 +41,7 @@ pub struct DscResource {
#[derive(Deserialize, JsonSchema)]
pub struct ShowResourceRequest {
#[schemars(description = "The type name of the resource to get detailed information.")]
pub r#type: String,
pub r#type: FullyQualifiedTypeName,
}

#[tool_router(router = show_dsc_resource_router, vis = "pub")]
Expand All @@ -59,7 +59,7 @@ impl McpServer {
pub async fn show_dsc_resource(&self, Parameters(ShowResourceRequest { r#type }): Parameters<ShowResourceRequest>) -> Result<Json<DscResource>, McpError> {
let result = task::spawn_blocking(move || {
let mut dsc = DscManager::new();
let Some(resource) = dsc.find_resource(&DiscoveryFilter::new(&r#type, None, None)).unwrap_or(None) else {
let Some(resource) = dsc.find_resource(&DiscoveryFilter::new_for_resource(&r#type, None, None)).unwrap_or(None) else {
return Err(McpError::invalid_params(t!("mcp.show_dsc_resource.resourceNotFound", type_name = r#type), None))
};
let schema = match resource.schema() {
Expand Down
Loading
Loading