Skip to content

Commit ae330c2

Browse files
authoredSep 16, 2024··
Merge pull request #530 from anmenaga/adapter_lookup_table
Adapter lookup table for non-list operations on adapted resources
2 parents cb8d736 + 72ca7e2 commit ae330c2

File tree

4 files changed

+162
-3
lines changed

4 files changed

+162
-3
lines changed
 

‎dsc/tests/dsc_discovery.tests.ps1

+54
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
Describe 'tests for resource discovery' {
55
BeforeAll {
66
$env:DSC_RESOURCE_PATH = $testdrive
7+
8+
$script:lookupTableFilePath = if ($IsWindows) {
9+
Join-Path $env:LocalAppData "dsc\AdaptedResourcesLookupTable.json"
10+
} else {
11+
Join-Path $env:HOME ".dsc" "AdaptedResourcesLookupTable.json"
12+
}
713
}
814

915
AfterEach {
@@ -96,4 +102,52 @@ Describe 'tests for resource discovery' {
96102
$env:DSC_RESOURCE_PATH = $oldPath
97103
}
98104
}
105+
106+
It 'Ensure List operation populates adapter lookup table' {
107+
# remove adapter lookup table file
108+
Remove-Item -Force -Path $script:lookupTableFilePath -ErrorAction SilentlyContinue
109+
Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeFalse
110+
111+
# perform List on an adapter - this should create adapter lookup table file
112+
$oldPSModulePath = $env:PSModulePath
113+
$TestClassResourcePath = Resolve-Path "$PSScriptRoot/../../powershell-adapter/Tests"
114+
$env:DSC_RESOURCE_PATH = $null
115+
$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestClassResourcePath
116+
dsc resource list -a Microsoft.DSC/PowerShell | Out-Null
117+
gc -raw $script:lookupTableFilePath
118+
$script:lookupTableFilePath | Should -FileContentMatchExactly 'Microsoft.DSC/PowerShell'
119+
Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeTrue
120+
$env:PSModulePath = $oldPSModulePath
121+
}
122+
123+
It 'Ensure non-List operation populates adapter lookup table' {
124+
125+
# remove adapter lookup table file
126+
Remove-Item -Force -Path $script:lookupTableFilePath -ErrorAction SilentlyContinue
127+
Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeFalse
128+
129+
# perform Get on an adapter - this should create adapter lookup table file
130+
$oldPSModulePath = $env:PSModulePath
131+
$TestClassResourcePath = Resolve-Path "$PSScriptRoot/../../powershell-adapter/Tests"
132+
$env:DSC_RESOURCE_PATH = $null
133+
$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestClassResourcePath
134+
"{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' | Out-Null
135+
136+
Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeTrue
137+
$script:lookupTableFilePath | Should -FileContentMatchExactly 'testclassresource/testclassresource'
138+
$env:PSModulePath = $oldPSModulePath
139+
}
140+
141+
It 'Verify adapter lookup table is used on repeat invocations' {
142+
143+
$oldPSModulePath = $env:PSModulePath
144+
$TestClassResourcePath = Resolve-Path "$PSScriptRoot/../../powershell-adapter/Tests"
145+
$env:DSC_RESOURCE_PATH = $null
146+
$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestClassResourcePath
147+
dsc resource list -a Microsoft.DSC/PowerShell | Out-Null
148+
"{'Name':'TestClassResource1'}" | dsc -l trace resource get -r 'TestClassResource/TestClassResource' 2> $TestDrive/tracing.txt
149+
150+
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Lookup table found resource 'testclassresource/testclassresource' in adapter 'Microsoft.DSC/PowerShell'"
151+
$env:PSModulePath = $oldPSModulePath
152+
}
99153
}

‎dsc_lib/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ clap = { version = "4.4", features = ["derive"] }
1010
derive_builder ="0.20.0"
1111
indicatif = "0.17.0"
1212
jsonschema = "0.18.0"
13+
linked-hash-map = "0.5.6"
1314
num-traits = "0.2.14"
1415
regex = "1.7.0"
1516
reqwest = { version = "0.12.0", features = ["rustls-tls"], default-features = false }

