Big Terminal refactor + most commands run directly / bypass 'sh -c "..."'

This commit is contained in:
mbecker20
2025-10-26 23:00:35 -07:00
parent e2ec5258fb
commit 4f0af960db
102 changed files with 4687 additions and 4250 deletions

View File

@@ -20,6 +20,7 @@ mod server;
mod stack;
mod sync;
mod tag;
mod terminal;
mod toml;
mod update;
mod user;
@@ -43,6 +44,7 @@ pub use server::*;
pub use stack::*;
pub use sync::*;
pub use tag::*;
pub use terminal::*;
pub use toml::*;
pub use update::*;
pub use user::*;

View File

@@ -7,7 +7,7 @@ use crate::entities::{
I64, Timelength,
server::{
PeripheryInformation, Server, ServerActionState, ServerListItem,
ServerQuery, ServerState, TerminalInfo, TerminalInfoWithServer,
ServerQuery, ServerState,
},
stats::{
SystemInformation, SystemProcess, SystemStats, SystemStatsRecord,
@@ -265,51 +265,3 @@ pub struct GetServersSummaryResponse {
/// The number of disabled servers.
pub disabled: I64,
}
//
/// List the current terminals on specified server.
/// Response: [ListTerminalsResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListTerminalsResponse)]
#[error(serror::Error)]
pub struct ListTerminals {
/// Id or name
#[serde(alias = "id", alias = "name")]
pub server: String,
/// Force a fresh call to Periphery for the list.
/// Otherwise the response will be cached for 30s
#[serde(default)]
pub fresh: bool,
}
#[typeshare]
pub type ListTerminalsResponse = Vec<TerminalInfo>;
//
/// List the current terminals on specified server.
/// Response: [ListAllTerminalsResponse].
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListAllTerminalsResponse)]
#[error(serror::Error)]
pub struct ListAllTerminals {
/// optional structured query to filter servers.
#[serde(default)]
pub query: ServerQuery,
/// Force a fresh call to Periphery for the list.
/// Otherwise the response will be cached for 30s
#[serde(default)]
pub fresh: bool,
}
#[typeshare]
pub type ListAllTerminalsResponse = Vec<TerminalInfoWithServer>;

View File

@@ -0,0 +1,30 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::terminal::{Terminal, TerminalTarget};
use super::KomodoReadRequest;
//
/// List Terminals.
/// Response: [ListTerminalsResponse].
#[typeshare]
#[derive(
Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoReadRequest)]
#[response(ListTerminalsResponse)]
#[error(serror::Error)]
pub struct ListTerminals {
/// Filter the Terminals returned by the Target.
pub target: Option<TerminalTarget>,
/// Return results with resource names instead of ids.
#[serde(default)]
pub use_names: bool,
}
#[typeshare]
pub type ListTerminalsResponse = Vec<Terminal>;

View File

@@ -1,181 +1,61 @@
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::api::write::TerminalRecreateMode;
use crate::entities::terminal::{
ContainerTerminalMode, TerminalRecreateMode, TerminalTarget,
};
/// Query to connect to a terminal (interactive shell over websocket) on the given server.
/// Connect to a Terminal.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectTerminalQuery {
/// Server Id or name
pub server: String,
/// Each periphery can keep multiple terminals open.
/// If a terminals with the specified name does not exist,
/// the call will fail.
/// Create a terminal using [CreateTerminal][super::write::server::CreateTerminal]
pub terminal: String,
/// The target to create terminal for.
pub target: TerminalTarget,
/// Terminal name to connect to.
/// If it may not exist yet, also pass 'init' params
/// to include initialization.
/// Default: Depends on target.
pub terminal: Option<String>,
/// Pass to init the terminal session
/// for when the terminal doesn't already exist.
///
/// Example: ?...(query)&init[command]=bash&init[recreate]=DifferentCommand
pub init: Option<InitTerminal>,
}
/// Args to init the Terminal if needed.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InitTerminal {
/// The shell command (eg `bash`) to init the shell.
///
/// Default:
/// - Server: Configured on each Periphery
/// - Container: `sh`
pub command: Option<String>,
/// Default: `Never`
#[serde(default)]
pub recreate: TerminalRecreateMode,
/// Only relevant for container-type terminals.
/// Specify the container terminal mode (`exec` or `attach`).
/// Default: `exec`
pub mode: Option<ContainerTerminalMode>,
}
/// Execute a terminal command on the given server.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteTerminalBody {
/// Server Id or name
pub server: String,
/// The name of the terminal on the server to use to execute.
pub terminal: String,
/// The target to create terminal for.
pub target: TerminalTarget,
/// Terminal name to connect to.
/// If it may not exist yet, also pass 'init' params
/// to include initialization.
/// Default: Depends on target.
pub terminal: Option<String>,
/// The command to execute.
pub command: String,
/// Pass to init the terminal session
/// for when the terminal doesn't already exist.
pub init: Option<InitTerminal>,
}
/// Init a terminal on the server.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InitTerminal {
/// The shell command (eg `bash`) to init the shell.
///
/// This can also include args:
/// `docker exec -it container sh`
///
/// Default: Configured on each Periphery
pub command: Option<String>,
/// Default: `Never`
#[serde(default)]
pub recreate: TerminalRecreateMode,
}
/// Query to connect to a container exec session (interactive shell over websocket) on the given server.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectContainerExecQuery {
/// Server Id or name
pub server: String,
/// The container name
pub container: String,
/// The shell to use (eg. `sh` or `bash`)
pub shell: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Query to connect to a container attach session (interactive shell over websocket) on the given server.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectContainerAttachQuery {
/// Server Id or name
pub server: String,
/// The container name
pub container: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Execute a command in the given containers shell.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteContainerExecBody {
/// Server Id or name
pub server: String,
/// The container name
pub container: String,
/// The shell to use (eg. `sh` or `bash`)
pub shell: String,
/// The command to execute.
pub command: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Query to connect to a container exec session (interactive shell over websocket) on the given Deployment.
/// This call will use access to the Deployment Terminal to permission the call.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectDeploymentExecQuery {
/// Deployment Id or name
pub deployment: String,
/// The shell to use (eg. `sh` or `bash`)
pub shell: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Query to connect to a container attach session (interactive shell over websocket) on the given Deployment.
/// This call will use access to the Deployment Terminal to permission the call.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectDeploymentAttachQuery {
/// Deployment Id or name
pub deployment: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Execute a command in the given containers shell.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteDeploymentExecBody {
/// Deployment Id or name
pub deployment: String,
/// The shell to use (eg. `sh` or `bash`)
pub shell: String,
/// The command to execute.
pub command: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Query to connect to a container exec session (interactive shell over websocket) on the given Stack / service.
/// This call will use access to the Stack Terminal to permission the call.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectStackExecQuery {
/// Stack Id or name
pub stack: String,
/// The service name to connect to
pub service: String,
/// The shell to use (eg. `sh` or `bash`)
pub shell: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Query to connect to a container attach session (interactive shell over websocket) on the given Stack / service.
/// This call will use access to the Stack Terminal to permission the call.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConnectStackAttachQuery {
/// Stack Id or name
pub stack: String,
/// The service name to attach to
pub service: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}
/// Execute a command in the given containers shell.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteStackExecBody {
/// Stack Id or name
pub stack: String,
/// The service name to connect to
pub service: String,
/// The shell to use (eg. `sh` or `bash`)
pub shell: String,
/// The command to execute.
pub command: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
pub recreate: Option<TerminalRecreateMode>,
}

View File

@@ -15,6 +15,7 @@ mod server;
mod stack;
mod sync;
mod tags;
mod terminal;
mod user;
mod user_group;
mod variable;
@@ -36,6 +37,7 @@ pub use server::*;
pub use stack::*;
pub use sync::*;
pub use tags::*;
pub use terminal::*;
pub use user::*;
pub use user_group::*;
pub use variable::*;

View File

@@ -1,12 +1,10 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use strum::AsRefStr;
use typeshare::typeshare;
use crate::entities::{
NoData,
server::{_PartialServerConfig, Server, ServerQuery},
server::{_PartialServerConfig, Server},
update::Update,
};
@@ -132,102 +130,6 @@ pub struct CreateNetwork {
//
/// Configures the behavior of [CreateTerminal] if the
/// specified terminal name already exists.
#[typeshare]
#[derive(
Debug, Clone, Copy, Default, Serialize, Deserialize, AsRefStr,
)]
pub enum TerminalRecreateMode {
/// Never kill the old terminal if it already exists.
/// If the command is different, returns error.
#[default]
Never,
/// Always kill the old terminal and create new one
Always,
/// Only kill and recreate if the command is different.
DifferentCommand,
}
/// Create a terminal on the server.
/// Response: [NoData]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct CreateTerminal {
/// Server Id or name
pub server: String,
/// The name of the terminal on the server to create.
pub name: String,
/// The shell command (eg `bash`) to init the shell.
///
/// This can also include args:
/// `docker exec -it container sh`
///
/// Default: Configured on each Periphery
pub command: Option<String>,
/// Default: `Never`
#[serde(default)]
pub recreate: TerminalRecreateMode,
}
//
/// Delete a terminal on the server.
/// Response: [NoData]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct DeleteTerminal {
/// Server Id or name
pub server: String,
/// The name of the terminal on the server to delete.
pub terminal: String,
}
//
/// Delete all terminals on the server.
/// Response: [NoData]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct DeleteAllTerminals {
/// Server Id or name
pub server: String,
}
//
/// Delete all terminals on many or all Servers.
/// Response: [NoData]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct BatchDeleteAllTerminals {
/// optional structured query to filter servers.
#[serde(default)]
pub query: ServerQuery,
}
//
/// Updates the Server with an explicit Public Key.
/// Response: [Update]
#[typeshare]

