mirror of
https://github.com/openai/codex.git
synced 2026-05-03 04:42:20 +03:00
execpolicy2 extension (#6627)
- enabling execpolicy2 parser to parse multiple policy files to build a combined `Policy` (useful if codex detects many `.codexpolicy` files) - adding functionality to `Policy` to allow evaluation of multiple cmds at once (useful when we have chained commands)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -13,8 +12,12 @@ use codex_execpolicy2::PolicyParser;
|
||||
enum Cli {
|
||||
/// Evaluate a command against a policy.
|
||||
Check {
|
||||
#[arg(short, long, value_name = "PATH")]
|
||||
policy: PathBuf,
|
||||
#[arg(short, long = "policy", value_name = "PATH", required = true)]
|
||||
policies: Vec<PathBuf>,
|
||||
|
||||
/// Pretty-print the JSON output.
|
||||
#[arg(long)]
|
||||
pretty: bool,
|
||||
|
||||
/// Command tokens to check.
|
||||
#[arg(
|
||||
@@ -30,25 +33,34 @@ enum Cli {
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
match cli {
|
||||
Cli::Check { policy, command } => cmd_check(policy, command),
|
||||
Cli::Check {
|
||||
policies,
|
||||
command,
|
||||
pretty,
|
||||
} => cmd_check(policies, command, pretty),
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_check(policy_path: PathBuf, args: Vec<String>) -> Result<()> {
|
||||
let policy = load_policy(&policy_path)?;
|
||||
fn cmd_check(policy_paths: Vec<PathBuf>, args: Vec<String>, pretty: bool) -> Result<()> {
|
||||
let policy = load_policies(&policy_paths)?;
|
||||
|
||||
let eval = policy.check(&args);
|
||||
let json = serde_json::to_string_pretty(&eval)?;
|
||||
let json = if pretty {
|
||||
serde_json::to_string_pretty(&eval)?
|
||||
} else {
|
||||
serde_json::to_string(&eval)?
|
||||
};
|
||||
println!("{json}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_policy(policy_path: &Path) -> Result<codex_execpolicy2::Policy> {
|
||||
let policy_file_contents = fs::read_to_string(policy_path)
|
||||
.with_context(|| format!("failed to read policy at {}", policy_path.display()))?;
|
||||
let policy_identifier = policy_path.to_string_lossy();
|
||||
Ok(PolicyParser::parse(
|
||||
policy_identifier.as_ref(),
|
||||
&policy_file_contents,
|
||||
)?)
|
||||
fn load_policies(policy_paths: &[PathBuf]) -> Result<codex_execpolicy2::Policy> {
|
||||
let mut parser = PolicyParser::new();
|
||||
for policy_path in policy_paths {
|
||||
let policy_file_contents = fs::read_to_string(policy_path)
|
||||
.with_context(|| format!("failed to read policy at {}", policy_path.display()))?;
|
||||
let policy_identifier = policy_path.to_string_lossy().to_string();
|
||||
parser.parse(&policy_identifier, &policy_file_contents)?;
|
||||
}
|
||||
Ok(parser.build())
|
||||
}
|
||||
|
||||
@@ -25,16 +25,26 @@ use crate::rule::RuleRef;
|
||||
use crate::rule::validate_match_examples;
|
||||
use crate::rule::validate_not_match_examples;
|
||||
|
||||
// todo: support parsing multiple policies
|
||||
pub struct PolicyParser;
|
||||
pub struct PolicyParser {
|
||||
builder: RefCell<PolicyBuilder>,
|
||||
}
|
||||
|
||||
impl Default for PolicyParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PolicyParser {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
builder: RefCell::new(PolicyBuilder::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a policy, tagging parser errors with `policy_identifier` so failures include the
|
||||
/// identifier alongside line numbers.
|
||||
pub fn parse(
|
||||
policy_identifier: &str,
|
||||
policy_file_contents: &str,
|
||||
) -> Result<crate::policy::Policy> {
|
||||
pub fn parse(&mut self, policy_identifier: &str, policy_file_contents: &str) -> Result<()> {
|
||||
let mut dialect = Dialect::Extended.clone();
|
||||
dialect.enable_f_strings = true;
|
||||
let ast = AstModule::parse(
|
||||
@@ -45,14 +55,16 @@ impl PolicyParser {
|
||||
.map_err(Error::Starlark)?;
|
||||
let globals = GlobalsBuilder::standard().with(policy_builtins).build();
|
||||
let module = Module::new();
|
||||
|
||||
let builder = RefCell::new(PolicyBuilder::new());
|
||||
{
|
||||
let mut eval = Evaluator::new(&module);
|
||||
eval.extra = Some(&builder);
|
||||
eval.extra = Some(&self.builder);
|
||||
eval.eval_module(ast, &globals).map_err(Error::Starlark)?;
|
||||
}
|
||||
Ok(builder.into_inner().build())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build(self) -> crate::policy::Policy {
|
||||
self.builder.into_inner().build()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,28 @@ impl Policy {
|
||||
None => Evaluation::NoMatch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_multiple<Commands>(&self, commands: Commands) -> Evaluation
|
||||
where
|
||||
Commands: IntoIterator,
|
||||
Commands::Item: AsRef<[String]>,
|
||||
{
|
||||
let matched_rules: Vec<RuleMatch> = commands
|
||||
.into_iter()
|
||||
.flat_map(|command| match self.check(command.as_ref()) {
|
||||
Evaluation::Match { matched_rules, .. } => matched_rules,
|
||||
Evaluation::NoMatch => Vec::new(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
match matched_rules.iter().map(RuleMatch::decision).max() {
|
||||
Some(decision) => Evaluation::Match {
|
||||
decision,
|
||||
matched_rules,
|
||||
},
|
||||
None => Evaluation::NoMatch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user