‎dsc_lib/src/discovery/command_discovery.rs

+105-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ use crate::dscresources::resource_manifest::{import_manifest, validate_semver, K
88
use crate::dscresources::command_resource::invoke_command;
99
use crate::dscerror::DscError;
1010
use indicatif::ProgressStyle;
11+
use linked_hash_map::LinkedHashMap;
1112
use regex::RegexBuilder;
1213
use semver::Version;
13-
use std::collections::{BTreeMap, HashSet};
14+
use std::collections::{BTreeMap, HashSet, HashMap};
1415
use std::env;
1516
use std::ffi::OsStr;
17+
use std::fs;
1618
use std::fs::File;
1719
use std::io::BufReader;
1820
use std::path::{Path, PathBuf};
@@ -279,6 +281,7 @@ impl ResourceDiscovery for CommandDiscovery {
279281
}
280282

281283
self.adapted_resources = adapted_resources;
284+
282285
Ok(())
283286
}
284287

@@ -294,6 +297,11 @@ impl ResourceDiscovery for CommandDiscovery {
294297
} else {
295298
self.discover_resources("*")?;
296299
self.discover_adapted_resources(type_name_filter, adapter_name_filter)?;
300+
301+
// add/update found adapted resources to the lookup_table
302+
add_resources_to_lookup_table(&self.adapted_resources);
303+
304+
// note: in next line 'BTreeMap::append' will leave self.adapted_resources empty
297305
resources.append(&mut self.adapted_resources);
298306
}
299307

