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
2 changes: 1 addition & 1 deletion dsc/Cargo.lock

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

2 changes: 1 addition & 1 deletion dsc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dsc"
version = "3.1.1"
version = "3.1.2"
edition = "2021"

[profile.release]
Expand Down
17 changes: 9 additions & 8 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ use std::{
collections::HashMap,
io::{self, IsTerminal},
path::Path,
process::exit
process::exit,
slice::from_ref,
};
use tracing::{debug, error, trace};

Expand Down Expand Up @@ -495,7 +496,7 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat)
continue;
}

resource_types.push(type_name.to_lowercase().to_string());
resource_types.push(type_name.to_lowercase().clone());
}
dsc.find_resources(&resource_types, progress_format);

Expand Down Expand Up @@ -578,16 +579,16 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat
list_resources(&mut dsc, resource_name.as_ref(), adapter_name.as_ref(), description.as_ref(), tags.as_ref(), output_format.as_ref(), progress_format);
},
ResourceSubCommand::Schema { resource , output_format } => {
dsc.find_resources(&[resource.to_string()], progress_format);
dsc.find_resources(from_ref(resource), progress_format);
resource_command::schema(&dsc, resource, output_format.as_ref());
},
ResourceSubCommand::Export { resource, input, file, output_format } => {
dsc.find_resources(&[resource.to_string()], progress_format);
dsc.find_resources(from_ref(resource), progress_format);
let parsed_input = get_input(input.as_ref(), file.as_ref(), false);
resource_command::export(&mut dsc, resource, &parsed_input, output_format.as_ref());
},
ResourceSubCommand::Get { resource, input, file: path, all, output_format } => {
dsc.find_resources(&[resource.to_string()], progress_format);
dsc.find_resources(from_ref(resource), progress_format);
if *all {
resource_command::get_all(&dsc, resource, output_format.as_ref());
}
Expand All @@ -601,17 +602,17 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat
}
},
ResourceSubCommand::Set { resource, input, file: path, output_format } => {
dsc.find_resources(&[resource.to_string()], progress_format);
dsc.find_resources(from_ref(resource), progress_format);
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
resource_command::set(&dsc, resource, &parsed_input, output_format.as_ref());
},
ResourceSubCommand::Test { resource, input, file: path, output_format } => {
dsc.find_resources(&[resource.to_string()], progress_format);
dsc.find_resources(from_ref(resource), progress_format);
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
resource_command::test(&dsc, resource, &parsed_input, output_format.as_ref());
},
ResourceSubCommand::Delete { resource, input, file: path } => {
dsc.find_resources(&[resource.to_string()], progress_format);
dsc.find_resources(from_ref(resource), progress_format);
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
resource_command::delete(&dsc, resource, &parsed_input);
},
Expand Down
51 changes: 51 additions & 0 deletions dsc/tests/dsc_discovery.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,55 @@ Describe 'tests for resource discovery' {
$env:DSC_RESOURCE_PATH = $oldPath
}
}

