1use std::fmt::Debug;
3
4use crate::{
5 params::{DeleteParams, PostParams},
6 request::{Error, Request, JSON_MIME},
7};
8
9pub use k8s_openapi::api::autoscaling::v1::{Scale, ScaleSpec, ScaleStatus};
10
11#[derive(Default, Clone, Debug)]
17pub struct LogParams {
18 pub container: Option<String>,
20 pub follow: bool,
22 pub limit_bytes: Option<i64>,
25 pub pretty: bool,
27 pub previous: bool,
29 pub since_seconds: Option<i64>,
33 pub since_time: Option<chrono::DateTime<chrono::Utc>>,
38 pub tail_lines: Option<i64>,
41 pub timestamps: bool,
43}
44
45impl Request {
46 pub fn logs(&self, name: &str, lp: &LogParams) -> Result<http::Request<Vec<u8>>, Error> {
48 let target = format!("{}/{}/log?", self.url_path, name);
49 let mut qp = form_urlencoded::Serializer::new(target);
50
51 if let Some(container) = &lp.container {
52 qp.append_pair("container", container);
53 }
54
55 if lp.follow {
56 qp.append_pair("follow", "true");
57 }
58
59 if let Some(lb) = &lp.limit_bytes {
60 qp.append_pair("limitBytes", &lb.to_string());
61 }
62
63 if lp.pretty {
64 qp.append_pair("pretty", "true");
65 }
66
67 if lp.previous {
68 qp.append_pair("previous", "true");
69 }
70
71 if let Some(ss) = &lp.since_seconds {
72 qp.append_pair("sinceSeconds", &ss.to_string());
73 } else if let Some(st) = &lp.since_time {
74 let ser_since = st.to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
75 qp.append_pair("sinceTime", &ser_since);
76 }
77
78 if let Some(tl) = &lp.tail_lines {
79 qp.append_pair("tailLines", &tl.to_string());
80 }
81
82 if lp.timestamps {
83 qp.append_pair("timestamps", "true");
84 }
85
86 let urlstr = qp.finish();
87 let req = http::Request::get(urlstr);
88 req.body(vec![]).map_err(Error::BuildRequest)
89 }
90}
91
92#[derive(Default, Clone)]
98pub struct EvictParams {
99 pub delete_options: Option<DeleteParams>,
101 pub post_options: PostParams,
103}
104
105impl Request {
106 pub fn evict(&self, name: &str, ep: &EvictParams) -> Result<http::Request<Vec<u8>>, Error> {
108 let target = format!("{}/{}/eviction?", self.url_path, name);
109 let pp = &ep.post_options;
111 pp.validate()?;
112 let mut qp = form_urlencoded::Serializer::new(target);
113 pp.populate_qp(&mut qp);
114 let urlstr = qp.finish();
115 let data = serde_json::to_vec(&serde_json::json!({
117 "delete_options": ep.delete_options,
118 "metadata": { "name": name }
119 }))
120 .map_err(Error::SerializeBody)?;
121 let req = http::Request::post(urlstr).header(http::header::CONTENT_TYPE, JSON_MIME);
122 req.body(data).map_err(Error::BuildRequest)
123 }
124}
125
126#[cfg(feature = "ws")]
134#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
135#[derive(Debug)]
136pub struct AttachParams {
137 pub container: Option<String>,
140 pub stdin: bool,
144 pub stdout: bool,
148 pub stderr: bool,
152 pub tty: bool,
156
157 pub max_stdin_buf_size: Option<usize>,
163 pub max_stdout_buf_size: Option<usize>,
169 pub max_stderr_buf_size: Option<usize>,
175}
176
177#[cfg(feature = "ws")]
178#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
179impl Default for AttachParams {
180 fn default() -> Self {
182 Self {
183 container: None,
184 stdin: false,
185 stdout: true,
186 stderr: true,
187 tty: false,
188 max_stdin_buf_size: None,
189 max_stdout_buf_size: None,
190 max_stderr_buf_size: None,
191 }
192 }
193}
194
195#[cfg(feature = "ws")]
196#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
197impl AttachParams {
198 #[must_use]
200 pub fn interactive_tty() -> Self {
201 Self {
202 stdin: true,
203 stdout: true,
204 stderr: false,
205 tty: true,
206 ..Default::default()
207 }
208 }
209
210 #[must_use]
212 pub fn container<T: Into<String>>(mut self, container: T) -> Self {
213 self.container = Some(container.into());
214 self
215 }
216
217 #[must_use]
219 pub fn stdin(mut self, enable: bool) -> Self {
220 self.stdin = enable;
221 self
222 }
223
224 #[must_use]
226 pub fn stdout(mut self, enable: bool) -> Self {
227 self.stdout = enable;
228 self
229 }
230
231 #[must_use]
233 pub fn stderr(mut self, enable: bool) -> Self {
234 self.stderr = enable;
235 self
236 }
237
238 #[must_use]
240 pub fn tty(mut self, enable: bool) -> Self {
241 self.tty = enable;
242 self
243 }
244
245 #[must_use]
247 pub fn max_stdin_buf_size(mut self, size: usize) -> Self {
248 self.max_stdin_buf_size = Some(size);
249 self
250 }
251
252 #[must_use]
254 pub fn max_stdout_buf_size(mut self, size: usize) -> Self {
255 self.max_stdout_buf_size = Some(size);
256 self
257 }
258
259 #[must_use]
261 pub fn max_stderr_buf_size(mut self, size: usize) -> Self {
262 self.max_stderr_buf_size = Some(size);
263 self
264 }
265
266 pub(crate) fn validate(&self) -> Result<(), Error> {
267 if !self.stdin && !self.stdout && !self.stderr {
268 return Err(Error::Validation(
269 "AttachParams: one of stdin, stdout, or stderr must be true".into(),
270 ));
271 }
272
273 if self.stderr && self.tty {
274 return Err(Error::Validation(
276 "AttachParams: tty and stderr cannot both be true".into(),
277 ));
278 }
279
280 Ok(())
281 }
282
283 fn append_to_url_serializer(&self, qp: &mut form_urlencoded::Serializer<String>) {
284 if self.stdin {
285 qp.append_pair("stdin", "true");
286 }
287 if self.stdout {
288 qp.append_pair("stdout", "true");
289 }
290 if self.stderr {
291 qp.append_pair("stderr", "true");
292 }
293 if self.tty {
294 qp.append_pair("tty", "true");
295 }
296 if let Some(container) = &self.container {
297 qp.append_pair("container", container);
298 }
299 }
300
301 #[cfg(feature = "kubelet-debug")]
302 pub(crate) fn append_to_url_serializer_local(&self, qp: &mut form_urlencoded::Serializer<String>) {
304 if self.stdin {
305 qp.append_pair("input", "1");
306 }
307 if self.stdout {
308 qp.append_pair("output", "1");
309 }
310 if self.stderr {
311 qp.append_pair("error", "1");
312 }
313 if self.tty {
314 qp.append_pair("tty", "1");
315 }
316 }
317}
318
319#[cfg(feature = "ws")]
320#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
321impl Request {
322 pub fn attach(&self, name: &str, ap: &AttachParams) -> Result<http::Request<Vec<u8>>, Error> {
324 ap.validate()?;
325
326 let target = format!("{}/{}/attach?", self.url_path, name);
327 let mut qp = form_urlencoded::Serializer::new(target);
328 ap.append_to_url_serializer(&mut qp);
329
330 let req = http::Request::get(qp.finish());
331 req.body(vec![]).map_err(Error::BuildRequest)
332 }
333}
334
335#[cfg(feature = "ws")]
339#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
340impl Request {
341 pub fn exec<I, T>(
343 &self,
344 name: &str,
345 command: I,
346 ap: &AttachParams,
347 ) -> Result<http::Request<Vec<u8>>, Error>
348 where
349 I: IntoIterator<Item = T>,
350 T: Into<String>,
351 {
352 ap.validate()?;
353
354 let target = format!("{}/{}/exec?", self.url_path, name);
355 let mut qp = form_urlencoded::Serializer::new(target);
356 ap.append_to_url_serializer(&mut qp);
357
358 for c in command.into_iter() {
359 qp.append_pair("command", &c.into());
360 }
361
362 let req = http::Request::get(qp.finish());
363 req.body(vec![]).map_err(Error::BuildRequest)
364 }
365}
366
367#[cfg(feature = "ws")]
371#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
372impl Request {
373 pub fn portforward(&self, name: &str, ports: &[u16]) -> Result<http::Request<Vec<u8>>, Error> {
375 if ports.is_empty() {
376 return Err(Error::Validation("ports cannot be empty".into()));
377 }
378 if ports.len() > 128 {
379 return Err(Error::Validation(
380 "the number of ports cannot be more than 128".into(),
381 ));
382 }
383
384 if ports.len() > 1 {
385 let mut seen = std::collections::HashSet::with_capacity(ports.len());
386 for port in ports.iter() {
387 if seen.contains(port) {
388 return Err(Error::Validation(format!(
389 "ports must be unique, found multiple {port}"
390 )));
391 }
392 seen.insert(port);
393 }
394 }
395
396 let base_url = format!("{}/{}/portforward?", self.url_path, name);
397 let mut qp = form_urlencoded::Serializer::new(base_url);
398 qp.append_pair(
399 "ports",
400 &ports.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(","),
401 );
402
403 let req = http::Request::get(qp.finish());
404 req.body(vec![]).map_err(Error::BuildRequest)
405 }
406}
407
408#[cfg(test)]
414mod test {
415 use crate::{request::Request, resource::Resource};
416 use chrono::{DateTime, TimeZone, Utc};
417 use k8s::core::v1 as corev1;
418 use k8s_openapi::api as k8s;
419
420 use crate::subresource::LogParams;
421
422 #[test]
423 fn logs_all_params() {
424 let url = corev1::Pod::url_path(&(), Some("ns"));
425 let lp = LogParams {
426 container: Some("nginx".into()),
427 follow: true,
428 limit_bytes: Some(10 * 1024 * 1024),
429 pretty: true,
430 previous: true,
431 since_seconds: Some(3600),
432 since_time: None,
433 tail_lines: Some(4096),
434 timestamps: true,
435 };
436 let req = Request::new(url).logs("mypod", &lp).unwrap();
437 assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/mypod/log?&container=nginx&follow=true&limitBytes=10485760&pretty=true&previous=true&sinceSeconds=3600&tailLines=4096×tamps=true");
438 }
439
440 #[test]
441 fn logs_since_time() {
442 let url = corev1::Pod::url_path(&(), Some("ns"));
443 let date: DateTime<Utc> = Utc.with_ymd_and_hms(2023, 10, 19, 13, 14, 26).unwrap();
444 let lp = LogParams {
445 since_seconds: None,
446 since_time: Some(date),
447 ..Default::default()
448 };
449 let req = Request::new(url).logs("mypod", &lp).unwrap();
450 assert_eq!(
451 req.uri(),
452 "/api/v1/namespaces/ns/pods/mypod/log?&sinceTime=2023-10-19T13%3A14%3A26Z" );
454 }
455}