Skip to content

Commit 5a126f6

Browse files
authored
Merge pull request #1235 from SteveL-MSFT/v3.1.2
Update to version 3.1.2 with fix for relative paths in resource manifest
2 parents 3945ad8 + b42df43 commit 5a126f6

File tree

12 files changed

+135
-61
lines changed

12 files changed

+135
-61
lines changed

dsc/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dsc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dsc"
3-
version = "3.1.1"
3+
version = "3.1.2"
44
edition = "2021"
55

66
[profile.release]

dsc/src/subcommand.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ use std::{
3535
collections::HashMap,
3636
io::{self, IsTerminal},
3737
path::Path,
38-
process::exit
38+
process::exit,
39+
slice::from_ref,
3940
};
4041
use tracing::{debug, error, trace};
4142

@@ -495,7 +496,7 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat)
495496
continue;
496497
}
497498

498-
resource_types.push(type_name.to_lowercase().to_string());
499+
resource_types.push(type_name.to_lowercase().clone());
499500
}
500501
dsc.find_resources(&resource_types, progress_format);
501502

@@ -578,16 +579,16 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat
578579
list_resources(&mut dsc, resource_name.as_ref(), adapter_name.as_ref(), description.as_ref(), tags.as_ref(), output_format.as_ref(), progress_format);
579580
},
580581
ResourceSubCommand::Schema { resource , output_format } => {
581-
dsc.find_resources(&[resource.to_string()], progress_format);
582+
dsc.find_resources(from_ref(resource), progress_format);
582583
resource_command::schema(&dsc, resource, output_format.as_ref());
583584
},
584585
ResourceSubCommand::Export { resource, input, file, output_format } => {
585-
dsc.find_resources(&[resource.to_string()], progress_format);
586+
dsc.find_resources(from_ref(resource), progress_format);
586587
let parsed_input = get_input(input.as_ref(), file.as_ref(), false);
587588
resource_command::export(&mut dsc, resource, &parsed_input, output_format.as_ref());
588589
},
589590
ResourceSubCommand::Get { resource, input, file: path, all, output_format } => {
590-
dsc.find_resources(&[resource.to_string()], progress_format);
591+
dsc.find_resources(from_ref(resource), progress_format);
591592
if *all {
592593
resource_command::get_all(&dsc, resource, output_format.as_ref());
593594
}
@@ -601,17 +602,17 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat
601602
}
602603
},
603604
ResourceSubCommand::Set { resource, input, file: path, output_format } => {
604-
dsc.find_resources(&[resource.to_string()], progress_format);
605+
dsc.find_resources(from_ref(resource), progress_format);
605606
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
606607
resource_command::set(&dsc, resource, &parsed_input, output_format.as_ref());
607608
},
608609
ResourceSubCommand::Test { resource, input, file: path, output_format } => {
609-
dsc.find_resources(&[resource.to_string()], progress_format);
610+
dsc.find_resources(from_ref(resource), progress_format);
610611
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
611612
resource_command::test(&dsc, resource, &parsed_input, output_format.as_ref());
612613
},
613614
ResourceSubCommand::Delete { resource, input, file: path } => {
614-
dsc.find_resources(&[resource.to_string()], progress_format);
615+
dsc.find_resources(from_ref(resource), progress_format);
615616
let parsed_input = get_input(input.as_ref(), path.as_ref(), false);
616617
resource_command::delete(&dsc, resource, &parsed_input);
617618
},