It 'Resource manifest using relative path to exe: <path>' -TestCases @(
@{ path = '../dscecho'; success = $true }
@{ path = '../foo/dscecho'; success = $false }
) {
param($path, $success)
$manifest = @"
{
"`$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"type": "Microsoft.DSC.Debug/Echo",
"version": "1.0.0",
"description": "Echo resource for testing and debugging purposes",
"get": {
"executable": "$path",
"args": [
{
"jsonInputArg": "--input",
"mandatory": true
}
]
},
"schema": {
"command": {
"executable": "$path"
}
}
}
"@
$dscEcho = Get-Command dscecho -ErrorAction Stop
# copy to testdrive
Copy-Item -Path "$($dscEcho.Source)" -Destination $testdrive
# create manifest in subfolder
$subfolder = Join-Path $testdrive 'subfolder'
New-Item -Path $subfolder -ItemType Directory -Force | Out-Null
Set-Content -Path (Join-Path $subfolder 'test.dsc.resource.json') -Value $manifest

try {
$env:DSC_RESOURCE_PATH = $subfolder
$out = dsc resource get -r 'Microsoft.DSC.Debug/Echo' -i '{"output":"RelativePathTest"}' 2> "$testdrive/error.txt" | ConvertFrom-Json
if ($success) {
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw -Path "$testdrive/error.txt")
$out.actualState.output | Should -BeExactly 'RelativePathTest'
} else {
$LASTEXITCODE | Should -Be 2 -Because (Get-Content -Raw -Path "$testdrive/error.txt")
(Get-Content -Raw -Path "$testdrive/error.txt") | Should -Match "ERROR.*?Executable '\.\./foo/dscecho(\.exe)?' not found"
}
}
finally {
$env:DSC_RESOURCE_PATH = $null
}
}
}
2 changes: 2 additions & 0 deletions dsc_lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,5 @@ foundSetting = "Found setting '%{name}' in %{path}"
notFoundSetting = "Setting '%{name}' not found in %{path}"
failedToGetExePath = "Can't get 'dsc' executable path"
settingNotFound = "Setting '%{name}' not found"
executableNotFoundInWorkingDirectory = "Executable '%{executable}' not found with working directory '%{cwd}'"
executableNotFound = "Executable '%{executable}' not found"
6 changes: 3 additions & 3 deletions dsc_lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ impl Configurator {
value.clone()
};
info!("{}", t!("configure.mod.setVariable", name = name, value = new_value));
self.context.variables.insert(name.to_string(), new_value);
self.context.variables.insert(name.clone(), new_value);
}
Ok(())
}
Expand Down Expand Up @@ -839,13 +839,13 @@ fn get_failure_from_error(err: &DscError) -> Option<Failure> {
match err {
DscError::CommandExit(_resource, exit_code, reason) => {
Some(Failure {
message: reason.to_string(),
message: reason.clone(),
exit_code: *exit_code,
})
},
DscError::CommandExitFromManifest(_resource, exit_code, reason) => {
Some(Failure {
message: reason.to_string(),
message: reason.clone(),
exit_code: *exit_code,
})
},
Expand Down
41 changes: 21 additions & 20 deletions dsc_lib/src/discovery/command_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tracing::{debug, info, trace, warn};
use which::which;

use crate::util::get_setting;
use crate::util::get_exe_path;
use crate::util::canonicalize_which;

const DSC_RESOURCE_EXTENSIONS: [&str; 3] = [".dsc.resource.json", ".dsc.resource.yaml", ".dsc.resource.yml"];
const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extension.yaml", ".dsc.extension.yml"];
Expand Down Expand Up @@ -121,10 +121,11 @@ impl CommandDiscovery {

let dsc_resource_path = env::var_os("DSC_RESOURCE_PATH");
if resource_path_setting.allow_env_override && dsc_resource_path.is_some(){
let value = dsc_resource_path.unwrap();
debug!("DSC_RESOURCE_PATH: {:?}", value.to_string_lossy());
using_custom_path = true;
paths.append(&mut env::split_paths(&value).collect::<Vec<_>>());
if let Some(value) = dsc_resource_path {
debug!("DSC_RESOURCE_PATH: {:?}", value.to_string_lossy());
using_custom_path = true;
paths.append(&mut env::split_paths(&value).collect::<Vec<_>>());
}
} else {
for p in resource_path_setting.directories {
let v = PathBuf::from_str(&p);
Expand Down Expand Up @@ -649,38 +650,38 @@ fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result<Ds

let mut capabilities: Vec<Capability> = vec![];
if let Some(get) = &manifest.get {
verify_executable(&manifest.resource_type, "get", &get.executable);
verify_executable(&manifest.resource_type, "get", &get.executable, path.parent().unwrap());
capabilities.push(Capability::Get);
}
if let Some(set) = &manifest.set {
verify_executable(&manifest.resource_type, "set", &set.executable);
verify_executable(&manifest.resource_type, "set", &set.executable, path.parent().unwrap());
capabilities.push(Capability::Set);
if set.handles_exist == Some(true) {
capabilities.push(Capability::SetHandlesExist);
}
}
if let Some(what_if) = &manifest.what_if {
verify_executable(&manifest.resource_type, "what_if", &what_if.executable);
verify_executable(&manifest.resource_type, "what_if", &what_if.executable, path.parent().unwrap());
capabilities.push(Capability::WhatIf);
}
if let Some(test) = &manifest.test {
verify_executable(&manifest.resource_type, "test", &test.executable);
verify_executable(&manifest.resource_type, "test", &test.executable, path.parent().unwrap());
capabilities.push(Capability::Test);
}
if let Some(delete) = &manifest.delete {
verify_executable(&manifest.resource_type, "delete", &delete.executable);
verify_executable(&manifest.resource_type, "delete", &delete.executable, path.parent().unwrap());
capabilities.push(Capability::Delete);
}
if let Some(export) = &manifest.export {
verify_executable(&manifest.resource_type, "export", &export.executable);
verify_executable(&manifest.resource_type, "export", &export.executable, path.parent().unwrap());
capabilities.push(Capability::Export);
}
if let Some(resolve) = &manifest.resolve {
verify_executable(&manifest.resource_type, "resolve", &resolve.executable);
verify_executable(&manifest.resource_type, "resolve", &resolve.executable, path.parent().unwrap());
capabilities.push(Capability::Resolve);
}
if let Some(SchemaKind::Command(command)) = &manifest.schema {
verify_executable(&manifest.resource_type, "schema", &command.executable);
verify_executable(&manifest.resource_type, "schema", &command.executable, path.parent().unwrap());
}

let resource = DscResource {
Expand All @@ -706,7 +707,7 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result<

let mut capabilities: Vec<dscextension::Capability> = vec![];
if let Some(discover) = &manifest.discover {
verify_executable(&manifest.r#type, "discover", &discover.executable);
verify_executable(&manifest.r#type, "discover", &discover.executable, path.parent().unwrap());
capabilities.push(dscextension::Capability::Discover);
}

Expand All @@ -724,8 +725,8 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result<
Ok(extension)
}

fn verify_executable(resource: &str, operation: &str, executable: &str) {
if which(executable).is_err() {
fn verify_executable(resource: &str, operation: &str, executable: &str, directory: &Path) {
if canonicalize_which(executable, Some(directory.to_string_lossy().as_ref())).is_err() {
warn!("{}", t!("discovery.commandDiscovery.executableNotFound", resource = resource, operation = operation, executable = executable));
}
}
Expand All @@ -739,15 +740,15 @@ fn sort_adapters_based_on_lookup_table(unsorted_adapters: &BTreeMap<String, Vec<
if let Some(adapter_name) = lookup_table.get(needed_resource) {
if let Some(resource_vec) = unsorted_adapters.get(adapter_name) {
debug!("Lookup table found resource '{}' in adapter '{}'", needed_resource, adapter_name);
result.insert(adapter_name.to_string(), resource_vec.clone());
result.insert(adapter_name.clone(), resource_vec.clone());
}
}
}

// now add remaining adapters
for (adapter_name, adapters) in unsorted_adapters {
if !result.contains_key(adapter_name) {
result.insert(adapter_name.to_string(), adapters.clone());
result.insert(adapter_name.clone(), adapters.clone());
}
}

Expand All @@ -761,8 +762,8 @@ fn add_resources_to_lookup_table(adapted_resources: &BTreeMap<String, Vec<DscRes
let mut lookup_table_changed = false;
for (resource_name, res_vec) in adapted_resources {
if let Some(adapter_name) = &res_vec[0].require_adapter {
let new_value = adapter_name.to_string();
let oldvalue = lookup_table.insert(resource_name.to_string().to_lowercase(), new_value.clone());
let new_value = adapter_name.clone();
let oldvalue = lookup_table.insert(resource_name.clone().to_lowercase(), new_value.clone());
if !lookup_table_changed && (oldvalue.is_none() || oldvalue.is_some_and(|val| val != new_value)) {
lookup_table_changed = true;
}
Expand Down
13 changes: 7 additions & 6 deletions 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, process::Stdio};
use crate::configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}};
use crate::{configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}}, util::canonicalize_which};
use crate::dscerror::DscError;
use super::{dscresource::get_diff, 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 Expand Up @@ -459,11 +459,11 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result<String, DscE
};

match schema_kind {
SchemaKind::Command(ref command) => {
SchemaKind::Command(command) => {
let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, command.args.clone(), None, Some(cwd), None, resource.exit_codes.as_ref())?;
Ok(stdout)
},
SchemaKind::Embedded(ref schema) => {
SchemaKind::Embedded(schema) => {
let json = serde_json::to_string(schema)?;
Ok(json)
},
Expand Down Expand Up @@ -662,7 +662,7 @@ async fn run_process_async(executable: &str, args: Option<Vec<String>>, input: O
if code != 0 {
if let Some(exit_codes) = exit_codes {
if let Some(error_message) = exit_codes.get(&code) {
return Err(DscError::CommandExitFromManifest(executable.to_string(), code, error_message.to_string()));
return Err(DscError::CommandExitFromManifest(executable.to_string(), code, error_message.clone()));
}
}
return Err(DscError::Command(executable.to_string(), code, stderr_result));
Expand Down Expand Up @@ -697,12 +697,13 @@ async fn run_process_async(executable: &str, args: Option<Vec<String>>, input: O
#[allow(clippy::implicit_hasher)]
pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option<&str>, cwd: Option<&str>, env: Option<HashMap<String, String>>, exit_codes: Option<&HashMap<i32, String>>) -> Result<(i32, String, String), DscError> {
debug!("{}", t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?}));
let executable = canonicalize_which(executable, cwd)?;

tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(run_process_async(executable, args, input, cwd, env, exit_codes))
.block_on(run_process_async(&executable, args, input, cwd, env, exit_codes))
}

/// Process the arguments for a command resource.
Expand Down Expand Up @@ -837,7 +838,7 @@ fn json_to_hashmap(json: &str) -> Result<HashMap<String, String>, DscError> {
},
Value::Null => {
// ignore null values
},
},
Value::Object(_) => {
return Err(DscError::Operation(t!("dscresources.commandResource.invalidKey", key = key).to_string()));
},
Expand Down
12 changes: 6 additions & 6 deletions dsc_lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
let sub_diff = get_diff(value, &actual[key]);
if !sub_diff.is_empty() {
debug!("{}", t!("dscresources.dscresource.subDiff", key = key));
diff_properties.push(key.to_string());
diff_properties.push(key.clone());
}
}
else {
Expand All @@ -532,22 +532,22 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
if let Some(actual_array) = actual[key].as_array() {
if !is_same_array(value_array, actual_array) {
info!("{}", t!("dscresources.dscresource.diffArray", key = key));
diff_properties.push(key.to_string());
diff_properties.push(key.clone());
}
} else {
info!("{}", t!("dscresources.dscresource.diffNotArray", key = actual[key]));
diff_properties.push(key.to_string());
diff_properties.push(key.clone());
}
} else if value != &actual[key] {
diff_properties.push(key.to_string());
diff_properties.push(key.clone());
}
} else {
info!("{}", t!("dscresources.dscresource.diffKeyMissing", key = key));
diff_properties.push(key.to_string());
diff_properties.push(key.clone());
}
} else {
info!("{}", t!("dscresources.dscresource.diffKeyNotObject", key = key));
diff_properties.push(key.to_string());
diff_properties.push(key.clone());
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions dsc_lib/src/dscresources/invoke_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ pub enum TestResult {
#[must_use]
pub fn get_in_desired_state(test_result: &TestResult) -> bool {
match test_result {
TestResult::Resource(ref resource_test_result) => {
TestResult::Resource(resource_test_result) => {
resource_test_result.in_desired_state
},
TestResult::Group(ref group_test_result) => {
TestResult::Group(group_test_result) => {
for result in group_test_result {
if !get_in_desired_state(&(result.result)) {
return false;
Expand Down
Loading
Loading