ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/salaryman/trunk/src/server/endpoints.rs
Revision: 15
Committed: Sat Jul 12 22:17:26 2025 UTC (2 months, 4 weeks ago) by yuzu
File size: 10794 byte(s)
Log Message:
add additional endpoints; change out mutexes for rwlocks

File Contents

# User Rev Content
1 yuzu 15 use super::Config;
2     use super::context::{SalarymanDContext, SalarymanService};
3     use dropshot::{HttpError, HttpResponseOk, Path, RequestContext, TypedBody, endpoint};
4     use salaryman::model::{NewService, ServicePath, StdinBuffer, UpdateConf};
5     use salaryman::service::{Service, ServiceConf};
6     use std::sync::Arc;
7     use tokio::fs::File;
8     use tokio::io::AsyncWriteExt;
9     use tokio::sync::RwLock;
10    
11     #[endpoint {
12     method = GET,
13     path = "/config",
14     }]
15     pub async fn endpoint_get_config(
16     rqctx: RequestContext<Arc<SalarymanDContext>>,
17     ) -> Result<HttpResponseOk<Config>, HttpError> {
18     let ctx = rqctx.context();
19     let lock = ctx.config.read().await;
20     let conf = lock.clone();
21     drop(lock);
22     Ok(HttpResponseOk(conf))
23     }
24     #[endpoint {
25     method = PUT,
26     path = "/config",
27     }]
28     pub async fn endpoint_put_config(
29     rqctx: RequestContext<Arc<SalarymanDContext>>,
30     body: TypedBody<UpdateConf>,
31     ) -> Result<HttpResponseOk<()>, HttpError> {
32     let ctx = rqctx.context();
33     let update = body.into_inner();
34     if let Some(addr) = update.address {
35     let mut lock = ctx.config.write().await;
36     lock.address = Some(addr);
37     drop(lock);
38     }
39     if let Some(port) = update.port {
40     let mut lock = ctx.config.write().await;
41     lock.port = Some(port);
42     drop(lock);
43     }
44     Ok(HttpResponseOk(()))
45     }
46     #[endpoint {
47     method = GET,
48     path = "/config/save"
49     }]
50     pub async fn endpoint_get_config_save(
51     rqctx: RequestContext<Arc<SalarymanDContext>>,
52     ) -> Result<HttpResponseOk<()>, HttpError> {
53     let ctx = rqctx.context();
54     let mut v: Vec<ServiceConf> = Vec::new();
55     let rlock = ctx.services.read().await;
56     for i in 0..rlock.len() {
57     v.push(rlock[i].config.clone());
58     }
59     drop(rlock);
60     let rlock = ctx.config.read().await;
61     let save = Config {
62     address: rlock.address,
63     port: rlock.port,
64     user: rlock.user.clone(),
65     service: v,
66     };
67     drop(rlock);
68     let mut f = match File::open(&ctx.save_file).await {
69     Ok(f) => f,
70     Err(_) => {
71     return Err(HttpError::for_internal_error(String::from(
72     "cannot open desired file",
73     )));
74     }
75     };
76     let save = match toml::to_string(&save) {
77     Ok(s) => s,
78     Err(_) => {
79     return Err(HttpError::for_internal_error(String::from(
80     "cannot serialize config!",
81     )));
82     }
83     };
84     match f.write_all(save.as_str().as_bytes()).await {
85     Ok(_) => (),
86     Err(_) => {
87     return Err(HttpError::for_internal_error(String::from(
88     "could not write to file!",
89     )));
90     }
91     }
92     Ok(HttpResponseOk(()))
93     }
94     #[endpoint {
95     method = GET,
96     path = "/service",
97     }]
98     pub async fn endpoint_get_services(
99     rqctx: RequestContext<Arc<SalarymanDContext>>,
100     ) -> Result<HttpResponseOk<Vec<ServiceConf>>, HttpError> {
101     let ret = {
102     let ctx = rqctx.context();
103     let mut v: Vec<ServiceConf> = Vec::new();
104     let lock = ctx.services.read().await;
105     for i in 0..lock.len() {
106     v.push(lock[i].config.clone());
107     }
108     v
109     };
110     Ok(HttpResponseOk(ret))
111     }
112     #[endpoint {
113     method = POST,
114     path = "/service",
115     }]
116     pub async fn endpoint_post_service(
117     rqctx: RequestContext<Arc<SalarymanDContext>>,
118     body: TypedBody<NewService>,
119     ) -> Result<HttpResponseOk<ServiceConf>, HttpError> {
120     let ctx = rqctx.context();
121     let body = body.into_inner();
122     let mut s: ServiceConf = ServiceConf::new();
123     if let Some(name) = &body.name {
124     s.name = name.clone().to_owned();
125     } else {
126     return Err(HttpError::for_bad_request(
127     None,
128     String::from("name field is required!"),
129     ));
130     }
131     if let Some(command) = &body.command {
132     s.command = command.clone().to_owned();
133     } else {
134     return Err(HttpError::for_bad_request(
135     None,
136     String::from("command field is required!"),
137     ));
138     }
139     if let Some(args) = &body.args {
140     if let Some(args) = args {
141     s.args = Some(args.clone().to_owned());
142     }
143     }
144     if let Some(dir) = &body.directory {
145     if let Some(dir) = dir {
146     s.directory = Some(dir.clone().to_owned());
147     }
148     }
149     if let Some(auto) = &body.autostart {
150     s.autostart = auto.clone().to_owned();
151     } else {
152     s.autostart = false;
153     }
154     let service: SalarymanService =
155     SalarymanService::from_parts(s.clone(), Arc::new(RwLock::new(Service::from_conf(&s))));
156     if service.config.autostart {
157     let mut lock = service.service.write().await;
158     match lock.start_with_output().await {
159     Ok(_) => (),
160     Err(_) => (),
161     }
162     drop(lock);
163     }
164     let mut lock = ctx.config.write().await;
165     lock.service.push(s.clone());
166     drop(lock);
167     let mut lock = ctx.services.write().await;
168     lock.push(Arc::new(service));
169     drop(lock);
170     Ok(HttpResponseOk(ServiceConf::new()))
171     }
172     #[endpoint {
173     method = GET,
174     path = "/service/{service_uuid}",
175     }]
176     pub async fn endpoint_get_service(
177     rqctx: RequestContext<Arc<SalarymanDContext>>,
178     path_params: Path<ServicePath>,
179     ) -> Result<HttpResponseOk<ServiceConf>, HttpError> {
180     let u = path_params.into_inner().service_uuid;
181     let ctx = rqctx.context();
182     let mut service: Option<Arc<SalarymanService>> = None;
183     let lock = ctx.services.read().await;
184     for i in 0..lock.len() {
185     if lock[i].config.uuid == u {
186     service = Some(lock[i].clone());
187     } else {
188     continue;
189     }
190     }
191     let s = match service {
192     Some(s) => s.config.clone(),
193     None => {
194     return Err(HttpError::for_unavail(
195     None,
196     String::from("Service Not Found"),
197     ));
198     }
199     };
200     Ok(HttpResponseOk(s))
201     }
202     #[endpoint {
203     method = GET,
204     path = "/service/{service_uuid}/start",
205     }]
206     pub async fn endpoint_start_service(
207     rqctx: RequestContext<Arc<SalarymanDContext>>,
208     path_params: Path<ServicePath>,
209     ) -> Result<HttpResponseOk<()>, HttpError> {
210     let u = path_params.into_inner().service_uuid;
211     let ctx = rqctx.context();
212     let mut service: Option<Arc<SalarymanService>> = None;
213     let lock = ctx.services.read().await;
214     for i in 0..lock.len() {
215     if lock[i].config.uuid == u {
216     service = Some(lock[i].clone());
217     } else {
218     continue;
219     }
220     }
221     match service {
222     Some(s) => {
223     let mut lock = s.service.write().await;
224     match lock.start_with_output().await {
225     Ok(_) => (),
226     Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
227     }
228     }
229     None => {
230     return Err(HttpError::for_unavail(
231     None,
232     String::from("Service Not Found"),
233     ));
234     }
235     };
236     Ok(HttpResponseOk(()))
237     }
238     #[endpoint {
239     method = GET,
240     path = "/service/{service_uuid}/stop",
241     }]
242     pub async fn endpoint_stop_service(
243     rqctx: RequestContext<Arc<SalarymanDContext>>,
244     path_params: Path<ServicePath>,
245     ) -> Result<HttpResponseOk<()>, HttpError> {
246     let u = path_params.into_inner().service_uuid;
247     let ctx = rqctx.context();
248     let mut service: Option<Arc<SalarymanService>> = None;
249     let lock = ctx.services.read().await;
250     for i in 0..lock.len() {
251     if lock[i].config.uuid == u {
252     service = Some(lock[i].clone());
253     } else {
254     continue;
255     }
256     }
257     match service {
258     Some(s) => {
259     let mut lock = s.service.write().await;
260     match lock.stop().await {
261     Ok(_) => (),
262     Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
263     }
264     }
265     None => {
266     return Err(HttpError::for_unavail(
267     None,
268     String::from("Service Not Found"),
269     ));
270     }
271     };
272     Ok(HttpResponseOk(()))
273     }
274     #[endpoint {
275     method = GET,
276     path = "/service/{service_uuid}/restart",
277     }]
278     pub async fn endpoint_restart_service(
279     rqctx: RequestContext<Arc<SalarymanDContext>>,
280     path_params: Path<ServicePath>,
281     ) -> Result<HttpResponseOk<()>, HttpError> {
282     let u = path_params.into_inner().service_uuid;
283     let ctx = rqctx.context();
284     let mut service: Option<Arc<SalarymanService>> = None;
285     let lock = ctx.services.read().await;
286     for i in 0..lock.len() {
287     if lock[i].config.uuid == u {
288     service = Some(lock[i].clone());
289     } else {
290     continue;
291     }
292     }
293     match service {
294     Some(s) => {
295     let mut lock = s.service.write().await;
296     match lock.restart_with_output().await {
297     Ok(_) => (),
298     Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
299     }
300     }
301     None => {
302     return Err(HttpError::for_unavail(
303     None,
304     String::from("Service Not Found"),
305     ));
306     }
307     };
308     Ok(HttpResponseOk(()))
309     }
310     #[endpoint {
311     method = PUT,
312     path = "/service/{service_uuid}/write"
313     }]
314     pub async fn endpoint_post_stdin(
315     rqctx: RequestContext<Arc<SalarymanDContext>>,
316     path_params: Path<ServicePath>,
317     update: TypedBody<StdinBuffer>,
318     ) -> Result<HttpResponseOk<()>, HttpError> {
319     let ctx = rqctx.context();
320     let stdin_str = update.into_inner();
321     let u = path_params.into_inner().service_uuid;
322     let mut service: Option<Arc<SalarymanService>> = None;
323     let lock = ctx.services.read().await;
324     for i in 0..lock.len() {
325     if lock[i].config.uuid == u {
326     service = Some(lock[i].clone());
327     } else {
328     continue;
329     }
330     }
331     match service {
332     Some(s) => {
333     let mut lock = s.service.write().await;
334     if lock.started().await {
335     let b = if let Some(endl) = stdin_str.endl {
336     if endl {
337     lock.writeln_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
338     } else {
339     lock.write_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
340     }
341     } else {
342     lock.writeln_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
343     };
344     match b {
345     Ok(_) => (),
346     Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
347     }
348     }
349     drop(lock);
350     }
351     None => {
352     return Err(HttpError::for_unavail(
353     None,
354     String::from("Service Not Found"),
355     ));
356     }
357     }
358     Ok(HttpResponseOk(()))
359     }