dsc/tests/dsc_discovery.tests.ps1

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,55 @@ Describe 'tests for resource discovery' {
251251
$env:DSC_RESOURCE_PATH = $oldPath
252252
}
253253
}
254+
255+
It 'Resource manifest using relative path to exe: <path>' -TestCases @(
256+
@{ path = '../dscecho'; success = $true }
257+
@{ path = '../foo/dscecho'; success = $false }
258+
) {
259+
param($path, $success)
260+
$manifest = @"
261+
{
262+
"`$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
263+
"type": "Microsoft.DSC.Debug/Echo",
264+
"version": "1.0.0",
265+
"description": "Echo resource for testing and debugging purposes",
266+
"get": {
267+
"executable": "$path",
268+
"args": [
269+
{
270+
"jsonInputArg": "--input",
271+
"mandatory": true
272+
}
273+
]
274+
},
275+
"schema": {
276+
"command": {
277+
"executable": "$path"
278+
}
279+
}
280+
}
281+
"@
282+
$dscEcho = Get-Command dscecho -ErrorAction Stop
283+
# copy to testdrive
284+
Copy-Item -Path "$($dscEcho.Source)" -Destination $testdrive
285+
# create manifest in subfolder
286+
$subfolder = Join-Path $testdrive 'subfolder'
287+
New-Item -Path $subfolder -ItemType Directory -Force | Out-Null
288+
Set-Content -Path (Join-Path $subfolder 'test.dsc.resource.json') -Value $manifest
289+
290+
try {
291+
$env:DSC_RESOURCE_PATH = $subfolder
292+
$out = dsc resource get -r 'Microsoft.DSC.Debug/Echo' -i '{"output":"RelativePathTest"}' 2> "$testdrive/error.txt" | ConvertFrom-Json
293+
if ($success) {
294+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw -Path "$testdrive/error.txt")
295+
$out.actualState.output | Should -BeExactly 'RelativePathTest'
296+
} else {
297+
$LASTEXITCODE | Should -Be 2 -Because (Get-Content -Raw -Path "$testdrive/error.txt")
298+
(Get-Content -Raw -Path "$testdrive/error.txt") | Should -Match "ERROR.*?Executable '\.\./foo/dscecho(\.exe)?' not found"
299+
}
300+
}
301+
finally {
302+
$env:DSC_RESOURCE_PATH = $null
303+
}
304+
}
254305
}

dsc_lib/locales/en-us.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,5 @@ foundSetting = "Found setting '%{name}' in %{path}"
370370
notFoundSetting = "Setting '%{name}' not found in %{path}"
371371
failedToGetExePath = "Can't get 'dsc' executable path"
372372
settingNotFound = "Setting '%{name}' not found"
373+
executableNotFoundInWorkingDirectory = "Executable '%{executable}' not found with working directory '%{cwd}'"
374+
executableNotFound = "Executable '%{executable}' not found"

