Skip to content
Merged
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
8 changes: 3 additions & 5 deletions dsc/src/mcp/invoke_dsc_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@

use crate::mcp::mcp_server::McpServer;
use dsc_lib::{
configure::config_doc::ExecutionKind,
dscresources::{
DscManager, configure::config_doc::ExecutionKind, dscresources::{
dscresource::Invoke,
invoke_result::{
ExportResult,
GetResult,
SetResult,
TestResult,
},
},
DscManager,
}, types::FullyQualifiedTypeName
};
use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters};
use rust_i18n::t;
Expand Down Expand Up @@ -51,7 +49,7 @@ pub struct InvokeDscResourceRequest {
#[schemars(description = "The operation to perform on the DSC resource")]
pub operation: DscOperation,
#[schemars(description = "The type name of the DSC resource to invoke")]
pub resource_type: String,
pub resource_type: FullyQualifiedTypeName,
#[schemars(description = "The properties to pass to the DSC resource as JSON. Must match the resource JSON schema from `show_dsc_resource` tool.")]
pub properties_json: String,
}
Expand Down
8 changes: 4 additions & 4 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::DiscoveryKind,
}, dscresources::resource_manifest::Kind, progress::ProgressFormat
}, dscresources::resource_manifest::Kind, progress::ProgressFormat, types::FullyQualifiedTypeName
};
use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters};
use rust_i18n::t;
Expand All @@ -22,11 +22,11 @@ pub struct ResourceListResult {

#[derive(Serialize, JsonSchema)]
pub struct ResourceSummary {
pub r#type: String,
pub r#type: FullyQualifiedTypeName,
pub kind: Kind,
pub description: Option<String>,
#[serde(rename = "requireAdapter")]
pub require_adapter: Option<String>,
pub require_adapter: Option<FullyQualifiedTypeName>,
}

#[derive(Deserialize, JsonSchema)]
Expand Down Expand Up @@ -70,7 +70,7 @@ impl McpServer {
r#type: resource.type_name.clone(),
kind: resource.kind.clone(),
description: resource.description.clone(),
require_adapter: resource.require_adapter.clone(),
require_adapter: resource.require_adapter,
};
resources.insert(resource.type_name.to_lowercase(), summary);
}
Expand Down
4 changes: 2 additions & 2 deletions dsc/src/mcp/show_dsc_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use dsc_lib::{
dscresources::{
dscresource::{Capability, Invoke},
resource_manifest::Kind
},
}, types::FullyQualifiedTypeName,
};
use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters};
use rust_i18n::t;
Expand All @@ -20,7 +20,7 @@ use tokio::task;
pub struct DscResource {
/// The namespaced name of the resource.
#[serde(rename="type")]
pub type_name: String,
pub type_name: FullyQualifiedTypeName,
/// The kind of resource.
pub kind: Kind,
/// The version of the resource.
Expand Down
6 changes: 3 additions & 3 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format

if write_table {
table.add_row(vec![
extension.type_name,
extension.type_name.to_string(),
extension.version,
capabilities,
extension.description.unwrap_or_default()
Expand Down Expand Up @@ -820,11 +820,11 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap

if write_table {
table.add_row(vec![
resource.type_name,
resource.type_name.to_string(),
format!("{:?}", resource.kind),
resource.version,
capabilities,
resource.require_adapter.unwrap_or_default(),
resource.require_adapter.unwrap_or_default().to_string(),
resource.description.unwrap_or_default()
]);
}
Expand Down
2 changes: 2 additions & 0 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,8 @@ forExecutable = "for executable"
function = "Function"
integerConversion = "Function integer argument conversion"
invalidConfiguration = "Invalid configuration"
invalidTypeNamePrefix = "Invalid type name"
invalidTypeNameSuffix = "valid resource type names must match the following pattern"
unsupportedManifestVersion = "Unsupported manifest version"
mustBe = "Must be"
invalidFunctionParameterCount = "Invalid function parameter count for"
Expand Down
35 changes: 35 additions & 0 deletions lib/dsc-lib/locales/schemas.definitions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
_version: 2
schemas:
definitions:
resourceType:
title: Fully qualified type name
description: >-
Uniquely identifies a DSC resource or extension.
markdownDescription: |-
The fully qualified type name of a DSC resource or extension uniquely identifies a resource
or extension.

Fully qualified type names use the following syntax:

```yaml
<owner>[.<namespace>...]/<name>
```

Where the type may have zero or more namespace segments for organizing the type. The
`owner`, `namespace`, and `name` segments must consist only of alphanumeric characters and
underscores.

Conventionally, the first character of each segment is capitalized. When a segment
contains a brand or proper name, use the correct casing for that word, like
`TailspinToys/Settings`, not `Tailspintoys/Settings`.

Example fully qualified type names include:

- `Microsoft/OSInfo`
- `Microsoft.SqlServer/Database`
- `Microsoft.Windows.IIS/WebApp`
patternErrorMessage: >-
Invalid type name. Valid resource type names always define an owner and a name separated by
a slash, like `Microsoft/OSInfo`. Type names may optionally include the group, area, and
subarea segments to namespace the resource under the owner, like
`Microsoft.Windows/Registry`.
8 changes: 4 additions & 4 deletions lib/dsc-lib/src/configure/config_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use serde::{Deserialize, Deserializer, Serialize};
use serde_json::{Map, Value};
use std::{collections::HashMap, fmt::Display};

use crate::schemas::{
use crate::{schemas::{
dsc_repo::DscRepoSchema,
transforms::{idiomaticize_externally_tagged_enum, idiomaticize_string_enum}
};
}, types::FullyQualifiedTypeName};

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -383,7 +383,7 @@ pub struct Resource {
pub condition: Option<String>,
/// The fully qualified name of the resource type
#[serde(rename = "type")]
pub resource_type: String,
pub resource_type: FullyQualifiedTypeName,
#[serde(skip_serializing_if = "Option::is_none", rename = "apiVersion")]
pub api_version: Option<String>,
/// A friendly name for the resource instance
Expand Down Expand Up @@ -452,7 +452,7 @@ impl Resource {
#[must_use]
pub fn new() -> Self {
Self {
resource_type: String::new(),
resource_type: FullyQualifiedTypeName::default(),
name: String::new(),
depends_on: None,
kind: None,
Expand Down
9 changes: 5 additions & 4 deletions lib/dsc-lib/src/configure/config_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use serde_json::{Map, Value};
use crate::dscresources::invoke_result::{GetResult, SetResult, TestResult};
use crate::configure::config_doc::{Configuration, Metadata};
use crate::schemas::{dsc_repo::DscRepoSchema, transforms::idiomaticize_string_enum};
use crate::types::FullyQualifiedTypeName;

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
Expand All @@ -24,7 +25,7 @@ pub enum MessageLevel {
pub struct ResourceMessage {
pub name: String,
#[serde(rename="type")]
pub resource_type: String,
pub resource_type: FullyQualifiedTypeName,
pub message: String,
pub level: MessageLevel,
}
Expand All @@ -37,7 +38,7 @@ pub struct ResourceGetResult {
pub metadata: Option<Metadata>,
pub name: String,
#[serde(rename="type")]
pub resource_type: String,
pub resource_type: FullyQualifiedTypeName,
pub result: GetResult,
}

Expand Down Expand Up @@ -108,7 +109,7 @@ pub struct ResourceSetResult {
pub metadata: Option<Metadata>,
pub name: String,
#[serde(rename="type")]
pub resource_type: String,
pub resource_type: FullyQualifiedTypeName,
pub result: SetResult,
}

Expand Down Expand Up @@ -184,7 +185,7 @@ pub struct ResourceTestResult {
pub metadata: Option<Metadata>,
pub name: String,
#[serde(rename="type")]
pub resource_type: String,
pub resource_type: FullyQualifiedTypeName,
pub result: TestResult,
}

Expand Down
6 changes: 4 additions & 2 deletions lib/dsc-lib/src/configure/depends_on.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::configure::config_doc::Resource;
use crate::configure::{Configuration, IntOrExpression, ProcessMode, invoke_property_expressions};
use crate::DscError;
use crate::parser::Statement;
use crate::types::FullyQualifiedTypeName;

use rust_i18n::t;
use serde_json::Value;
Expand Down Expand Up @@ -131,14 +132,15 @@ fn unroll_and_push(order: &mut Vec<Resource>, resource: &Resource, parser: &mut
Ok(())
}

fn get_type_and_name(statement: &str) -> Result<(&str, String), DscError> {
fn get_type_and_name(statement: &str) -> Result<(FullyQualifiedTypeName, String), DscError> {
let parts: Vec<&str> = statement.split(':').collect();
if parts.len() != 2 {
return Err(DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = statement).to_string()));
}
// the name is url encoded so we need to decode it
let decoded_name = urlencoding::decode(parts[1]).map_err(|_| DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = statement).to_string()))?;
Ok((parts[0], decoded_name.into_owned()))
let type_name = parts[0].parse()?;
Ok((type_name, decoded_name.into_owned()))
}

#[cfg(test)]
Expand Down
8 changes: 4 additions & 4 deletions lib/dsc-lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ impl Configurator {
continue;
}
let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else {
return Err(DscError::ResourceNotFound(resource.resource_type, resource.api_version.as_deref().unwrap_or("").to_string()));
return Err(DscError::ResourceNotFound(resource.resource_type.to_string(), resource.api_version.as_deref().unwrap_or("").to_string()));
};
let properties = self.get_properties(&resource, &dsc_resource.kind)?;
let filter = add_metadata(dsc_resource, properties, resource.metadata.clone())?;
Expand Down Expand Up @@ -448,7 +448,7 @@ impl Configurator {
continue;
}
let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else {
return Err(DscError::ResourceNotFound(resource.resource_type, resource.api_version.as_deref().unwrap_or("").to_string()));
return Err(DscError::ResourceNotFound(resource.resource_type.to_string(), resource.api_version.as_deref().unwrap_or("").to_string()));
};
let properties = self.get_properties(&resource, &dsc_resource.kind)?;
debug!("resource_type {}", &resource.resource_type);
Expand Down Expand Up @@ -616,7 +616,7 @@ impl Configurator {
continue;
}
let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else {
return Err(DscError::ResourceNotFound(resource.resource_type, resource.api_version.as_deref().unwrap_or("").to_string()));
return Err(DscError::ResourceNotFound(resource.resource_type.to_string(), resource.api_version.as_deref().unwrap_or("").to_string()));
};
let properties = self.get_properties(&resource, &dsc_resource.kind)?;
debug!("resource_type {}", &resource.resource_type);
Expand Down Expand Up @@ -699,7 +699,7 @@ impl Configurator {
continue;
}
let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else {
return Err(DscError::ResourceNotFound(resource.resource_type.clone(), resource.api_version.as_deref().unwrap_or("").to_string()));
return Err(DscError::ResourceNotFound(resource.resource_type.to_string(), resource.api_version.as_deref().unwrap_or("").to_string()));
};
let properties = self.get_properties(resource, &dsc_resource.kind)?;
let input = add_metadata(dsc_resource, properties, resource.metadata.clone())?;
Expand Down
8 changes: 4 additions & 4 deletions lib/dsc-lib/src/discovery/command_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,18 +277,18 @@ impl ResourceDiscovery for CommandDiscovery {
if regex.is_match(&extension.type_name) {
trace!("{}", t!("discovery.commandDiscovery.extensionFound", extension = extension.type_name, version = extension.version));
// we only keep newest version of the extension so compare the version and only keep the newest
if let Some(existing_extension) = extensions.get_mut(&extension.type_name) {
if let Some(existing_extension) = extensions.get_mut(extension.type_name.as_ref()) {
let Ok(existing_version) = Version::parse(&existing_extension.version) else {
return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = existing_extension.type_name, version = existing_extension.version).to_string()));
};
let Ok(new_version) = Version::parse(&extension.version) else {
return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = extension.type_name, version = extension.version).to_string()));
};
if new_version > existing_version {
extensions.insert(extension.type_name.clone(), extension.clone());
extensions.insert(extension.type_name.to_string(), extension.clone());
}
} else {
extensions.insert(extension.type_name.clone(), extension.clone());
extensions.insert(extension.type_name.to_string(), extension.clone());
}
}
},
Expand Down Expand Up @@ -427,7 +427,7 @@ impl ResourceDiscovery for CommandDiscovery {
match serde_json::from_str::<DscResource>(line){
Result::Ok(resource) => {
if resource.require_adapter.is_none() {
warn!("{}", DscError::MissingRequires(adapter_name.clone(), resource.type_name.clone()).to_string());
warn!("{}", DscError::MissingRequires(adapter_name.clone(), resource.type_name.to_string()).to_string());
continue;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/dsc-lib/src/dscerror.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ pub enum DscError {
#[error("{t} '{0}': {1}", t = t!("dscerror.invalidRequiredVersion"))]
InvalidRequiredVersion(String, String),

#[error("{t} '{0}' - {t2}: '{1}'", t = t!("dscerror.invalidTypeNamePrefix"), t2 = t!("dscerror.InvalidTypeNameSuffix"))]
InvalidTypeName(String, String),

#[error("IO: {0}")]
Io(#[from] std::io::Error),

Expand Down
Loading