feat: kube protocol support (#267)

This commit is contained in:
Christian Visintin
2024-07-17 11:59:30 +02:00
committed by GitHub
parent cf529c1678
commit f757336d75
50 changed files with 1863 additions and 418 deletions

View File

@@ -2,6 +2,10 @@
//!
//! `bookmarks` is the module which provides data types and de/serializer for bookmarks
mod aws_s3;
mod kube;
mod smb;
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
@@ -9,9 +13,12 @@ use std::str::FromStr;
use serde::de::Error as DeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use self::aws_s3::S3Params;
pub use self::kube::KubeParams;
pub use self::smb::SmbParams;
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams as TransferSmbParams,
WebDAVProtocolParams,
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams,
SmbParams as TransferSmbParams, WebDAVProtocolParams,
};
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
@@ -44,32 +51,14 @@ pub struct Bookmark {
pub remote_path: Option<PathBuf>,
/// local folder to open at startup
pub local_path: Option<PathBuf>,
/// Kube params; optional. When used other fields are empty for sure
pub kube: Option<KubeParams>,
/// S3 params; optional. When used other fields are empty for sure
pub s3: Option<S3Params>,
/// SMB params; optional. Extra params required for SMB protocol
pub smb: Option<SmbParams>,
}
/// Connection parameters for Aws s3 protocol
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
pub struct S3Params {
pub bucket: String,
pub region: Option<String>,
pub endpoint: Option<String>,
pub profile: Option<String>,
pub access_key: Option<String>,
pub secret_access_key: Option<String>,
/// NOTE: there are no session token and security token since they are always temporary
pub new_path_style: Option<bool>,
}
/// Extra Connection parameters for SMB protocol
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
pub struct SmbParams {
pub share: String,
pub workgroup: Option<String>,
}
// -- impls
impl From<FileTransferParams> for Bookmark {
@@ -87,6 +76,7 @@ impl From<FileTransferParams> for Bookmark {
password: params.password,
remote_path,
local_path,
kube: None,
s3: None,
smb: None,
},
@@ -98,9 +88,22 @@ impl From<FileTransferParams> for Bookmark {
password: None,
remote_path,
local_path,
kube: None,
s3: Some(S3Params::from(params)),
smb: None,
},
ProtocolParams::Kube(params) => Self {
protocol,
address: None,
port: None,
username: None,
password: None,
remote_path,
local_path,
kube: Some(KubeParams::from(params)),
s3: None,
smb: None,
},
ProtocolParams::Smb(params) => Self {
smb: Some(SmbParams::from(params.clone())),
protocol,
@@ -113,6 +116,7 @@ impl From<FileTransferParams> for Bookmark {
password: params.password,
remote_path,
local_path,
kube: None,
s3: None,
},
ProtocolParams::WebDAV(parms) => Self {
@@ -123,6 +127,7 @@ impl From<FileTransferParams> for Bookmark {
password: Some(parms.password),
remote_path,
local_path,
kube: None,
s3: None,
smb: None,
},
@@ -149,6 +154,11 @@ impl From<Bookmark> for FileTransferParams {
.password(bookmark.password);
Self::new(bookmark.protocol, ProtocolParams::Generic(params))
}
FileTransferProtocol::Kube => {
let params = bookmark.kube.unwrap_or_default();
let params = KubeProtocolParams::from(params);
Self::new(bookmark.protocol, ProtocolParams::Kube(params))
}
#[cfg(unix)]
FileTransferProtocol::Smb => {
let params = TransferSmbParams::new(
@@ -187,50 +197,6 @@ impl From<Bookmark> for FileTransferParams {
}
}
impl From<AwsS3Params> for S3Params {
fn from(params: AwsS3Params) -> Self {
S3Params {
bucket: params.bucket_name,
region: params.region,
endpoint: params.endpoint,
profile: params.profile,
access_key: params.access_key,
secret_access_key: params.secret_access_key,
new_path_style: Some(params.new_path_style),
}
}
}
impl From<S3Params> for AwsS3Params {
fn from(params: S3Params) -> Self {
AwsS3Params::new(params.bucket, params.region, params.profile)
.endpoint(params.endpoint)
.access_key(params.access_key)
.secret_access_key(params.secret_access_key)
.new_path_style(params.new_path_style.unwrap_or(false))
}
}
#[cfg(unix)]
impl From<TransferSmbParams> for SmbParams {
fn from(params: TransferSmbParams) -> Self {
Self {
share: params.share,
workgroup: params.workgroup,
}
}
}
#[cfg(windows)]
impl From<TransferSmbParams> for SmbParams {
fn from(params: TransferSmbParams) -> Self {
Self {
share: params.share,
workgroup: None,
}
}
}
fn deserialize_protocol<'de, D>(deserializer: D) -> Result<FileTransferProtocol, D::Error>
where
D: Deserializer<'de>,
@@ -276,6 +242,7 @@ mod tests {
password: Some(String::from("password")),
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
kube: None,
s3: None,
smb: None,
};
@@ -287,6 +254,7 @@ mod tests {
password: Some(String::from("password")),
remote_path: Some(PathBuf::from("/home")),
local_path: Some(PathBuf::from("/usr")),
kube: None,
s3: None,
smb: None,
};
@@ -380,6 +348,38 @@ mod tests {
assert_eq!(s3.secret_access_key.as_deref().unwrap(), "pluto");
}
#[test]
fn bookmark_from_kube_ftparams() {
let params = ProtocolParams::Kube(KubeProtocolParams {
pod: "pod".to_string(),
container: "container".to_string(),
namespace: Some("default".to_string()),
username: Some("root".to_string()),
cluster_url: Some("https://localhost:6443".to_string()),
client_cert: Some("cert".to_string()),
client_key: Some("key".to_string()),
});
let params: FileTransferParams =
FileTransferParams::new(FileTransferProtocol::Kube, params);
let bookmark = Bookmark::from(params);
assert_eq!(bookmark.protocol, FileTransferProtocol::Kube);
assert!(bookmark.address.is_none());
assert!(bookmark.port.is_none());
assert!(bookmark.username.is_none());
assert!(bookmark.password.is_none());
let kube: &KubeParams = bookmark.kube.as_ref().unwrap();
assert_eq!(kube.pod_name.as_str(), "pod");
assert_eq!(kube.container.as_str(), "container");
assert_eq!(kube.namespace.as_deref().unwrap(), "default");
assert_eq!(
kube.cluster_url.as_deref().unwrap(),
"https://localhost:6443"
);
assert_eq!(kube.username.as_deref().unwrap(), "root");
assert_eq!(kube.client_cert.as_deref().unwrap(), "cert");
assert_eq!(kube.client_key.as_deref().unwrap(), "key");
}
#[test]
fn ftparams_from_generic_bookmark() {
let bookmark: Bookmark = Bookmark {
@@ -390,6 +390,7 @@ mod tests {
password: Some(String::from("password")),
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
kube: None,
s3: None,
smb: None,
};
@@ -420,6 +421,7 @@ mod tests {
password: Some(String::from("password")),
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
kube: None,
s3: None,
smb: None,
};
@@ -449,6 +451,7 @@ mod tests {
password: None,
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
kube: None,
s3: Some(S3Params {
bucket: String::from("veeso"),
region: Some(String::from("eu-west-1")),
@@ -480,6 +483,50 @@ mod tests {
assert_eq!(gparams.new_path_style, true);
}
#[test]
fn ftparams_from_kube_bookmark() {
let bookmark: Bookmark = Bookmark {
protocol: FileTransferProtocol::Kube,
address: None,
port: None,
username: None,
password: None,
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
kube: Some(KubeParams {
pod_name: String::from("pod"),
container: String::from("container"),
namespace: Some(String::from("default")),
cluster_url: Some(String::from("https://localhost:6443")),
username: Some(String::from("root")),
client_cert: Some(String::from("cert")),
client_key: Some(String::from("key")),
}),
s3: None,
smb: None,
};
let params = FileTransferParams::from(bookmark);
assert_eq!(params.protocol, FileTransferProtocol::Kube);
assert_eq!(
params.remote_path.as_deref().unwrap(),
std::path::Path::new("/tmp")
);
assert_eq!(
params.local_path.as_deref().unwrap(),
std::path::Path::new("/usr")
);
let gparams = params.params.kube_params().unwrap();
assert_eq!(gparams.pod.as_str(), "pod");
assert_eq!(gparams.namespace.as_deref().unwrap(), "default");
assert_eq!(
gparams.cluster_url.as_deref().unwrap(),
"https://localhost:6443"
);
assert_eq!(gparams.username.as_deref().unwrap(), "root");
assert_eq!(gparams.client_cert.as_deref().unwrap(), "cert");
assert_eq!(gparams.client_key.as_deref().unwrap(), "key");
}
#[test]
#[cfg(unix)]
fn should_get_ftparams_from_smb_bookmark() {
@@ -491,6 +538,7 @@ mod tests {
password: Some("bar".to_string()),
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
kube: None,
s3: None,
smb: Some(SmbParams {
share: "test".to_string(),
@@ -529,6 +577,7 @@ mod tests {
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
s3: None,
kube: None,
smb: Some(SmbParams {
share: "test".to_string(),
workgroup: None,

View File

@@ -0,0 +1,40 @@
use serde::{Deserialize, Serialize};
use crate::filetransfer::params::AwsS3Params;
/// Connection parameters for Aws s3 protocol
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
pub struct S3Params {
pub bucket: String,
pub region: Option<String>,
pub endpoint: Option<String>,
pub profile: Option<String>,
pub access_key: Option<String>,
pub secret_access_key: Option<String>,
/// NOTE: there are no session token and security token since they are always temporary
pub new_path_style: Option<bool>,
}
impl From<AwsS3Params> for S3Params {
fn from(params: AwsS3Params) -> Self {
S3Params {
bucket: params.bucket_name,
region: params.region,
endpoint: params.endpoint,
profile: params.profile,
access_key: params.access_key,
secret_access_key: params.secret_access_key,
new_path_style: Some(params.new_path_style),
}
}
}
impl From<S3Params> for AwsS3Params {
fn from(params: S3Params) -> Self {
AwsS3Params::new(params.bucket, params.region, params.profile)
.endpoint(params.endpoint)
.access_key(params.access_key)
.secret_access_key(params.secret_access_key)
.new_path_style(params.new_path_style.unwrap_or(false))
}
}

View File

@@ -0,0 +1,43 @@
use serde::{Deserialize, Serialize};
use crate::filetransfer::params::KubeProtocolParams;
/// Extra Connection parameters for Kube protocol
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
pub struct KubeParams {
pub pod_name: String,
pub container: String,
pub namespace: Option<String>,
pub cluster_url: Option<String>,
pub username: Option<String>,
pub client_cert: Option<String>,
pub client_key: Option<String>,
}
impl From<KubeParams> for KubeProtocolParams {
fn from(value: KubeParams) -> Self {
Self {
pod: value.pod_name,
container: value.container,
namespace: value.namespace,
cluster_url: value.cluster_url,
username: value.username,
client_cert: value.client_cert,
client_key: value.client_key,
}
}
}
impl From<KubeProtocolParams> for KubeParams {
fn from(value: KubeProtocolParams) -> Self {
Self {
pod_name: value.pod,
container: value.container,
namespace: value.namespace,
cluster_url: value.cluster_url,
username: value.username,
client_cert: value.client_cert,
client_key: value.client_key,
}
}
}

View File

@@ -0,0 +1,30 @@
use serde::{Deserialize, Serialize};
use crate::filetransfer::params::SmbParams as TransferSmbParams;
/// Extra Connection parameters for SMB protocol
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
pub struct SmbParams {
pub share: String,
pub workgroup: Option<String>,
}
#[cfg(unix)]
impl From<TransferSmbParams> for SmbParams {
fn from(params: TransferSmbParams) -> Self {
Self {
share: params.share,
workgroup: params.workgroup,
}
}
}
#[cfg(windows)]
impl From<TransferSmbParams> for SmbParams {
fn from(params: TransferSmbParams) -> Self {
Self {
share: params.share,
workgroup: None,
}
}
}

View File

@@ -115,7 +115,7 @@ mod tests {
use tuirealm::tui::style::Color;
use super::*;
use crate::config::bookmarks::{Bookmark, S3Params, SmbParams, UserHosts};
use crate::config::bookmarks::{Bookmark, KubeParams, S3Params, SmbParams, UserHosts};
use crate::config::params::UserConfig;
use crate::config::themes::Theme;
use crate::filetransfer::FileTransferProtocol;
@@ -366,7 +366,7 @@ mod tests {
assert_eq!(host.username.as_deref().unwrap(), "root");
assert_eq!(host.password, None);
// Verify bookmarks
assert_eq!(hosts.bookmarks.len(), 5);
assert_eq!(hosts.bookmarks.len(), 6);
let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap();
assert_eq!(host.address.as_deref().unwrap(), "192.168.1.31");
assert_eq!(host.port.unwrap(), 22);
@@ -404,6 +404,21 @@ mod tests {
assert_eq!(s3.access_key.as_deref().unwrap(), "pippo");
assert_eq!(s3.secret_access_key.as_deref().unwrap(), "pluto");
assert_eq!(s3.new_path_style.unwrap(), true);
// Kube pod
let host: &Bookmark = hosts.bookmarks.get("pod").unwrap();
assert_eq!(host.address, None);
assert_eq!(host.port, None);
assert_eq!(host.username, None);
assert_eq!(host.password, None);
assert_eq!(host.protocol, FileTransferProtocol::Kube);
let kube = host.kube.as_ref().unwrap();
assert_eq!(kube.pod_name.as_str(), "my-pod");
assert_eq!(kube.container.as_str(), "my-container");
assert_eq!(kube.namespace.as_deref().unwrap(), "my-namespace");
assert_eq!(kube.cluster_url.as_deref().unwrap(), "https://my-cluster");
assert_eq!(kube.username.as_deref().unwrap(), "my-username");
assert_eq!(kube.client_cert.as_deref().unwrap(), "my-cert");
assert_eq!(kube.client_key.as_deref().unwrap(), "my-key");
// smb
let host = hosts.bookmarks.get("smb").unwrap();
@@ -443,6 +458,7 @@ mod tests {
password: None,
remote_path: None,
local_path: None,
kube: None,
s3: None,
smb: None,
},
@@ -457,6 +473,7 @@ mod tests {
password: Some(String::from("password")),
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
kube: None,
s3: None,
smb: None,
},
@@ -480,9 +497,35 @@ mod tests {
secret_access_key: None,
new_path_style: None,
}),
kube: None,
smb: None,
},
);
// push kube pod
bookmarks.insert(
String::from("pod"),
Bookmark {
address: None,
port: None,
protocol: FileTransferProtocol::Kube,
username: None,
password: None,
remote_path: None,
local_path: None,
s3: None,
smb: None,
kube: Some(KubeParams {
pod_name: "my-pod".to_string(),
container: "my-container".to_string(),
namespace: Some("my-namespace".to_string()),
cluster_url: Some("https://my-cluster".to_string()),
username: Some("my-username".to_string()),
client_cert: Some("my-cert".to_string()),
client_key: Some("my-key".to_string()),
}),
},
);
let smb_params: Option<SmbParams> = Some(SmbParams {
share: "test".to_string(),
workgroup: None,
@@ -498,6 +541,7 @@ mod tests {
remote_path: None,
local_path: None,
s3: None,
kube: None,
smb: smb_params,
},
);
@@ -513,6 +557,7 @@ mod tests {
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
s3: None,
kube: None,
smb: None,
},
);
@@ -569,6 +614,17 @@ mod tests {
secret_access_key = "pluto"
new_path_style = true
[bookmarks.pod]
protocol = "KUBE"
[bookmarks.pod.kube]
pod_name = "my-pod"
container = "my-container"
namespace = "my-namespace"
cluster_url = "https://my-cluster"
username = "my-username"
client_cert = "my-cert"
client_key = "my-key"
[bookmarks.smb]
protocol = "SMB"
address = "localhost"

View File

@@ -3,10 +3,12 @@
//! Remotefs client builder
use std::path::PathBuf;
use std::sync::Arc;
use remotefs::RemoteFs;
use remotefs_aws_s3::AwsS3Fs;
use remotefs_ftp::FtpFs;
use remotefs_kube::KubeFs;
#[cfg(smb_unix)]
use remotefs_smb::SmbOptions;
#[cfg(smb)]
@@ -14,11 +16,11 @@ use remotefs_smb::{SmbCredentials, SmbFs};
use remotefs_ssh::{ScpFs, SftpFs, SshAgentIdentity, SshConfigParseRule, SshOpts};
use remotefs_webdav::WebDAVFs;
use super::params::WebDAVProtocolParams;
#[cfg(not(smb))]
use super::params::{AwsS3Params, GenericProtocolParams};
#[cfg(smb)]
use super::params::{AwsS3Params, GenericProtocolParams, SmbParams};
use super::params::{KubeProtocolParams, WebDAVProtocolParams};
use super::{FileTransferProtocol, ProtocolParams};
use crate::system::config_client::ConfigClient;
use crate::system::sshkey_storage::SshKeyStorage;
@@ -43,6 +45,9 @@ impl Builder {
(FileTransferProtocol::Ftp(secure), ProtocolParams::Generic(params)) => {
Box::new(Self::ftp_client(params, secure))
}
(FileTransferProtocol::Kube, ProtocolParams::Kube(params)) => {
Box::new(Self::kube_client(params))
}
(FileTransferProtocol::Scp, ProtocolParams::Generic(params)) => {
Box::new(Self::scp_client(params, config_client))
}
@@ -105,6 +110,23 @@ impl Builder {
client
}
/// Build kube client
fn kube_client(params: KubeProtocolParams) -> KubeFs {
let rt = Arc::new(
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.expect("Unable to create tokio runtime"),
);
let kube_fs = KubeFs::new(&params.pod, &params.container, &rt);
if let Some(config) = params.config() {
kube_fs.config(config)
} else {
kube_fs
}
}
/// Build scp client
fn scp_client(params: GenericProtocolParams, config_client: &ConfigClient) -> ScpFs {
Self::build_ssh_opts(params, config_client).into()
@@ -256,6 +278,21 @@ mod test {
let _ = Builder::build(FileTransferProtocol::Ftp(true), params, &config_client);
}
#[test]
fn test_should_build_kube_fs() {
let params = ProtocolParams::Kube(KubeProtocolParams {
pod: "pod".to_string(),
container: "container".to_string(),
namespace: Some("namespace".to_string()),
cluster_url: Some("cluster_url".to_string()),
username: Some("username".to_string()),
client_cert: Some("client_cert".to_string()),
client_key: Some("client_key".to_string()),
});
let config_client = get_config_client();
let _ = Builder::build(FileTransferProtocol::Kube, params, &config_client);
}
#[test]
fn should_build_scp_fs() {
let params = ProtocolParams::Generic(

View File

@@ -15,6 +15,7 @@ pub use params::{FileTransferParams, ProtocolParams};
pub enum FileTransferProtocol {
AwsS3,
Ftp(bool), // Bool is for secure (true => ftps)
Kube,
Scp,
Sftp,
Smb,
@@ -34,6 +35,7 @@ impl std::fmt::Display for FileTransferProtocol {
true => "FTPS",
false => "FTP",
},
FileTransferProtocol::Kube => "KUBE",
FileTransferProtocol::Scp => "SCP",
FileTransferProtocol::Sftp => "SFTP",
FileTransferProtocol::Smb => "SMB",
@@ -49,6 +51,7 @@ impl std::str::FromStr for FileTransferProtocol {
match s.to_ascii_uppercase().as_str() {
"FTP" => Ok(FileTransferProtocol::Ftp(false)),
"FTPS" => Ok(FileTransferProtocol::Ftp(true)),
"KUBE" => Ok(FileTransferProtocol::Kube),
"S3" => Ok(FileTransferProtocol::AwsS3),
"SCP" => Ok(FileTransferProtocol::Scp),
"SFTP" => Ok(FileTransferProtocol::Sftp),
@@ -114,6 +117,14 @@ mod tests {
FileTransferProtocol::from_str("scp").ok().unwrap(),
FileTransferProtocol::Scp
);
assert_eq!(
FileTransferProtocol::from_str("kube").ok().unwrap(),
FileTransferProtocol::Kube
);
assert_eq!(
FileTransferProtocol::from_str("KUBE").ok().unwrap(),
FileTransferProtocol::Kube
);
assert_eq!(
FileTransferProtocol::from_str("SMB").ok().unwrap(),
FileTransferProtocol::Smb
@@ -153,5 +164,6 @@ mod tests {
FileTransferProtocol::WebDAV.to_string(),
String::from("WEBDAV")
);
assert_eq!(FileTransferProtocol::Kube.to_string(), String::from("KUBE"));
}
}

View File

@@ -2,8 +2,17 @@
//!
//! file transfer parameters
mod aws_s3;
mod kube;
mod smb;
mod webdav;
use std::path::{Path, PathBuf};
pub use self::aws_s3::AwsS3Params;
pub use self::kube::KubeProtocolParams;
pub use self::smb::SmbParams;
pub use self::webdav::WebDAVProtocolParams;
use super::FileTransferProtocol;
/// Holds connection parameters for file transfers
@@ -20,6 +29,7 @@ pub struct FileTransferParams {
pub enum ProtocolParams {
Generic(GenericProtocolParams),
AwsS3(AwsS3Params),
Kube(KubeProtocolParams),
Smb(SmbParams),
WebDAV(WebDAVProtocolParams),
}
@@ -33,33 +43,6 @@ pub struct GenericProtocolParams {
pub password: Option<String>,
}
/// Connection parameters for AWS S3 protocol
#[derive(Debug, Clone)]
pub struct AwsS3Params {
pub bucket_name: String,
pub region: Option<String>,
pub endpoint: Option<String>,
pub profile: Option<String>,
pub access_key: Option<String>,
pub secret_access_key: Option<String>,
pub security_token: Option<String>,
pub session_token: Option<String>,
pub new_path_style: bool,
}
/// Connection parameters for SMB protocol
#[derive(Debug, Clone)]
pub struct SmbParams {
pub address: String,
#[cfg(unix)]
pub port: u16,
pub share: String,
pub username: Option<String>,
pub password: Option<String>,
#[cfg(unix)]
pub workgroup: Option<String>,
}
impl FileTransferParams {
/// Instantiates a new `FileTransferParams`
pub fn new(protocol: FileTransferProtocol, params: ProtocolParams) -> Self {
@@ -89,6 +72,7 @@ impl FileTransferParams {
match &self.params {
ProtocolParams::AwsS3(params) => params.password_missing(),
ProtocolParams::Generic(params) => params.password_missing(),
ProtocolParams::Kube(params) => params.password_missing(),
ProtocolParams::Smb(params) => params.password_missing(),
ProtocolParams::WebDAV(params) => params.password_missing(),
}
@@ -99,30 +83,13 @@ impl FileTransferParams {
match &mut self.params {
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
ProtocolParams::Generic(params) => params.set_default_secret(secret),
ProtocolParams::Kube(params) => params.set_default_secret(secret),
ProtocolParams::Smb(params) => params.set_default_secret(secret),
ProtocolParams::WebDAV(params) => params.set_default_secret(secret),
}
}
}
/// Protocol params used by WebDAV
#[derive(Debug, Clone)]
pub struct WebDAVProtocolParams {
pub uri: String,
pub username: String,
pub password: String,
}
impl WebDAVProtocolParams {
fn set_default_secret(&mut self, secret: String) {
self.password = secret;
}
fn password_missing(&self) -> bool {
self.password.is_empty()
}
}
impl Default for FileTransferParams {
fn default() -> Self {
Self::new(FileTransferProtocol::Sftp, ProtocolParams::default())
@@ -162,6 +129,15 @@ impl ProtocolParams {
}
}
#[cfg(test)]
/// Retrieve Kube params parameters if any
pub fn kube_params(&self) -> Option<&KubeProtocolParams> {
match self {
ProtocolParams::Kube(params) => Some(params),
_ => None,
}
}
#[cfg(test)]
/// Retrieve SMB parameters if any
pub fn smb_params(&self) -> Option<&SmbParams> {
@@ -231,127 +207,6 @@ impl GenericProtocolParams {
}
}
// -- S3 params
impl AwsS3Params {
/// Instantiates a new `AwsS3Params` struct
pub fn new<S: AsRef<str>>(bucket: S, region: Option<S>, profile: Option<S>) -> Self {
Self {
bucket_name: bucket.as_ref().to_string(),
region: region.map(|x| x.as_ref().to_string()),
profile: profile.map(|x| x.as_ref().to_string()),
endpoint: None,
access_key: None,
secret_access_key: None,
security_token: None,
session_token: None,
new_path_style: false,
}
}
/// Construct aws s3 params with specified endpoint
pub fn endpoint<S: AsRef<str>>(mut self, endpoint: Option<S>) -> Self {
self.endpoint = endpoint.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided access key
pub fn access_key<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.access_key = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided secret_access_key
pub fn secret_access_key<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.secret_access_key = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided security_token
pub fn security_token<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.security_token = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided session_token
pub fn session_token<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.session_token = key.map(|x| x.as_ref().to_string());
self
}
/// Specify new path style when constructing aws s3 params
pub fn new_path_style(mut self, new_path_style: bool) -> Self {
self.new_path_style = new_path_style;
self
}
/// Returns whether a password is supposed to be required for this protocol params.
/// The result true is returned ONLY if the supposed secret is MISSING!!!
pub fn password_missing(&self) -> bool {
self.secret_access_key.is_none() && self.security_token.is_none()
}
/// Set password
pub fn set_default_secret(&mut self, secret: String) {
self.secret_access_key = Some(secret);
}
}
// -- SMB params
impl SmbParams {
/// Instantiates a new `AwsS3Params` struct
pub fn new<S: AsRef<str>>(address: S, share: S) -> Self {
Self {
address: address.as_ref().to_string(),
#[cfg(unix)]
port: 445,
share: share.as_ref().to_string(),
username: None,
password: None,
#[cfg(unix)]
workgroup: None,
}
}
#[cfg(unix)]
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn username(mut self, username: Option<impl ToString>) -> Self {
self.username = username.map(|x| x.to_string());
self
}
pub fn password(mut self, password: Option<impl ToString>) -> Self {
self.password = password.map(|x| x.to_string());
self
}
#[cfg(unix)]
pub fn workgroup(mut self, workgroup: Option<impl ToString>) -> Self {
self.workgroup = workgroup.map(|x| x.to_string());
self
}
/// Returns whether a password is supposed to be required for this protocol params.
/// The result true is returned ONLY if the supposed secret is MISSING!!!
pub fn password_missing(&self) -> bool {
self.password.is_none()
}
/// Set password
#[cfg(unix)]
pub fn set_default_secret(&mut self, secret: String) {
self.password = Some(secret);
}
#[cfg(windows)]
pub fn set_default_secret(&mut self, _secret: String) {}
}
#[cfg(test)]
mod test {
@@ -386,87 +241,6 @@ mod test {
assert!(params.password.is_none());
}
#[test]
fn should_init_aws_s3_params() {
let params: AwsS3Params = AwsS3Params::new("omar", Some("eu-west-1"), Some("test"));
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_deref().unwrap(), "eu-west-1");
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert!(params.endpoint.is_none());
assert!(params.access_key.is_none());
assert!(params.secret_access_key.is_none());
assert!(params.security_token.is_none());
assert!(params.session_token.is_none());
assert_eq!(params.new_path_style, false);
}
#[test]
fn should_init_aws_s3_params_with_optionals() {
let params: AwsS3Params = AwsS3Params::new("omar", Some("eu-west-1"), Some("test"))
.endpoint(Some("http://omar.it"))
.access_key(Some("pippo"))
.secret_access_key(Some("pluto"))
.security_token(Some("omar"))
.session_token(Some("gerry-scotti"))
.new_path_style(true);
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_deref().unwrap(), "eu-west-1");
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert_eq!(params.endpoint.as_deref().unwrap(), "http://omar.it");
assert_eq!(params.access_key.as_deref().unwrap(), "pippo");
assert_eq!(params.secret_access_key.as_deref().unwrap(), "pluto");
assert_eq!(params.security_token.as_deref().unwrap(), "omar");
assert_eq!(params.session_token.as_deref().unwrap(), "gerry-scotti");
assert_eq!(params.new_path_style, true);
}
#[test]
fn should_init_smb_params() {
let params = SmbParams::new("localhost", "temp");
assert_eq!(&params.address, "localhost");
#[cfg(unix)]
assert_eq!(params.port, 445);
assert_eq!(&params.share, "temp");
#[cfg(unix)]
assert!(params.username.is_none());
#[cfg(unix)]
assert!(params.password.is_none());
#[cfg(unix)]
assert!(params.workgroup.is_none());
}
#[test]
#[cfg(unix)]
fn should_init_smb_params_with_optionals() {
let params = SmbParams::new("localhost", "temp")
.port(3456)
.username(Some("foo"))
.password(Some("bar"))
.workgroup(Some("baz"));
assert_eq!(&params.address, "localhost");
assert_eq!(params.port, 3456);
assert_eq!(&params.share, "temp");
assert_eq!(params.username.as_deref().unwrap(), "foo");
assert_eq!(params.password.as_deref().unwrap(), "bar");
assert_eq!(params.workgroup.as_deref().unwrap(), "baz");
}
#[test]
#[cfg(windows)]
fn should_init_smb_params_with_optionals() {
let params = SmbParams::new("localhost", "temp")
.username(Some("foo"))
.password(Some("bar"));
assert_eq!(&params.address, "localhost");
assert_eq!(&params.share, "temp");
assert_eq!(params.username.as_deref().unwrap(), "foo");
assert_eq!(params.password.as_deref().unwrap(), "bar");
}
#[test]
fn references() {
let mut params =

View File

@@ -0,0 +1,121 @@
/// Connection parameters for AWS S3 protocol
#[derive(Debug, Clone)]
pub struct AwsS3Params {
pub bucket_name: String,
pub region: Option<String>,
pub endpoint: Option<String>,
pub profile: Option<String>,
pub access_key: Option<String>,
pub secret_access_key: Option<String>,
pub security_token: Option<String>,
pub session_token: Option<String>,
pub new_path_style: bool,
}
// -- S3 params
impl AwsS3Params {
/// Instantiates a new `AwsS3Params` struct
pub fn new<S: AsRef<str>>(bucket: S, region: Option<S>, profile: Option<S>) -> Self {
Self {
bucket_name: bucket.as_ref().to_string(),
region: region.map(|x| x.as_ref().to_string()),
profile: profile.map(|x| x.as_ref().to_string()),
endpoint: None,
access_key: None,
secret_access_key: None,
security_token: None,
session_token: None,
new_path_style: false,
}
}
/// Construct aws s3 params with specified endpoint
pub fn endpoint<S: AsRef<str>>(mut self, endpoint: Option<S>) -> Self {
self.endpoint = endpoint.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided access key
pub fn access_key<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.access_key = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided secret_access_key
pub fn secret_access_key<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.secret_access_key = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided security_token
pub fn security_token<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.security_token = key.map(|x| x.as_ref().to_string());
self
}
/// Construct aws s3 params with provided session_token
pub fn session_token<S: AsRef<str>>(mut self, key: Option<S>) -> Self {
self.session_token = key.map(|x| x.as_ref().to_string());
self
}
/// Specify new path style when constructing aws s3 params
pub fn new_path_style(mut self, new_path_style: bool) -> Self {
self.new_path_style = new_path_style;
self
}
/// Returns whether a password is supposed to be required for this protocol params.
/// The result true is returned ONLY if the supposed secret is MISSING!!!
pub fn password_missing(&self) -> bool {
self.secret_access_key.is_none() && self.security_token.is_none()
}
/// Set password
pub fn set_default_secret(&mut self, secret: String) {
self.secret_access_key = Some(secret);
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn should_init_aws_s3_params() {
let params: AwsS3Params = AwsS3Params::new("omar", Some("eu-west-1"), Some("test"));
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_deref().unwrap(), "eu-west-1");
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert!(params.endpoint.is_none());
assert!(params.access_key.is_none());
assert!(params.secret_access_key.is_none());
assert!(params.security_token.is_none());
assert!(params.session_token.is_none());
assert_eq!(params.new_path_style, false);
}
#[test]
fn should_init_aws_s3_params_with_optionals() {
let params: AwsS3Params = AwsS3Params::new("omar", Some("eu-west-1"), Some("test"))
.endpoint(Some("http://omar.it"))
.access_key(Some("pippo"))
.secret_access_key(Some("pluto"))
.security_token(Some("omar"))
.session_token(Some("gerry-scotti"))
.new_path_style(true);
assert_eq!(params.bucket_name.as_str(), "omar");
assert_eq!(params.region.as_deref().unwrap(), "eu-west-1");
assert_eq!(params.profile.as_deref().unwrap(), "test");
assert_eq!(params.endpoint.as_deref().unwrap(), "http://omar.it");
assert_eq!(params.access_key.as_deref().unwrap(), "pippo");
assert_eq!(params.secret_access_key.as_deref().unwrap(), "pluto");
assert_eq!(params.security_token.as_deref().unwrap(), "omar");
assert_eq!(params.session_token.as_deref().unwrap(), "gerry-scotti");
assert_eq!(params.new_path_style, true);
}
}

View File

@@ -0,0 +1,37 @@
use remotefs_kube::Config;
/// Protocol params used by WebDAV
#[derive(Debug, Clone)]
pub struct KubeProtocolParams {
pub pod: String,
pub container: String,
pub namespace: Option<String>,
pub cluster_url: Option<String>,
pub username: Option<String>,
pub client_cert: Option<String>,
pub client_key: Option<String>,
}
impl KubeProtocolParams {
pub fn set_default_secret(&mut self, _secret: String) {}
pub fn password_missing(&self) -> bool {
false
}
pub fn config(self) -> Option<Config> {
if let Some(cluster_url) = self.cluster_url {
let mut config = Config::new(cluster_url.parse().unwrap_or_default());
config.auth_info.username = self.username;
config.auth_info.client_certificate = self.client_cert;
config.auth_info.client_key = self.client_key;
if let Some(namespace) = self.namespace {
config.default_namespace = namespace;
}
Some(config)
} else {
None
}
}
}

View File

@@ -0,0 +1,122 @@
/// Connection parameters for SMB protocol
#[derive(Debug, Clone)]
pub struct SmbParams {
pub address: String,
#[cfg(unix)]
pub port: u16,
pub share: String,
pub username: Option<String>,
pub password: Option<String>,
#[cfg(unix)]
pub workgroup: Option<String>,
}
// -- SMB params
impl SmbParams {
/// Instantiates a new `AwsS3Params` struct
pub fn new<S: AsRef<str>>(address: S, share: S) -> Self {
Self {
address: address.as_ref().to_string(),
#[cfg(unix)]
port: 445,
share: share.as_ref().to_string(),
username: None,
password: None,
#[cfg(unix)]
workgroup: None,
}
}
#[cfg(unix)]
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn username(mut self, username: Option<impl ToString>) -> Self {
self.username = username.map(|x| x.to_string());
self
}
pub fn password(mut self, password: Option<impl ToString>) -> Self {
self.password = password.map(|x| x.to_string());
self
}
#[cfg(unix)]
pub fn workgroup(mut self, workgroup: Option<impl ToString>) -> Self {
self.workgroup = workgroup.map(|x| x.to_string());
self
}
/// Returns whether a password is supposed to be required for this protocol params.
/// The result true is returned ONLY if the supposed secret is MISSING!!!
pub fn password_missing(&self) -> bool {
self.password.is_none()
}
/// Set password
#[cfg(unix)]
pub fn set_default_secret(&mut self, secret: String) {
self.password = Some(secret);
}
#[cfg(windows)]
pub fn set_default_secret(&mut self, _secret: String) {}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn should_init_smb_params() {
let params = SmbParams::new("localhost", "temp");
assert_eq!(&params.address, "localhost");
#[cfg(unix)]
assert_eq!(params.port, 445);
assert_eq!(&params.share, "temp");
#[cfg(unix)]
assert!(params.username.is_none());
#[cfg(unix)]
assert!(params.password.is_none());
#[cfg(unix)]
assert!(params.workgroup.is_none());
}
#[test]
#[cfg(unix)]
fn should_init_smb_params_with_optionals() {
let params = SmbParams::new("localhost", "temp")
.port(3456)
.username(Some("foo"))
.password(Some("bar"))
.workgroup(Some("baz"));
assert_eq!(&params.address, "localhost");
assert_eq!(params.port, 3456);
assert_eq!(&params.share, "temp");
assert_eq!(params.username.as_deref().unwrap(), "foo");
assert_eq!(params.password.as_deref().unwrap(), "bar");
assert_eq!(params.workgroup.as_deref().unwrap(), "baz");
}
#[test]
#[cfg(windows)]
fn should_init_smb_params_with_optionals() {
let params = SmbParams::new("localhost", "temp")
.username(Some("foo"))
.password(Some("bar"));
assert_eq!(&params.address, "localhost");
assert_eq!(&params.share, "temp");
assert_eq!(params.username.as_deref().unwrap(), "foo");
assert_eq!(params.password.as_deref().unwrap(), "bar");
}
}

View File

@@ -0,0 +1,17 @@
/// Protocol params used by WebDAV
#[derive(Debug, Clone)]
pub struct WebDAVProtocolParams {
pub uri: String,
pub username: String,
pub password: String,
}
impl WebDAVProtocolParams {
pub fn set_default_secret(&mut self, secret: String) {
self.password = secret;
}
pub fn password_missing(&self) -> bool {
self.password.is_empty()
}
}

View File

@@ -5,7 +5,8 @@
// Locals
use super::{AuthActivity, FileTransferParams};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams, WebDAVProtocolParams,
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams, SmbParams,
WebDAVProtocolParams,
};
impl AuthActivity {
@@ -164,6 +165,8 @@ impl AuthActivity {
);
match bookmark.params {
ProtocolParams::AwsS3(params) => self.load_bookmark_s3_into_gui(params),
ProtocolParams::Kube(params) => self.load_bookmark_kube_into_gui(params),
ProtocolParams::Generic(params) => self.load_bookmark_generic_into_gui(params),
ProtocolParams::Smb(params) => self.load_bookmark_smb_into_gui(params),
ProtocolParams::WebDAV(params) => self.load_bookmark_webdav_into_gui(params),
@@ -189,6 +192,16 @@ impl AuthActivity {
self.mount_s3_new_path_style(params.new_path_style);
}
fn load_bookmark_kube_into_gui(&mut self, params: KubeProtocolParams) {
self.mount_kube_pod_name(params.pod.as_str());
self.mount_kube_container(&params.container);
self.mount_kube_cluster_url(params.cluster_url.as_deref().unwrap_or(""));
self.mount_kube_namespace(params.namespace.as_deref().unwrap_or(""));
self.mount_kube_client_cert(params.client_cert.as_deref().unwrap_or(""));
self.mount_kube_client_key(params.client_key.as_deref().unwrap_or(""));
self.mount_kube_username(params.username.as_deref().unwrap_or(""));
}
fn load_bookmark_smb_into_gui(&mut self, params: SmbParams) {
self.mount_address(params.address.as_str());
#[cfg(unix)]

View File

@@ -10,8 +10,8 @@ use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use super::{FileTransferProtocol, FormMsg, Msg, UiMsg};
use crate::ui::activities::auth::{
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_KUBE, RADIO_PROTOCOL_S3,
RADIO_PROTOCOL_SCP, RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
};
// -- protocol
@@ -31,9 +31,9 @@ impl ProtocolRadio {
.modifiers(BorderType::Rounded),
)
.choices(if cfg!(smb) {
&["SFTP", "SCP", "FTP", "FTPS", "S3", "WebDAV", "SMB"]
&["SFTP", "SCP", "FTP", "FTPS", "S3", "Kube", "WebDAV", "SMB"]
} else {
&["SFTP", "SCP", "FTP", "FTPS", "S3", "WebDAV"]
&["SFTP", "SCP", "FTP", "FTPS", "S3", "Kube", "WebDAV"]
})
.foreground(color)
.rewind(true)
@@ -50,6 +50,7 @@ impl ProtocolRadio {
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
RADIO_PROTOCOL_KUBE => FileTransferProtocol::Kube,
RADIO_PROTOCOL_WEBDAV => FileTransferProtocol::WebDAV,
_ => FileTransferProtocol::Sftp,
}
@@ -63,6 +64,7 @@ impl ProtocolRadio {
FileTransferProtocol::Ftp(false) => RADIO_PROTOCOL_FTP,
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
FileTransferProtocol::Kube => RADIO_PROTOCOL_KUBE,
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
FileTransferProtocol::WebDAV => RADIO_PROTOCOL_WEBDAV,
}
@@ -827,3 +829,252 @@ impl Component<Msg, NoUserEvent> for InputWebDAVUri {
)
}
}
// kube
#[derive(MockComponent)]
pub struct InputKubePodName {
component: Input,
}
impl InputKubePodName {
pub fn new(bucket: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder("pod-name", Style::default().fg(Color::Rgb(128, 128, 128)))
.title("Pod name", Alignment::Left)
.input_type(InputType::Text)
.value(bucket),
}
}
}
impl Component<Msg, NoUserEvent> for InputKubePodName {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::KubePodNameBlurDown),
Msg::Ui(UiMsg::KubePodNameBlurUp),
)
}
}
#[derive(MockComponent)]
pub struct InputKubeNamespace {
component: Input,
}
impl InputKubeNamespace {
pub fn new(bucket: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder("namespace", Style::default().fg(Color::Rgb(128, 128, 128)))
.title("Pod namespace (optional)", Alignment::Left)
.input_type(InputType::Text)
.value(bucket),
}
}
}
impl Component<Msg, NoUserEvent> for InputKubeNamespace {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::KubeNamespaceBlurDown),
Msg::Ui(UiMsg::KubeNamespaceBlurUp),
)
}
}
#[derive(MockComponent)]
pub struct InputKubeClusterUrl {
component: Input,
}
impl InputKubeClusterUrl {
pub fn new(bucket: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder(
"cluster url",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Kube cluster url (optional)", Alignment::Left)
.input_type(InputType::Text)
.value(bucket),
}
}
}
impl Component<Msg, NoUserEvent> for InputKubeClusterUrl {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::KubeClusterUrlBlurDown),
Msg::Ui(UiMsg::KubeClusterUrlBlurUp),
)
}
}
#[derive(MockComponent)]
pub struct InputKubeContainer {
component: Input,
}
impl InputKubeContainer {
pub fn new(bucket: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder("container", Style::default().fg(Color::Rgb(128, 128, 128)))
.title("Kube container", Alignment::Left)
.input_type(InputType::Text)
.value(bucket),
}
}
}
impl Component<Msg, NoUserEvent> for InputKubeContainer {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::KubeContainerBlurDown),
Msg::Ui(UiMsg::KubeContainerBlurUp),
)
}
}
#[derive(MockComponent)]
pub struct InputKubeUsername {
component: Input,
}
impl InputKubeUsername {
pub fn new(bucket: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder("username", Style::default().fg(Color::Rgb(128, 128, 128)))
.title("Kube username (optional)", Alignment::Left)
.input_type(InputType::Text)
.value(bucket),
}
}
}
impl Component<Msg, NoUserEvent> for InputKubeUsername {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::KubeUsernameBlurDown),
Msg::Ui(UiMsg::KubeUsernameBlurUp),
)
}
}
#[derive(MockComponent)]
pub struct InputKubeClientCert {
component: Input,
}
impl InputKubeClientCert {
pub fn new(bucket: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder(
"/home/user/.kube/client.crt",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Kube client cert path (optional)", Alignment::Left)
.input_type(InputType::Text)
.value(bucket),
}
}
}
impl Component<Msg, NoUserEvent> for InputKubeClientCert {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::KubeClientCertBlurDown),
Msg::Ui(UiMsg::KubeClientCertBlurUp),
)
}
}
#[derive(MockComponent)]
pub struct InputKubeClientKey {
component: Input,
}
impl InputKubeClientKey {
pub fn new(bucket: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder(
"/home/user/.kube/client.key",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Kube client key path (optional)", Alignment::Left)
.input_type(InputType::Text)
.value(bucket),
}
}
}
impl Component<Msg, NoUserEvent> for InputKubeClientKey {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::KubeClientKeyBlurDown),
Msg::Ui(UiMsg::KubeClientKeyBlurUp),
)
}
}

View File

@@ -16,10 +16,12 @@ pub use bookmarks::{
#[cfg(unix)]
pub use form::InputSmbWorkgroup;
pub use form::{
InputAddress, InputLocalDirectory, InputPassword, InputPort, InputRemoteDirectory,
InputS3AccessKey, InputS3Bucket, InputS3Endpoint, InputS3Profile, InputS3Region,
InputS3SecretAccessKey, InputS3SecurityToken, InputS3SessionToken, InputSmbShare,
InputUsername, InputWebDAVUri, ProtocolRadio, RadioS3NewPathStyle,
InputAddress, InputKubeClientCert, InputKubeClientKey, InputKubeClusterUrl, InputKubeContainer,
InputKubeNamespace, InputKubePodName, InputKubeUsername, InputLocalDirectory, InputPassword,
InputPort, InputRemoteDirectory, InputS3AccessKey, InputS3Bucket, InputS3Endpoint,
InputS3Profile, InputS3Region, InputS3SecretAccessKey, InputS3SecurityToken,
InputS3SessionToken, InputSmbShare, InputUsername, InputWebDAVUri, ProtocolRadio,
RadioS3NewPathStyle,
};
pub use popup::{
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,

View File

@@ -14,6 +14,7 @@ impl AuthActivity {
FileTransferProtocol::Sftp | FileTransferProtocol::Scp => 22,
FileTransferProtocol::Ftp(_) => 21,
FileTransferProtocol::AwsS3 => 22, // Doesn't matter, since not used
FileTransferProtocol::Kube => 22, // Doesn't matter, since not used
FileTransferProtocol::Smb => 445,
FileTransferProtocol::WebDAV => 80, // Doesn't matter, since not used
}
@@ -38,6 +39,7 @@ impl AuthActivity {
pub(super) fn collect_host_params(&self) -> Result<FileTransferParams, &'static str> {
match self.protocol {
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(),
FileTransferProtocol::Kube => self.collect_kube_host_params(),
FileTransferProtocol::Smb => self.collect_smb_host_params(),
FileTransferProtocol::Ftp(_)
| FileTransferProtocol::Scp
@@ -80,6 +82,20 @@ impl AuthActivity {
})
}
/// Get input values from fields or return an error if fields are invalid to work as aws s3
pub(super) fn collect_kube_host_params(&self) -> Result<FileTransferParams, &'static str> {
let params = self.get_kube_params_input();
if params.pod.is_empty() {
return Err("Invalid pod name");
}
Ok(FileTransferParams {
protocol: FileTransferProtocol::Kube,
params: ProtocolParams::Kube(params),
local_path: self.get_input_local_directory(),
remote_path: self.get_input_remote_directory(),
})
}
pub(super) fn collect_smb_host_params(&self) -> Result<FileTransferParams, &'static str> {
let params = self.get_smb_params_input();
if params.address.is_empty() {

View File

@@ -29,8 +29,9 @@ const RADIO_PROTOCOL_SCP: usize = 1;
const RADIO_PROTOCOL_FTP: usize = 2;
const RADIO_PROTOCOL_FTPS: usize = 3;
const RADIO_PROTOCOL_S3: usize = 4;
const RADIO_PROTOCOL_WEBDAV: usize = 5;
const RADIO_PROTOCOL_SMB: usize = 6;
const RADIO_PROTOCOL_KUBE: usize = 5;
const RADIO_PROTOCOL_WEBDAV: usize = 6;
const RADIO_PROTOCOL_SMB: usize = 7;
// -- components
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
@@ -47,6 +48,13 @@ pub enum Id {
InfoPopup,
InstallUpdatePopup,
Keybindings,
KubePodName,
KubeContainer,
KubeNamespace,
KubeClusterUrl,
KubeUsername,
KubeClientCert,
KubeClientKey,
LocalDirectory,
NewVersionChangelog,
NewVersionDisclaimer,
@@ -111,6 +119,20 @@ pub enum UiMsg {
CloseKeybindingsPopup,
CloseQuitPopup,
CloseSaveBookmark,
KubePodNameBlurDown,
KubePodNameBlurUp,
KubeContainerBlurDown,
KubeContainerBlurUp,
KubeNamespaceBlurDown,
KubeNamespaceBlurUp,
KubeClusterUrlBlurDown,
KubeClusterUrlBlurUp,
KubeUsernameBlurDown,
KubeUsernameBlurUp,
KubeClientCertBlurDown,
KubeClientCertBlurUp,
KubeClientKeyBlurDown,
KubeClientKeyBlurUp,
LocalDirectoryBlurDown,
LocalDirectoryBlurUp,
ParamsFormBlur,
@@ -167,6 +189,7 @@ pub enum UiMsg {
enum InputMask {
Generic,
AwsS3,
Kube,
Smb,
WebDAV,
}
@@ -244,6 +267,7 @@ impl AuthActivity {
FileTransferProtocol::Ftp(_)
| FileTransferProtocol::Scp
| FileTransferProtocol::Sftp => InputMask::Generic,
FileTransferProtocol::Kube => InputMask::Kube,
FileTransferProtocol::Smb => InputMask::Smb,
FileTransferProtocol::WebDAV => InputMask::WebDAV,
}

View File

@@ -70,6 +70,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Password,
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::Kube => &Id::KubePodName,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
@@ -83,6 +84,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Password,
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::Kube => &Id::KubePodName,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
@@ -179,6 +181,7 @@ impl AuthActivity {
#[cfg(windows)]
InputMask::Smb => &Id::RemoteDirectory,
InputMask::AwsS3 => panic!("this shouldn't happen (password on s3)"),
InputMask::Kube => panic!("this shouldn't happen (password on kube)"),
InputMask::WebDAV => &Id::RemoteDirectory,
})
.is_ok());
@@ -192,8 +195,8 @@ impl AuthActivity {
.active(match self.input_mask() {
InputMask::Generic => &Id::Username,
InputMask::Smb => &Id::SmbShare,
InputMask::AwsS3 | InputMask::WebDAV =>
panic!("this shouldn't happen (port on s3)"),
InputMask::AwsS3 | InputMask::Kube | InputMask::WebDAV =>
panic!("this shouldn't happen (port on s3/kube/webdav)"),
})
.is_ok());
}
@@ -207,6 +210,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Address,
InputMask::Smb => &Id::Address,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::Kube => &Id::KubePodName,
InputMask::WebDAV => &Id::WebDAVUri,
})
.is_ok());
@@ -229,6 +233,7 @@ impl AuthActivity {
InputMask::Smb => &Id::SmbWorkgroup,
#[cfg(windows)]
InputMask::Smb => &Id::Password,
InputMask::Kube => &Id::KubeClientKey,
InputMask::AwsS3 => &Id::S3NewPathStyle,
InputMask::WebDAV => &Id::Password,
})
@@ -288,6 +293,48 @@ impl AuthActivity {
UiMsg::S3NewPathStyleBlurUp => {
assert!(self.app.active(&Id::S3SessionToken).is_ok());
}
UiMsg::KubeClientCertBlurDown => {
assert!(self.app.active(&Id::KubeClientKey).is_ok());
}
UiMsg::KubeClientCertBlurUp => {
assert!(self.app.active(&Id::KubeUsername).is_ok());
}
UiMsg::KubeClientKeyBlurDown => {
assert!(self.app.active(&Id::RemoteDirectory).is_ok());
}
UiMsg::KubeClientKeyBlurUp => {
assert!(self.app.active(&Id::KubeClientCert).is_ok());
}
UiMsg::KubeContainerBlurDown => {
assert!(self.app.active(&Id::KubeNamespace).is_ok());
}
UiMsg::KubeContainerBlurUp => {
assert!(self.app.active(&Id::KubePodName).is_ok());
}
UiMsg::KubePodNameBlurDown => {
assert!(self.app.active(&Id::KubeContainer).is_ok());
}
UiMsg::KubePodNameBlurUp => {
assert!(self.app.active(&Id::Protocol).is_ok());
}
UiMsg::KubeNamespaceBlurDown => {
assert!(self.app.active(&Id::KubeClusterUrl).is_ok());
}
UiMsg::KubeNamespaceBlurUp => {
assert!(self.app.active(&Id::KubeContainer).is_ok());
}
UiMsg::KubeClusterUrlBlurDown => {
assert!(self.app.active(&Id::KubeUsername).is_ok());
}
UiMsg::KubeClusterUrlBlurUp => {
assert!(self.app.active(&Id::KubeNamespace).is_ok());
}
UiMsg::KubeUsernameBlurDown => {
assert!(self.app.active(&Id::KubeClientCert).is_ok());
}
UiMsg::KubeUsernameBlurUp => {
assert!(self.app.active(&Id::KubeClusterUrl).is_ok());
}
UiMsg::SmbShareBlurDown => {
assert!(self.app.active(&Id::Username).is_ok());
}
@@ -337,6 +384,7 @@ impl AuthActivity {
.active(match self.input_mask() {
InputMask::Generic => &Id::Port,
InputMask::Smb => &Id::SmbShare,
InputMask::Kube => panic!("this shouldn't happen (username on kube)"),
InputMask::AwsS3 => panic!("this shouldn't happen (username on s3)"),
InputMask::WebDAV => &Id::WebDAVUri,
})

View File

@@ -13,7 +13,8 @@ use tuirealm::{State, StateValue, Sub, SubClause, SubEventClause};
use super::{components, AuthActivity, Context, FileTransferProtocol, Id, InputMask};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams, WebDAVProtocolParams,
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams, SmbParams,
WebDAVProtocolParams,
};
use crate::filetransfer::FileTransferParams;
use crate::utils::ui::{Popup, Size};
@@ -60,6 +61,13 @@ impl AuthActivity {
self.mount_s3_security_token("");
self.mount_s3_session_token("");
self.mount_s3_new_path_style(false);
self.mount_kube_client_cert("");
self.mount_kube_client_key("");
self.mount_kube_cluster_url("");
self.mount_kube_container("");
self.mount_kube_namespace("");
self.mount_kube_pod_name("");
self.mount_kube_username("");
self.mount_smb_share("");
#[cfg(unix)]
self.mount_smb_workgroup("");
@@ -155,6 +163,16 @@ impl AuthActivity {
)
.direction(Direction::Vertical)
.split(auth_chunks[4]),
InputMask::Kube => Layout::default()
.constraints([
Constraint::Length(3), // ...
Constraint::Length(3), // ...
Constraint::Length(3), // ...
Constraint::Length(3), // ...
Constraint::Length(3), // remote directory
])
.direction(Direction::Vertical)
.split(auth_chunks[4]),
InputMask::Generic => Layout::default()
.constraints(
[
@@ -238,6 +256,13 @@ impl AuthActivity {
self.app.view(&view_ids[2], f, input_mask[2]);
self.app.view(&view_ids[3], f, input_mask[3]);
}
InputMask::Kube => {
let view_ids = self.get_kube_view();
self.app.view(&view_ids[0], f, input_mask[0]);
self.app.view(&view_ids[1], f, input_mask[1]);
self.app.view(&view_ids[2], f, input_mask[2]);
self.app.view(&view_ids[3], f, input_mask[3]);
}
InputMask::Smb => {
let view_ids = self.get_smb_view();
self.app.view(&view_ids[0], f, input_mask[0]);
@@ -791,6 +816,90 @@ impl AuthActivity {
.is_ok());
}
pub(super) fn mount_kube_pod_name(&mut self, value: &str) {
let color = self.theme().auth_address;
assert!(self
.app
.remount(
Id::KubePodName,
Box::new(components::InputKubePodName::new(value, color)),
vec![]
)
.is_ok());
}
pub(super) fn mount_kube_container(&mut self, value: &str) {
let color = self.theme().auth_password;
assert!(self
.app
.remount(
Id::KubeContainer,
Box::new(components::InputKubeContainer::new(value, color)),
vec![]
)
.is_ok());
}
pub(super) fn mount_kube_namespace(&mut self, value: &str) {
let color = self.theme().auth_port;
assert!(self
.app
.remount(
Id::KubeNamespace,
Box::new(components::InputKubeNamespace::new(value, color)),
vec![]
)
.is_ok());
}
pub(super) fn mount_kube_cluster_url(&mut self, value: &str) {
let color = self.theme().auth_username;
assert!(self
.app
.remount(
Id::KubeClusterUrl,
Box::new(components::InputKubeClusterUrl::new(value, color)),
vec![]
)
.is_ok());
}
pub(super) fn mount_kube_username(&mut self, value: &str) {
let color = self.theme().auth_password;
assert!(self
.app
.remount(
Id::KubeUsername,
Box::new(components::InputKubeUsername::new(value, color)),
vec![]
)
.is_ok());
}
pub(super) fn mount_kube_client_cert(&mut self, value: &str) {
let color = self.theme().auth_address;
assert!(self
.app
.remount(
Id::KubeClientCert,
Box::new(components::InputKubeClientCert::new(value, color)),
vec![]
)
.is_ok());
}
pub(super) fn mount_kube_client_key(&mut self, value: &str) {
let color = self.theme().auth_port;
assert!(self
.app
.remount(
Id::KubeClientKey,
Box::new(components::InputKubeClientKey::new(value, color)),
vec![]
)
.is_ok());
}
pub(crate) fn mount_smb_share(&mut self, share: &str) {
let color = self.theme().auth_password;
assert!(self
@@ -863,6 +972,26 @@ impl AuthActivity {
.new_path_style(new_path_style)
}
/// Collect s3 input values from view
pub(super) fn get_kube_params_input(&self) -> KubeProtocolParams {
let pod = self.get_input_kube_pod_name();
let container = self.get_input_kube_container();
let namespace = self.get_input_kube_namespace();
let cluster_url = self.get_input_kube_cluster_url();
let username = self.get_input_kube_username();
let client_cert = self.get_input_kube_client_cert();
let client_key = self.get_input_kube_client_key();
KubeProtocolParams {
pod,
container,
namespace,
cluster_url,
username,
client_cert,
client_key,
}
}
/// Collect s3 input values from view
#[cfg(unix)]
pub(super) fn get_smb_params_input(&self) -> SmbParams {
@@ -1025,6 +1154,55 @@ impl AuthActivity {
)
}
pub(super) fn get_input_kube_pod_name(&self) -> String {
match self.app.state(&Id::KubePodName) {
Ok(State::One(StateValue::String(x))) => x,
_ => String::new(),
}
}
pub(super) fn get_input_kube_container(&self) -> String {
match self.app.state(&Id::KubeContainer) {
Ok(State::One(StateValue::String(x))) => x,
_ => String::new(),
}
}
pub(super) fn get_input_kube_namespace(&self) -> Option<String> {
match self.app.state(&Id::KubeNamespace) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_kube_cluster_url(&self) -> Option<String> {
match self.app.state(&Id::KubeClusterUrl) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_kube_username(&self) -> Option<String> {
match self.app.state(&Id::KubeUsername) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_kube_client_cert(&self) -> Option<String> {
match self.app.state(&Id::KubeClientCert) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_kube_client_key(&self) -> Option<String> {
match self.app.state(&Id::KubeClientKey) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
_ => None,
}
}
pub(super) fn get_input_smb_share(&self) -> String {
match self.app.state(&Id::SmbShare) {
Ok(State::One(StateValue::String(x))) => x,
@@ -1063,6 +1241,7 @@ impl AuthActivity {
match self.input_mask() {
InputMask::AwsS3 => 12,
InputMask::Generic => 12,
InputMask::Kube => 12,
InputMask::Smb => 12,
InputMask::WebDAV => 12,
}
@@ -1104,6 +1283,24 @@ impl AuthActivity {
protocol, username, params.address, params.port
)
}
ProtocolParams::Kube(params) => {
format!(
"{}://{}@{}{}{}",
protocol,
params.container,
params.pod,
params
.namespace
.as_deref()
.map(|x| format!("/{x}"))
.unwrap_or_default(),
params
.cluster_url
.as_deref()
.map(|x| format!("@{x}"))
.unwrap_or_default()
)
}
#[cfg(unix)]
ProtocolParams::Smb(params) => {
let username: String = match params.username {
@@ -1189,6 +1386,54 @@ impl AuthActivity {
}
}
/// Get the visible element in the kube form, based on current focus
fn get_kube_view(&self) -> [Id; 4] {
match self.app.focus() {
Some(&Id::KubePodName) => [
Id::KubePodName,
Id::KubeContainer,
Id::KubeNamespace,
Id::KubeClusterUrl,
],
Some(&Id::KubeUsername) => [
Id::KubeContainer,
Id::KubeNamespace,
Id::KubeClusterUrl,
Id::KubeUsername,
],
Some(&Id::KubeClientCert) => [
Id::KubeNamespace,
Id::KubeClusterUrl,
Id::KubeUsername,
Id::KubeClientCert,
],
Some(&Id::KubeClientKey) => [
Id::KubeClusterUrl,
Id::KubeUsername,
Id::KubeClientCert,
Id::KubeClientKey,
],
Some(&Id::RemoteDirectory) => [
Id::KubeUsername,
Id::KubeClientCert,
Id::KubeClientKey,
Id::RemoteDirectory,
],
Some(&Id::LocalDirectory) => [
Id::KubeClientCert,
Id::KubeClientKey,
Id::RemoteDirectory,
Id::LocalDirectory,
],
_ => [
Id::KubePodName,
Id::KubeContainer,
Id::KubeNamespace,
Id::KubeClusterUrl,
],
}
}
#[cfg(unix)]
fn get_smb_view(&self) -> [Id; 4] {
match self.app.focus() {

View File

@@ -111,6 +111,7 @@ impl FileTransferActivity {
match &ft_params.params {
ProtocolParams::Generic(params) => params.address.clone(),
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
ProtocolParams::Kube(params) => params.pod.clone(),
ProtocolParams::Smb(params) => params.address.clone(),
ProtocolParams::WebDAV(params) => params.uri.clone(),
}
@@ -135,6 +136,13 @@ impl FileTransferActivity {
);
format!("Connecting to {}", params.bucket_name)
}
ProtocolParams::Kube(params) => {
info!(
"Client is not connected to remote; connecting to pod {}",
params.pod,
);
format!("Connecting to {}", params.pod)
}
ProtocolParams::Smb(params) => {
info!(
"Client is not connected to remote; connecting to {}:{}",

View File

@@ -12,8 +12,8 @@ use super::{ConfigMsg, Msg};
use crate::explorer::GroupDirs as GroupDirsEnum;
use crate::filetransfer::FileTransferProtocol;
use crate::ui::activities::setup::{
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_KUBE, RADIO_PROTOCOL_S3,
RADIO_PROTOCOL_SCP, RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
};
use crate::utils::parser::parse_bytesize;
@@ -67,7 +67,7 @@ impl DefaultProtocol {
.color(Color::Cyan)
.modifiers(BorderType::Rounded),
)
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB", "WebDAV"])
.choices(&["SFTP", "SCP", "FTP", "FTPS", "Kube", "S3", "SMB", "WebDAV"])
.foreground(Color::Cyan)
.rewind(true)
.title("Default protocol", Alignment::Left)
@@ -76,6 +76,7 @@ impl DefaultProtocol {
FileTransferProtocol::Scp => RADIO_PROTOCOL_SCP,
FileTransferProtocol::Ftp(false) => RADIO_PROTOCOL_FTP,
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
FileTransferProtocol::Kube => RADIO_PROTOCOL_KUBE,
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
FileTransferProtocol::WebDAV => RADIO_PROTOCOL_WEBDAV,

View File

@@ -29,9 +29,10 @@ const RADIO_PROTOCOL_SFTP: usize = 0;
const RADIO_PROTOCOL_SCP: usize = 1;
const RADIO_PROTOCOL_FTP: usize = 2;
const RADIO_PROTOCOL_FTPS: usize = 3;
const RADIO_PROTOCOL_S3: usize = 4;
const RADIO_PROTOCOL_SMB: usize = 5;
const RADIO_PROTOCOL_WEBDAV: usize = 6;
const RADIO_PROTOCOL_KUBE: usize = 4;
const RADIO_PROTOCOL_S3: usize = 5;
const RADIO_PROTOCOL_SMB: usize = 6;
const RADIO_PROTOCOL_WEBDAV: usize = 7;
// -- components
#[derive(Debug, Eq, PartialEq, Clone, Hash)]

View File

@@ -11,7 +11,8 @@ use tuirealm::tui::layout::{Constraint, Direction, Layout};
use tuirealm::{State, StateValue};
use super::{
components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout, RADIO_PROTOCOL_WEBDAV,
components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout, RADIO_PROTOCOL_KUBE,
RADIO_PROTOCOL_WEBDAV,
};
use crate::explorer::GroupDirs;
use crate::filetransfer::FileTransferProtocol;
@@ -277,6 +278,7 @@ impl SetupActivity {
RADIO_PROTOCOL_SCP => FileTransferProtocol::Scp,
RADIO_PROTOCOL_FTP => FileTransferProtocol::Ftp(false),
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
RADIO_PROTOCOL_KUBE => FileTransferProtocol::Kube,
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
RADIO_PROTOCOL_WEBDAV => FileTransferProtocol::WebDAV,

View File

@@ -15,7 +15,7 @@ use tuirealm::utils::parser as tuirealm_parser;
#[cfg(smb)]
use crate::filetransfer::params::SmbParams;
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, WebDAVProtocolParams,
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams, WebDAVProtocolParams,
};
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
#[cfg(not(test))] // NOTE: don't use configuration during tests
@@ -53,7 +53,15 @@ static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
* - group 4: Some(path) | None
*/
static REMOTE_WEBDAV_OPT_REGEX: Lazy<Regex> =
lazy_regex!(r"(?:([^:]+):)(?:(.+[^@])@)(?:([^/]+))(?:/(.+))?");
lazy_regex!(r"(?:([^:]+):)(?:(.+[^@])@)(?:([^/]+))(?:(.+))?");
/**
* Regex matches: {container}@{pod}/{path}
* - group 1: Container
* - group 2: Pod
* - group 3: Some(path) | None
*/
static REMOTE_KUBE_OPT_REGEX: Lazy<Regex> = lazy_regex!(r"(?:(.+[^@])@)(?:([^/]+))(?:(.+))?");
/**
* Regex matches:
@@ -162,6 +170,7 @@ pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
// Match against regex for protocol type
match protocol {
FileTransferProtocol::AwsS3 => parse_s3_remote_opt(remote.as_str()),
FileTransferProtocol::Kube => parse_kube_remote_opt(remote.as_str()),
#[cfg(smb)]
FileTransferProtocol::Smb => parse_smb_remote_opts(remote.as_str()),
FileTransferProtocol::WebDAV => {
@@ -302,6 +311,37 @@ fn parse_s3_remote_opt(s: &str) -> Result<FileTransferParams, String> {
}
}
fn parse_kube_remote_opt(s: &str) -> Result<FileTransferParams, String> {
match REMOTE_KUBE_OPT_REGEX.captures(s) {
Some(groups) => {
let container: String = groups
.get(1)
.map(|x| x.as_str().to_string())
.unwrap_or_default();
let pod: String = groups
.get(2)
.map(|x| x.as_str().to_string())
.unwrap_or_default();
let remote_path: Option<PathBuf> =
groups.get(3).map(|group| PathBuf::from(group.as_str()));
Ok(FileTransferParams::new(
FileTransferProtocol::Kube,
ProtocolParams::Kube(KubeProtocolParams {
pod,
container,
namespace: None,
cluster_url: None,
username: None,
client_cert: None,
client_key: None,
}),
)
.remote_path(remote_path))
}
None => Err(String::from("Bad remote host syntax!")),
}
}
/// Parse remote options for smb protocol
#[cfg(smb_unix)]
fn parse_smb_remote_opts(s: &str) -> Result<FileTransferParams, String> {
@@ -630,6 +670,12 @@ mod tests {
assert_eq!(params.uri.as_str(), "http://myserver:4445");
assert_eq!(params.username.as_str(), "omar");
assert_eq!(params.password.as_str(), "password");
// remote path
assert_eq!(
result.remote_path.unwrap(),
PathBuf::from("/myshare/dir/subdir")
);
}
#[test]
@@ -706,6 +752,24 @@ mod tests {
assert_eq!(params.region.as_deref().unwrap(), "eu-central-1");
}
#[test]
fn should_parse_kube_address() {
let result = parse_remote_opt("kube://alpine@my-pod/tmp").ok().unwrap();
let params = result.params.kube_params().unwrap();
assert_eq!(params.container.as_str(), "alpine");
assert_eq!(params.pod.as_str(), "my-pod");
assert_eq!(params.namespace, None);
assert_eq!(params.cluster_url, None);
assert_eq!(params.username, None);
assert_eq!(params.client_cert, None);
assert_eq!(params.client_key, None);
assert_eq!(
result.remote_path.as_deref().unwrap(),
std::path::Path::new("/tmp")
);
}
#[test]
#[cfg(smb_unix)]
fn should_parse_smb_address() {