View File

@@ -0,0 +1,100 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::{
NoData,
server::ServerQuery,
terminal::{
ContainerTerminalMode, TerminalRecreateMode, TerminalTarget,
},
};
use super::KomodoWriteRequest;
//
/// Create a Terminal.
/// Requires minimum Read + Terminal permission on the target Resource.
/// Response: [NoData]
#[typeshare]
#[derive(
Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct CreateTerminal {
/// A name for the Terminal session.
pub name: String,
/// The target to create terminal for
pub target: TerminalTarget,
/// The shell command (eg `bash`) to init the shell.
///
/// Default:
/// - Server: Configured on each Periphery
/// - ContainerExec: `sh`
/// - Attach: unused
pub command: Option<String>,
/// For container terminals, choose 'exec' or 'attach'.
///
/// Default
/// - Server: ignored
/// - Container / Stack / Deployment: `exec`
pub mode: Option<ContainerTerminalMode>,
/// Default: `Never`
#[serde(default)]
pub recreate: TerminalRecreateMode,
}
//
/// Delete a terminal.
/// Response: [NoData]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct DeleteTerminal {
/// Server / Container / Stack / Deployment
pub target: TerminalTarget,
/// The name of the Terminal to delete.
pub terminal: String,
}
//
/// Delete all Terminals on the Server.
/// Response: [NoData]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct DeleteAllTerminals {
/// Server Id or name
pub server: String,
}
//
/// Delete all terminals on many or all Servers.
/// Response: [NoData]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
)]
#[empty_traits(KomodoWriteRequest)]
#[response(NoData)]
#[error(serror::Error)]
pub struct BatchDeleteAllTerminals {
/// Optional structured query to filter servers.
#[serde(default)]
pub query: ServerQuery,
}

View File

@@ -66,6 +66,8 @@ pub mod stats;
pub mod sync;
/// Subtypes of [Tag][tag::Tag].
pub mod tag;
pub mod terminal;
/// Subtypes of [ResourcesToml][toml::ResourcesToml].
pub mod toml;
/// Subtypes of [Update][update::Update].
@@ -605,28 +607,6 @@ pub struct RepoExecutionResponse {
pub commit_message: Option<String>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Default,
Serialize,
Deserialize,
Display,
EnumString,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ContainerTerminalMode {
#[default]
Exec,
Attach,
}
#[typeshare]
#[derive(
Debug,

View File

@@ -246,6 +246,47 @@ impl PermissionLevel {
}
impl PermissionLevelAndSpecifics {
/// Elevates self by permissions in other:
/// - If other.level > self.level, set self.level = other.level
/// - If other includes more specifics, add them.
pub fn elevate(&mut self, other: &impl HasLevelAndSpecific) {
let other_level = other.level();
if other_level > self.level {
self.level = other_level;
}
self.specific.extend(other.specific().iter().cloned());
}
/// Joins permissions in self with other to produce a new PermissionsLevelAndSpecifics:
/// - If other.level > self.level, set self.level = other.level
/// - If other includes more specifics, add them.
pub fn join(
&self,
other: &impl HasLevelAndSpecific,
) -> PermissionLevelAndSpecifics {
let mut specific = self.specific.clone();
specific.extend(other.specific().iter().cloned());
PermissionLevelAndSpecifics {
level: std::cmp::max(self.level, other.level()),
specific,
}
}
/// Joins permissions in self with other to produce a new PermissionsLevelAndSpecifics:
/// - If other.level > self.level, set self.level = other.level
/// - If other includes more specifics, add them.
pub fn join_permission(
&self,
other: &Permission,
) -> PermissionLevelAndSpecifics {
let mut specific = self.specific.clone();
specific.extend(other.specific.iter().cloned());
PermissionLevelAndSpecifics {
level: std::cmp::max(self.level, other.level),
specific,
}
}
/// Returns true when self.level >= other.level,
/// and has all required specific permissions.
pub fn fulfills(
@@ -332,3 +373,26 @@ impl PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Processes)
}
}
pub trait HasLevelAndSpecific {
fn level(&self) -> PermissionLevel;
fn specific(&self) -> &IndexSet<SpecificPermission>;
}
impl HasLevelAndSpecific for Permission {
fn level(&self) -> PermissionLevel {
self.level
}
fn specific(&self) -> &IndexSet<SpecificPermission> {
&self.specific
}
}
impl HasLevelAndSpecific for PermissionLevelAndSpecifics {
fn level(&self) -> PermissionLevel {
self.level
}
fn specific(&self) -> &IndexSet<SpecificPermission> {
&self.specific
}
}

