1 use std
::collections
::HashMap
;
2 use std
::path
::PathBuf
;
3 use std
::time
::SystemTime
;
5 use std
::sync
::{Arc, Mutex, RwLock}
;
7 use anyhow
::{bail, Error, format_err}
;
8 use hyper
::{Method, Body, Response}
;
9 use hyper
::http
::request
::Parts
;
11 use handlebars
::Handlebars
;
14 use proxmox
::api
::{ApiMethod, Router, RpcEnvironmentType}
;
15 use proxmox
::tools
::fs
::{create_path, CreateOptions}
;
17 use crate::{ApiAuth, FileLogger, FileLogOptions, CommandoSocket}
;
19 pub type GetIndexFn
= fn(Option
<String
>, Option
<String
>, &ApiConfig
, Parts
) -> Response
<Body
>;
21 pub struct ApiConfig
{
23 router
: &'
static Router
,
24 aliases
: HashMap
<String
, PathBuf
>,
25 env_type
: RpcEnvironmentType
,
26 templates
: RwLock
<Handlebars
<'
static>>,
27 template_files
: RwLock
<HashMap
<String
, (SystemTime
, PathBuf
)>>,
28 request_log
: Option
<Arc
<Mutex
<FileLogger
>>>,
29 auth_log
: Option
<Arc
<Mutex
<FileLogger
>>>,
30 pub api_auth
: Arc
<dyn ApiAuth
+ Send
+ Sync
>,
31 get_index_fn
: GetIndexFn
,
35 pub fn new
<B
: Into
<PathBuf
>>(
37 router
: &'
static Router
,
38 env_type
: RpcEnvironmentType
,
39 api_auth
: Arc
<dyn ApiAuth
+ Send
+ Sync
>,
40 get_index_fn
: GetIndexFn
,
41 ) -> Result
<Self, Error
> {
43 basedir
: basedir
.into(),
45 aliases
: HashMap
::new(),
47 templates
: RwLock
::new(Handlebars
::new()),
48 template_files
: RwLock
::new(HashMap
::new()),
58 auth_id
: Option
<String
>,
59 language
: Option
<String
>,
62 (self.get_index_fn
)(auth_id
, language
, self, parts
)
69 uri_param
: &mut HashMap
<String
, String
>,
70 ) -> Option
<&'
static ApiMethod
> {
72 self.router
.find_method(components
, method
, uri_param
)
75 pub fn find_alias(&self, components
: &[&str]) -> PathBuf
{
77 let mut prefix
= String
::new();
78 let mut filename
= self.basedir
.clone();
79 let comp_len
= components
.len();
81 prefix
.push_str(components
[0]);
82 if let Some(subdir
) = self.aliases
.get(&prefix
) {
83 filename
.push(subdir
);
84 components
.iter().skip(1).for_each(|comp
| filename
.push(comp
));
86 components
.iter().for_each(|comp
| filename
.push(comp
));
92 pub fn add_alias
<S
, P
>(&mut self, alias
: S
, path
: P
)
93 where S
: Into
<String
>,
96 self.aliases
.insert(alias
.into(), path
.into());
99 pub fn env_type(&self) -> RpcEnvironmentType
{
103 pub fn register_template
<P
>(&self, name
: &str, path
: P
) -> Result
<(), Error
>
107 if self.template_files
.read().unwrap().contains_key(name
) {
108 bail
!("template already registered");
111 let path
: PathBuf
= path
.into();
112 let metadata
= metadata(&path
)?
;
113 let mtime
= metadata
.modified()?
;
115 self.templates
.write().unwrap().register_template_file(name
, &path
)?
;
116 self.template_files
.write().unwrap().insert(name
.to_string(), (mtime
, path
));
121 /// Checks if the template was modified since the last rendering
122 /// if yes, it loads a the new version of the template
123 pub fn render_template
<T
>(&self, name
: &str, data
: &T
) -> Result
<String
, Error
>
130 let template_files
= self.template_files
.read().unwrap();
131 let (old_mtime
, old_path
) = template_files
.get(name
).ok_or_else(|| format_err
!("template not found"))?
;
133 mtime
= metadata(old_path
)?
.modified()?
;
134 if mtime
<= *old_mtime
{
135 return self.templates
.read().unwrap().render(name
, data
).map_err(|err
| format_err
!("{}", err
));
137 path
= old_path
.to_path_buf();
141 let mut template_files
= self.template_files
.write().unwrap();
142 let mut templates
= self.templates
.write().unwrap();
144 templates
.register_template_file(name
, &path
)?
;
145 template_files
.insert(name
.to_string(), (mtime
, path
));
147 templates
.render(name
, data
).map_err(|err
| format_err
!("{}", err
))
151 pub fn enable_file_log
<P
>(
154 dir_opts
: Option
<CreateOptions
>,
155 file_opts
: Option
<CreateOptions
>,
156 commando_sock
: &mut CommandoSocket
,
157 ) -> Result
<(), Error
>
161 let path
: PathBuf
= path
.into();
162 if let Some(base
) = path
.parent() {
164 create_path(base
, None
, dir_opts
).map_err(|err
| format_err
!("{}", err
))?
;
168 let logger_options
= FileLogOptions
{
170 file_opts
: file_opts
.unwrap_or(CreateOptions
::default()),
173 let request_log
= Arc
::new(Mutex
::new(FileLogger
::new(&path
, logger_options
)?
));
174 self.request_log
= Some(Arc
::clone(&request_log
));
176 commando_sock
.register_command("api-access-log-reopen".into(), move |_args
| {
177 println
!("re-opening access-log file");
178 request_log
.lock().unwrap().reopen()?
;
179 Ok(serde_json
::Value
::Null
)
185 pub fn enable_auth_log
<P
>(
188 dir_opts
: Option
<CreateOptions
>,
189 file_opts
: Option
<CreateOptions
>,
190 commando_sock
: &mut CommandoSocket
,
191 ) -> Result
<(), Error
>
195 let path
: PathBuf
= path
.into();
196 if let Some(base
) = path
.parent() {
198 create_path(base
, None
, dir_opts
).map_err(|err
| format_err
!("{}", err
))?
;
202 let logger_options
= FileLogOptions
{
205 file_opts
: file_opts
.unwrap_or(CreateOptions
::default()),
208 let auth_log
= Arc
::new(Mutex
::new(FileLogger
::new(&path
, logger_options
)?
));
209 self.auth_log
= Some(Arc
::clone(&auth_log
));
211 commando_sock
.register_command("api-auth-log-reopen".into(), move |_args
| {
212 println
!("re-opening auth-log file");
213 auth_log
.lock().unwrap().reopen()?
;
214 Ok(serde_json
::Value
::Null
)
220 pub fn get_access_log(&self) -> Option
<&Arc
<Mutex
<FileLogger
>>> {
221 self.request_log
.as_ref()
224 pub fn get_auth_log(&self) -> Option
<&Arc
<Mutex
<FileLogger
>>> {
225 self.auth_log
.as_ref()