Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3493ca5
Stage 1
Aug 22, 2024
057452e
Stage 2
Aug 23, 2024
b091bbf
Stage 3
Aug 24, 2024
6e1339b
Revert "Blocked non-list operations on adapters"
Aug 24, 2024
86c55a0
Stage 4
Aug 27, 2024
aea50e2
Stage 5
Aug 27, 2024
5004d37
Blocked non-list operations on adapters
Aug 21, 2024
e8e48d1
Stage 1
Aug 22, 2024
2efd5d1
Stage 2
Aug 23, 2024
78a3c35
Stage 3
Aug 24, 2024
2280426
Revert "Blocked non-list operations on adapters"
Aug 24, 2024
1e1daec
Stage 4
Aug 27, 2024
2872f03
Stage 5
Aug 27, 2024
68e805e
Merge branch 'adapter_lookup_table' of https://github.com/anmenaga/DS…
Aug 27, 2024
9cff470
Stage 6
Aug 27, 2024
d828c51
Stage 7
Aug 27, 2024
36b362e
Stage 8
Aug 27, 2024
db8daa9
Stage 9
Aug 27, 2024
85dbd62
Updated tests
Aug 29, 2024
a7ae36f
create directory before saving lookup_table file
Aug 29, 2024
17c38ba
clippy fix
Aug 29, 2024
650c34c
added tests for adapter lookup table
Aug 30, 2024
f6d6016
Merge branch 'main' into adapter_lookup_table
Aug 30, 2024
0bf2373
Merge branch 'main' into adapter_lookup_table
Sep 4, 2024
28dd171
test update 1
Sep 4, 2024
3b27828
test update 2
Sep 4, 2024
fbc50a1
test update 3
Sep 4, 2024
d1a3d0d
test update 4
Sep 5, 2024
79c0a0a
test update 5
Sep 5, 2024
1bdcf36
test update 6
Sep 5, 2024
3aaee80
test update 7
Sep 16, 2024
410fb12
Merge branch 'main' into adapter_lookup_table
Sep 16, 2024
cfb0132
clippy fix
Sep 16, 2024
13a1271
Merge branch 'adapter_lookup_table' of https://github.com/anmenaga/DS…
Sep 16, 2024
72ca7e2
debug-info
Sep 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions dsc/tests/dsc_discovery.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
Describe 'tests for resource discovery' {
BeforeAll {
$env:DSC_RESOURCE_PATH = $testdrive

$script:lookupTableFilePath = if ($IsWindows) {
Join-Path $env:LocalAppData "dsc\AdaptedResourcesLookupTable.json"
} else {
Join-Path $env:HOME ".dsc" "AdaptedResourcesLookupTable.json"
}
}

AfterEach {
Expand Down Expand Up @@ -96,4 +102,52 @@ Describe 'tests for resource discovery' {
$env:DSC_RESOURCE_PATH = $oldPath
}
}

It 'Ensure List operation populates adapter lookup table' {
# remove adapter lookup table file
Remove-Item -Force -Path $script:lookupTableFilePath -ErrorAction SilentlyContinue
Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeFalse

# perform List on an adapter - this should create adapter lookup table file
$oldPSModulePath = $env:PSModulePath
$TestClassResourcePath = Resolve-Path "$PSScriptRoot/../../powershell-adapter/Tests"
$env:DSC_RESOURCE_PATH = $null
$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestClassResourcePath
dsc resource list -a Microsoft.DSC/PowerShell | Out-Null
gc -raw $script:lookupTableFilePath
$script:lookupTableFilePath | Should -FileContentMatchExactly 'Microsoft.DSC/PowerShell'
Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeTrue
$env:PSModulePath = $oldPSModulePath
}

It 'Ensure non-List operation populates adapter lookup table' {

# remove adapter lookup table file
Remove-Item -Force -Path $script:lookupTableFilePath -ErrorAction SilentlyContinue
Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeFalse

# perform Get on an adapter - this should create adapter lookup table file
$oldPSModulePath = $env:PSModulePath
$TestClassResourcePath = Resolve-Path "$PSScriptRoot/../../powershell-adapter/Tests"
$env:DSC_RESOURCE_PATH = $null
$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestClassResourcePath
"{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' | Out-Null

Test-Path $script:lookupTableFilePath -PathType Leaf | Should -BeTrue
$script:lookupTableFilePath | Should -FileContentMatchExactly 'testclassresource/testclassresource'
$env:PSModulePath = $oldPSModulePath
}

It 'Verify adapter lookup table is used on repeat invocations' {

$oldPSModulePath = $env:PSModulePath
$TestClassResourcePath = Resolve-Path "$PSScriptRoot/../../powershell-adapter/Tests"
$env:DSC_RESOURCE_PATH = $null
$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestClassResourcePath
dsc resource list -a Microsoft.DSC/PowerShell | Out-Null
"{'Name':'TestClassResource1'}" | dsc -l trace resource get -r 'TestClassResource/TestClassResource' 2> $TestDrive/tracing.txt

"$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Lookup table found resource 'testclassresource/testclassresource' in adapter 'Microsoft.DSC/PowerShell'"
$env:PSModulePath = $oldPSModulePath
}
}
1 change: 1 addition & 0 deletions dsc_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ clap = { version = "4.4", features = ["derive"] }
derive_builder ="0.20.0"
indicatif = "0.17.0"
jsonschema = "0.18.0"
linked-hash-map = "0.5.6"
num-traits = "0.2.14"
regex = "1.7.0"
reqwest = { version = "0.12.0", features = ["rustls-tls"], default-features = false }
Expand Down
107 changes: 105 additions & 2 deletions dsc_lib/src/discovery/command_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ use crate::dscresources::resource_manifest::{import_manifest, validate_semver, K
use crate::dscresources::command_resource::invoke_command;
use crate::dscerror::DscError;
use indicatif::ProgressStyle;
use linked_hash_map::LinkedHashMap;
use regex::RegexBuilder;
use semver::Version;
use std::collections::{BTreeMap, HashSet};
use std::collections::{BTreeMap, HashSet, HashMap};
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -279,6 +281,7 @@ impl ResourceDiscovery for CommandDiscovery {
}

self.adapted_resources = adapted_resources;

Ok(())
}