View File

@@ -10,7 +10,7 @@ use crate::{
deserializers::{
option_string_list_deserializer, string_list_deserializer,
},
entities::{I64, MaintenanceWindow, Timelength},
entities::{MaintenanceWindow, Timelength},
};
use super::{
@@ -364,62 +364,6 @@ pub struct PeripheryInformation {
pub public_ip: Option<String>,
}
/// Info about an active terminal on a server.
/// Retrieve with [ListTerminals][crate::api::read::server::ListTerminals].
#[typeshare]
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct TerminalInfo {
/// The name of the terminal.
pub name: String,
/// The root program / args of the pty
pub command: String,
/// The size of the terminal history in memory.
pub stored_size_kb: f64,
/// When the Terminal was created.
pub created_at: I64,
}
/// Info about an active terminal on a server.
/// Retrieve with [ListAllTerminals][crate::api::read::server::ListAllTerminals].
#[typeshare]
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct TerminalInfoWithServer {
/// The server id.
pub server_id: String,
/// The server name.
pub server_name: String,
/// The name of the terminal.
pub name: String,
/// The root program / args of the pty
pub command: String,
/// The size of the terminal history in memory.
pub stored_size_kb: f64,
/// When the Terminal was created in unix milliseconds.
pub created_at: I64,
}
impl TerminalInfoWithServer {
pub fn from_terminal_info(
server_id: impl Into<String>,
server_name: impl Into<String>,
TerminalInfo {
name,
command,
stored_size_kb,
created_at,
}: TerminalInfo,
) -> Self {
Self {
server_id: server_id.into(),
server_name: server_name.into(),
name,
command,
stored_size_kb,
created_at,
}
}
}
/// Current pending actions on the server.
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]

View File

@@ -0,0 +1,110 @@
use serde::{Deserialize, Serialize};
use strum::AsRefStr;
use typeshare::typeshare;
use crate::entities::I64;
/// Represents an active terminal on a server.
/// Retrieve with [ListTerminals][crate::api::read::server::ListTerminals].
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Terminal {
/// The name of the terminal.
pub name: String,
/// The target resource of the Terminal.
pub target: TerminalTarget,
/// The command used to init the shell.
pub command: String,
/// The size of the terminal history in memory.
pub stored_size_kb: f64,
/// When the Terminal was created.
/// Unix timestamp milliseconds.
pub created_at: I64,
}
#[typeshare]
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
)]
#[serde(tag = "type", content = "params")]
pub enum TerminalTarget {
Server {
server: Option<String>,
},
Container {
server: String,
container: String,
},
Stack {
stack: String,
service: Option<String>,
},
Deployment {
deployment: String,
},
}
impl TerminalTarget {
// Checks for target match in a fixed server context.
pub fn matches_on_server(&self, other: &TerminalTarget) -> bool {
match (self, other) {
(
TerminalTarget::Server { .. },
TerminalTarget::Server { .. },
) => true,
(
TerminalTarget::Container {
container: target, ..
},
TerminalTarget::Container { container, .. },
) => target == container,
(
TerminalTarget::Stack { stack: target, .. },
TerminalTarget::Stack { stack, .. },
) => target == stack,
(
TerminalTarget::Deployment { deployment: target },
TerminalTarget::Deployment { deployment },
) => target == deployment,
_ => false,
}
}
}
/// JSON structure to send new terminal window dimensions
#[typeshare]
#[derive(Clone, Serialize, Deserialize)]
pub struct ResizeDimensions {
pub rows: u16,
pub cols: u16,
}
/// Specify the container terminal mode (exec or attach)
#[typeshare]
#[derive(
Debug, Clone, Copy, Default, Serialize, Deserialize, AsRefStr,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ContainerTerminalMode {
#[default]
Exec,
Attach,
}
/// Configures the behavior of [CreateTerminal] if the
/// specified terminal name already exists.
#[typeshare]
#[derive(
Debug, Clone, Copy, Default, Serialize, Deserialize, AsRefStr,
)]
pub enum TerminalRecreateMode {
/// Never kill the old terminal if it already exists.
/// If the init command is different, returns error.
#[default]
Never,
/// Always kill the old terminal and create new one
Always,
/// Only kill and recreate if the command is different.
DifferentCommand,
}

View File