dsc_lib/src/configure/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ impl Configurator {
703703
value.clone()
704704
};
705705
info!("{}", t!("configure.mod.setVariable", name = name, value = new_value));
706-
self.context.variables.insert(name.to_string(), new_value);
706+
self.context.variables.insert(name.clone(), new_value);
707707
}
708708
Ok(())
709709
}
@@ -839,13 +839,13 @@ fn get_failure_from_error(err: &DscError) -> Option<Failure> {
839839
match err {
840840
DscError::CommandExit(_resource, exit_code, reason) => {
841841
Some(Failure {
842-
message: reason.to_string(),
842+
message: reason.clone(),
843843
exit_code: *exit_code,
844844
})
845845
},
846846
DscError::CommandExitFromManifest(_resource, exit_code, reason) => {
847847
Some(Failure {
848-
message: reason.to_string(),
848+
message: reason.clone(),
849849
exit_code: *exit_code,
850850
})
851851
},

dsc_lib/src/discovery/command_discovery.rs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ use std::fs;
2222
use std::path::{Path, PathBuf};
2323
use std::str::FromStr;
2424
use tracing::{debug, info, trace, warn};
25-
use which::which;
2625

2726
use crate::util::get_setting;
2827
use crate::util::get_exe_path;
28+
use crate::util::canonicalize_which;
2929

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

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

650651
let mut capabilities: Vec<Capability> = vec![];
651652
if let Some(get) = &manifest.get {
652-
verify_executable(&manifest.resource_type, "get", &get.executable);
653+
verify_executable(&manifest.resource_type, "get", &get.executable, path.parent().unwrap());
653654
capabilities.push(Capability::Get);
654655
}
655656
if let Some(set) = &manifest.set {
656-
verify_executable(&manifest.resource_type, "set", &set.executable);
657+
verify_executable(&manifest.resource_type, "set", &set.executable, path.parent().unwrap());
657658
capabilities.push(Capability::Set);
658659
if set.handles_exist == Some(true) {
659660
capabilities.push(Capability::SetHandlesExist);
660661
}
661662
}
662663
if let Some(what_if) = &manifest.what_if {
663-
verify_executable(&manifest.resource_type, "what_if", &what_if.executable);
664+
verify_executable(&manifest.resource_type, "what_if", &what_if.executable, path.parent().unwrap());
664665
capabilities.push(Capability::WhatIf);
665666
}
666667
if let Some(test) = &manifest.test {
667-
verify_executable(&manifest.resource_type, "test", &test.executable);
668+
verify_executable(&manifest.resource_type, "test", &test.executable, path.parent().unwrap());
668669
capabilities.push(Capability::Test);
669670
}
670671
if let Some(delete) = &manifest.delete {
671-
verify_executable(&manifest.resource_type, "delete", &delete.executable);
672+
verify_executable(&manifest.resource_type, "delete", &delete.executable, path.parent().unwrap());
672673
capabilities.push(Capability::Delete);
673674
}
674675
if let Some(export) = &manifest.export {
675-
verify_executable(&manifest.resource_type, "export", &export.executable);
676+
verify_executable(&manifest.resource_type, "export", &export.executable, path.parent().unwrap());
676677
capabilities.push(Capability::Export);
677678
}
678679
if let Some(resolve) = &manifest.resolve {
679-
verify_executable(&manifest.resource_type, "resolve", &resolve.executable);
680+
verify_executable(&manifest.resource_type, "resolve", &resolve.executable, path.parent().unwrap());
680681
capabilities.push(Capability::Resolve);
681682
}
682683
if let Some(SchemaKind::Command(command)) = &manifest.schema {
683-
verify_executable(&manifest.resource_type, "schema", &command.executable);
684+
verify_executable(&manifest.resource_type, "schema", &command.executable, path.parent().unwrap());
684685
}
685686

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

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

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

727-
fn verify_executable(resource: &str, operation: &str, executable: &str) {
728-
if which(executable).is_err() {
728+
fn verify_executable(resource: &str, operation: &str, executable: &str, directory: &Path) {
729+
if canonicalize_which(executable, Some(directory.to_string_lossy().as_ref())).is_err() {
729730
warn!("{}", t!("discovery.commandDiscovery.executableNotFound", resource = resource, operation = operation, executable = executable));
730731
}
731732
}
@@ -739,15 +740,15 @@ fn sort_adapters_based_on_lookup_table(unsorted_adapters: &BTreeMap<String, Vec<
739740
if let Some(adapter_name) = lookup_table.get(needed_resource) {
740741
if let Some(resource_vec) = unsorted_adapters.get(adapter_name) {
741742
debug!("Lookup table found resource '{}' in adapter '{}'", needed_resource, adapter_name);
742-
result.insert(adapter_name.to_string(), resource_vec.clone());
743+
result.insert(adapter_name.clone(), resource_vec.clone());
743744
}
744745
}
745746
}
746747

747748
// now add remaining adapters
748749
for (adapter_name, adapters) in unsorted_adapters {
749750
if !result.contains_key(adapter_name) {
750-
result.insert(adapter_name.to_string(), adapters.clone());
751+
result.insert(adapter_name.clone(), adapters.clone());
751752
}
752753
}
753754

@@ -761,8 +762,8 @@ fn add_resources_to_lookup_table(adapted_resources: &BTreeMap<String, Vec<DscRes
761762
let mut lookup_table_changed = false;
762763
for (resource_name, res_vec) in adapted_resources {
763764
if let Some(adapter_name) = &res_vec[0].require_adapter {
764-
let new_value = adapter_name.to_string();
765-
let oldvalue = lookup_table.insert(resource_name.to_string().to_lowercase(), new_value.clone());
765+
let new_value = adapter_name.clone();
766+
let oldvalue = lookup_table.insert(resource_name.clone().to_lowercase(), new_value.clone());
766767
if !lookup_table_changed && (oldvalue.is_none() || oldvalue.is_some_and(|val| val != new_value)) {
767768
lookup_table_changed = true;
768769
}

dsc_lib/src/dscresources/command_resource.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rust_i18n::t;
77
use serde::Deserialize;
88
use serde_json::{Map, Value};
99
use std::{collections::HashMap, env, process::Stdio};
10-
use crate::configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}};
10+
use crate::{configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}}, util::canonicalize_which};
1111
use crate::dscerror::DscError;
1212
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}};
1313
use tracing::{error, warn, info, debug, trace};
@@ -459,11 +459,11 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result<String, DscE
459459
};
460460