@@ -334,7 +342,8 @@ impl ResourceDiscovery for CommandDiscovery {
334342
debug!("Found {} matching non-adapter-based resources", found_resources.len());
335343

336344
// now go through the adapters
337-
for (adapter_name, adapters) in self.adapters.clone() {
345+
let sorted_adapters = sort_adapters_based_on_lookup_table(&self.adapters, &remaining_required_resource_types);
346+
for (adapter_name, adapters) in sorted_adapters {
338347
// TODO: handle version requirements
339348
let Some(adapter) = adapters.first() else {
340349
// skip if no adapters
@@ -353,6 +362,8 @@ impl ResourceDiscovery for CommandDiscovery {
353362
}
354363

355364
self.discover_adapted_resources("*", &adapter_name)?;
365+
// add/update found adapted resources to the lookup_table
366+
add_resources_to_lookup_table(&self.adapted_resources);
356367

357368
// now go through the adapter resources and add them to the list of resources
358369
for (adapted_name, adapted_resource) in &self.adapted_resources {
@@ -496,3 +507,95 @@ fn load_manifest(path: &Path) -> Result<DscResource, DscError> {
496507

497508
Ok(resource)
498509
}
510+
511+
fn sort_adapters_based_on_lookup_table(unsorted_adapters: &BTreeMap<String, Vec<DscResource>>, needed_resource_types: &Vec<String>) -> LinkedHashMap<String, Vec<DscResource>>
512+
{
513+
let mut result = LinkedHashMap::<String, Vec<DscResource>>::new();
514+
let lookup_table = load_adapted_resources_lookup_table();
515+
// first add adapters (for needed types) that can be found in the lookup table
516+
for needed_resource in needed_resource_types {
517+
if let Some(adapter_name) = lookup_table.get(needed_resource) {
518+
if let Some(resource_vec) = unsorted_adapters.get(adapter_name) {
519+
debug!("Lookup table found resource '{}' in adapter '{}'", needed_resource, adapter_name);
520+
result.insert(adapter_name.to_string(), resource_vec.clone());
521+
}
522+
}
523+
}
524+
525+
// now add remaining adapters
526+
for (adapter_name, adapters) in unsorted_adapters {
527+
if !result.contains_key(adapter_name) {
528+
result.insert(adapter_name.to_string(), adapters.clone());
529+
}
530+
}
531+
532+
result
533+
}
534+
535+
fn add_resources_to_lookup_table(adapted_resources: &BTreeMap<String, Vec<DscResource>>)
536+
{
537+
let mut lookup_table = load_adapted_resources_lookup_table();
538+
539+
for (resource_name, res_vec) in adapted_resources {
540+
if let Some(adapter_name) = &res_vec[0].require_adapter {
541+
lookup_table.insert(resource_name.to_string().to_lowercase(), adapter_name.to_string());
542+
} else {
543+
info!("Resource '{resource_name}' in 'adapted_resources' is missing 'require_adapter' field.");
544+
}
545+
};
546+
547+
save_adapted_resources_lookup_table(&lookup_table);
548+
}
549+
550+
fn save_adapted_resources_lookup_table(lookup_table: &HashMap<String, String>)
551+
{
552+
if let Ok(lookup_table_json) = serde_json::to_string(&lookup_table) {
553+
let file_path = get_lookup_table_file_path();
554+
debug!("Saving lookup table with {} items to {:?}", lookup_table.len(), file_path);
555+
556+
let path = std::path::Path::new(&file_path);
557+
if let Some(prefix) = path.parent() {
558+
if fs::create_dir_all(prefix).is_ok() {
559+
if fs::write(file_path.clone(), lookup_table_json).is_err() {
560+
info!("Unable to write lookup_table file {file_path:?}");
561+
}
562+
} else {
563+
info!("Unable to create parent directories of the lookup_table file {file_path:?}");
564+
}
565+
} else {
566+
info!("Unable to get directory of the lookup_table file {file_path:?}");
567+
}
568+
} else {
569+
info!("Unable to serialize lookup_table to json");
570+
}
571+
}
572+
573+
fn load_adapted_resources_lookup_table() -> HashMap<String, String>
574+
{
575+
let file_path = get_lookup_table_file_path();
576+
577+
let lookup_table: HashMap<String, String> = match fs::read(file_path.clone()){
578+
Ok(data) => { serde_json::from_slice(&data).unwrap_or_default() },
579+
Err(_) => { HashMap::new() }
580+
};
581+
582+
debug!("Read {} items into lookup table from {:?}", lookup_table.len(), file_path);
583+
lookup_table
584+
}
585+
586+
#[cfg(target_os = "windows")]
587+
fn get_lookup_table_file_path() -> String
588+
{
589+
// $env:LocalAppData+"dsc\AdaptedResourcesLookupTable.json"
590+
let Ok(local_app_data_path) = std::env::var("LocalAppData") else { return String::new(); };
591+
592+
Path::new(&local_app_data_path).join("dsc").join("AdaptedResourcesLookupTable.json").display().to_string()
593+
}
594+
595+
#[cfg(not(target_os = "windows"))]
596+
fn get_lookup_table_file_path() -> String
597+
{
598+
// $env:HOME+".dsc/AdaptedResourcesLookupTable.json"
599+
let Ok(home_path) = std::env::var("HOME") else { return String::new(); };
600+
Path::new(&home_path).join(".dsc").join("AdaptedResourcesLookupTable.json").display().to_string()
601+
}

‎powershell-adapter/Tests/powershellgroup.resource.tests.ps1

+2-1
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,10 @@ Describe 'PowerShell adapter resource tests' {
270270
}
271271
}
272272

273-
It 'Dsc can process large resource output' -Tag z1{
273+
It 'Dsc can process large resource output' {
274274
$env:TestClassResourceResultCount = 5000 # with sync resource invocations this was not possible
275275

276+
dsc resource list -a Microsoft.DSC/PowerShell | Out-Null
276277
$r = dsc resource export -r TestClassResource/TestClassResource
277278
$LASTEXITCODE | Should -Be 0
278279
$res = $r | ConvertFrom-Json

0 commit comments

Comments
 (0)
Please sign in to comment.