@@ -9,7 +9,10 @@ use tokio_tungstenite::{
};
use typeshare::typeshare;
use crate::{KomodoClient, api::write::TerminalRecreateMode};
use crate::{
KomodoClient, api::terminal::ConnectTerminalQuery,
entities::terminal::TerminalRecreateMode,
};
pub mod update;
@@ -36,13 +39,12 @@ impl WsLoginMessage {
impl KomodoClient {
pub async fn connect_terminal_websocket(
&self,
server: &str,
terminal: &str,
query: &ConnectTerminalQuery,
) -> anyhow::Result<WebSocketStream<MaybeTlsStream<TcpStream>>> {
self
.connect_login_user_websocket(
"/terminal",
Some(&format!("server={server}&terminal={terminal}")),
Some(&serde_qs::to_string(query)?),
)
.await
}
@@ -52,13 +54,12 @@ impl KomodoClient {
server: &str,
container: &str,
shell: &str,
recreate: Option<TerminalRecreateMode>,
recreate: TerminalRecreateMode,
) -> anyhow::Result<WebSocketStream<MaybeTlsStream<TcpStream>>> {
let mut query =
format!("server={server}&container={container}&shell={shell}");
if let Some(recreate) = recreate {
let _ = write!(&mut query, "&recreate={}", recreate.as_ref());
}
let query = format!(
"server={server}&container={container}&shell={shell}&recreate={}",
recreate.as_ref()
);
self
.connect_login_user_websocket(
"/container/terminal",
@@ -71,12 +72,12 @@ impl KomodoClient {
&self,
server: &str,
container: &str,
recreate: Option<TerminalRecreateMode>,
recreate: TerminalRecreateMode,
) -> anyhow::Result<WebSocketStream<MaybeTlsStream<TcpStream>>> {
let mut query = format!("server={server}&container={container}");
if let Some(recreate) = recreate {
let _ = write!(&mut query, "&recreate={}", recreate.as_ref());
}
let query = format!(
"server={server}&container={container}&recreate={}",
recreate.as_ref()
);
self
.connect_login_user_websocket(
"/container/terminal/attach",

View File

@@ -5,12 +5,7 @@ import {
UserResponses,
WriteResponses,
} from "./responses.js";
import {
terminal_methods,
ConnectExecQuery,
ExecuteExecBody,
TerminalCallbacks,
} from "./terminal.js";
import { terminal_methods, TerminalCallbacks } from "./terminal.js";
import {
AuthRequest,
BatchExecutionResponse,
@@ -28,7 +23,7 @@ import {
export * as Types from "./types.js";
export type { ConnectExecQuery, ExecuteExecBody, TerminalCallbacks };
export type { TerminalCallbacks };
export type InitOptions =
| { type: "jwt"; params: { jwt: string } }
@@ -327,25 +322,8 @@ export function KomodoClient(url: string, options: InitOptions) {
}
};
const {
connect_terminal,
execute_terminal,
execute_terminal_stream,
connect_exec,
connect_attach,
connect_container_exec,
connect_container_attach,
execute_container_exec,
execute_container_exec_stream,
connect_deployment_exec,
connect_deployment_attach,
execute_deployment_exec,
execute_deployment_exec_stream,
connect_stack_exec,
connect_stack_attach,
execute_stack_exec,
execute_stack_exec_stream,
} = terminal_methods(url, state);
const { connect_terminal, execute_terminal, execute_terminal_stream } =
terminal_methods(url, state);
return {
/**
@@ -454,9 +432,18 @@ export function KomodoClient(url: string, options: InitOptions) {
* ```ts
* await komodo.execute_terminal(
* {
* server: "my-server",
* target: {
* type: "Server",
* params: {
* server: "my-server"
* }
* },
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* },
* {
* onLine: (line) => console.log(line),
@@ -479,9 +466,18 @@ export function KomodoClient(url: string, options: InitOptions) {
*
* ```ts
* const stream = await komodo.execute_terminal_stream({
* server: "my-server",
* target: {
* type: "Server",
* params: {
* server: "my-server"
* }
* },
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* });
*
* for await (const line of stream) {
@@ -490,188 +486,5 @@ export function KomodoClient(url: string, options: InitOptions) {
* ```
*/
execute_terminal_stream,
/**
* Subscribes to container exec io over websocket message,
* for use with xtermjs. Can connect to container on a Server,
* or associated with a Deployment or Stack.
* Terminal permission on connecting resource required.
*/
connect_exec,
/**
* Subscribes to container attach io over websocket message,
* for use with xtermjs. Can attach to container on a Server,
* or associated with a Deployment or Stack.
* Terminal permission on connecting resource required.
*/
connect_attach,
/**
* Subscribes to container exec io over websocket message,
* for use with xtermjs. Can connect to Container on a Server.
* Server Terminal permission required.
*/
connect_container_exec,
/**
* Subscribes to container attach io over websocket message,
* for use with xtermjs. Can attach to Container on a Server.
* Server Terminal permission required.
*/
connect_container_attach,
/**
* Executes a command on a given container,
* and gives a callback to handle the output as it comes in.
*
* ```ts
* await komodo.execute_container_exec(
* {
* server: "my-server",
* container: "name",
* shell: "bash",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_container_exec,
/**
* Executes a command on a given container,
* and returns a stream to process the output as it comes in.
*
* Note. The final line of the stream will usually be
* `__KOMODO_EXIT_CODE__:0`. The number
* is the exit code of the command.
*
* If this line is NOT present, it means the stream
* was terminated early, ie like running `exit`.
*
* ```ts
* const stream = await komodo.execute_container_exec_stream({
* server: "my-server",
* container: "name",
* shell: "bash",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* });
*
* for await (const line of stream) {
* console.log(line);
* }
* ```
*/
execute_container_exec_stream,
/**
* Subscribes to deployment container exec io over websocket message,
* for use with xtermjs. Can connect to Deployment container.
* Deployment Terminal permission required.
*/
connect_deployment_exec,
/**
* Subscribes to deployment container attach io over websocket message,
* for use with xtermjs. Can attach to Deployment container.
* Deployment Terminal permission required.
*/
connect_deployment_attach,
/**
* Executes a command on a given deployment container,
* and gives a callback to handle the output as it comes in.
*
* ```ts
* await komodo.execute_deployment_exec(
* {
* deployment: "my-deployment",
* shell: "bash",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_deployment_exec,
/**
* Executes a command on a given deployment container,
* and returns a stream to process the output as it comes in.
*
* Note. The final line of the stream will usually be
* `__KOMODO_EXIT_CODE__:0`. The number
* is the exit code of the command.
*
* If this line is NOT present, it means the stream
* was terminated early, ie like running `exit`.
*
* ```ts
* const stream = await komodo.execute_deployment_exec_stream({
* deployment: "my-deployment",
* shell: "bash",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* });
*
* for await (const line of stream) {
* console.log(line);
* }
* ```
*/
execute_deployment_exec_stream,
/**
* Subscribes to container exec io over websocket message,
* for use with xtermjs. Can connect to Stack service container.
* Stack Terminal permission required.
*/
connect_stack_exec,
/**
* Subscribes to container attach io over websocket message,
* for use with xtermjs. Can attach to Stack service container.
* Stack Terminal permission required.
*/
connect_stack_attach,
/**
* Executes a command on a given stack service container,
* and gives a callback to handle the output as it comes in.
*
* ```ts
* await komodo.execute_stack_exec(
* {
* stack: "my-stack",
* service: "database"
* shell: "bash",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_stack_exec,
/**
* Executes a command on a given stack service container,
* and returns a stream to process the output as it comes in.
*
* Note. The final line of the stream will usually be
* `__KOMODO_EXIT_CODE__:0`. The number
* is the exit code of the command.
*
* If this line is NOT present, it means the stream
* was terminated early, ie like running `exit`.
*
* ```ts
* const stream = await komodo.execute_stack_exec_stream({
* stack: "my-stack",
* service: "service1",
* shell: "bash",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* });
*
* for await (const line of stream) {
* console.log(line);
* }
* ```
*/
execute_stack_exec_stream,
};
}

View File

@@ -63,7 +63,6 @@ export type ReadResponses = {
ListServers: Types.ListServersResponse;
ListFullServers: Types.ListFullServersResponse;
ListTerminals: Types.ListTerminalsResponse;
ListAllTerminals: Types.ListAllTerminalsResponse;
// ==== DOCKER ====
GetDockerContainersSummary: Types.GetDockerContainersSummaryResponse;
@@ -217,10 +216,6 @@ export type WriteResponses = {
UpdateServer: Types.Server;
RenameServer: Types.Update;
CreateNetwork: Types.Update;
CreateTerminal: Types.NoData;
DeleteTerminal: Types.NoData;
DeleteAllTerminals: Types.NoData;
BatchDeleteAllTerminals: Types.NoData;
UpdateServerPublicKey: Types.Update;
RotateServerKeys: Types.Update;
@@ -296,6 +291,12 @@ export type WriteResponses = {
WriteSyncFileContents: Types.Update;
RefreshResourceSyncPending: Types.ResourceSync;
// ==== TERMINAL ====
CreateTerminal: Types.NoData;
DeleteTerminal: Types.NoData;
DeleteAllTerminals: Types.NoData;
BatchDeleteAllTerminals: Types.NoData;
// ==== TAG ====
CreateTag: Types.Tag;
DeleteTag: Types.Tag;

View File

@@ -1,16 +1,8 @@
import { ClientState } from "./lib";
import { ClientState, Types } from "./lib";
import {
ConnectContainerAttachQuery,
ConnectContainerExecQuery,
ConnectDeploymentAttachQuery,
ConnectDeploymentExecQuery,
ConnectStackAttachQuery,
ConnectStackExecQuery,
ConnectTerminalQuery,
ExecuteContainerExecBody,
ExecuteDeploymentExecBody,
ExecuteStackExecBody,
ExecuteTerminalBody,
TerminalTarget,
WsLoginMessage,
} from "./types";
@@ -21,48 +13,6 @@ export type TerminalCallbacks = {
on_close?: () => void;
};
export type ConnectExecQuery =
| {
type: "container";
query: ConnectContainerExecQuery;
}
| {
type: "deployment";
query: ConnectDeploymentExecQuery;
}
| {
type: "stack";
query: ConnectStackExecQuery;
};
export type ConnectAttachQuery =
| {
type: "container";
query: ConnectContainerAttachQuery;
}
| {
type: "deployment";
query: ConnectDeploymentAttachQuery;
}
| {
type: "stack";
query: ConnectStackAttachQuery;
};
export type ExecuteExecBody =
| {
type: "container";
body: ExecuteContainerExecBody;
}
| {
type: "deployment";
body: ExecuteDeploymentExecBody;
}
| {
type: "stack";
body: ExecuteStackExecBody;
};
export type ExecuteCallbacks = {
onLine?: (line: string) => void | Promise<void>;
onFinish?: (code: string) => void | Promise<void>;
@@ -70,7 +20,7 @@ export type ExecuteCallbacks = {
export const terminal_methods = (url: string, state: ClientState) => {
const connect_terminal = ({
query,
query: { target, terminal, init },
on_message,
on_login,
on_open,
@@ -78,9 +28,19 @@ export const terminal_methods = (url: string, state: ClientState) => {
}: {
query: ConnectTerminalQuery;
} & TerminalCallbacks) => {
const url_query = new URLSearchParams(
query as any as Record<string, string>
).toString();
let url_query = connect_terminal_target_query(target);
if (terminal) {
url_query += `&terminal=${terminal}`;
}
if (init?.command) {
url_query += `&init[command]=${init.command}`;
}
if (init?.recreate) {
url_query += `&init[recreate]=${init.recreate}`;
}
if (init?.mode) {
url_query += `&init[mode]=${init.mode}`;
}
const ws = new WebSocket(
url.replace("http", "ws") + "/ws/terminal?" + url_query
);
@@ -140,200 +100,6 @@ export const terminal_methods = (url: string, state: ClientState) => {
const execute_terminal_stream = (request: ExecuteTerminalBody) =>
execute_stream("/terminal/execute", request);
const connect_container_exec = ({
query,
...callbacks
}: {
query: ConnectContainerExecQuery;
} & TerminalCallbacks) =>
connect_exec({ query: { type: "container", query }, ...callbacks });
const connect_deployment_exec = ({
query,
...callbacks
}: {
query: ConnectDeploymentExecQuery;
} & TerminalCallbacks) =>
connect_exec({ query: { type: "deployment", query }, ...callbacks });
const connect_stack_exec = ({
query,
...callbacks
}: {
query: ConnectStackExecQuery;
} & TerminalCallbacks) =>
connect_exec({ query: { type: "stack", query }, ...callbacks });
const connect_exec = ({
query: { type, query },
on_message,
on_login,
on_open,
on_close,
}: {
query: ConnectExecQuery;
} & TerminalCallbacks) => {
const url_query = new URLSearchParams(
query as any as Record<string, string>
).toString();
const ws = new WebSocket(
url.replace("http", "ws") + `/ws/${type}/terminal?` + url_query
);
// Handle login on websocket open
ws.onopen = () => {
const login_msg: WsLoginMessage = state.jwt
? {
type: "Jwt",
params: {
jwt: state.jwt,
},
}
: {
type: "ApiKeys",
params: {
key: state.key!,
secret: state.secret!,
},
};
ws.send(JSON.stringify(login_msg));
on_open?.();
};
ws.onmessage = (e) => {
if (e.data == "LOGGED_IN") {
ws.binaryType = "arraybuffer";
ws.onmessage = (e) => on_message?.(e);
on_login?.();
return;
} else {
on_message?.(e);
}
};
ws.onclose = () => on_close?.();
return ws;
};
const connect_container_attach = ({
query,
...callbacks
}: {
query: ConnectContainerAttachQuery;
} & TerminalCallbacks) =>
connect_attach({ query: { type: "container", query }, ...callbacks });
const connect_deployment_attach = ({
query,
...callbacks
}: {
query: ConnectDeploymentAttachQuery;
} & TerminalCallbacks) =>
connect_attach({ query: { type: "deployment", query }, ...callbacks });
const connect_stack_attach = ({
query,
...callbacks
}: {
query: ConnectStackAttachQuery;
} & TerminalCallbacks) =>
connect_attach({ query: { type: "stack", query }, ...callbacks });
const connect_attach = ({
query: { type, query },
on_message,
on_login,
on_open,
on_close,
}: {
query: ConnectAttachQuery;
} & TerminalCallbacks) => {
const url_query = new URLSearchParams(
query as any as Record<string, string>
).toString();
const ws = new WebSocket(
url.replace("http", "ws") + `/ws/${type}/terminal/attach?` + url_query
);
// Handle login on websocket open
ws.onopen = () => {
const login_msg: WsLoginMessage = state.jwt
? {
type: "Jwt",
params: {
jwt: state.jwt,
},
}
: {
type: "ApiKeys",
params: {
key: state.key!,
secret: state.secret!,
},
};
ws.send(JSON.stringify(login_msg));
on_open?.();
};
ws.onmessage = (e) => {
if (e.data == "LOGGED_IN") {
ws.binaryType = "arraybuffer";
ws.onmessage = (e) => on_message?.(e);
on_login?.();
return;
} else {
on_message?.(e);
}
};
ws.onclose = () => on_close?.();
return ws;
};
const execute_container_exec = (
body: ExecuteContainerExecBody,
callbacks?: ExecuteCallbacks
) => execute_exec({ type: "container", body }, callbacks);
const execute_deployment_exec = (
body: ExecuteDeploymentExecBody,
callbacks?: ExecuteCallbacks
) => execute_exec({ type: "deployment", body }, callbacks);
const execute_stack_exec = (
body: ExecuteStackExecBody,
callbacks?: ExecuteCallbacks
) => execute_exec({ type: "stack", body }, callbacks);
const execute_exec = async (
request: ExecuteExecBody,
callbacks?: ExecuteCallbacks
) => {
const stream = await execute_exec_stream(request);
for await (const line of stream) {
if (line.startsWith("__KOMODO_EXIT_CODE")) {
await callbacks?.onFinish?.(line.split(":")[1]);
return;
} else {
await callbacks?.onLine?.(line);
}
}
// This is hit if no __KOMODO_EXIT_CODE is sent, ie early exit
await callbacks?.onFinish?.("Early exit without code");
};
const execute_container_exec_stream = (body: ExecuteContainerExecBody) =>
execute_exec_stream({ type: "container", body });
const execute_deployment_exec_stream = (body: ExecuteDeploymentExecBody) =>
execute_exec_stream({ type: "deployment", body });
const execute_stack_exec_stream = (body: ExecuteStackExecBody) =>
execute_exec_stream({ type: "stack", body });
const execute_exec_stream = (request: ExecuteExecBody) =>
execute_stream(`/terminal/execute/${request.type}`, request.body);
const execute_stream = (path: string, request: any) =>
new Promise<AsyncIterable<string>>(async (res, rej) => {
try {
@@ -412,19 +178,25 @@ export const terminal_methods = (url: string, state: ClientState) => {
connect_terminal,
execute_terminal,
execute_terminal_stream,
connect_exec,
connect_attach,
connect_container_exec,
connect_container_attach,
execute_container_exec,
execute_container_exec_stream,
connect_deployment_exec,
connect_deployment_attach,
execute_deployment_exec,
execute_deployment_exec_stream,
connect_stack_exec,
connect_stack_attach,
execute_stack_exec,
execute_stack_exec_stream,
};
};
const connect_terminal_target_query = (target: TerminalTarget) => {
const base = `target[type]=${target.type}&`;
switch (target.type) {
case "Server":
return base + `target[params][server]=${target.params.server}`;
case "Container":
return (
base +
`target[params][server]=${target.params.server}&target[params][container]=${target.params.container}`
);
case "Stack":
return (
base +
`target[params][stack]=${target.params.stack}&target[params][service]=${target.params.service}`
);
case "Deployment":
return base + `target[params][deployment]=${target.params.deployment}`;
}
};

View File

@@ -3554,27 +3554,6 @@ export interface ContainerListItem {
export type ListAllDockerContainersResponse = ContainerListItem[];
/**
* Info about an active terminal on a server.
* Retrieve with [ListAllTerminals][crate::api::read::server::ListAllTerminals].
*/
export interface TerminalInfoWithServer {
/** The server id. */
server_id: string;
/** The server name. */
server_name: string;
/** The name of the terminal. */
name: string;
/** The root program / args of the pty */
command: string;
/** The size of the terminal history in memory. */
stored_size_kb: number;
/** When the Terminal was created in unix milliseconds. */
created_at: I64;
}
export type ListAllTerminalsResponse = TerminalInfoWithServer[];
/** An api key used to authenticate requests via request headers. */
export interface ApiKey {
/** Unique key associated with secret */
@@ -4110,22 +4089,43 @@ export type ListSystemProcessesResponse = SystemProcess[];
export type ListTagsResponse = Tag[];
export type TerminalTarget =
| { type: "Server", params: {
server?: string;
}}
| { type: "Container", params: {
server: string;
container: string;
}}
| { type: "Stack", params: {
stack: string;
service?: string;
}}
| { type: "Deployment", params: {
deployment: string;
}};
/**
* Info about an active terminal on a server.
* Represents an active terminal on a server.
* Retrieve with [ListTerminals][crate::api::read::server::ListTerminals].
*/
export interface TerminalInfo {
export interface Terminal {
/** The name of the terminal. */
name: string;
/** The root program / args of the pty */
/** The target resource of the Terminal. */
target: TerminalTarget;
/** The command used to init the shell. */
command: string;
/** The size of the terminal history in memory. */
stored_size_kb: number;
/** When the Terminal was created. */
/**
* When the Terminal was created.
* Unix timestamp milliseconds.
*/
created_at: I64;
}
export type ListTerminalsResponse = TerminalInfo[];
export type ListTerminalsResponse = Terminal[];
export type ListUserGroupsResponse = UserGroup[];
@@ -4383,7 +4383,7 @@ export interface BatchCloneRepo {
* Response: [NoData]
*/
export interface BatchDeleteAllTerminals {
/** optional structured query to filter servers. */
/** Optional structured query to filter servers. */
query?: ServerQuery;
}
@@ -4655,7 +4655,7 @@ export interface CommitSync {
export enum TerminalRecreateMode {
/**
* Never kill the old terminal if it already exists.
* If the command is different, returns error.
* If the init command is different, returns error.
*/
Never = "Never",
/** Always kill the old terminal and create new one */
@@ -4664,109 +4664,50 @@ export enum TerminalRecreateMode {
DifferentCommand = "DifferentCommand",
}
/** Query to connect to a container attach session (interactive shell over websocket) on the given server. */
export interface ConnectContainerAttachQuery {
/** Server Id or name */
server: string;
/** The container name */
container: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
/** Specify the container terminal mode (exec or attach) */
export enum ContainerTerminalMode {
Exec = "exec",
Attach = "attach",
}
/** Query to connect to a container exec session (interactive shell over websocket) on the given server. */
export interface ConnectContainerExecQuery {
/** Server Id or name */
server: string;
/** The container name */
container: string;
/** The shell to use (eg. `sh` or `bash`) */
shell: string;
/** Args to init the Terminal if needed. */
export interface InitTerminal {
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
* The shell command (eg `bash`) to init the shell.
*
* Default:
* - Server: Configured on each Periphery
* - Container: `sh`
*/
command?: string;
/** Default: `Never` */
recreate?: TerminalRecreateMode;
/**
* Only relevant for container-type terminals.
* Specify the container terminal mode (`exec` or `attach`).
* Default: `exec`
*/
mode?: ContainerTerminalMode;
}
/**
* Query to connect to a container attach session (interactive shell over websocket) on the given Deployment.
* This call will use access to the Deployment Terminal to permission the call.
*/
export interface ConnectDeploymentAttachQuery {
/** Deployment Id or name */
deployment: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
}
/**
* Query to connect to a container exec session (interactive shell over websocket) on the given Deployment.
* This call will use access to the Deployment Terminal to permission the call.
*/
export interface ConnectDeploymentExecQuery {
/** Deployment Id or name */
deployment: string;
/** The shell to use (eg. `sh` or `bash`) */
shell: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
}
/**
* Query to connect to a container attach session (interactive shell over websocket) on the given Stack / service.
* This call will use access to the Stack Terminal to permission the call.
*/
export interface ConnectStackAttachQuery {
/** Stack Id or name */
stack: string;
/** The service name to attach to */
service: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
}
/**
* Query to connect to a container exec session (interactive shell over websocket) on the given Stack / service.
* This call will use access to the Stack Terminal to permission the call.
*/
export interface ConnectStackExecQuery {
/** Stack Id or name */
stack: string;
/** The service name to connect to */
service: string;
/** The shell to use (eg. `sh` or `bash`) */
shell: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
}
/** Query to connect to a terminal (interactive shell over websocket) on the given server. */
/** Connect to a Terminal. */
export interface ConnectTerminalQuery {
/** Server Id or name */
server: string;
/** The target to create terminal for. */
target: TerminalTarget;
/**
* Each periphery can keep multiple terminals open.
* If a terminals with the specified name does not exist,
* the call will fail.
* Create a terminal using [CreateTerminal][super::write::server::CreateTerminal]
* Terminal name to connect to.
* If it may not exist yet, also pass 'init' params
* to include initialization.
* Default: Depends on target.
*/
terminal: string;
terminal?: string;
/**
* Pass to init the terminal session
* for when the terminal doesn't already exist.
*
* Example: ?...(query)&init[command]=bash&init[recreate]=DifferentCommand
*/
init?: InitTerminal;
}
/** Blkio stats entry. This type is Linux-specific and omitted for Windows containers. */
@@ -5330,23 +5271,32 @@ export interface CreateTag {
}
/**
* Create a terminal on the server.
* Create a Terminal.
* Requires minimum Read + Terminal permission on the target Resource.
* Response: [NoData]
*/
export interface CreateTerminal {
/** Server Id or name */
server: string;
/** The name of the terminal on the server to create. */
/** A name for the Terminal session. */
name: string;
/** The target to create terminal for */
target: TerminalTarget;
/**
* The shell command (eg `bash`) to init the shell.
*
* This can also include args:
* `docker exec -it container sh`
*
* Default: Configured on each Periphery
* Default:
* - Server: Configured on each Periphery
* - ContainerExec: `sh`
* - Attach: unused
*/
command?: string;
/**
* For container terminals, choose 'exec' or 'attach'.
*
* Default
* - Server: ignored
* - Container / Stack / Deployment: `exec`
*/
mode?: ContainerTerminalMode;
/** Default: `Never` */
recreate?: TerminalRecreateMode;
}
@@ -5394,7 +5344,7 @@ export interface DeleteAlerter {
}
/**
* Delete all terminals on the server.
* Delete all Terminals on the Server.
* Response: [NoData]
*/
export interface DeleteAllTerminals {
@@ -5553,13 +5503,13 @@ export interface DeleteTag {
}
/**
* Delete a terminal on the server.
* Delete a terminal.
* Response: [NoData]
*/
export interface DeleteTerminal {
/** Server Id or name */
server: string;
/** The name of the terminal on the server to delete. */
/** Server / Container / Stack / Deployment */
target: TerminalTarget;
/** The name of the Terminal to delete. */
terminal: string;
}
@@ -5721,76 +5671,17 @@ export interface ExchangeForJwt {
token: string;
}
/** Execute a command in the given containers shell. */
export interface ExecuteContainerExecBody {
/** Server Id or name */
server: string;
/** The container name */
container: string;
/** The shell to use (eg. `sh` or `bash`) */
shell: string;
/** The command to execute. */
command: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
}
/** Execute a command in the given containers shell. */
export interface ExecuteDeploymentExecBody {
/** Deployment Id or name */
deployment: string;
/** The shell to use (eg. `sh` or `bash`) */
shell: string;
/** The command to execute. */
command: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
}
/** Execute a command in the given containers shell. */
export interface ExecuteStackExecBody {
/** Stack Id or name */
stack: string;
/** The service name to connect to */
service: string;
/** The shell to use (eg. `sh` or `bash`) */
shell: string;
/** The command to execute. */
command: string;
/**
* Specify the recreate behavior.
* Default is 'DifferentCommand'
*/
recreate?: TerminalRecreateMode;
}
/** Init a terminal on the server. */
export interface InitTerminal {
/**
* The shell command (eg `bash`) to init the shell.
*
* This can also include args:
* `docker exec -it container sh`
*
* Default: Configured on each Periphery
*/
command?: string;
/** Default: `Never` */
recreate?: TerminalRecreateMode;
}
/** Execute a terminal command on the given server. */
export interface ExecuteTerminalBody {
/** Server Id or name */
server: string;
/** The name of the terminal on the server to use to execute. */
terminal: string;
/** The target to create terminal for. */
target: TerminalTarget;
/**
* Terminal name to connect to.
* If it may not exist yet, also pass 'init' params
* to include initialization.
* Default: Depends on target.
*/
terminal?: string;
/** The command to execute. */
command: string;
/**
@@ -6747,20 +6638,8 @@ export interface ListAlertsResponse {
export interface ListAllDockerContainers {
/** Filter by server id or name. */
servers?: string[];
}
/**
* List the current terminals on specified server.
* Response: [ListAllTerminalsResponse].
*/
export interface ListAllTerminals {
/** optional structured query to filter servers. */
query?: ServerQuery;
/**
* Force a fresh call to Periphery for the list.
* Otherwise the response will be cached for 30s
*/
fresh?: boolean;
/** Filter by container name. */
containers?: string[];
}
/**
@@ -7119,17 +6998,14 @@ export interface ListTags {
}
/**
* List the current terminals on specified server.
* List Terminals.
* Response: [ListTerminalsResponse].
*/
export interface ListTerminals {
/** Id or name */
server: string;
/**
* Force a fresh call to Periphery for the list.
* Otherwise the response will be cached for 30s
*/
fresh?: boolean;
/** Filter the Terminals returned by the Target. */
target?: TerminalTarget;
/** Return results with resource names instead of ids. */
use_names?: boolean;
}
/**
@@ -7643,6 +7519,12 @@ export interface RepoExecutionResponse {
commit_message?: string;
}
/** JSON structure to send new terminal window dimensions */
export interface ResizeDimensions {
rows: number;
cols: number;
}
export interface ResourceToml<PartialConfig> {
/** The resource name. Required */
name: string;
@@ -8634,11 +8516,6 @@ export type AuthRequest =
| { type: "ExchangeForJwt", params: ExchangeForJwt }
| { type: "GetUser", params: GetUser };
export enum ContainerTerminalMode {
Exec = "exec",
Attach = "attach",
}
/** Days of the week */
export enum DayOfWeek {
Monday = "Monday",
@@ -8841,7 +8718,6 @@ export type ReadRequest =
| { type: "ListServers", params: ListServers }
| { type: "ListFullServers", params: ListFullServers }
| { type: "ListTerminals", params: ListTerminals }
| { type: "ListAllTerminals", params: ListAllTerminals }
| { type: "GetDockerContainersSummary", params: GetDockerContainersSummary }
| { type: "ListAllDockerContainers", params: ListAllDockerContainers }
| { type: "ListDockerContainers", params: ListDockerContainers }
@@ -9012,10 +8888,6 @@ export type WriteRequest =
| { type: "UpdateServer", params: UpdateServer }
| { type: "RenameServer", params: RenameServer }
| { type: "CreateNetwork", params: CreateNetwork }
| { type: "CreateTerminal", params: CreateTerminal }
| { type: "DeleteTerminal", params: DeleteTerminal }
| { type: "DeleteAllTerminals", params: DeleteAllTerminals }
| { type: "BatchDeleteAllTerminals", params: BatchDeleteAllTerminals }
| { type: "UpdateServerPublicKey", params: UpdateServerPublicKey }
| { type: "RotateServerKeys", params: RotateServerKeys }
| { type: "CreateStack", params: CreateStack }
@@ -9072,6 +8944,10 @@ export type WriteRequest =
| { type: "WriteSyncFileContents", params: WriteSyncFileContents }
| { type: "CommitSync", params: CommitSync }
| { type: "RefreshResourceSyncPending", params: RefreshResourceSyncPending }
| { type: "CreateTerminal", params: CreateTerminal }
| { type: "DeleteTerminal", params: DeleteTerminal }
| { type: "DeleteAllTerminals", params: DeleteAllTerminals }
| { type: "BatchDeleteAllTerminals", params: BatchDeleteAllTerminals }
| { type: "CreateTag", params: CreateTag }
| { type: "DeleteTag", params: DeleteTag }
| { type: "RenameTag", params: RenameTag }

View File

@@ -1,6 +1,6 @@
use komodo_client::{
api::write::TerminalRecreateMode,
entities::{NoData, server::TerminalInfo},
use komodo_client::entities::{
NoData,
terminal::{Terminal, TerminalRecreateMode, TerminalTarget},
};
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
@@ -11,19 +11,18 @@ pub const START_OF_OUTPUT: &str = "__KOMODO_START_OF_OUTPUT__";
pub const END_OF_OUTPUT: &str = "__KOMODO_END_OF_OUTPUT__";
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(Vec<TerminalInfo>)]
#[response(Vec<Terminal>)]
#[error(anyhow::Error)]
pub struct ListTerminals {
/// If none, only includes non-container terminals.
/// if Some, only includes that containers terminals.
pub container: Option<String>,
/// Optionally restrict list to specific target.
pub target: Option<TerminalTarget>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(NoData)]
#[error(anyhow::Error)]
pub struct CreateTerminal {
/// The name of the terminal to create
pub struct CreateServerTerminal {
/// A name for the terminal session
pub name: String,
/// The shell command (eg `bash`) to init the shell.
///
@@ -32,6 +31,46 @@ pub struct CreateTerminal {
///
/// Default: Set in Periphery config.
pub command: Option<String>,
/// Specify the recreate behavior.
/// Default: `Never`
#[serde(default)]
pub recreate: TerminalRecreateMode,
}
//
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(NoData)]
#[error(anyhow::Error)]
pub struct CreateContainerExecTerminal {
/// A name for the terminal session
pub name: String,
/// The target for the terminal sessions (Container, Stack, Deployment).
pub target: TerminalTarget,
/// The name of the container to connect to
pub container: String,
/// The command to init shell inside container.
/// Default: `sh`
pub command: Option<String>,
/// Specify the recreate behavior.
/// Default: `Never`
#[serde(default)]
pub recreate: TerminalRecreateMode,
}
//
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(NoData)]
#[error(anyhow::Error)]
pub struct CreateContainerAttachTerminal {
/// A name for the terminal session
pub name: String,
/// The target for the terminal sessions (Container, Stack, Deployment).
pub target: TerminalTarget,
/// The name of the container to attach to
pub container: String,
/// Specify the recreate behavior.
/// Default: `Never`
#[serde(default)]
pub recreate: TerminalRecreateMode,
@@ -45,38 +84,8 @@ pub struct CreateTerminal {
pub struct ConnectTerminal {
/// The name of the terminal to connect to
pub terminal: String,
}
//
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(Uuid)]
#[error(anyhow::Error)]
pub struct ConnectContainerExec {
/// The name of the container to connect to.
pub container: String,
/// The shell to start inside container.
/// Default: `sh`
#[serde(default = "default_container_shell")]
pub shell: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
#[serde(default = "default_container_recreate_mode")]
pub recreate: TerminalRecreateMode,
}
//
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(Uuid)]
#[error(anyhow::Error)]
pub struct ConnectContainerAttach {
/// The name of the container to attach to.
pub container: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
#[serde(default = "default_container_recreate_mode")]
pub recreate: TerminalRecreateMode,
/// The target for the terminal session
pub target: TerminalTarget,
}
//
@@ -96,8 +105,10 @@ pub struct DisconnectTerminal {
#[response(NoData)]
#[error(anyhow::Error)]
pub struct DeleteTerminal {
/// The name of the terminal to delete
/// The name of the terminal to delete.
pub terminal: String,
/// The terminal target.
pub target: TerminalTarget,
}
//
@@ -116,34 +127,8 @@ pub struct DeleteAllTerminals {}
pub struct ExecuteTerminal {
/// Specify the terminal to execute the command on.
pub terminal: String,
/// The terminal target.
pub target: TerminalTarget,
/// The command to execute.
pub command: String,
}
//
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(Uuid)]
#[error(anyhow::Error)]
pub struct ExecuteContainerExec {
/// The name of the container to execute command in.
pub container: String,
/// The shell to start inside container.
/// Default: `sh`
#[serde(default = "default_container_shell")]
pub shell: String,
/// The command to execute.
pub command: String,
/// Specify the recreate behavior.
/// Default is 'DifferentCommand'
#[serde(default = "default_container_recreate_mode")]
pub recreate: TerminalRecreateMode,
}
fn default_container_shell() -> String {
String::from("sh")
}
fn default_container_recreate_mode() -> TerminalRecreateMode {
TerminalRecreateMode::DifferentCommand
}