Skip to content

Commit d74420b

Browse files
committed
Enable resource manifests to have relative paths
1 parent 5d8d877 commit d74420b

File tree

3 files changed

+77
-14
lines changed

3 files changed

+77
-14
lines changed

dsc/tests/dsc_discovery.tests.ps1

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,46 @@ Describe 'tests for resource discovery' {
245245
$env:DSC_RESOURCE_PATH = $oldPath
246246
}
247247
}
248+
249+
It 'Resource manifest using relative path to exe works' {
250+
$manifest = @'
251+
{
252+
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
253+
"type": "Microsoft.DSC.Debug/Echo",
254+
"version": "1.0.0",
255+
"description": "Echo resource for testing and debugging purposes",
256+
"get": {
257+
"executable": "../dscecho",
258+
"args": [
259+
{
260+
"jsonInputArg": "--input",
261+
"mandatory": true
262+
}
263+
]
264+
},
265+
"schema": {
266+
"command": {
267+
"executable": "../dscecho"
268+
}
269+
}
270+
}
271+
'@
272+
$dscEcho = Get-Command dscecho -ErrorAction Stop
273+
# copy to testdrive
274+
Copy-Item -Path "$($dscEcho.Source)" -Destination $testdrive
275+
# create manifest in subfolder
276+
$subfolder = Join-Path $testdrive 'subfolder'
277+
New-Item -Path $subfolder -ItemType Directory | Out-Null
278+
Set-Content -Path (Join-Path $subfolder 'test.dsc.resource.json') -Value $manifest
279+
280+
try {
281+
$env:DSC_RESOURCE_PATH = $subfolder
282+
$out = dsc resource get -r 'Microsoft.DSC.Debug/Echo' -i '{"output":"RelativePathTest"}' 2> "$testdrive/error.txt" | ConvertFrom-Json
283+
$LASTEXITCODE | Should -Be 0
284+
$out.actualState.output | Should -BeExactly 'RelativePathTest'
285+
}
286+
finally {
287+
$env:DSC_RESOURCE_PATH = $null
288+
}
289+
}
248290
}

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -711,38 +711,38 @@ fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result<Ds
711711

712712
let mut capabilities: Vec<Capability> = vec![];
713713
if let Some(get) = &manifest.get {
714-
verify_executable(&manifest.resource_type, "get", &get.executable);
714+
verify_executable(&manifest.resource_type, "get", &get.executable, path.parent().unwrap());
715715
capabilities.push(Capability::Get);
716716
}
717717
if let Some(set) = &manifest.set {
718-
verify_executable(&manifest.resource_type, "set", &set.executable);
718+
verify_executable(&manifest.resource_type, "set", &set.executable, path.parent().unwrap());
719719
capabilities.push(Capability::Set);
720720
if set.handles_exist == Some(true) {
721721
capabilities.push(Capability::SetHandlesExist);
722722
}
723723
}
724724
if let Some(what_if) = &manifest.what_if {
725-
verify_executable(&manifest.resource_type, "what_if", &what_if.executable);
725+
verify_executable(&manifest.resource_type, "what_if", &what_if.executable, path.parent().unwrap());
726726
capabilities.push(Capability::WhatIf);
727727
}
728728
if let Some(test) = &manifest.test {
729-
verify_executable(&manifest.resource_type, "test", &test.executable);
729+
verify_executable(&manifest.resource_type, "test", &test.executable, path.parent().unwrap());
730730
capabilities.push(Capability::Test);
731731
}
732732
if let Some(delete) = &manifest.delete {
733-
verify_executable(&manifest.resource_type, "delete", &delete.executable);
733+
verify_executable(&manifest.resource_type, "delete", &delete.executable, path.parent().unwrap());
734734
capabilities.push(Capability::Delete);
735735
}
736736
if let Some(export) = &manifest.export {
737-
verify_executable(&manifest.resource_type, "export", &export.executable);
737+
verify_executable(&manifest.resource_type, "export", &export.executable, path.parent().unwrap());
738738
capabilities.push(Capability::Export);
739739
}
740740
if let Some(resolve) = &manifest.resolve {
741-
verify_executable(&manifest.resource_type, "resolve", &resolve.executable);
741+
verify_executable(&manifest.resource_type, "resolve", &resolve.executable, path.parent().unwrap());
742742
capabilities.push(Capability::Resolve);
743743
}
744744
if let Some(SchemaKind::Command(command)) = &manifest.schema {
745-
verify_executable(&manifest.resource_type, "schema", &command.executable);
745+
verify_executable(&manifest.resource_type, "schema", &command.executable, path.parent().unwrap());
746746
}
747747

