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

[WIP] Add metadata from resources #554

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
parse metadata from set result
  • Loading branch information
tgauth committed Jun 25, 2024
commit 077710683a054ec550b59e8aa81b5603a44540d7
5 changes: 2 additions & 3 deletions dsc_lib/src/configure/config_doc.rs
Original file line number Diff line number Diff line change
@@ -59,15 +59,14 @@ pub struct MicrosoftDscMetadata {
/// Identifies if the operation is part of a configuration
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<ContextKind>,
/// The messages returned from a resource
#[serde(rename = "_messages", skip_serializing_if = "Option::is_none")]
pub messages: Option<Vec<String>>,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
pub struct Metadata {
#[serde(rename = "Microsoft.DSC", skip_serializing_if = "Option::is_none")]
pub microsoft: Option<MicrosoftDscMetadata>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub resource: Option<Value>,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
51 changes: 42 additions & 9 deletions dsc_lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
@@ -247,7 +247,8 @@ impl Configurator {
duration: Some(end_datetime.signed_duration_since(start_datetime).to_string()),
..Default::default()
}
)
),
resource: None
}
),
name: resource.name.clone(),
@@ -313,11 +314,10 @@ impl Configurator {
let start_datetime;
let end_datetime;
let set_result;
let messages;
if exist || dsc_resource.capabilities.contains(&Capability::SetHandlesExist) {
debug!("Resource handles _exist or _exist is true");
start_datetime = chrono::Local::now();
(set_result, messages) = dsc_resource.set(&desired, skip_test, &self.context.execution_type)?;
set_result = dsc_resource.set(&desired, skip_test, &self.context.execution_type)?;
end_datetime = chrono::Local::now();
} else if dsc_resource.capabilities.contains(&Capability::Delete) {
if self.context.execution_type == ExecutionKind::WhatIf {
@@ -348,27 +348,28 @@ impl Configurator {
},
};
end_datetime = chrono::Local::now();
messages = None;
} else {
return Err(DscError::NotImplemented(format!("Resource '{}' does not support `delete` and does not handle `_exist` as false", resource.resource_type)));
}

self.context.outputs.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&set_result)?);
// Process SetResult by checking for _metadata field
let (set_result_parsed, resource_metadata) = Configurator::parse_metadata(set_result)?;
let resource_result = config_result::ResourceSetResult {
metadata: Some(
Metadata {
microsoft: Some(
MicrosoftDscMetadata {
duration: Some(end_datetime.signed_duration_since(start_datetime).to_string()),
messages,
..Default::default()
}
)
),
resource: resource_metadata
}
),
name: resource.name.clone(),
resource_type: resource.resource_type.clone(),
result: set_result,
result: set_result_parsed,
};
result.results.push(resource_result);
}
@@ -418,7 +419,8 @@ impl Configurator {
duration: Some(end_datetime.signed_duration_since(start_datetime).to_string()),
..Default::default()
}
)
),
resource: None
}
),
name: resource.name.clone(),
@@ -559,7 +561,8 @@ impl Configurator {
security_context: Some(self.context.security_context.clone()),
..Default::default()
}
)
),
resource: None
}
}

@@ -671,4 +674,34 @@ impl Configurator {
}
Ok(Some(result))
}

fn parse_metadata(set_result: SetResult) -> Result<(SetResult, Option<Value>), DscError> {
match set_result {
SetResult::Resource(mut result) => {
if let Value::Object(mut map) = result.after_state.take() {
if let Some(removed_value) = map.remove("_metadata") {
let modified_value = Value::Object(map);
let set_result_parsed = SetResult::Resource(ResourceSetResponse {
before_state: result.before_state,
after_state: modified_value,
changed_properties: result.changed_properties
});
Ok((set_result_parsed, Some(removed_value)))
} else {
let set_result_copy = SetResult::Resource(ResourceSetResponse {
before_state: result.before_state,
after_state: result.after_state,
changed_properties: result.changed_properties
});
Ok((set_result_copy, None))
}
} else {
Err(DscError::Operation("Invalid serde value. Expected an object.".to_string()))
}
},
SetResult::Group(results) => {
Err(DscError::NotImplemented("group resources not implemented yet".to_string()))
}
}
}
}
27 changes: 12 additions & 15 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
@@ -93,12 +93,12 @@ 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, Option<Vec<String>>), DscError> {
pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError> {
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), None));
return Ok(SetResult::Group(config_result.results));
}

