mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: Complete target system with C# bindings and resource inference (#458)
* feat: Add Schema Registry and Validation Framework This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects. - Thread-safe, in-memory registry for schema storage and management - Global registry patterns for effects and resources - Concurrent access with proper error handling - Unicode schema names support - JSON Schema-compliant validation for all primitive types - Advanced constraint validation (patterns, ranges, length limits) - Discriminated union support with anyOf schemas - Detailed error reporting with nested validation paths - Discriminated subobject validation for polymorphic schemas - **Registry Tests**: All registry operations - **Effect Tests**: Policy effect validation - **Resource Tests**: Resource validation - **Validation Tests**: Core validation engine - Thread-safety, error handling, integration scenarios, edge cases - **Dependencies**: dashmap, once_cell, regex - **Thread Safety**: Minimal locking with Rc<Schema> sharing - **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc. - Complete schema registry and validation subsystem - Comprehensive test coverage - Foundation for policy validation in Regorus Benchmarks: - Criterion benchmarks for basic types, effects and Azure resources - Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation) - String withs patterns validation: 30.2µs. Need to explore whether regex caching helps bring this down. - Azure policy effects: 188ns-1.4µs Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * feat: Complete target system with C# bindings and resource inference - Add comprehensive target system with TargetRegistry and target-aware compilation - Implement resource type inference from policy equality expressions - Create modular C# bindings with separate wrapper classes for each concept - Add thread-safe CompiledPolicy with reference counting for safe disposal - Enhance FFI with detailed error propagation and target functionality - Create TargetExampleApp demonstrating Azure Policy integration - Add CI/CD pipeline testing for all C# applications - Support target definitions with schema validation and resource selectors - Implement PolicyModule struct and target-aware compilation methods - Add comprehensive test coverage for target functionality Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> --------- Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
3c33d31d08
commit
cc917ea75d
@@ -9,6 +9,119 @@ use anyhow::{bail, Result};
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use test_generator::test_resources;
|
||||
|
||||
#[cfg(feature = "azure_policy")]
|
||||
mod load_target_definitions {
|
||||
use super::*;
|
||||
use std::{eprintln, sync::Once};
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
/// Load and register all target definitions from tests/interpreter/target/definitions
|
||||
/// This function is called once and loads all JSON target definition files.
|
||||
pub fn load() -> Result<()> {
|
||||
INIT.call_once(|| {
|
||||
if let Err(e) = load_target_definitions_impl() {
|
||||
eprintln!("Failed to load target definitions: {}", e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_target_definitions_impl() -> Result<()> {
|
||||
use crate::registry::targets;
|
||||
use crate::target::Target;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
let definitions_path = Path::new("tests/interpreter/cases/target/definitions");
|
||||
|
||||
if !definitions_path.exists() {
|
||||
eprintln!("Target definitions directory does not exist");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let entries = fs::read_dir(definitions_path)?;
|
||||
let mut found = false;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
// Only process JSON files
|
||||
if path.extension().and_then(|s| s.to_str()) == Some("json") {
|
||||
let contents = fs::read_to_string(&path)?;
|
||||
|
||||
match Target::from_json_str(&contents) {
|
||||
Ok(target) => {
|
||||
let target_name = target.name.clone();
|
||||
let target_rc = Rc::new(target);
|
||||
found = true;
|
||||
if let Err(e) = targets::register(target_rc.clone()) {
|
||||
eprintln!("Failed to register target '{}': {}", target_name, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Failed to parse target definition from {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
eprintln!("No target definitions were found");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_target_definitions() -> Result<()> {
|
||||
use crate::registry::targets;
|
||||
|
||||
// Load target definitions
|
||||
let _ = load()?;
|
||||
|
||||
// Check that the sample targets were loaded
|
||||
assert!(
|
||||
targets::contains("target.tests.sample_test_target"),
|
||||
"Sample target should be loaded"
|
||||
);
|
||||
assert!(
|
||||
targets::contains("target.tests.azure_compute"),
|
||||
"Azure compute target should be loaded"
|
||||
);
|
||||
|
||||
// Verify we can retrieve the targets
|
||||
let sample_target = targets::get("target.tests.sample_test_target");
|
||||
assert!(
|
||||
sample_target.is_some(),
|
||||
"Should be able to retrieve sample target"
|
||||
);
|
||||
|
||||
let azure_target = targets::get("target.tests.azure_compute");
|
||||
assert!(
|
||||
azure_target.is_some(),
|
||||
"Should be able to retrieve azure target"
|
||||
);
|
||||
|
||||
// Verify target properties
|
||||
if let Some(target) = sample_target {
|
||||
assert_eq!(target.name.as_ref(), "target.tests.sample_test_target");
|
||||
assert_eq!(target.version.as_ref(), "1.0.0");
|
||||
}
|
||||
|
||||
if let Some(target) = azure_target {
|
||||
assert_eq!(target.name.as_ref(), "target.tests.azure_compute");
|
||||
assert_eq!(target.version.as_ref(), "1.0.0");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Process test value specified in json/yaml to interpret special encodings.
|
||||
pub fn process_value(v: &Value) -> Result<Value> {
|
||||
match v {
|
||||
@@ -211,6 +324,64 @@ pub fn eval_file(
|
||||
Ok((results, engine.take_prints()?))
|
||||
}
|
||||
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub fn eval_file_with_rule_evaluation(
|
||||
regos: &[String],
|
||||
data_opt: Option<Value>,
|
||||
input_opt: Option<ValueOrVec>,
|
||||
query: &str,
|
||||
_enable_tracing: bool,
|
||||
strict: bool,
|
||||
) -> Result<(Vec<Value>, Vec<String>)> {
|
||||
let mut engine: Engine = Engine::new();
|
||||
engine.set_rego_v0(true);
|
||||
engine.set_strict_builtin_errors(strict);
|
||||
engine.set_gather_prints(true);
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
let mut results = vec![];
|
||||
let mut files = vec![];
|
||||
|
||||
for (idx, _) in regos.iter().enumerate() {
|
||||
files.push(format!("rego_{idx}"));
|
||||
}
|
||||
|
||||
for (idx, file) in files.iter().enumerate() {
|
||||
let contents = regos[idx].as_str();
|
||||
engine.add_policy(file.to_string(), contents.to_string())?;
|
||||
}
|
||||
|
||||
if let Some(data) = data_opt {
|
||||
engine.add_data(data)?;
|
||||
}
|
||||
|
||||
// Also test using the newer CompilerPolicy API.
|
||||
let compiled_policy = engine.clone().compile_for_target()?;
|
||||
|
||||
let mut inputs = vec![];
|
||||
match input_opt {
|
||||
Some(ValueOrVec::Single(single_input)) => inputs.push(single_input),
|
||||
Some(ValueOrVec::Many(mut many_input)) => inputs.append(&mut many_input),
|
||||
_ => {
|
||||
// For target tests without input, use an empty object as default
|
||||
inputs.push(Value::new_object());
|
||||
}
|
||||
}
|
||||
|
||||
for input in inputs {
|
||||
engine.set_input(input.clone());
|
||||
// Use eval_rule instead of eval_query for target tests
|
||||
let r_engine = engine.eval_rule(query.to_string())?;
|
||||
let r_compiled_policy = compiled_policy.eval_with_input(input)?;
|
||||
assert_eq!(r_engine, r_compiled_policy);
|
||||
results.push(r_engine);
|
||||
}
|
||||
|
||||
Ok((results, engine.take_prints()?))
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ValueOrVec {
|
||||
Single(Value),
|
||||
@@ -280,6 +451,9 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
let yaml_str = std::fs::read_to_string(file)?;
|
||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||
|
||||
#[cfg(feature = "azure_policy")]
|
||||
load_target_definitions::load().expect("Failed to load target definitions");
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
// Skip tests that depend on bultins that need std feature.
|
||||
@@ -329,14 +503,36 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
|
||||
let enable_tracing = case.traces.is_some() && case.traces.unwrap();
|
||||
|
||||
match eval_file(
|
||||
&case.modules,
|
||||
case.data,
|
||||
case.input,
|
||||
case.query.as_str(),
|
||||
enable_tracing,
|
||||
case.strict,
|
||||
) {
|
||||
let is_target_test = file.contains("target");
|
||||
|
||||
let result = if is_target_test {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
{
|
||||
eval_file_with_rule_evaluation(
|
||||
&case.modules,
|
||||
case.data,
|
||||
case.input,
|
||||
case.query.as_str(),
|
||||
enable_tracing,
|
||||
case.strict,
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "azure_policy"))]
|
||||
{
|
||||
panic!("Target tests require azure_policy feature")
|
||||
}
|
||||
} else {
|
||||
eval_file(
|
||||
&case.modules,
|
||||
case.data,
|
||||
case.input,
|
||||
case.query.as_str(),
|
||||
enable_tracing,
|
||||
case.strict,
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok((results, prints)) => match case.want_result {
|
||||
Some(want_result) => {
|
||||
let mut expected_results = vec![];
|
||||
@@ -392,6 +588,12 @@ fn yaml_test(file: &str) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Targets are supported only with azure_policy feature.
|
||||
#[cfg(not(feature = "azure_policy"))]
|
||||
if file.contains("target") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match yaml_test_impl(file) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
|
||||
Reference in New Issue
Block a user