Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 27 additions & 3 deletions dsc/src/resource_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,24 @@ pub fn get(dsc: &DscManager, resource_type: &str, input: &Option<String>, stdin:
}

pub fn get_all(dsc: &DscManager, resource_type: &str, _input: &Option<String>, _stdin: &Option<String>, format: &Option<OutputFormat>) {
let Some(resource) = get_resource(dsc, resource_type) else {
let mut input = String::new() ;
let Some(mut resource) = get_resource(dsc, resource_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.to_string()).to_string());
return
};

debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as);
let export_result = match resource.export() {
if let Some(requires) = &resource.requires {
input = add_type_name_to_json(input, resource.type_name.clone());
if let Some(pr) = get_resource(dsc, requires) {
resource = pr;
} else {
error!("Provider '{}' not found", requires);
return;
};
}

let export_result = match resource.export(&input) {
Ok(export) => { export }
Err(err) => {
error!("Error: {err}");
Expand Down Expand Up @@ -203,14 +215,26 @@ pub fn schema(dsc: &DscManager, resource_type: &str, format: &Option<OutputForma
}

pub fn export(dsc: &mut DscManager, resource_type: &str, format: &Option<OutputFormat>) {
let mut input = String::new();
let Some(dsc_resource) = get_resource(dsc, resource_type) else {
error!("{}", DscError::ResourceNotFound(resource_type.to_string()).to_string());
return
};

let mut provider_resource: Option<&DscResource> = None;
if let Some(requires) = &dsc_resource.requires {
input = add_type_name_to_json(input, dsc_resource.type_name.clone());
if let Some(pr) = get_resource(dsc, requires) {
provider_resource = Some(pr);
} else {
error!("Provider '{}' not found", requires);
return;
};
}

let mut conf = Configuration::new();

if let Err(err) = add_resource_export_results_to_configuration(dsc_resource, &mut conf) {
if let Err(err) = add_resource_export_results_to_configuration(dsc_resource, provider_resource, &mut conf, &input) {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
Expand Down
16 changes: 13 additions & 3 deletions dsc_lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,19 @@ pub enum ErrorAction {
/// * `resource` - The resource to export.
/// * `conf` - The configuration to add the results to.
///
/// # Panics
///
/// Doesn't panic because there is a match/Some check before unwrap(); false positive.
///
/// # Errors
///
/// This function will return an error if the underlying resource fails.
pub fn add_resource_export_results_to_configuration(resource: &DscResource, conf: &mut Configuration) -> Result<(), DscError> {
let export_result = resource.export()?;
pub fn add_resource_export_results_to_configuration(resource: &DscResource, provider_resource: Option<&DscResource>, conf: &mut Configuration, input: &str) -> Result<(), DscError> {

let export_result = match provider_resource {
Some(_) => provider_resource.unwrap().export(input)?,
_ => resource.export(input)?
};

for (i, instance) in export_result.actual_state.iter().enumerate() {
let mut r = config_doc::Resource::new();
Expand Down Expand Up @@ -269,7 +277,9 @@ impl Configurator {
let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type.to_lowercase()) else {
return Err(DscError::ResourceNotFound(resource.resource_type.clone()));
};
add_resource_export_results_to_configuration(dsc_resource, &mut conf)?;

let input = serde_json::to_string(&resource.properties)?;
add_resource_export_results_to_configuration(dsc_resource, Some(dsc_resource), &mut conf, input.as_str())?;
}

result.result = Some(conf);
Expand Down
5 changes: 3 additions & 2 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result<String, DscE
///
/// * `resource` - The resource manifest
/// * `cwd` - The current working directory
/// * `input` - Input to the command
///
/// # Returns
///
Expand All @@ -368,13 +369,13 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result<String, DscE
/// # Errors
///
/// Error returned if the resource does not successfully export the current state
pub fn invoke_export(resource: &ResourceManifest, cwd: &str) -> Result<ExportResult, DscError> {
pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>) -> Result<ExportResult, DscError> {

let Some(export) = resource.export.as_ref() else {
return Err(DscError::Operation(format!("Export is not supported by resource {}", &resource.resource_type)))
};

let (exit_code, stdout, stderr) = invoke_command(&export.executable, export.args.clone(), None, Some(cwd), None)?;
let (exit_code, stdout, stderr) = invoke_command(&export.executable, export.args.clone(), input, Some(cwd), None)?;
if exit_code != 0 {
return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr));
}
Expand Down
25 changes: 11 additions & 14 deletions dsc_lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,15 @@ pub trait Invoke {
fn schema(&self) -> Result<String, DscError>;

/// Invoke the export operation on the resource.
///
/// # Arguments
///
/// * `input` - Input for export operation.
///
/// # Errors
///
/// This function will return an error if the underlying resource fails.
fn export(&self) -> Result<ExportResult, DscError>;
fn export(&self, input: &str) -> Result<ExportResult, DscError>;
}

impl Invoke for DscResource {
Expand Down Expand Up @@ -266,19 +270,12 @@ impl Invoke for DscResource {
}
}

fn export(&self) -> Result<ExportResult, DscError> {
match &self.implemented_as {
ImplementedAs::Custom(_custom) => {
Err(DscError::NotImplemented("export custom resources".to_string()))
},
ImplementedAs::Command => {
let Some(manifest) = &self.manifest else {
return Err(DscError::MissingManifest(self.type_name.clone()));
};
let resource_manifest = import_manifest(manifest.clone())?;
command_resource::invoke_export(&resource_manifest, &self.directory)
},
}
fn export(&self, input: &str) -> Result<ExportResult, DscError> {
let Some(manifest) = &self.manifest else {
return Err(DscError::MissingManifest(self.type_name.clone()));
};
let resource_manifest = import_manifest(manifest.clone())?;
command_resource::invoke_export(&resource_manifest, &self.directory, Some(input))
}
}

Expand Down
15 changes: 15 additions & 0 deletions powershellgroup/Tests/PSTestModule/TestClassResource.psm1
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using namespace System.Collections.Generic

enum EnumPropEnumeration {
Unexpected
Expected
Expand Down Expand Up @@ -40,6 +42,19 @@ class TestClassResource
$this.EnumProp = [EnumPropEnumeration]::Expected
return $this
}

static [TestClassResource[]] Export()
{
$resultList = [List[TestClassResource]]::new()
1..5 | %{
$obj = New-Object TestClassResource
$obj.Name = "Object$_"
$obj.Prop1 = "Property of object$_"
$resultList.Add($obj)
}

return $resultList.ToArray()
}
}

function Hello-World()
Expand Down
22 changes: 22 additions & 0 deletions powershellgroup/Tests/powershellgroup.config.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,26 @@ Describe 'PowerShellGroup resource tests' {
$res.results.result.afterState[0].RebootRequired | Should -Not -BeNull
$res.results.result.afterState[1].RebootRequired | Should -Not -BeNull
}

It 'Export works on config with class-based resources' -Skip:(!$IsWindows){

$yaml = @'
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json
resources:
- name: Working with class-based resources
type: DSC/PowerShellGroup
properties:
resources:
- name: Class-resource Info
type: PSTestModule/TestClassResource
'@
$out = $yaml | dsc config export
$LASTEXITCODE | Should -Be 0
$res = $out | ConvertFrom-Json
$res.'$schema' | Should -BeExactly 'https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json'
$res.'resources' | Should -Not -BeNullOrEmpty
$res.resources.count | Should -Be 5
$res.resources[0].properties.Name | Should -Be "Object1"
$res.resources[0].properties.Prop1 | Should -Be "Property of object1"
}
}
20 changes: 20 additions & 0 deletions powershellgroup/Tests/powershellgroup.resource.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,24 @@ Describe 'PowerShellGroup resource tests' {
$res = $r | ConvertFrom-Json
$res.afterState.RebootRequired | Should -Not -BeNull
}

It 'Export works on PS class-based resource' -Skip:(!$IsWindows){

$r = dsc resource export -r PSTestModule/TestClassResource
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.resources.count | Should -Be 5
$res.resources[0].type | Should -Be "PSTestModule/TestClassResource"
$res.resources[0].properties.Name | Should -Be "Object1"
$res.resources[0].properties.Prop1 | Should -Be "Property of object1"
}

It 'Get --all works on PS class-based resource' -Skip:(!$IsWindows){

$r = dsc resource get --all -r PSTestModule/TestClassResource
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.count | Should -Be 5
$res | % {$_.actualState | Should -Not -BeNullOrEmpty}
}
}
12 changes: 12 additions & 0 deletions powershellgroup/powershellgroup.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@
"input": "stdin",
"return": "state"
},
"export": {
"executable": "pwsh",
"args": [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-Command",
"$Input | ./powershellgroup.resource.ps1 Export"
],
"input": "stdin",
"return": "state"
},
"validate": {
"executable": "pwsh",
"args": [
Expand Down
76 changes: 75 additions & 1 deletion powershellgroup/powershellgroup.resource.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[CmdletBinding()]
param(
[ValidateSet('List','Get','Set','Test','Validate')]
[ValidateSet('List','Get','Set','Test','Export','Validate')]
$Operation = 'List',
[Switch]
$WinPS = $false,
Expand Down Expand Up @@ -327,6 +327,80 @@ elseif ($Operation -eq 'Test')

$result | ConvertTo-Json
}
elseif ($Operation -eq 'Export')
{
$inputobj_pscustomobj = $null
if ($stdinput)
{
$inputobj_pscustomobj = $stdinput | ConvertFrom-Json
}

$result = @()

RefreshCache

if ($inputobj_pscustomobj.resources) # we are processing a config batch
{
foreach($r in $inputobj_pscustomobj.resources)
{
$cachedResourceInfo = $script:ResourceCache[$r.type]
if ($cachedResourceInfo)
{
$path = $cachedResourceInfo.Path # for class-based resources - this is path to psd1 of their defining module

$typeparts = $r.type -split "/"
$ResourceTypeName = $typeparts[1]

$scriptBody = "using module '$path'"
$script = [ScriptBlock]::Create($scriptBody)
. $script

$t = [Type]$ResourceTypeName
$method = $t.GetMethod('Export')
$resultArray = $method.Invoke($null,$null)
foreach ($instance in $resultArray)
{
$instance | ConvertTo-Json -Compress | Write-Output
}
}
else
{
$errmsg = "Can not find type " + $r.type + "; please ensure that Get-DscResource returns this resource type"
Write-Error $errmsg
exit 1
}
}
}
else # we are processing an individual resource call
{
$cachedResourceInfo = $script:ResourceCache[$inputobj_pscustomobj.type]
if ($cachedResourceInfo)
{
$path = $cachedResourceInfo.Path # for class-based resources - this is path to psd1 of their defining module

$typeparts = $inputobj_pscustomobj.type -split "/"
$ResourceTypeName = $typeparts[1]

$scriptBody = "using module '$path'"
$script = [ScriptBlock]::Create($scriptBody)
. $script

$t = [Type]$ResourceTypeName
$method = $t.GetMethod('Export')
$resultArray = $method.Invoke($null,$null)
foreach ($instance in $resultArray)
{
$instance | ConvertTo-Json -Compress | Write-Output
}
}
else
{
$errmsg = "Can not find type " + $inputobj_pscustomobj.type + "; please ensure that Get-DscResource returns this resource type"
Write-Error $errmsg
exit 1
}
}
}
elseif ($Operation -eq 'Validate')
{
# TODO: this is placeholder
Expand Down