Skip to content
Draft
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
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,10 @@ ipnetwork = { version = "0.21" }
cc = { version = "1.2" }

# test-only dependencies
# dsc-lib-jsonschema
# dsc-lib-jsonschema, dsc-lib
pretty_assertions = { version = "1.4.1" }
# dsc-lib
test-case = { version = "3.3" }

# Workspace crates as dependencies
dsc-lib = { path = "lib/dsc-lib" }
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 @@ -13,7 +13,7 @@ use dsc_lib::{
SetResult,
TestResult,
},
}, types::FullyQualifiedTypeName
}, FullyQualifiedTypeName
};
use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters};
use rust_i18n::t;
Expand Down
2 changes: 1 addition & 1 deletion 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, FullyQualifiedTypeName
};
use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters};
use rust_i18n::t;
Expand Down
6 changes: 4 additions & 2 deletions dsc/src/mcp/show_dsc_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
use crate::mcp::mcp_server::McpServer;
use dsc_lib::{
DscManager,
FullyQualifiedTypeName,
TypeVersion,
discovery::discovery_trait::DiscoveryFilter,
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 @@ -25,7 +27,7 @@ pub struct DscResource {
/// The kind of resource.
pub kind: Kind,
/// The version of the resource.
pub version: String,
pub version: TypeVersion,
/// The capabilities of the resource.
pub capabilities: Vec<Capability>,
/// The description of the resource.
Expand Down
2 changes: 1 addition & 1 deletion dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap
table.add_row(vec![
resource.type_name.to_string(),
format!("{:?}", resource.kind),
resource.version,
resource.version.to_string(),
capabilities,
resource.require_adapter.unwrap_or_default().to_string(),
resource.description.unwrap_or_default()
Expand Down
8 changes: 6 additions & 2 deletions lib/dsc-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
thiserror = { workspace = true }
semver = { workspace = true }
semver = { workspace = true, features = ["serde"] }
tokio = { workspace = true, features = [
"io-util",
"macros",
Expand All @@ -38,7 +38,7 @@ tokio = { workspace = true, features = [
tracing = { workspace = true }
tracing-indicatif = { workspace = true }
tree-sitter = { workspace = true }
tree-sitter-rust = { workspace = true}
tree-sitter-rust = { workspace = true }
uuid = { workspace = true }
url = { workspace = true }
urlencoding = { workspace = true }
Expand All @@ -52,6 +52,10 @@ tree-sitter-dscexpression = { workspace = true }

[dev-dependencies]
serde_yaml = { workspace = true }
# Helps review complex comparisons, like schemas
pretty_assertions = { workspace = true }
# Enables parameterized test cases
test-case = { workspace = true }

[build-dependencies]
cc = { workspace = true }
1 change: 1 addition & 0 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ resourceManifestNotFound = "Resource manifest not found"
schema = "Schema"
schemaNotAvailable = "No Schema found and `validate` is not supported"
securityContext = "Security context"
typeVersionToSemverConversion = "Can't convert arbitrary string `Version` to `semver::Version`"
utf8Conversion = "UTF-8 conversion"
unknown = "Unknown"
validation = "Validation"
Expand Down
134 changes: 103 additions & 31 deletions lib/dsc-lib/locales/schemas.definitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,106 @@ _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`.
title:
en-us: Fully qualified type name
description:
en-us: >-
Uniquely identifies a DSC resource or extension.
markdownDescription:
en-us: |-
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:
en-us: >-
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`.

semver:
title:
en-us: Semantic version
description:
en-us: |-
A valid semantic version (semver) string.

For reference, see https://semver.org/
markdownDescription:
en-us: |-
A valid semantic version ([semver][01]) string.

This value uses the [suggested regular expression][02] to validate whether the string is
valid semver. This is the same pattern, made multi-line for easier readability:

```regex
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
(?:-(
(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))
*))?
(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
```

The first line matches the `major.minor.patch` components of the version. The middle
lines match the pre-release components. The last line matches the build metadata
component.

[01]: https://semver.org/
[02]: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
patternErrorMessage:
en-us: |-
Invalid value, must be a semantic version like `<major>.<minor>.<patch>`, such as `1.2.3`.

The value may also include pre-release version information and build metadata.

version:
title:
en-us: Version
description:
en-us: >-
Defines the version for the type as either a semantic version (semver) or arbitrary string.
markdownDescription:
en-us: |-
Defines the version for the type as either a semantic version (semver) or arbitrary string.

If the type adheres to [semantic versioning][01], its manifest should define the version as
a valid semantic version like `1.2.3`. When a resource or extension specifies a semantic
version, DSC uses the latest available version of that resource or extension by default.

Instead of specifying a semantic version, the type can specify any arbitrary string. In
that case, DSC uses simple string sorting to determine the default version to use.

Users can override the default behavior and require a specific version of a resource with
the `something` field.
stringVariant:
title:
en-us: Arbitrary version string
description:
en-us: >-
Defines the version for the type as an arbitrary string.
markdownDescription:
en-us: |-
Defines the version for the type as an arbitrary string. When the version for the type
isn't a valid semantic version, DSC treats the version as a string. This enables
DSC to support non-semantically-versioned types, such as using a release date as the
version.
2 changes: 1 addition & 1 deletion lib/dsc-lib/src/configure/config_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::{collections::HashMap, fmt::Display};
use crate::{schemas::{
dsc_repo::DscRepoSchema,
transforms::{idiomaticize_externally_tagged_enum, idiomaticize_string_enum}
}, types::FullyQualifiedTypeName};
}, FullyQualifiedTypeName};

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)]
#[serde(rename_all = "camelCase")]
Expand Down
2 changes: 1 addition & 1 deletion lib/dsc-lib/src/configure/config_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +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;
use crate::FullyQualifiedTypeName;

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
Expand Down
2 changes: 1 addition & 1 deletion lib/dsc-lib/src/configure/depends_on.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +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 crate::FullyQualifiedTypeName;

use rust_i18n::t;
use serde_json::Value;
Expand Down
16 changes: 8 additions & 8 deletions lib/dsc-lib/src/discovery/command_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ impl ResourceDiscovery for CommandDiscovery {
fn filter_resources(found_resources: &mut BTreeMap<String, Vec<DscResource>>, required_resources: &mut HashMap<DiscoveryFilter, bool>, resources: &[DscResource], filter: &DiscoveryFilter) {
for resource in resources {
if let Some(required_version) = filter.version() {
if let Ok(resource_version) = Version::parse(&resource.version) {
if let Some(resource_version) = resource.semantic_version() {
if let Ok(version_req) = VersionReq::parse(required_version) {
if version_req.matches(&resource_version) && matches_adapter_requirement(resource, filter) {
found_resources.entry(filter.resource_type().to_string()).or_default().push(resource.clone());
Expand Down Expand Up @@ -595,15 +595,15 @@ fn insert_resource(resources: &mut BTreeMap<String, Vec<DscResource>>, resource:
// compare the resource versions and insert newest to oldest using semver
let mut insert_index = resource_versions.len();
for (index, resource_instance) in resource_versions.iter().enumerate() {
let resource_instance_version = match Version::parse(&resource_instance.version) {
Ok(v) => v,
Err(_err) => {
let resource_instance_version = match resource_instance.semantic_version() {
Some(v) => v,
None => {
continue;
},
};
let resource_version = match Version::parse(&resource.version) {
Ok(v) => v,
Err(_err) => {
let resource_version = match resource.semantic_version() {
Some(v) => v,
None => {
continue;
},
};
Expand Down Expand Up @@ -737,7 +737,7 @@ pub fn load_manifest(path: &Path) -> Result<Vec<ImportedManifest>, DscError> {
}

fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result<DscResource, DscError> {
if let Err(err) = validate_semver(&manifest.version) {
if let Err(err) = validate_semver(&manifest.version.to_string()) {
warn!("{}", t!("discovery.commandDiscovery.invalidManifestVersion", path = path.to_string_lossy(), err = err).to_string());
}

Expand Down
2 changes: 1 addition & 1 deletion lib/dsc-lib/src/discovery/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ impl Discovery {
let version = fix_semver(version);
if let Ok(version_req) = VersionReq::parse(&version) {
for resource in resources {
if let Ok(resource_version) = Version::parse(&resource.version) {
if let Some(resource_version) = resource.semantic_version() {
if version_req.matches(&resource_version) && matches_adapter_requirement(resource, filter) {
return Ok(Some(resource));
}
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 @@ -121,6 +121,9 @@ pub enum DscError {
#[error("semver: {0}")]
SemVer(#[from] semver::Error),

#[error("{t}: '{0}'", t = t!("dscerror.typeVersionToSemverConversion"))]
TypeVersionToSemverConversion(String),

#[error("{t}: {0}", t = t!("dscerror.utf8Conversion"))]
Utf8Conversion(#[from] Utf8Error),

Expand Down
2 changes: 1 addition & 1 deletion lib/dsc-lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rust_i18n::t;
use serde::Deserialize;
use serde_json::{Map, Value};
use std::{collections::HashMap, env, path::Path, process::Stdio};
use crate::{configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}}, types::FullyQualifiedTypeName, util::canonicalize_which};
use crate::{configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}}, FullyQualifiedTypeName, util::canonicalize_which};
use crate::dscerror::DscError;
use super::{dscresource::{get_diff, redact}, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
use tracing::{error, warn, info, debug, trace};
Expand Down
Loading