Expand All @@ -294,6 +297,11 @@ impl ResourceDiscovery for CommandDiscovery {
} else {
self.discover_resources("*")?;
self.discover_adapted_resources(type_name_filter, adapter_name_filter)?;

// add/update found adapted resources to the lookup_table
add_resources_to_lookup_table(&self.adapted_resources);

// note: in next line 'BTreeMap::append' will leave self.adapted_resources empty
resources.append(&mut self.adapted_resources);
}

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

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

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

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

Ok(resource)
}

fn sort_adapters_based_on_lookup_table(unsorted_adapters: &BTreeMap<String, Vec<DscResource>>, needed_resource_types: &Vec<String>) -> LinkedHashMap<String, Vec<DscResource>>
{
let mut result = LinkedHashMap::<String, Vec<DscResource>>::new();
let lookup_table = load_adapted_resources_lookup_table();
// first add adapters (for needed types) that can be found in the lookup table
for needed_resource in needed_resource_types {
if let Some(adapter_name) = lookup_table.get(needed_resource) {
if let Some(resource_vec) = unsorted_adapters.get(adapter_name) {
debug!("Lookup table found resource '{}' in adapter '{}'", needed_resource, adapter_name);
result.insert(adapter_name.to_string(), resource_vec.clone());
}
}
}

// now add remaining adapters
for (adapter_name, adapters) in unsorted_adapters {
if !result.contains_key(adapter_name) {
result.insert(adapter_name.to_string(), adapters.clone());
}
}

result
}

fn add_resources_to_lookup_table(adapted_resources: &BTreeMap<String, Vec<DscResource>>)
{
let mut lookup_table = load_adapted_resources_lookup_table();

for (resource_name, res_vec) in adapted_resources {
if let Some(adapter_name) = &res_vec[0].require_adapter {
lookup_table.insert(resource_name.to_string().to_lowercase(), adapter_name.to_string());
} else {
info!("Resource '{resource_name}' in 'adapted_resources' is missing 'require_adapter' field.");
}
};

save_adapted_resources_lookup_table(&lookup_table);
}

fn save_adapted_resources_lookup_table(lookup_table: &HashMap<String, String>)
{
if let Ok(lookup_table_json) = serde_json::to_string(&lookup_table) {
let file_path = get_lookup_table_file_path();
debug!("Saving lookup table with {} items to {:?}", lookup_table.len(), file_path);

let path = std::path::Path::new(&file_path);
if let Some(prefix) = path.parent() {
if fs::create_dir_all(prefix).is_ok() {
if fs::write(file_path.clone(), lookup_table_json).is_err() {
info!("Unable to write lookup_table file {file_path:?}");
}
} else {
info!("Unable to create parent directories of the lookup_table file {file_path:?}");
}
} else {
info!("Unable to get directory of the lookup_table file {file_path:?}");
}
} else {
info!("Unable to serialize lookup_table to json");
}
}

fn load_adapted_resources_lookup_table() -> HashMap<String, String>
{
let file_path = get_lookup_table_file_path();

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

debug!("Read {} items into lookup table from {:?}", lookup_table.len(), file_path);
lookup_table
}

#[cfg(target_os = "windows")]
fn get_lookup_table_file_path() -> String
{
// $env:LocalAppData+"dsc\AdaptedResourcesLookupTable.json"
let Ok(local_app_data_path) = std::env::var("LocalAppData") else { return String::new(); };

Path::new(&local_app_data_path).join("dsc").join("AdaptedResourcesLookupTable.json").display().to_string()
}

#[cfg(not(target_os = "windows"))]
fn get_lookup_table_file_path() -> String
{
// $env:HOME+".dsc/AdaptedResourcesLookupTable.json"
let Ok(home_path) = std::env::var("HOME") else { return String::new(); };
Path::new(&home_path).join(".dsc").join("AdaptedResourcesLookupTable.json").display().to_string()
}
3 changes: 2 additions & 1 deletion powershell-adapter/Tests/powershellgroup.resource.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,10 @@ Describe 'PowerShell adapter resource tests' {
}
}

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

dsc resource list -a Microsoft.DSC/PowerShell | Out-Null
$r = dsc resource export -r TestClassResource/TestClassResource
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
Expand Down