461461
match schema_kind {
462-
SchemaKind::Command(ref command) => {
462+
SchemaKind::Command(command) => {
463463
let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, command.args.clone(), None, Some(cwd), None, resource.exit_codes.as_ref())?;
464464
Ok(stdout)
465465
},
466-
SchemaKind::Embedded(ref schema) => {
466+
SchemaKind::Embedded(schema) => {
467467
let json = serde_json::to_string(schema)?;
468468
Ok(json)
469469
},
@@ -662,7 +662,7 @@ async fn run_process_async(executable: &str, args: Option<Vec<String>>, input: O
662662
if code != 0 {
663663
if let Some(exit_codes) = exit_codes {
664664
if let Some(error_message) = exit_codes.get(&code) {
665-
return Err(DscError::CommandExitFromManifest(executable.to_string(), code, error_message.to_string()));
665+
return Err(DscError::CommandExitFromManifest(executable.to_string(), code, error_message.clone()));
666666
}
667667
}
668668
return Err(DscError::Command(executable.to_string(), code, stderr_result));
@@ -697,12 +697,13 @@ async fn run_process_async(executable: &str, args: Option<Vec<String>>, input: O
697697
#[allow(clippy::implicit_hasher)]
698698
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> {
699699
debug!("{}", t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?}));
700+
let executable = canonicalize_which(executable, cwd)?;
700701

701702
tokio::runtime::Builder::new_multi_thread()
702703
.enable_all()
703704
.build()
704705
.unwrap()
705-
.block_on(run_process_async(executable, args, input, cwd, env, exit_codes))
706+
.block_on(run_process_async(&executable, args, input, cwd, env, exit_codes))
706707
}
707708

708709
/// Process the arguments for a command resource.
@@ -837,7 +838,7 @@ fn json_to_hashmap(json: &str) -> Result<HashMap<String, String>, DscError> {
837838
},
838839
Value::Null => {
839840
// ignore null values
840-
},
841+
},
841842
Value::Object(_) => {
842843
return Err(DscError::Operation(t!("dscresources.commandResource.invalidKey", key = key).to_string()));
843844
},

dsc_lib/src/dscresources/dscresource.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
517517
let sub_diff = get_diff(value, &actual[key]);
518518
if !sub_diff.is_empty() {
519519
debug!("{}", t!("dscresources.dscresource.subDiff", key = key));
520-
diff_properties.push(key.to_string());
520+
diff_properties.push(key.clone());
521521
}
522522
}
523523
else {
@@ -532,22 +532,22 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
532532
if let Some(actual_array) = actual[key].as_array() {
533533
if !is_same_array(value_array, actual_array) {
534534
info!("{}", t!("dscresources.dscresource.diffArray", key = key));
535-
diff_properties.push(key.to_string());
535+
diff_properties.push(key.clone());
536536
}
537537
} else {
538538
info!("{}", t!("dscresources.dscresource.diffNotArray", key = actual[key]));
539-
diff_properties.push(key.to_string());
539+
diff_properties.push(key.clone());
540540
}
541541
} else if value != &actual[key] {
542-
diff_properties.push(key.to_string());
542+
diff_properties.push(key.clone());
543543
}
544544
} else {
545545
info!("{}", t!("dscresources.dscresource.diffKeyMissing", key = key));
546-
diff_properties.push(key.to_string());
546+
diff_properties.push(key.clone());
547547
}
548548
} else {
549549
info!("{}", t!("dscresources.dscresource.diffKeyNotObject", key = key));
550-
diff_properties.push(key.to_string());
550+
diff_properties.push(key.clone());
551551
}
552552
}
553553
}

dsc_lib/src/dscresources/invoke_result.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ pub enum TestResult {
9393
#[must_use]
9494
pub fn get_in_desired_state(test_result: &TestResult) -> bool {
9595
match test_result {
96-
TestResult::Resource(ref resource_test_result) => {
96+
TestResult::Resource(resource_test_result) => {
9797
resource_test_result.in_desired_state
9898
},
99-
TestResult::Group(ref group_test_result) => {
99+
TestResult::Group(group_test_result) => {
100100
for result in group_test_result {
101101
if !get_in_desired_state(&(result.result)) {
102102
return false;

0 commit comments

Comments
 (0)