Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable set and test for Import resources #464

Merged
merged 2 commits into from
Jun 18, 2024
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
4 changes: 4 additions & 0 deletions dsc/examples/osinfo_parameters.dsc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ resources:
type: Microsoft/OSInfo
properties:
family: "[parameters('osFamily')]"
- name: another os instance
type: Microsoft/OSInfo
properties:
family: macOS
38 changes: 4 additions & 34 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ use crate::tablewriter::Table;
use crate::util::{DSC_CONFIG_ROOT, EXIT_DSC_ERROR, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, EXIT_VALIDATION_FAILED, get_schema, write_output, get_input, set_dscconfigroot, validate_json};
use dsc_lib::configure::{Configurator, config_doc::ExecutionKind, config_result::ResourceGetResult};
use dsc_lib::dscerror::DscError;
use dsc_lib::dscresources::invoke_result::{
GroupResourceSetResponse, GroupResourceTestResponse, ResolveResult, TestResult
};
use dsc_lib::dscresources::invoke_result::ResolveResult;
use dsc_lib::{
DscManager,
dscresources::invoke_result::ValidateResult,
Expand All @@ -27,11 +25,7 @@ pub fn config_get(configurator: &mut Configurator, format: &Option<OutputFormat>
match configurator.invoke_get() {
Ok(result) => {
if *as_group {
let mut group_result = Vec::<ResourceGetResult>::new();
for result in result.results {
group_result.push(result);
};
let json = match serde_json::to_string(&group_result) {
let json = match serde_json::to_string(&(result.results)) {
Ok(json) => json,
Err(err) => {
error!("JSON Error: {err}");
Expand Down Expand Up @@ -66,10 +60,7 @@ pub fn config_set(configurator: &mut Configurator, format: &Option<OutputFormat>
match configurator.invoke_set(false) {
Ok(result) => {
if *as_group {
let group_result = GroupResourceSetResponse {
results: result.results
};
let json = match serde_json::to_string(&group_result) {
let json = match serde_json::to_string(&(result.results)) {
Ok(json) => json,
Err(err) => {
error!("JSON Error: {err}");
Expand Down Expand Up @@ -104,23 +95,6 @@ pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat
match configurator.invoke_test() {
Ok(result) => {
if *as_group {
let mut in_desired_state = true;
for test_result in &result.results {
match &test_result.result {
TestResult::Resource(resource_test_result) => {
if !resource_test_result.in_desired_state {
in_desired_state = false;
break;
}
},
TestResult::Group(group_resource_test_result) => {
if !group_resource_test_result.in_desired_state {
in_desired_state = false;
break;
}
}
}
}
let json = if *as_get {
let mut group_result = Vec::<ResourceGetResult>::new();
for test_result in result.results {
Expand All @@ -135,11 +109,7 @@ pub fn config_test(configurator: &mut Configurator, format: &Option<OutputFormat
}
}
else {
let group_result = GroupResourceTestResponse {
results: result.results,
in_desired_state
};
match serde_json::to_string(&group_result) {
match serde_json::to_string(&(result.results)) {
Ok(json) => json,
Err(err) => {
error!("JSON Error: {err}");
Expand Down
43 changes: 34 additions & 9 deletions dsc/tests/dsc_include.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,6 @@ Describe 'Include tests' {
$osinfoParametersConfigPath = Get-Item (Join-Path $includePath 'osinfo.parameters.yaml')

$logPath = Join-Path $TestDrive 'stderr.log'

$includeConfig = @'
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
resources:
- name: Echo
type: Test/Echo
properties:
output: Hello World
'@
}

It 'Include config with default parameters' {
Expand Down Expand Up @@ -183,4 +174,38 @@ resources:
$out.results[1].result[0].result[0].type | Should -Be 'Test/Echo'
$out.results[1].result[0].result[0].result[0].actualState.output | Should -Be 'one'
}

It 'Set with include works' {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test for test operation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be added once test is complete

$echoConfig = @'
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
resources:
- name: one
type: Test/Echo
properties:
output: Hello World
'@

$echoConfigPath = Join-Path $TestDrive 'echo.dsc.yaml'
$echoConfig | Set-Content -Path $echoConfigPath -Encoding utf8
# need to escape backslashes for YAML
$echoConfigPathParent = (Split-Path $echoConfigPath -Parent).Replace('\', '\\')
$echoConfigPathLeaf = (Split-Path $echoConfigPath -Leaf).Replace('\', '\\')
$directorySeparator = [System.IO.Path]::DirectorySeparatorChar.ToString().Replace('\', '\\')

$includeConfig = @"
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
resources:
- name: nested
type: Microsoft.DSC/Include
properties:
configurationFile: "[concat('$echoConfigPathParent', '$directorySeparator', '$echoConfigPathLeaf')]"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why using concat instead of using just full path, aka $echoConfigPath ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was to test that expressions get resolved for Include

"@

$out = dsc config set -d $includeConfig | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$out.results[0].result[0].name | Should -Be 'one'
$out.results[0].result[0].type | Should -Be 'Test/Echo'
$out.results[0].result[0].result.afterState.output | Should -Be 'Hello World'
$out.hadErrors | Should -Be $false
}
}
9 changes: 5 additions & 4 deletions dsc_lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
use crate::configure::config_doc::{ExecutionKind, Metadata};
use crate::configure::parameters::Input;
use crate::dscerror::DscError;
use crate::dscresources::dscresource::get_diff;
use crate::dscresources::invoke_result::GetResult;
use crate::dscresources::{dscresource::{Capability, Invoke}, invoke_result::{SetResult, ResourceSetResponse}};
use crate::dscresources::resource_manifest::Kind;
use crate::dscresources::{
{dscresource::{Capability, Invoke, get_diff}, invoke_result::{SetResult, ResourceSetResponse}},
invoke_result::GetResult,
resource_manifest::Kind,
};
use crate::DscResource;
use crate::discovery::Discovery;
use crate::parser::Statement;
Expand Down
3 changes: 3 additions & 0 deletions dsc_lib/src/dscerror.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ pub enum DscError {
#[error("Resource not found: {0}")]
ResourceNotFound(String),

#[error("Resource manifest not found: {0}")]
ResourceManifestNotFound(String),

#[error("Schema: {0}")]
Schema(String),

Expand Down
26 changes: 19 additions & 7 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde_json::Value;
use std::{collections::HashMap, env, io::{Read, Write}, process::{Command, Stdio}};
use crate::{configure::{config_doc::ExecutionKind, {config_result::ResourceGetResult, parameters, Configurator}}, util::parse_input_to_json};
use crate::dscerror::DscError;
use super::{dscresource::get_diff, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
use super::{dscresource::get_diff, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
use tracing::{error, warn, info, debug, trace};

pub const EXIT_PROCESS_TERMINATED: i32 = 0x102;
Expand Down Expand Up @@ -94,7 +94,13 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
/// Error returned if the resource does not successfully set the desired state
#[allow(clippy::too_many_lines)]
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError> {
// TODO: support import resources
debug!("Invoking set for '{}'", &resource.resource_type);
if resource.kind == Some(Kind::Import) {
let mut configurator = get_configurator(resource, cwd, desired)?;
let config_result = configurator.invoke_set(skip_test)?;
return Ok(SetResult::Group(config_result.results));
}

let operation_type: String;
let mut is_synthetic_what_if = false;
let set_method = match execution_type {
Expand Down Expand Up @@ -124,16 +130,17 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
if is_synthetic_what_if {
return Ok(test_result.into());
}
let (in_desired_state, actual_state) = match test_result {
let (in_desired_state, actual_state) = match &test_result {
TestResult::Group(group_response) => {
let in_desired_state = get_in_desired_state(&test_result);
let mut result_array: Vec<Value> = Vec::new();
for result in group_response.results {
for result in group_response {
result_array.push(serde_json::to_value(result)?);
}
(group_response.in_desired_state, Value::from(result_array))
(in_desired_state, Value::from(result_array))
},
TestResult::Resource(response) => {
(response.in_desired_state, response.actual_state)
(response.in_desired_state, response.actual_state.clone())
}
};

Expand Down Expand Up @@ -267,7 +274,12 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
///
/// Error is returned if the underlying command returns a non-zero exit code.
pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Result<TestResult, DscError> {
// TODO: support import resources
debug!("Invoking test for '{}'", &resource.resource_type);
if resource.kind == Some(Kind::Import) {
let mut configurator = get_configurator(resource, cwd, expected)?;
let config_result = configurator.invoke_test()?;
return Ok(TestResult::Group(config_result.results));
}

let Some(test) = &resource.test else {
info!("Resource '{}' does not implement test, performing synthetic test", &resource.resource_type);
Expand Down
21 changes: 19 additions & 2 deletions dsc_lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use tracing::debug;

use super::{command_resource, dscerror, invoke_result::{ExportResult, GetResult, ResolveResult, ResourceTestResponse, SetResult, TestResult, ValidateResult}, resource_manifest::import_manifest};

Expand Down Expand Up @@ -187,6 +188,7 @@ pub trait Invoke {

impl Invoke for DscResource {
fn get(&self, filter: &str) -> Result<GetResult, DscError> {
debug!("Invoking get for resource: {}", self.type_name);
match &self.implemented_as {
ImplementedAs::Custom(_custom) => {
Err(DscError::NotImplemented("get custom resources".to_string()))
Expand All @@ -202,6 +204,7 @@ impl Invoke for DscResource {
}

fn set(&self, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError> {
debug!("Invoking set for resource: {}", self.type_name);
match &self.implemented_as {
ImplementedAs::Custom(_custom) => {
Err(DscError::NotImplemented("set custom resources".to_string()))
Expand All @@ -217,6 +220,7 @@ impl Invoke for DscResource {
}

fn test(&self, expected: &str) -> Result<TestResult, DscError> {
debug!("Invoking test for resource: {}", self.type_name);
match &self.implemented_as {
ImplementedAs::Custom(_custom) => {
Err(DscError::NotImplemented("test custom resources".to_string()))
Expand All @@ -230,7 +234,15 @@ impl Invoke for DscResource {
let resource_manifest = import_manifest(manifest.clone())?;
if resource_manifest.test.is_none() {
let get_result = self.get(expected)?;
let desired_state = serde_json::from_str(expected)?;
let desired_state = if self.kind == Kind::Import {
let config = self.resolve(expected)?.configuration;
// TODO: implement way to resolve entire config doc including expressions and parameters
// as the raw configuration (desired state) won't match the result, also convert the desired
// state to a TestResult so the comparison is consistent
serde_json::to_value(config["resources"].clone())?
} else {
serde_json::from_str(expected)?
};
let actual_state = match get_result {
GetResult::Group(results) => {
let mut result_array: Vec<Value> = Vec::new();
Expand All @@ -245,7 +257,7 @@ impl Invoke for DscResource {
};
let diff_properties = get_diff( &desired_state, &actual_state);
let test_result = TestResult::Resource(ResourceTestResponse {
desired_state: serde_json::from_str(expected)?,
desired_state,
actual_state,
in_desired_state: diff_properties.is_empty(),
diff_properties,
Expand All @@ -260,6 +272,7 @@ impl Invoke for DscResource {
}

fn delete(&self, filter: &str) -> Result<(), DscError> {
debug!("Invoking delete for resource: {}", self.type_name);
match &self.implemented_as {
ImplementedAs::Custom(_custom) => {
Err(DscError::NotImplemented("set custom resources".to_string()))
Expand All @@ -275,6 +288,7 @@ impl Invoke for DscResource {
}

fn validate(&self, config: &str) -> Result<ValidateResult, DscError> {
debug!("Invoking validate for resource: {}", self.type_name);
match &self.implemented_as {
ImplementedAs::Custom(_custom) => {
Err(DscError::NotImplemented("validate custom resources".to_string()))
Expand All @@ -290,6 +304,7 @@ impl Invoke for DscResource {
}

fn schema(&self) -> Result<String, DscError> {
debug!("Invoking schema for resource: {}", self.type_name);
match &self.implemented_as {
ImplementedAs::Custom(_custom) => {
Err(DscError::NotImplemented("schema custom resources".to_string()))
Expand All @@ -305,6 +320,7 @@ impl Invoke for DscResource {
}

fn export(&self, input: &str) -> Result<ExportResult, DscError> {
debug!("Invoking export for resource: {}", self.type_name);
let Some(manifest) = &self.manifest else {
return Err(DscError::MissingManifest(self.type_name.clone()));
};
Expand All @@ -313,6 +329,7 @@ impl Invoke for DscResource {
}

fn resolve(&self, input: &str) -> Result<ResolveResult, DscError> {
debug!("Invoking resolve for resource: {}", self.type_name);
let Some(manifest) = &self.manifest else {
return Err(DscError::MissingManifest(self.type_name.clone()));
};
Expand Down
Loading
Loading