@@ -3,12 +3,13 @@ use crate::errors;
33use crate :: image;
44use anyhow:: Error ;
55use serde:: { Deserialize , Serialize } ;
6+ use tera:: from_value;
67use tera:: Context ;
78use tera:: Tera ;
89
910use std:: collections:: BTreeMap ;
1011use std:: collections:: HashMap ;
11- use std:: path;
12+ use std:: path:: { Path , PathBuf } ;
1213
1314#[ derive( Debug , PartialEq , Serialize , Deserialize ) ]
1415#[ serde( untagged) ]
@@ -56,7 +57,7 @@ pub(crate) struct Volume {
5657 pub ( crate ) shared : bool ,
5758 /// The mount path is the path at which the volume is mounted
5859 /// inside the floki container.
59- pub ( crate ) mount : path :: PathBuf ,
60+ pub ( crate ) mount : PathBuf ,
6061}
6162
6263#[ derive( Debug , PartialEq , Serialize , Deserialize ) ]
@@ -83,7 +84,7 @@ pub(crate) struct FlokiConfig {
8384 #[ serde( default = "default_shell" ) ]
8485 pub ( crate ) shell : Shell ,
8586 #[ serde( default = "default_mount" ) ]
86- pub ( crate ) mount : path :: PathBuf ,
87+ pub ( crate ) mount : PathBuf ,
8788 #[ serde( default = "Vec::new" ) ]
8889 pub ( crate ) docker_switches : Vec < String > ,
8990 #[ serde( default = "default_to_false" ) ]
@@ -98,27 +99,80 @@ pub(crate) struct FlokiConfig {
9899 pub ( crate ) entrypoint : Entrypoint ,
99100}
100101
101- impl FlokiConfig {
102- pub fn from_file ( file : & path:: Path ) -> Result < FlokiConfig , Error > {
103- debug ! ( "Reading configuration file: {:?}" , file) ;
102+ fn path_from_args ( args : & HashMap < String , tera:: Value > ) -> tera:: Result < String > {
103+ let file = match args. get ( "file" ) {
104+ Some ( file) => file,
105+ None => return Err ( "file parameter is required" . into ( ) ) ,
106+ } ;
107+ Ok ( from_value :: < String > ( file. clone ( ) ) ?)
108+ }
109+
110+ fn yamlloader ( args : & HashMap < String , tera:: Value > ) -> tera:: Result < tera:: Value > {
111+ let path = path_from_args ( args) ?;
112+ let f = std:: fs:: File :: open ( path) ?;
113+ serde_yaml:: from_reader ( f) . map_err ( |_| "Failed to read file" . into ( ) )
114+ }
115+
116+ fn jsonloader ( args : & HashMap < String , tera:: Value > ) -> tera:: Result < tera:: Value > {
117+ let path = path_from_args ( args) ?;
118+ let f = std:: fs:: File :: open ( path) ?;
119+ serde_json:: from_reader ( f) . map_err ( |_| "Failed to read file" . into ( ) )
120+ }
121+
122+ fn tomlloader ( args : & HashMap < String , tera:: Value > ) -> tera:: Result < tera:: Value > {
123+ let path = path_from_args ( args) ?;
124+ let contents = std:: fs:: read_to_string ( path) ?;
125+ toml:: from_str ( & contents) . map_err ( |_| "Failed to read file" . into ( ) )
126+ }
127+
128+ // Renders a template from a given string.
129+ pub fn render_template ( template : & str , source_filename : & Path ) -> Result < String , Error > {
130+ let template_path = source_filename. display ( ) . to_string ( ) ;
131+
132+ debug ! ( "Rendering template: {template_path}" ) ;
133+
134+ // Read the template using tera
135+ let mut tera = Tera :: default ( ) ;
136+
137+ // Allow templates to load variables files as Values.
138+ tera. register_function ( "yaml" , yamlloader) ;
139+ tera. register_function ( "json" , jsonloader) ;
140+ tera. register_function ( "toml" , tomlloader) ;
141+
142+ tera. add_raw_template ( & template_path, template)
143+ . map_err ( |e| errors:: FlokiError :: ProblemRenderingTemplate {
144+ name : template_path. clone ( ) ,
145+ error : e,
146+ } ) ?;
147+
148+ // Read the environment variables and store them in a tera context
149+ // under the `env` name.
150+ let vars: HashMap < String , String > = std:: env:: vars ( ) . collect ( ) ;
151+ let mut context = Context :: new ( ) ;
152+ context. insert ( "env" , & vars) ;
104153
105- // Read the template using tera
106- let mut tera = Tera :: default ( ) ;
107- tera. add_template_file ( file, Some ( "floki" ) ) . map_err ( |e| {
154+ // Render the floki file to string using the context.
155+ Ok ( tera. render ( & template_path, & context) ?)
156+ }
157+
158+ impl FlokiConfig {
159+ pub fn render ( file : & Path ) -> Result < String , Error > {
160+ let content = std:: fs:: read_to_string ( file) . map_err ( |e| {
108161 errors:: FlokiError :: ProblemOpeningConfigYaml {
109162 name : file. display ( ) . to_string ( ) ,
110163 error : e,
111164 }
112165 } ) ?;
113166
114- // Read the environment variables and store them in a tera context
115- // under the `env` name.
116- let vars: HashMap < String , String > = std:: env:: vars ( ) . collect ( ) ;
117- let mut context = Context :: new ( ) ;
118- context. insert ( "env" , & vars) ;
167+ // Render the template first before parsing it.
168+ render_template ( & content, file)
169+ }
170+
171+ pub fn from_file ( file : & Path ) -> Result < Self , Error > {
172+ debug ! ( "Reading configuration file: {:?}" , file) ;
119173
120- // Render the floki file to string using the context .
121- let output = tera . render ( "floki" , & context ) ?;
174+ // Render the output from the configuration file before parsing .
175+ let output = Self :: render ( file ) ?;
122176
123177 // Parse the rendered floki file from the string.
124178 let mut config: FlokiConfig = serde_yaml:: from_str ( & output) . map_err ( |e| {
@@ -161,8 +215,8 @@ fn default_shell() -> Shell {
161215 Shell :: Shell ( "sh" . into ( ) )
162216}
163217
164- fn default_mount ( ) -> path :: PathBuf {
165- path :: Path :: new ( "/src" ) . to_path_buf ( )
218+ fn default_mount ( ) -> PathBuf {
219+ Path :: new ( "/src" ) . to_path_buf ( )
166220}
167221
168222fn default_to_false ( ) -> bool {
@@ -176,9 +230,6 @@ fn default_entrypoint() -> Entrypoint {
176230#[ cfg( test) ]
177231mod test {
178232 use super :: * ;
179- use crate :: image:: Image ;
180- use std:: io:: Write ;
181- use tempfile:: NamedTempFile ;
182233
183234 #[ derive( Debug , PartialEq , Serialize , Deserialize ) ]
184235 struct TestShellConfig {
@@ -264,19 +315,37 @@ mod test {
264315 }
265316
266317 #[ test]
267- fn test_tera_file ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
268- let yaml = r#"{% set var = "test" %}image: {{ var }}"# ;
318+ fn test_tera_render ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
319+ let template = r#"{% set var = "test" %}image: {{ var }}"# ;
320+ let config = render_template ( template, Path :: new ( "floki" ) ) ?;
321+ assert_eq ! ( config, "image: test" ) ;
322+ Ok ( ( ) )
323+ }
269324
270- // Write to a temporary file.
271- let mut tmp = NamedTempFile :: new ( ) ?;
272- tmp. write_all ( yaml. as_bytes ( ) ) ?;
273- let ( _file, path) = tmp. keep ( ) ?;
325+ #[ test]
326+ fn test_tera_yamlload ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
327+ let template =
328+ r#"{% set values = yaml(file="test_resources/values.yaml") %}shell: {{ values.foo }}"# ;
329+ let config = render_template ( template, Path :: new ( "floki" ) ) ?;
330+ assert_eq ! ( config, "shell: bar" ) ;
331+ Ok ( ( ) )
332+ }
274333
275- // Let's try and parse the file.
276- let config = FlokiConfig :: from_file ( & path) ?;
334+ #[ test]
335+ fn test_tera_jsonload ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
336+ let template =
337+ r#"{% set values = json(file="test_resources/values.json") %}shell: {{ values.foo }}"# ;
338+ let config = render_template ( template, Path :: new ( "floki" ) ) ?;
339+ assert_eq ! ( config, "shell: bar" ) ;
340+ Ok ( ( ) )
341+ }
277342
278- println ! ( "Config: {:?}" , config) ;
279- assert_eq ! ( config. image, Image :: Name ( "test" . to_string( ) ) ) ;
343+ #[ test]
344+ fn test_tera_tomlload ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
345+ let template =
346+ r#"{% set values = toml(file="Cargo.toml") %}floki: {{ values.package.name }}"# ;
347+ let config = render_template ( template, Path :: new ( "floki" ) ) ?;
348+ assert_eq ! ( config, "floki: floki" ) ;
280349 Ok ( ( ) )
281350 }
282351}
0 commit comments