let operation_type: String;
@@ -128,7 +128,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
info!("No pretest, invoking test {}", &resource.resource_type);
let test_result = invoke_test(resource, cwd, desired)?;
if is_synthetic_what_if {
return Ok((test_result.into(), None));
return Ok(test_result.into());
}
let (in_desired_state, actual_state) = match &test_result {
TestResult::Group(group_response) => {
@@ -145,11 +145,11 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
};

if in_desired_state && execution_type == &ExecutionKind::Actual {
return Ok((SetResult::Resource(ResourceSetResponse{
return Ok(SetResult::Resource(ResourceSetResponse{
before_state: serde_json::from_str(desired)?,
after_state: actual_state,
changed_properties: None,
}), None));
}));
}
}

@@ -213,11 +213,11 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te

// for changed_properties, we compare post state to pre state
let diff_properties = get_diff( &actual_value, &pre_state);
Ok((SetResult::Resource(ResourceSetResponse{
Ok(SetResult::Resource(ResourceSetResponse{
before_state: pre_state,
after_state: actual_value,
changed_properties: Some(diff_properties),
}), None))
}))
},
Some(ReturnKind::StateAndDiff) => {
// command should be returning actual state as a JSON line and a list of properties that differ as separate JSON line
@@ -230,15 +230,12 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
let Some(diff_line) = lines.next() else {
return Err(DscError::Command(resource.resource_type.clone(), exit_code, "Command did not return expected diff output".to_string()));
};
// let diff_properties: Vec<String> = serde_json::from_str(diff_line)?;
// temp change for prototyping metadata from resource
let messages: Vec<String> = serde_json::from_str(diff_line)?;
let diff_properties = get_diff( &actual_value, &pre_state);
Ok((SetResult::Resource(ResourceSetResponse {
let diff_properties: Vec<String> = serde_json::from_str(diff_line)?;
Ok(SetResult::Resource(ResourceSetResponse {
before_state: pre_state,
after_state: actual_value,
changed_properties: Some(diff_properties),
}), Some(messages)))
}))
},
None => {
// perform a get and compare the result to the expected state
@@ -257,11 +254,11 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
}
};
let diff_properties = get_diff( &actual_state, &pre_state);
Ok((SetResult::Resource(ResourceSetResponse {
Ok(SetResult::Resource(ResourceSetResponse {
before_state: pre_state,
after_state: actual_state,
changed_properties: Some(diff_properties),
}), None))
}))
},
}
}
4 changes: 2 additions & 2 deletions dsc_lib/src/dscresources/dscresource.rs
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ pub trait Invoke {
/// # Errors
///
/// This function will return an error if the underlying resource fails.
fn set(&self, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<(SetResult, Option<Vec<String>>), DscError>;
fn set(&self, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<SetResult, DscError>;

/// Invoke the test operation on the resource.
///
@@ -203,7 +203,7 @@ impl Invoke for DscResource {
}
}

fn set(&self, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result<(SetResult, Option<Vec<String>>), DscError> {
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) => {
2 changes: 1 addition & 1 deletion dsc_lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ impl DscManager {
/// This function will return an error if the underlying resource fails.
///
pub fn resource_set(&self, resource: &DscResource, input: &str, skip_test: bool) -> Result<SetResult, DscError> {
let (set_result, _) = resource.set(input, skip_test, &ExecutionKind::Actual)?;
let set_result = resource.set(input, skip_test, &ExecutionKind::Actual)?;
Ok(set_result)
}

8 changes: 0 additions & 8 deletions tools/dsctest/src/args.rs
Original file line number Diff line number Diff line change
@@ -49,14 +49,6 @@ pub enum SubCommand {
input: String,
},

#[clap(name = "metadata", about = "Return metadata from the resource in addition to input")]
Metadata {
#[clap(name = "input", short, long, help = "The input to the metadata command as JSON")]
input: String,
#[clap(name = "metadata", short, long, help = "Return metadata in addition to input")]
metadata: bool,
},

#[clap(name = "schema", about = "Get the JSON schema for a subcommand")]
Schema {
#[clap(name = "subcommand", short, long, help = "The subcommand to get the schema for")]
9 changes: 9 additions & 0 deletions tools/dsctest/src/echo.rs
Original file line number Diff line number Diff line change
@@ -40,4 +40,13 @@ pub enum Output {
#[serde(deny_unknown_fields)]
pub struct Echo {
pub output: Output,
#[serde(rename="_metadata", skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Metadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub messages: Option<Vec<String>>
}
9 changes: 0 additions & 9 deletions tools/dsctest/src/main.rs
Original file line number Diff line number Diff line change
@@ -77,15 +77,6 @@ fn main() {
}
input
},
SubCommand::Metadata { input , metadata } => {
if metadata {
let metadata = r#"["first message", "second message"]"#;
format!("{input}\n{metadata}")
}
else {
input
}
},
SubCommand::Schema { subcommand } => {
let schema = match subcommand {
Schemas::Delete => {