748748
let resource = DscResource {
@@ -768,15 +768,15 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result<
768768

769769
let mut capabilities: Vec<dscextension::Capability> = vec![];
770770
if let Some(discover) = &manifest.discover {
771-
verify_executable(&manifest.r#type, "discover", &discover.executable);
771+
verify_executable(&manifest.r#type, "discover", &discover.executable, path.parent().unwrap());
772772
capabilities.push(dscextension::Capability::Discover);
773773
}
774774
if let Some(secret) = &manifest.secret {
775-
verify_executable(&manifest.r#type, "secret", &secret.executable);
775+
verify_executable(&manifest.r#type, "secret", &secret.executable, path.parent().unwrap());
776776
capabilities.push(dscextension::Capability::Secret);
777777
}
778778
let import_extensions = if let Some(import) = &manifest.import {
779-
verify_executable(&manifest.r#type, "import", &import.executable);
779+
verify_executable(&manifest.r#type, "import", &import.executable, path.parent().unwrap());
780780
capabilities.push(dscextension::Capability::Import);
781781
if import.file_extensions.is_empty() {
782782
warn!("{}", t!("discovery.commandDiscovery.importExtensionsEmpty", extension = manifest.r#type));
@@ -803,7 +803,17 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result<
803803
Ok(extension)
804804
}
805805

806-
fn verify_executable(resource: &str, operation: &str, executable: &str) {
806+
fn verify_executable(resource: &str, operation: &str, executable: &str, directory: &Path) {
807+
// check if executable has a relative path
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+
}
807817
if which(executable).is_err() {
808818
info!("{}", t!("discovery.commandDiscovery.executableNotFound", resource = resource, operation = operation, executable = executable));
809819
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use jsonschema::Validator;
66
use rust_i18n::t;
77
use serde::Deserialize;
88
use serde_json::{Map, Value};
9-
use std::{collections::HashMap, env, process::Stdio};
9+
use std::{collections::HashMap, env, path::Path, process::Stdio};
1010
use crate::configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}};
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}};
@@ -763,6 +763,17 @@ fn convert_hashmap_string_keys_to_i32(input: Option<&HashMap<String, String>>) -
763763
#[allow(clippy::implicit_hasher)]
764764
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> {
765765
let exit_codes = convert_hashmap_string_keys_to_i32(exit_codes)?;
766+
let mut executable = executable.to_string();
767+
if !Path::new(&executable).is_absolute() && cwd.is_some() {
768+
if let Some(cwd) = cwd {
769+
let cwd_path = Path::new(cwd);
770+
let executable_path = cwd_path.join(&executable);
771+
if !executable_path.exists() {
772+
return Err(DscError::CommandOperation(t!("dscresources.commandResource.executableNotFound", executable = executable_path.display()).to_string(), executable.to_string()));
773+
}
774+
executable = executable_path.to_string_lossy().to_string();
775+
}
776+
}
766777

767778
tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(
768779
async {
@@ -771,7 +782,7 @@ pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option
771782
trace!("{}", t!("dscresources.commandResource.commandCwd", cwd = cwd));
772783
}
773784

774-
match run_process_async(executable, args, input, cwd, env, exit_codes.as_ref()).await {
785+
match run_process_async(&executable, args, input, cwd, env, exit_codes.as_ref()).await {
775786
Ok((code, stdout, stderr)) => {
776787
Ok((code, stdout, stderr))
777788
},

0 commit comments

Comments
 (0)