Skip to content

Commit 6cd4079

Browse files
Steve Lee (POWERSHELL HE/HIM) (from Dev Box)SteveL-MSFT
authored andcommitted
add helper
1 parent a83d5c0 commit 6cd4079

File tree

6 files changed

+40
-35
lines changed

6 files changed

+40
-35
lines changed

dsc/tests/dsc_discovery.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ Describe 'tests for resource discovery' {
280280
try {
281281
$env:DSC_RESOURCE_PATH = $subfolder
282282
$out = dsc resource get -r 'Microsoft.DSC.Debug/Echo' -i '{"output":"RelativePathTest"}' 2> "$testdrive/error.txt" | ConvertFrom-Json
283-
$LASTEXITCODE | Should -Be 0
283+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw -Path "$testdrive/error.txt")
284284
$out.actualState.output | Should -BeExactly 'RelativePathTest'
285285
}
286286
finally {

lib/dsc-lib/locales/en-us.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ invalidKey = "Unsupported value for key '%{key}'. Only string, bool, number, an
169169
inDesiredStateNotBool = "'_inDesiredState' is not a boolean"
170170
exportNotSupportedUsingGet = "Export is not supported by resource '%{resource}' using get operation"
171171
runProcessError = "Failed to run process '%{executable}': %{error}"
172-
executableNotFound = "Executable '%{executable}' not found"
173172

174173
[dscresources.dscresource]
175174
invokeGet = "Invoking get for '%{resource}'"
@@ -699,3 +698,4 @@ failedToGetExePath = "Can't get 'dsc' executable path"
699698
settingNotFound = "Setting '%{name}' not found"
700699
failedToAbsolutizePath = "Failed to absolutize path '%{path}'"
701700
invalidExitCodeKey = "Invalid exit code key '%{key}'"
701+
executableNotFound = "Executable '%{executable}' not found with working directory '%{cwd}'"

lib/dsc-lib/src/discovery/command_discovery.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ use serde::Deserialize;
2020
use std::{collections::{BTreeMap, HashMap, HashSet}, sync::{LazyLock, RwLock}};
2121
use std::env;
2222
use std::ffi::OsStr;
23-
use std::fs;
23+
use std::fs::{create_dir_all, read, read_to_string, write};
2424
use std::path::{Path, PathBuf};
2525
use std::str::FromStr;
2626
use tracing::{debug, info, trace, warn};
27-
use which::which;
2827

2928
use crate::util::get_setting;
30-
use crate::util::get_exe_path;
29+
use crate::util::{canonicalize_which, get_exe_path};
3130

3231
const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extension.yaml", ".dsc.extension.yml"];
3332
const DSC_MANIFEST_LIST_EXTENSIONS: [&str; 3] = [".dsc.manifests.json", ".dsc.manifests.yaml", ".dsc.manifests.yml"];
@@ -621,7 +620,7 @@ fn insert_resource(resources: &mut BTreeMap<String, Vec<DscResource>>, resource:
621620
///
622621
/// * Returns a `DscError` if the manifest could not be loaded or parsed.
623622
pub fn load_manifest(path: &Path) -> Result<Vec<ImportedManifest>, DscError> {
624-
let contents = fs::read_to_string(path)?;
623+
let contents = read_to_string(path)?;
625624
let file_name_lowercase = path.file_name().and_then(OsStr::to_str).unwrap_or("").to_lowercase();
626625
let extension_is_json = path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("json"));
627626
if DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) {
@@ -804,17 +803,7 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result<
804803
}
805804

806805
fn verify_executable(resource: &str, operation: &str, executable: &str, directory: &Path) {
807-
if which(executable).is_err() {
808-
if !Path::new(executable).is_absolute() {
809-
// combine with directory and see if it exists
810-
let exe_path = directory.join(executable);
811-
if exe_path.exists() {
812-
return;
813-
}
814-
info!("{}", t!("discovery.commandDiscovery.executableNotFound", resource = resource, operation = operation, executable = exe_path.to_string_lossy()));
815-
return;
816-
}
817-
806+
if canonicalize_which(executable, Some(directory.to_string_lossy().as_ref())).is_err() {
818807
info!("{}", t!("discovery.commandDiscovery.executableNotFound", resource = resource, operation = operation, executable = executable));
819808
}
820809
}
@@ -849,8 +838,8 @@ fn save_adapted_resources_lookup_table(lookup_table: &HashMap<String, String>)
849838

850839
let path = std::path::Path::new(&file_path);
851840
if let Some(prefix) = path.parent() {
852-
if fs::create_dir_all(prefix).is_ok() {
853-
if fs::write(file_path.clone(), lookup_table_json).is_err() {
841+
if create_dir_all(prefix).is_ok() {
842+
if write(file_path.clone(), lookup_table_json).is_err() {
854843
info!("Unable to write lookup_table file {file_path:?}");
855844
}
856845
} else {
@@ -868,7 +857,7 @@ fn load_adapted_resources_lookup_table() -> HashMap<String, String>
868857
{
869858
let file_path = get_lookup_table_file_path();
870859

871-
let lookup_table: HashMap<String, String> = match fs::read(file_path.clone()){
860+
let lookup_table: HashMap<String, String> = match read(file_path.clone()){
872861
Ok(data) => { serde_json::from_slice(&data).unwrap_or_default() },
873862
Err(_) => { HashMap::new() }
874863
};

lib/dsc-lib/src/dscerror.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ pub enum DscError {
2626
#[error("{t} '{0}' [{t2} {1}] {t3}: {2}", t = t!("dscerror.commandResource"), t2 = t!("dscerror.exitCode"), t3 = t!("dscerror.manifestDescription"))]
2727
CommandExitFromManifest(String, i32, String),
2828

29+
#[error("{0}")]
30+
CommandNotFound(String),
31+
2932
#[error("{t} {0} {t2} '{1}'", t = t!("dscerror.commandOperation"), t2 = t!("dscerror.forExecutable"))]
3033
CommandOperation(String, String),
3134

lib/dsc-lib/src/dscresources/command_resource.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ use jsonschema::Validator;
66
use rust_i18n::t;
77
use serde::Deserialize;
88
use serde_json::{Map, Value};
9-
use std::{collections::HashMap, env, path::Path, process::Stdio};
10-
use crate::configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}};
9+
use std::{collections::HashMap, env, process::Stdio};
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, redact}, 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};
1414
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, process::Command};
15-
use which::which;
1615

1716
pub const EXIT_PROCESS_TERMINATED: i32 = 0x102;
1817

@@ -764,17 +763,7 @@ fn convert_hashmap_string_keys_to_i32(input: Option<&HashMap<String, String>>) -
764763
#[allow(clippy::implicit_hasher)]
765764
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<String, String>>) -> Result<(i32, String, String), DscError> {
766765
let exit_codes = convert_hashmap_string_keys_to_i32(exit_codes)?;
767-
let mut executable = executable.to_string();
768-
if which(&executable).is_err() && !Path::new(&executable).is_absolute() && cwd.is_some() {
769-
if let Some(cwd) = cwd {
770-
let cwd_path = Path::new(cwd);
771-
let executable_path = cwd_path.join(&executable);
772-
if !executable_path.exists() {
773-
return Err(DscError::CommandOperation(t!("dscresources.commandResource.executableNotFound", executable = executable_path.display()).to_string(), executable.to_string()));
774-
}
775-
executable = executable_path.to_string_lossy().to_string();
776-
}
777-
}
766+
let executable = canonicalize_which(executable, cwd)?;
778767

779768
tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(
780769
async {

lib/dsc-lib/src/util.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ use rust_i18n::t;
66
use serde_json::Value;
77
use std::{
88
fs,
9-
fs::File,
9+
fs::{canonicalize, File},
1010
io::BufReader,
1111
path::{Path, PathBuf},
1212
env,
1313
};
1414
use tracing::debug;
15+
use which::which;
1516

1617
pub struct DscSettingValue {
1718
pub setting: Value,
@@ -232,6 +233,29 @@ pub fn resource_id(type_name: &str, name: &str) -> String {
232233
result
233234
}
234235

236+
pub fn canonicalize_which(executable: &str, cwd: Option<&str>) -> Result<String, DscError> {
237+
let mut executable = executable.to_string().replace("/", std::path::MAIN_SEPARATOR_STR);
238+
if cfg!(target_os = "windows") && !executable.ends_with(".exe") {
239+
let mut exe_path = PathBuf::from(&executable);
240+
exe_path.set_extension("exe");
241+
executable = exe_path.to_string_lossy().to_string();
242+
}
243+
if which(&executable).is_err() && !Path::new(&executable).is_absolute() && cwd.is_some() {
244+
if let Some(cwd) = cwd {
245+
let cwd_path = Path::new(cwd);
246+
match canonicalize(cwd_path.join(&executable)) {
247+
Err(_err) => {
248+
return Err(DscError::CommandOperation(t!("util.executableNotFound", executable = &executable, cwd = cwd).to_string(), executable.to_string()));
249+
},
250+
Ok(canonical_path) => {
251+
executable = canonical_path.to_string_lossy().to_string();
252+
}
253+
}
254+
}
255+
}
256+
Ok(executable)
257+
}
258+
235259
#[macro_export]
236260
macro_rules! locked_is_empty {
237261
($lockable:expr) => {{

0 commit comments

Comments
 (0)