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

# Content
1 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 }