Skip to content

Commit 45e9cd9

Browse files
author
Max Dymond
authored
Merge pull request #303 from Metaswitch/md/relativeloading
Load values from files relative to the floki config file, not from the current working directory.
2 parents cd19d08 + 2859c72 commit 45e9cd9

2 files changed

Lines changed: 46 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Status: Available for use
1717

1818
### Fixed
1919
- #62: Use shell_words to split the outer shell so that more complex outer shells can be used.
20+
- Load values from files relative to the floki config file, not from the
21+
current working directory.
2022

2123
## [1.2.0] - 2023-07-07
2224

src/config.rs

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,37 +106,60 @@ fn path_from_args(args: &HashMap<String, tera::Value>) -> tera::Result<String> {
106106
Ok(from_value::<String>(file.clone())?)
107107
}
108108

109-
fn yamlloader(args: &HashMap<String, tera::Value>) -> tera::Result<tera::Value> {
110-
let path = path_from_args(args)?;
111-
let f = std::fs::File::open(path)?;
112-
serde_yaml::from_reader(f).map_err(|_| "Failed to read file".into())
109+
enum LoaderType {
110+
Yaml,
111+
Json,
112+
Toml,
113113
}
114114

115-
fn jsonloader(args: &HashMap<String, tera::Value>) -> tera::Result<tera::Value> {
116-
let path = path_from_args(args)?;
117-
let f = std::fs::File::open(path)?;
118-
serde_json::from_reader(f).map_err(|_| "Failed to read file".into())
119-
}
120-
121-
fn tomlloader(args: &HashMap<String, tera::Value>) -> tera::Result<tera::Value> {
122-
let path = path_from_args(args)?;
123-
let contents = std::fs::read_to_string(path)?;
124-
toml::from_str(&contents).map_err(|_| "Failed to read file".into())
115+
fn makeloader(path: &Path, loader: LoaderType) -> impl tera::Function {
116+
// Get the dirname of the Path given (if a file), or just the directory.
117+
let directory = if path.is_file() {
118+
path.parent().expect("File should have a parent directory")
119+
} else {
120+
path
121+
}
122+
.to_path_buf();
123+
124+
Box::new(move |args: &HashMap<String, tera::Value>| {
125+
path_from_args(args)
126+
// Calculate the full path using the parent directory
127+
.map(|path| directory.join(path))
128+
// Read the file as a string
129+
.and_then(|full_path| std::fs::read_to_string(full_path).map_err(Into::into))
130+
// Parse the file using the relevant parser
131+
.and_then(|contents| match loader {
132+
LoaderType::Yaml => serde_yaml::from_str(&contents)
133+
.map_err(|err| format!("Failed to parse file as YAML: {err}").into()),
134+
LoaderType::Json => serde_json::from_str(&contents)
135+
.map_err(|err| format!("Failed to parse file as JSON: {err}").into()),
136+
LoaderType::Toml => toml::from_str(&contents)
137+
.map_err(|err| format!("Failed to parse file as TOML: {err}").into()),
138+
})
139+
})
125140
}
126141

127142
// Renders a template from a given string.
128143
pub fn render_template(template: &str, source_filename: &Path) -> Result<String, FlokiError> {
129144
let template_path = source_filename.display().to_string();
130-
131145
debug!("Rendering template: {template_path}");
132146

147+
// Get the canonical path for the template.
148+
let canonical_path = std::fs::canonicalize(source_filename).map_err(|err| {
149+
FlokiError::ProblemNormalizingFilePath {
150+
name: template_path.clone(),
151+
error: err,
152+
}
153+
})?;
154+
debug!("Canonical path: {canonical_path:?}");
155+
133156
// Read the template using tera
134157
let mut tera = Tera::default();
135158

136159
// Allow templates to load variables files as Values.
137-
tera.register_function("yaml", yamlloader);
138-
tera.register_function("json", jsonloader);
139-
tera.register_function("toml", tomlloader);
160+
tera.register_function("yaml", makeloader(&canonical_path, LoaderType::Yaml));
161+
tera.register_function("json", makeloader(&canonical_path, LoaderType::Json));
162+
tera.register_function("toml", makeloader(&canonical_path, LoaderType::Toml));
140163

141164
tera.add_raw_template(&template_path, template)
142165
.map_err(|e| FlokiError::ProblemRenderingTemplate {
@@ -327,7 +350,7 @@ mod test {
327350
fn test_tera_yamlload() -> Result<(), Box<dyn std::error::Error>> {
328351
let template =
329352
r#"{% set values = yaml(file="test_resources/values.yaml") %}shell: {{ values.foo }}"#;
330-
let config = render_template(template, Path::new("floki"))?;
353+
let config = render_template(template, Path::new("floki.yaml"))?;
331354
assert_eq!(config, "shell: bar");
332355
Ok(())
333356
}
@@ -336,7 +359,7 @@ mod test {
336359
fn test_tera_jsonload() -> Result<(), Box<dyn std::error::Error>> {
337360
let template =
338361
r#"{% set values = json(file="test_resources/values.json") %}shell: {{ values.foo }}"#;
339-
let config = render_template(template, Path::new("floki"))?;
362+
let config = render_template(template, Path::new("floki.yaml"))?;
340363
assert_eq!(config, "shell: bar");
341364
Ok(())
342365
}
@@ -345,7 +368,7 @@ mod test {
345368
fn test_tera_tomlload() -> Result<(), Box<dyn std::error::Error>> {
346369
let template =
347370
r#"{% set values = toml(file="Cargo.toml") %}floki: {{ values.package.name }}"#;
348-
let config = render_template(template, Path::new("floki"))?;
371+
let config = render_template(template, Path::new("floki.yaml"))?;
349372
assert_eq!(config, "floki: floki");
350373
Ok(())
351374
}

0 commit comments

Comments
 (0)