Skip to content

Commit de15a96

Browse files
authored
Merge pull request #688 from SteveL-MSFT/validate-resource-exe
Emit warning if executable in resource manifest can't be found
2 parents 3e670b1 + ce1a82a commit de15a96

File tree

8 files changed

+205
-13
lines changed

8 files changed

+205
-13
lines changed

build.ps1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,12 @@ if (!$SkipBuild) {
377377
}
378378
}
379379

380-
Copy-Item "*.dsc.resource.json" $target -Force -ErrorAction Ignore
380+
if ($IsWindows) {
381+
Copy-Item "*.dsc.resource.json" $target -Force -ErrorAction Ignore
382+
}
383+
else { # don't copy WindowsPowerShell resource manifest
384+
Copy-Item "*.dsc.resource.json" $target -Exclude 'windowspowershell.dsc.resource.json' -Force -ErrorAction Ignore
385+
}
381386

382387
# be sure that the files that should be executable are executable
383388
if ($IsLinux -or $IsMacOS) {

dsc/Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dsc/tests/dsc_discovery.tests.ps1

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,41 @@ Describe 'tests for resource discovery' {
183183
$out = dsc resource schema -r abc/def
184184
$LASTEXITCODE | Should -Be 7
185185
}
186+
187+
It 'Verify warning message when executable not found for: <operation>' -TestCases @(
188+
@{ operation = 'get' }
189+
@{ operation = 'set' }
190+
@{ operation = 'test' }
191+
@{ operation = 'delete' }
192+
@{ operation = 'export' }
193+
@{ operation = 'resolve' }
194+
@{ operation = 'whatIf' }
195+
) {
196+
param($operation)
197+
198+
$manifest = @"
199+
{
200+
"`$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
201+
"type": "Test/ExecutableNotFound",
202+
"version": "0.1.0",
203+
"$operation": {
204+
"executable": "doesNotExist"
205+
}
206+
}
207+
"@
208+
$oldPath = $env:DSC_RESOURCE_PATH
209+
try {
210+
$env:DSC_RESOURCE_PATH = $testdrive
211+
Set-Content -Path "$testdrive/test.dsc.resource.json" -Value $manifest
212+
$out = dsc resource list 'Test/ExecutableNotFound' 2> "$testdrive/error.txt" | ConvertFrom-Json
213+
$LASTEXITCODE | Should -Be 0
214+
$out.Count | Should -Be 1
215+
$out.Type | Should -BeExactly 'Test/ExecutableNotFound'
216+
$out.Kind | Should -BeExactly 'resource'
217+
Get-Content -Path "$testdrive/error.txt" | Should -Match "WARN.*?Executable 'doesNotExist' not found"
218+
}
219+
finally {
220+
$env:DSC_RESOURCE_PATH = $oldPath
221+
}
222+
}
186223
}

dsc_lib/Cargo.lock

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dsc_lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ tree-sitter = "0.25"
3636
tree-sitter-rust = "0.23"
3737
tree-sitter-dscexpression = { path = "../tree-sitter-dscexpression" }
3838
uuid = { version = "1.15", features = ["v4"] }
39+
which = "7.0"
3940

4041
[dev-dependencies]
4142
serde_yaml = "0.9"

dsc_lib/locales/en-us.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ progressSearching = "Searching for resources"
8282
foundResourceManifest = "Found resource manifest: %{path}"
8383
adapterFound = "Resource adapter '%{adapter}' found"
8484
resourceFound = "Resource '%{resource}' found"
85+
executableNotFound = "Executable '%{executable}' not found for operation '%{operation}' for resource '%{resource}'"
8586

8687
[dscresources.commandResource]
8788
invokeGet = "Invoking get for '%{resource}'"

dsc_lib/src/discovery/command_discovery.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use crate::discovery::discovery_trait::ResourceDiscovery;
55
use crate::discovery::convert_wildcard_to_regex;
66
use crate::dscresources::dscresource::{Capability, DscResource, ImplementedAs};
7-
use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest};
7+
use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest, SchemaKind};
88
use crate::dscresources::command_resource::invoke_command;
99
use crate::dscerror::DscError;
1010
use crate::progress::{ProgressBar, ProgressFormat};
@@ -22,6 +22,7 @@ use std::io::BufReader;
2222
use std::path::{Path, PathBuf};
2323
use std::str::FromStr;
2424
use tracing::{debug, info, trace, warn};
25+
use which::which;
2526

2627
use crate::util::get_setting;
2728
use crate::util::get_exe_path;
@@ -531,33 +532,41 @@ fn load_manifest(path: &Path) -> Result<DscResource, DscError> {
531532
Kind::Resource
532533
};
533534

534-
// all command based resources are required to support `get`
535-
let mut capabilities = if manifest.get.is_some() {
536-
vec![Capability::Get]
537-
} else {
538-
vec![]
539-
};
535+
let mut capabilities: Vec<Capability> = vec![];
536+
if let Some(get) = &manifest.get {
537+
verify_executable(&manifest.resource_type, "get", &get.executable);
538+
capabilities.push(Capability::Get);
539+
}
540540
if let Some(set) = &manifest.set {
541+
verify_executable(&manifest.resource_type, "set", &set.executable);
541542
capabilities.push(Capability::Set);
542543
if set.handles_exist == Some(true) {
543544
capabilities.push(Capability::SetHandlesExist);
544545
}
545546
}
546-
if manifest.what_if.is_some() {
547+
if let Some(what_if) = &manifest.what_if {
548+
verify_executable(&manifest.resource_type, "what_if", &what_if.executable);
547549
capabilities.push(Capability::WhatIf);
548550
}
549-
if manifest.test.is_some() {
551+
if let Some(test) = &manifest.test {
552+
verify_executable(&manifest.resource_type, "test", &test.executable);
550553
capabilities.push(Capability::Test);
551554
}
552-
if manifest.delete.is_some() {
555+
if let Some(delete) = &manifest.delete {
556+
verify_executable(&manifest.resource_type, "delete", &delete.executable);
553557
capabilities.push(Capability::Delete);
554558
}
555-
if manifest.export.is_some() {
559+
if let Some(export) = &manifest.export {
560+
verify_executable(&manifest.resource_type, "export", &export.executable);
556561
capabilities.push(Capability::Export);
557562
}
558-
if manifest.resolve.is_some() {
563+
if let Some(resolve) = &manifest.resolve {
564+
verify_executable(&manifest.resource_type, "resolve", &resolve.executable);
559565
capabilities.push(Capability::Resolve);
560566
}
567+
if let Some(SchemaKind::Command(command)) = &manifest.schema {
568+
verify_executable(&manifest.resource_type, "schema", &command.executable);
569+
}
561570

562571
let resource = DscResource {
563572
type_name: manifest.resource_type.clone(),
@@ -575,6 +584,12 @@ fn load_manifest(path: &Path) -> Result<DscResource, DscError> {
575584
Ok(resource)
576585
}
577586

587+
fn verify_executable(resource: &str, operation: &str, executable: &str) {
588+
if which(executable).is_err() {
589+
warn!("{}", t!("discovery.commandDiscovery.executableNotFound", resource = resource, operation = operation, executable = executable));
590+
}
591+
}
592+
578593
fn sort_adapters_based_on_lookup_table(unsorted_adapters: &BTreeMap<String, Vec<DscResource>>, needed_resource_types: &Vec<String>) -> LinkedHashMap<String, Vec<DscResource>>
579594
{
580595
let mut result = LinkedHashMap::<String, Vec<DscResource>>::new();

0 commit comments

Comments
 (0)