fix: resolve {{ recipe_dir }} in nested sub-recipe paths during secret discovery (#7797)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use crate::recipes::search_recipe::load_recipe_file;
|
use crate::recipes::search_recipe::load_recipe_file;
|
||||||
use goose::agents::extension::ExtensionConfig;
|
use goose::agents::extension::ExtensionConfig;
|
||||||
use goose::recipe::Recipe;
|
use goose::recipe::Recipe;
|
||||||
|
use regex::{NoExpand, Regex};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
/// Represents a secret requirement discovered from a recipe extension
|
/// Represents a secret requirement discovered from a recipe extension
|
||||||
@@ -96,9 +97,9 @@ fn discover_recipe_secrets_recursive(
|
|||||||
visited_recipes.insert(sub_recipe.path.clone());
|
visited_recipes.insert(sub_recipe.path.clone());
|
||||||
|
|
||||||
match load_sub_recipe(&sub_recipe.path) {
|
match load_sub_recipe(&sub_recipe.path) {
|
||||||
Ok(loaded_recipe) => {
|
Ok((loaded_recipe, parent_dir)) => {
|
||||||
let sub_secrets =
|
let sub_secrets =
|
||||||
discover_recipe_secrets_recursive(&loaded_recipe, visited_recipes);
|
discover_sub_recipe_secrets(&loaded_recipe, &parent_dir, visited_recipes);
|
||||||
for sub_secret in sub_secrets {
|
for sub_secret in sub_secrets {
|
||||||
if seen_keys.insert(sub_secret.key.clone()) {
|
if seen_keys.insert(sub_secret.key.clone()) {
|
||||||
secrets.push(sub_secret);
|
secrets.push(sub_secret);
|
||||||
@@ -115,14 +116,32 @@ fn discover_recipe_secrets_recursive(
|
|||||||
secrets
|
secrets
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads a recipe from a file path for sub-recipe secret discovery
|
/// Discovers secrets from a loaded sub-recipe, resolving `{{ recipe_dir }}` in nested
|
||||||
|
/// sub-recipe paths so they can be loaded without triggering confusing lookup failures.
|
||||||
|
fn discover_sub_recipe_secrets(
|
||||||
|
recipe: &Recipe,
|
||||||
|
parent_dir: &str,
|
||||||
|
visited_recipes: &mut HashSet<String>,
|
||||||
|
) -> Vec<SecretRequirement> {
|
||||||
|
let re = Regex::new(r"\{\{\s*recipe_dir\s*\}\}").expect("valid regex");
|
||||||
|
let mut resolved = recipe.clone();
|
||||||
|
if let Some(ref mut sub_recipes) = resolved.sub_recipes {
|
||||||
|
for sr in sub_recipes.iter_mut() {
|
||||||
|
sr.path = re.replace_all(&sr.path, NoExpand(parent_dir)).into_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
discover_recipe_secrets_recursive(&resolved, visited_recipes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a recipe from a file path for sub-recipe secret discovery.
|
||||||
///
|
///
|
||||||
/// For secret discovery, we only need the recipe structure (extensions and env_keys),
|
/// Returns the parsed recipe along with its parent directory path, which is
|
||||||
/// not parameter-substituted content, so we parse the raw YAML directly for speed and robustness.
|
/// needed to resolve `{{ recipe_dir }}` in any nested sub-recipe paths.
|
||||||
fn load_sub_recipe(recipe_path: &str) -> Result<Recipe, Box<dyn std::error::Error>> {
|
fn load_sub_recipe(recipe_path: &str) -> Result<(Recipe, String), Box<dyn std::error::Error>> {
|
||||||
let recipe_file = load_recipe_file(recipe_path)?;
|
let recipe_file = load_recipe_file(recipe_path)?;
|
||||||
let recipe: Recipe = serde_yaml::from_str(&recipe_file.content)?;
|
let recipe: Recipe = serde_yaml::from_str(&recipe_file.content)?;
|
||||||
Ok(recipe)
|
let parent_dir = recipe_file.parent_dir.display().to_string();
|
||||||
|
Ok((recipe, parent_dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user