1use std::time::Duration;
2
3use bytes::Bytes;
4use k8s_openapi::apimachinery::pkg::apis::meta::v1::Status;
5
6use futures::{
7 FutureExt, SinkExt, StreamExt,
8 channel::{mpsc, oneshot},
9};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12use tokio::{
13 io::{AsyncRead, AsyncWrite, AsyncWriteExt, DuplexStream},
14 select, time,
15};
16use tokio_tungstenite::tungstenite as ws;
17
18use crate::client::Connection;
19
20use super::AttachParams;
21
22type StatusReceiver = oneshot::Receiver<Status>;
23type StatusSender = oneshot::Sender<Status>;
24
25type TerminalSizeReceiver = mpsc::Receiver<TerminalSize>;
26type TerminalSizeSender = mpsc::Sender<TerminalSize>;
27
28#[derive(Debug, Serialize, Deserialize)]
30#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
31#[serde(rename_all = "PascalCase")]
32pub struct TerminalSize {
33 pub width: u16,
35 pub height: u16,
37}
38
39#[derive(Debug, Error)]
41pub enum Error {
42 #[error("failed to read from stdin: {0}")]
44 ReadStdin(#[source] std::io::Error),
45
46 #[error("failed to send a stdin data: {0}")]
48 SendStdin(#[source] ws::Error),
49
50 #[error("failed to write to stdout: {0}")]
52 WriteStdout(#[source] std::io::Error),
53
54 #[error("failed to write to stderr: {0}")]
56 WriteStderr(#[source] std::io::Error),
57
58 #[error("failed to receive a WebSocket message: {0}")]
60 ReceiveWebSocketMessage(#[source] ws::Error),
61
62 #[error("failed to complete the background task: {0}")]
64 Spawn(#[source] tokio::task::JoinError),
65
66 #[error("failed to send a WebSocket close message: {0}")]
68 SendClose(#[source] ws::Error),
69
70 #[error("failed to send a WebSocket ping message: {0}")]
72 SendPing(#[source] ws::Error),
73
74 #[error("failed to deserialize status object: {0}")]
76 DeserializeStatus(#[source] serde_json::Error),
77
78 #[error("failed to send status object")]
80 SendStatus,
81
82 #[error("failed to serialize TerminalSize object: {0}")]
84 SerializeTerminalSize(#[source] serde_json::Error),
85
86 #[error("failed to send terminal size message: {0}")]
88 SendTerminalSize(#[source] ws::Error),
89
90 #[error("failed to set terminal size, tty need to be true to resize the terminal")]
92 TtyNeedToBeTrue,
93}
94
95const MAX_BUF_SIZE: usize = 1024;
96
97#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
106pub struct AttachedProcess {
107 has_stdin: bool,
108 has_stdout: bool,
109 has_stderr: bool,
110 stdin_writer: Option<DuplexStream>,
111 stdout_reader: Option<DuplexStream>,
112 stderr_reader: Option<DuplexStream>,
113 status_rx: Option<StatusReceiver>,
114 terminal_resize_tx: Option<TerminalSizeSender>,
115 task: Option<tokio::task::JoinHandle<Result<(), Error>>>,
116}
117
118impl Drop for AttachedProcess {
119 fn drop(&mut self) {
120 if let Some(task) = &self.task {
121 task.abort();
122 }
123 }
124}
125
126impl AttachedProcess {
127 pub(crate) fn new(connection: Connection, ap: &AttachParams) -> Self {
128 let (stdin_writer, stdin_reader) = tokio::io::duplex(ap.max_stdin_buf_size.unwrap_or(MAX_BUF_SIZE));
131 let (stdout_writer, stdout_reader) = if ap.stdout {
132 let (w, r) = tokio::io::duplex(ap.max_stdout_buf_size.unwrap_or(MAX_BUF_SIZE));
133 (Some(w), Some(r))
134 } else {
135 (None, None)
136 };
137 let (stderr_writer, stderr_reader) = if ap.stderr {
138 let (w, r) = tokio::io::duplex(ap.max_stderr_buf_size.unwrap_or(MAX_BUF_SIZE));
139 (Some(w), Some(r))
140 } else {
141 (None, None)
142 };
143 let (status_tx, status_rx) = oneshot::channel();
144 let (terminal_resize_tx, terminal_resize_rx) = if ap.tty {
145 let (w, r) = mpsc::channel(10);
146 (Some(w), Some(r))
147 } else {
148 (None, None)
149 };
150
151 let task = tokio::spawn(start_message_loop(
152 connection,
153 stdin_reader,
154 stdout_writer,
155 stderr_writer,
156 status_tx,
157 terminal_resize_rx,
158 ));
159
160 AttachedProcess {
161 has_stdin: ap.stdin,
162 has_stdout: ap.stdout,
163 has_stderr: ap.stderr,
164 task: Some(task),
165 stdin_writer: Some(stdin_writer),
166 stdout_reader,
167 stderr_reader,
168 terminal_resize_tx,
169 status_rx: Some(status_rx),
170 }
171 }
172
173 pub fn stdin(&mut self) -> Option<impl AsyncWrite + Unpin + use<>> {
186 if !self.has_stdin {
187 return None;
188 }
189 self.stdin_writer.take()
190 }
191
192 pub fn stdout(&mut self) -> Option<impl AsyncRead + Unpin + use<>> {
206 if !self.has_stdout {
207 return None;
208 }
209 self.stdout_reader.take()
210 }
211
212 pub fn stderr(&mut self) -> Option<impl AsyncRead + Unpin + use<>> {
226 if !self.has_stderr {
227 return None;
228 }
229 self.stderr_reader.take()
230 }
231
232 #[inline]
234 pub fn abort(&self) {
235 if let Some(task) = &self.task {
236 task.abort();
237 }
238 }
239
240 pub async fn join(mut self) -> Result<(), Error> {
242 self.stdin_writer = None;
247 self.stdout_reader = None;
248 self.stderr_reader = None;
249 self.status_rx = None;
250 self.terminal_resize_tx = None;
251 match self.task.take() {
252 Some(task) => task.await.unwrap_or_else(|e| Err(Error::Spawn(e))),
253 None => Ok(()),
254 }
255 }
256
257 pub fn take_status(&mut self) -> Option<impl Future<Output = Option<Status>> + use<>> {
261 self.status_rx.take().map(|recv| recv.map(|res| res.ok()))
262 }
263
264 pub fn terminal_size(&mut self) -> Option<TerminalSizeSender> {
281 self.terminal_resize_tx.take()
282 }
283}
284
285const STDIN_CHANNEL: u8 = 0;
287const STDOUT_CHANNEL: u8 = 1;
288const STDERR_CHANNEL: u8 = 2;
289const STATUS_CHANNEL: u8 = 3;
291const RESIZE_CHANNEL: u8 = 4;
293const CLOSE_CHANNEL: u8 = 255;
295
296async fn start_message_loop(
297 connection: Connection,
298 stdin: impl AsyncRead + Unpin,
299 mut stdout: Option<impl AsyncWrite + Unpin>,
300 mut stderr: Option<impl AsyncWrite + Unpin>,
301 status_tx: StatusSender,
302 mut terminal_size_rx: Option<TerminalSizeReceiver>,
303) -> Result<(), Error> {
304 let supports_stream_close = connection.supports_stream_close();
305 let stream = connection.into_stream();
306 let mut stdin_stream = tokio_util::io::ReaderStream::new(stdin);
307 let (mut server_send, raw_server_recv) = stream.split();
308 let mut server_recv = raw_server_recv.filter_map(filter_message).boxed();
310 let mut have_terminal_size_rx = terminal_size_rx.is_some();
311
312 let mut stdin_is_open = true;
314
315 let mut ping_interval = time::interval(Duration::from_secs(60));
316 ping_interval.set_missed_tick_behavior(time::MissedTickBehavior::Delay);
317 ping_interval.reset();
318
319 loop {
320 let terminal_size_next = async {
321 match terminal_size_rx.as_mut() {
322 Some(tmp) => Some(tmp.next().await),
323 None => None,
324 }
325 };
326
327 select! {
328 _ = ping_interval.tick() => {
329 server_send
331 .send(ws::Message::Ping(Bytes::new()))
332 .await
333 .map_err(Error::SendPing)?;
334 },
335
336 server_message = server_recv.next() => {
337 match server_message {
338 Some(Ok(Message::Stdout(bin))) => {
339 if let Some(stdout) = stdout.as_mut() {
340 stdout.write_all(&bin[1..]).await.map_err(Error::WriteStdout)?;
341 }
342 },
343 Some(Ok(Message::Stderr(bin))) => {
344 if let Some(stderr) = stderr.as_mut() {
345 stderr.write_all(&bin[1..]).await.map_err(Error::WriteStderr)?;
346 }
347 },
348 Some(Ok(Message::Status(bin))) => {
349 let status = serde_json::from_slice::<Status>(&bin[1..]).map_err(Error::DeserializeStatus)?;
350 status_tx.send(status).map_err(|_| Error::SendStatus)?;
351 break
352 },
353 Some(Err(err)) => {
354 return Err(Error::ReceiveWebSocketMessage(err));
355 },
356 None => {
357 break
359 },
360 }
361 },
362 stdin_message = stdin_stream.next(), if stdin_is_open => {
363 match stdin_message {
364 Some(Ok(bytes)) => {
365 if !bytes.is_empty() {
366 let mut vec = Vec::with_capacity(bytes.len() + 1);
367 vec.push(STDIN_CHANNEL);
368 vec.extend_from_slice(&bytes[..]);
369 server_send
370 .send(ws::Message::binary(vec))
371 .await
372 .map_err(Error::SendStdin)?;
373 }
374 },
375 Some(Err(err)) => {
376 return Err(Error::ReadStdin(err));
377 }
378 None => {
379 if supports_stream_close {
382 let vec = vec![CLOSE_CHANNEL, STDIN_CHANNEL];
385 server_send
386 .send(ws::Message::binary(vec))
387 .await
388 .map_err(Error::SendStdin)?;
389 } else {
390 server_send.close().await.map_err(Error::SendClose)?;
394 }
395
396 stdin_is_open = false;
398 }
399 }
400 },
401 Some(terminal_size_message) = terminal_size_next, if have_terminal_size_rx => {
402 match terminal_size_message {
403 Some(new_size) => {
404 let new_size = serde_json::to_vec(&new_size).map_err(Error::SerializeTerminalSize)?;
405 let mut vec = Vec::with_capacity(new_size.len() + 1);
406 vec.push(RESIZE_CHANNEL);
407 vec.extend_from_slice(&new_size[..]);
408 server_send.send(ws::Message::Binary(vec.into())).await.map_err(Error::SendTerminalSize)?;
409 },
410 None => {
411 have_terminal_size_rx = false;
412 }
413 }
414 },
415 }
416 }
417
418 Ok(())
419}
420
421enum Message {
423 Stdout(Vec<u8>),
425 Stderr(Vec<u8>),
427 Status(Vec<u8>),
429}
430
431async fn filter_message(wsm: Result<ws::Message, ws::Error>) -> Option<Result<Message, ws::Error>> {
433 match wsm {
434 Ok(ws::Message::Binary(bin)) if bin.len() > 1 => match bin[0] {
437 STDOUT_CHANNEL => Some(Ok(Message::Stdout(bin.into()))),
438 STDERR_CHANNEL => Some(Ok(Message::Stderr(bin.into()))),
439 STATUS_CHANNEL => Some(Ok(Message::Status(bin.into()))),
440 _ => None,
442 },
443 Ok(_) => None,
447 Err(err) => Some(Err(err)),
450 }
451}