diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e1b042..e66e30b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Released on ?? - [Issue 226](https://github.com/veeso/termscp/issues/226): Use ssh-agent - [Issue 241](https://github.com/veeso/termscp/issues/241): Jump to next entry after select +- [Issue 242](https://github.com/veeso/termscp/issues/242): Added `Kube` protocol support - [Issue 255](https://github.com/veeso/termscp/issues/255): New keybindings `Ctrl + Shift + A` to deselect all files - [Issue 256](https://github.com/veeso/termscp/issues/256): Filter files in current folder. You can now filter files by pressing `/`. Both wildmatch and regex are accepted to filter files. - [Issue 257](https://github.com/veeso/termscp/issues/257): CLI remote args cannot handle '@' in the username diff --git a/Cargo.lock b/Cargo.lock index 2a58ec56..dc84ec75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,7 @@ dependencies = [ "rust-ini", "serde", "thiserror", - "time 0.3.36", + "time", "url", ] @@ -299,9 +299,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" @@ -454,17 +454,17 @@ checksum = "77e53693616d3075149f4ead59bdeecd204ac6b8192d8969757601b74bddf00f" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", - "time 0.1.45", + "serde", "wasm-bindgen", - "winapi 0.3.9", + "windows-targets 0.52.4", ] [[package]] @@ -591,7 +591,7 @@ dependencies = [ "bitflags 1.3.2", "crossterm_winapi", "libc", - "mio 0.8.8", + "mio 0.8.11", "parking_lot 0.12.1", "signal-hook", "signal-hook-mio", @@ -607,7 +607,7 @@ dependencies = [ "bitflags 2.4.2", "crossterm_winapi", "libc", - "mio 0.8.8", + "mio 0.8.11", "parking_lot 0.12.1", "signal-hook", "signal-hook-mio", @@ -689,6 +689,12 @@ dependencies = [ "parking_lot_core 0.9.8", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "dbus" version = "0.9.7" @@ -1181,7 +1187,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1230,6 +1236,30 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 1.1.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.1.0", +] + [[package]] name = "heck" version = "0.4.1" @@ -1299,9 +1329,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1326,7 +1356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.0.0", + "http 1.1.0", ] [[package]] @@ -1337,7 +1367,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "pin-project-lite", ] @@ -1387,7 +1417,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "httparse", "itoa", @@ -1397,6 +1427,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-http-proxy" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d06dbdfbacf34d996c6fb540a71a684a7aae9056c71951163af8a8a4c07b9a4" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http 1.1.0", + "hyper 1.4.0", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-rustls" version = "0.27.2" @@ -1404,10 +1454,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", - "http 1.0.0", + "http 1.1.0", "hyper 1.4.0", "hyper-util", + "log", "rustls 0.23.11", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1415,6 +1467,19 @@ dependencies = [ "webpki-roots 0.26.3", ] +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper 1.4.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1437,7 +1502,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "hyper 1.4.0", "pin-project-lite", @@ -1618,6 +1683,34 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonpath-rust" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d8fe85bd70ff715f31ce8c739194b423d79811a19602115d611a3ec85d6200" +dependencies = [ + "lazy_static", + "once_cell", + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "k8s-openapi" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19501afb943ae5806548bc3ebd7f3374153ca057a38f480ef30adfde5ef09755" +dependencies = [ + "base64 0.22.1", + "chrono", + "serde", + "serde-value", + "serde_json", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1642,6 +1735,72 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "kube" +version = "0.92.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231c5a5392d9e2a9b0d923199760d3f1dd73b95288f2871d16c7c90ba4954506" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", +] + +[[package]] +name = "kube-client" +version = "0.92.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4bf54135062ff60e2a0dfb3e7a9c8e931fc4a535b4d6bd561e0a1371321c61" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "either", + "futures", + "home", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-http-proxy", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "rand", + "rustls 0.23.11", + "rustls-pemfile", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.92.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fb9bd8141cbc0fe6b0d9112d371679b4cb607b45c31dd68d92e40864a12975" +dependencies = [ + "chrono", + "form_urlencoded", + "http 1.1.0", + "k8s-openapi", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "lazy-regex" version = "2.5.0" @@ -1789,7 +1948,7 @@ dependencies = [ "dirs-next", "objc-foundation", "objc_id", - "time 0.3.36", + "time", ] [[package]] @@ -1897,13 +2056,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -2237,6 +2396,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.6.0" @@ -2358,12 +2526,67 @@ dependencies = [ "thiserror", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "pest_meta" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.7", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -2706,6 +2929,27 @@ dependencies = [ "users", ] +[[package]] +name = "remotefs-kube" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6fea485470d4504bb9d7572e2fdee09866b3031331cc49e0eca2e2e31975d9" +dependencies = [ + "chrono", + "futures-util", + "k8s-openapi", + "kube", + "lazy-regex 3.1.0", + "log", + "path-slash 0.2.1", + "remotefs", + "tar", + "tempfile", + "thiserror", + "tokio", + "tokio-util", +] + [[package]] name = "remotefs-smb" version = "0.2.0" @@ -2744,7 +2988,7 @@ checksum = "e88503e1cd53067ab639150625718bb8d88e6bcd1e165205de0d941beca66bde" dependencies = [ "bytes", "bytestring", - "http 1.0.0", + "http 1.1.0", "httpdate", "indexmap 2.2.5", "log", @@ -2754,7 +2998,7 @@ dependencies = [ "remotefs", "rustydav", "thiserror", - "time 0.3.36", + "time", ] [[package]] @@ -2763,7 +3007,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -2805,7 +3049,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "hyper 1.4.0", @@ -2893,7 +3137,7 @@ dependencies = [ "attohttpc", "aws-creds", "aws-region", - "base64 0.21.2", + "base64 0.21.7", "bytes", "cfg-if 1.0.0", "hex", @@ -2909,7 +3153,7 @@ dependencies = [ "serde_json", "sha2 0.10.7", "thiserror", - "time 0.3.36", + "time", "url", ] @@ -2966,6 +3210,7 @@ version = "0.23.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -2974,6 +3219,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "2.1.2" @@ -3066,6 +3324,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + [[package]] name = "secret-service" version = "3.0.1" @@ -3158,6 +3426,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.197" @@ -3212,6 +3490,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.2.5", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serial_test" version = "3.0.0" @@ -3289,7 +3580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" dependencies = [ "libc", - "mio 0.8.8", + "mio 0.8.11", "signal-hook", ] @@ -3326,7 +3617,7 @@ checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" dependencies = [ "log", "termcolor", - "time 0.3.36", + "time", ] [[package]] @@ -3531,7 +3822,7 @@ dependencies = [ [[package]] name = "termscp" -version = "0.13.0" +version = "0.14.0" dependencies = [ "argh", "bitflags 2.4.2", @@ -3557,6 +3848,7 @@ dependencies = [ "remotefs", "remotefs-aws-s3", "remotefs-ftp", + "remotefs-kube", "remotefs-smb", "remotefs-ssh", "remotefs-webdav", @@ -3568,6 +3860,7 @@ dependencies = [ "ssh2-config", "tempfile", "thiserror", + "tokio", "toml", "tui-realm-stdlib", "tuirealm", @@ -3620,17 +3913,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", -] - [[package]] name = "time" version = "0.3.36" @@ -3690,21 +3972,33 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", - "mio 0.8.8", + "mio 0.8.11", "num_cpus", "pin-project-lite", - "socket2 0.4.9", + "signal-hook-registry", + "socket2 0.5.7", + "tokio-macros", "windows-sys 0.48.0", ] +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -3726,6 +4020,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -3796,8 +4102,29 @@ dependencies = [ "pin-project", "pin-project-lite", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "base64 0.21.7", + "bitflags 2.4.2", + "bytes", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", ] [[package]] @@ -3819,6 +4146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3900,12 +4228,36 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uds_windows" version = "1.0.2" @@ -3959,6 +4311,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -3992,6 +4350,12 @@ dependencies = [ "log", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4035,12 +4399,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4635,7 +4993,7 @@ dependencies = [ "indexmap 2.2.5", "memchr", "thiserror", - "time 0.3.36", + "time", "zopfli", ] diff --git a/Cargo.toml b/Cargo.toml index 0a77b8a4..cba5502c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Christian Visintin "] categories = ["command-line-utilities"] -description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3" +description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV" edition = "2021" homepage = "https://termscp.veeso.dev" include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"] @@ -16,7 +16,7 @@ license = "MIT" name = "termscp" readme = "README.md" repository = "https://github.com/veeso/termscp" -version = "0.13.0" +version = "0.14.0" [package.metadata.rpm] package = "termscp" @@ -61,6 +61,7 @@ remotefs-aws-s3 = { version = "^0.2.4", default-features = false, features = [ "find", "rustls", ] } +remotefs-kube = "0.2" remotefs-webdav = "^0.1.1" rpassword = "^7.0" self_update = { version = "^0.41", default-features = false, features = [ @@ -75,6 +76,7 @@ simplelog = "^0.12" ssh2-config = "^0.2" tempfile = "^3.4" thiserror = "^1" +tokio = { version = "1", features = ["rt"] } toml = "^0.8" tui-realm-stdlib = "^1.3.1" tuirealm = "^1.9.1" diff --git a/README.md b/README.md index 0a77cf66..921bf573 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ ## About termscp 🖥 -Termscp is a feature rich terminal file transfer and explorer, with support for SCP/SFTP/FTP/S3. So basically is a terminal utility with an TUI to connect to a remote server to retrieve and upload files and to interact with the local file system. It is **Linux**, **MacOS**, **FreeBSD**, **NetBSD** and **Windows** compatible. +Termscp is a feature rich terminal file transfer and explorer, with support for SCP/SFTP/FTP/Kube/S3/WebDAV. So basically is a terminal utility with an TUI to connect to a remote server to retrieve and upload files and to interact with the local file system. It is **Linux**, **MacOS**, **FreeBSD**, **NetBSD** and **Windows** compatible. ![Explorer](assets/images/explorer.gif) @@ -139,6 +139,7 @@ Termscp is a feature rich terminal file transfer and explorer, with support for - **SFTP** - **SCP** - **FTP** and **FTPS** + - **Kube** - **S3** - **SMB** - **WebDAV** @@ -277,6 +278,7 @@ termscp is powered by these awesome projects: - [crossterm](https://github.com/crossterm-rs/crossterm) - [edit](https://github.com/milkey-mouse/edit) - [keyring-rs](https://github.com/hwchen/keyring-rs) +- [kube](https://github.com/kube-rs/kube) - [open-rs](https://github.com/Byron/open-rs) - [pavao](https://github.com/veeso/pavao) - [remotefs](https://github.com/veeso/remotefs-rs) diff --git a/dist/build/freebsd.sh b/dist/build/freebsd.sh index b8fc1402..f5dccbc9 100755 --- a/dist/build/freebsd.sh +++ b/dist/build/freebsd.sh @@ -34,9 +34,9 @@ rm manifest echo -e "name: \"termscp\"" > manifest echo -e "version: $VERSION" >> manifest echo -e "origin: veeso/termscp" >> manifest -echo -e "comment: \"A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/S3\"" >> manifest +echo -e "comment: \"A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV\"" >> manifest echo -e "desc: <@ +``` + #### SMB Adressargument SMB hat eine andere Syntax für CLI-Adressargumente, die je nach System unterschiedlich ist: diff --git a/docs/es/README.md b/docs/es/README.md index fadace6a..2daf6aae 100644 --- a/docs/es/README.md +++ b/docs/es/README.md @@ -132,7 +132,7 @@ ## Sobre termscp 🖥 -Termscp es un explorador y transferencia de archivos de terminal rico en funciones, con apoyo para SCP/SFTP/FTP/S3. Básicamente, es una utilidad de terminal con una TUI para conectarse a un servidor remoto para recuperar y cargar archivos e interactuar con el sistema de archivos local. Es compatible con **Linux**, **MacOS**, **FreeBSD** y **Windows**. +Termscp es un explorador y transferencia de archivos de terminal rico en funciones, con apoyo para SCP/SFTP/FTP/Kube/S3/WebDAV. Básicamente, es una utilidad de terminal con una TUI para conectarse a un servidor remoto para recuperar y cargar archivos e interactuar con el sistema de archivos local. Es compatible con **Linux**, **MacOS**, **FreeBSD** y **Windows**. ![Explorer](/assets/images/explorer.gif) @@ -144,6 +144,7 @@ Termscp es un explorador y transferencia de archivos de terminal rico en funcion - **SFTP** - **SCP** - **FTP** y **FTPS** + - **Kube** - **S3** - **SMB** - **WebDAV** diff --git a/docs/es/man.md b/docs/es/man.md index 8fd0651f..cceaad19 100644 --- a/docs/es/man.md +++ b/docs/es/man.md @@ -4,6 +4,7 @@ - [Uso ❓](#uso-) - [Argumento dirección 🌎](#argumento-dirección-) - [Argumento dirección por AWS S3](#argumento-dirección-por-aws-s3) + - [Argumento de dirección Kube](#argumento-de-dirección-kube) - [Argumento de dirección de WebDAV](#argumento-de-dirección-de-webdav) - [Argumento dirección por SMB](#argumento-dirección-por-smb) - [Cómo se puede proporcionar la contraseña 🔐](#cómo-se-puede-proporcionar-la-contraseña-) @@ -104,6 +105,14 @@ por ejemplo s3://buckethead@eu-central-1:default:/assets ``` +#### Argumento de dirección Kube + +En caso de que quieras conectarte a Kube, utiliza la siguiente sintaxis + +```txt +kube://@ +``` + #### Argumento de dirección de WebDAV En caso de que quieras conectarte a WebDAV utiliza la siguiente sintaxis diff --git a/docs/fr/README.md b/docs/fr/README.md index 4aa51ca3..c90f4c0f 100644 --- a/docs/fr/README.md +++ b/docs/fr/README.md @@ -132,7 +132,7 @@ ## À propos des termscp 🖥 -Termscp est un file transfer et explorateur de fichiers de terminal riche en fonctionnalités, avec support pour SCP/SFTP/FTP/S3. Essentiellement c'est une utilitaire terminal avec une TUI pour se connecter à un serveur distant pour télécharger de fichiers et interagir avec le système de fichiers local. Il est compatible avec **Linux**, **MacOS**, **FreeBSD** et **Windows**. +Termscp est un file transfer et explorateur de fichiers de terminal riche en fonctionnalités, avec support pour SCP/SFTP/FTP/Kube/S3/WebDAV. Essentiellement c'est une utilitaire terminal avec une TUI pour se connecter à un serveur distant pour télécharger de fichiers et interagir avec le système de fichiers local. Il est compatible avec **Linux**, **MacOS**, **FreeBSD** et **Windows**. ![Explorer](/assets/images/explorer.gif) @@ -144,6 +144,7 @@ Termscp est un file transfer et explorateur de fichiers de terminal riche en fon - **SFTP** - **SCP** - **FTP** et **FTPS** + - **Kube** - **S3** - **SMB** - **WebDAV** diff --git a/docs/fr/man.md b/docs/fr/man.md index c5e016c4..8e7e3c58 100644 --- a/docs/fr/man.md +++ b/docs/fr/man.md @@ -4,6 +4,7 @@ - [Usage ❓](#usage-) - [Argument d'adresse 🌎](#argument-dadresse-) - [Argument d'adresse AWS S3](#argument-dadresse-aws-s3) + - [Argument d'adresse Kube](#argument-dadresse-kube) - [Argument d'adresse WebDAV](#argument-dadresse-webdav) - [Argument d'adresse SMB](#argument-dadresse-smb) - [Comment le mot de passe peut être fourni 🔐](#comment-le-mot-de-passe-peut-être-fourni-) @@ -102,6 +103,14 @@ e.g. s3://buckethead@eu-central-1:default:/assets ``` +#### Argument d'adresse Kube + +Si vous souhaitez vous connecter à Kube, utilisez la syntaxe suivante + +```txt +kube://@ +``` + #### Argument d'adresse WebDAV Dans le cas où vous souhaitez vous connecter à WebDAV, utilisez la syntaxe suivante diff --git a/docs/it/README.md b/docs/it/README.md index 34851bc9..85874764 100644 --- a/docs/it/README.md +++ b/docs/it/README.md @@ -132,7 +132,7 @@ ## Riguardo a termscp 🖥 -Termscp è un file transfer ed explorer ricco di funzionalità, con supporto a SCP/SFTP/FTP/S3. In pratica è un utility su terminale con una terminal user-interface per connettersi a server remoti per scambiare file ed interagire con il file system sia locale che remoto. È compatibile con **Linux**, **MacOS**, **FreeBSD** e **Windows**. +Termscp è un file transfer ed explorer ricco di funzionalità, con supporto a SCP/SFTP/FTP/Kube/S3/WebDAV. In pratica è un utility su terminale con una terminal user-interface per connettersi a server remoti per scambiare file ed interagire con il file system sia locale che remoto. È compatibile con **Linux**, **MacOS**, **FreeBSD** e **Windows**. ![Explorer](/assets/images/explorer.gif) @@ -144,6 +144,7 @@ Termscp è un file transfer ed explorer ricco di funzionalità, con supporto a S - **SFTP** - **SCP** - **FTP** and **FTPS** + - **Kube** - **S3** - **SMB** - **WebDAV** diff --git a/docs/it/man.md b/docs/it/man.md index 4f54a15c..93d7c800 100644 --- a/docs/it/man.md +++ b/docs/it/man.md @@ -4,6 +4,7 @@ - [Argomenti da linea di comando ❓](#argomenti-da-linea-di-comando-) - [Argomento indirizzo 🌎](#argomento-indirizzo-) - [Argomento indirizzo per AWS S3](#argomento-indirizzo-per-aws-s3) + - [Argomento indirizzo Kube](#argomento-indirizzo-kube) - [Argomento indirizzo per WebDAV](#argomento-indirizzo-per-webdav) - [Indirizzo SMB](#indirizzo-smb) - [Come fornire la password 🔐](#come-fornire-la-password-) @@ -100,6 +101,14 @@ e.g. s3://buckethead@eu-central-1:default:/assets ``` +#### Argomento indirizzo Kube + +Nel caso tu voglia connetterti a Kube usa la seguente sintassi + +```txt +kube://@ +``` + #### Argomento indirizzo per WebDAV Nel caso in cui si desideri connettersi a WebDAV utilizzare la seguente sintassi diff --git a/docs/man.md b/docs/man.md index f444d50d..a89996d9 100644 --- a/docs/man.md +++ b/docs/man.md @@ -4,6 +4,7 @@ - [Usage ❓](#usage-) - [Address argument 🌎](#address-argument-) - [AWS S3 address argument](#aws-s3-address-argument) + - [Kube address argument](#kube-address-argument) - [WebDAV address argument](#webdav-address-argument) - [SMB address argument](#smb-address-argument) - [How Password can be provided 🔐](#how-password-can-be-provided-) @@ -105,6 +106,14 @@ e.g. s3://buckethead@eu-central-1:default:/assets ``` +#### Kube address argument + +In case you want to connect to Kube use the following syntax + +```txt +kube://@ +``` + #### WebDAV address argument In case you want to connect to webDAV use the following syntax diff --git a/docs/misc/README.deb.txt b/docs/misc/README.deb.txt index 09847a58..7effc5ee 100644 --- a/docs/misc/README.deb.txt +++ b/docs/misc/README.deb.txt @@ -1,4 +1,4 @@ -Termscp is a feature rich terminal file transfer and explorer, with support for SCP/SFTP/FTP/S3. +Termscp is a feature rich terminal file transfer and explorer, with support for SCP/SFTP/FTP/Kube/S3/WebDAV. Basically is a terminal utility with an TUI to connect to a remote server to retrieve and upload files and to interact with the local file system. diff --git a/docs/ptbr/README.md b/docs/ptbr/README.md index 780a2ff9..08f359c9 100644 --- a/docs/ptbr/README.md +++ b/docs/ptbr/README.md @@ -127,7 +127,7 @@ ## Sobre o termscp 🖥 -Termscp é um explorador e utilitário de transferência de arquivos com uma interface de terminal, com suporte para SCP/SFTP/FTP/S3. Basicamente, é uma ferramenta de terminal com uma interface de usuário para conectar-se a um servidor remoto para baixar e enviar arquivos e interagir com o sistema de arquivos local. Ele é compatível com **Linux**, **MacOS**, **FreeBSD**, **NetBSD** e **Windows**. +Termscp é um explorador e utilitário de transferência de arquivos com uma interface de terminal, com suporte para SCP/SFTP/FTP/Kube/S3/WebDAV. Basicamente, é uma ferramenta de terminal com uma interface de usuário para conectar-se a um servidor remoto para baixar e enviar arquivos e interagir com o sistema de arquivos local. Ele é compatível com **Linux**, **MacOS**, **FreeBSD**, **NetBSD** e **Windows**. ![Explorer](/assets/images/explorer.gif) @@ -139,6 +139,7 @@ Termscp é um explorador e utilitário de transferência de arquivos com uma int - **SFTP** - **SCP** - **FTP** e **FTPS** + - **Kube** - **S3** - **SMB** - **WebDAV** diff --git a/docs/ptbr/man.md b/docs/ptbr/man.md index 75140142..6f0d5b19 100644 --- a/docs/ptbr/man.md +++ b/docs/ptbr/man.md @@ -4,6 +4,7 @@ - [Uso ❓](#uso-) - [Argumento de Endereço 🌎](#argumento-de-endereço-) - [Argumento de Endereço do AWS S3](#argumento-de-endereço-do-aws-s3) + - [Argumento de endereço Kube](#argumento-de-endereço-kube) - [Argumento de Endereço do WebDAV](#argumento-de-endereço-do-webdav) - [Argumento de Endereço do SMB](#argumento-de-endereço-do-smb) - [Como a Senha Pode Ser Fornecida 🔐](#como-a-senha-pode-ser-fornecida-) @@ -105,6 +106,14 @@ Exemplo: s3://buckethead@eu-central-1:default:/assets ``` +#### Argumento de endereço Kube + +Caso queira se conectar ao Kube, use a seguinte sintaxe + +```txt +kube://@ +``` + #### Argumento de Endereço do WebDAV Caso você queira se conectar ao WebDAV, use a seguinte sintaxe: diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index d8d98414..e939f647 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -132,7 +132,7 @@ ## 关于 termscp 🖥 -termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/SFTP/FTP/S3。 作为一个带有 TUI 的命令行工具,它可以连接到远程服务器进行文件检索和上传,并能够与本地文件系统进行交互。 +termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/SFTP/FTP/Kube/S3/WebDAV。 作为一个带有 TUI 的命令行工具,它可以连接到远程服务器进行文件检索和上传,并能够与本地文件系统进行交互。 兼容 **Linux**、**MacOS**、**FreeBSD** 和 **Windows** 操作系统。 @@ -146,6 +146,7 @@ termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/S - **SFTP** - **SCP** - **FTP** and **FTPS** + - **Kube** - **S3** - **SMB** - **WebDAV** diff --git a/docs/zh-CN/man.md b/docs/zh-CN/man.md index 40eeb589..2c8a3ff9 100644 --- a/docs/zh-CN/man.md +++ b/docs/zh-CN/man.md @@ -4,6 +4,7 @@ - [用法](#用法) - [地址参数](#地址参数) - [AWS S3 地址参数](#aws-s3-地址参数) + - [Kube 地址参数](#kube-地址参数) - [WebDAV 地址参数](#webdav-地址参数) - [SMB 地址参数](#smb-地址参数) - [如何输入密码](#如何输入密码) @@ -102,6 +103,14 @@ s3://@[:profile][:/wrkdir] s3://buckethead@eu-central-1:default:/assets ``` +#### Kube 地址参数 + +如果您想连接到 Kube,请使用以下语法 + +```txt +kube://@ +``` + #### WebDAV 地址参数 如果您想要连接到 WebDAV,请使用以下语法 diff --git a/site/changelog.html b/site/changelog.html index c7ce21d2..6b45fbc7 100644 --- a/site/changelog.html +++ b/site/changelog.html @@ -3,13 +3,13 @@ - termscp is a terminal file transfer and explorer for SCP/SFTP/FTP/S3/SMB/WebDAV | termscp + termscp is a terminal file transfer and explorer for SCP/SFTP/FTP/Kube/S3/WebDAV/SMB/WebDAV | termscp + content="termscp is a feature rich terminal file transfer and explorer, with support for SCP/SFTP/FTP/Kube/S3/WebDAV. It is Linux, MacOS, FreeBSD, NetBSD and Windows compatible" /> - + content="termscp is a feature rich terminal file transfer and explorer, with support for SCP/SFTP/FTP/Kube/S3/WebDAV. It is Linux, MacOS, FreeBSD, NetBSD and Windows compatible" /> + diff --git a/site/html/home.html b/site/html/home.html index 40b80664..89e3bd4c 100644 --- a/site/html/home.html +++ b/site/html/home.html @@ -5,14 +5,14 @@

termscp

logo

A feature rich terminal UI file transfer and explorer with support for - SCP/SFTP/FTP/S3/SMB/WebDAV + SCP/SFTP/FTP/Kube/S3/WebDAV/SMB/WebDAV

- termscp 0.13.0 is NOW out! Download it from  + termscp 0.14.0 is NOW out! Download it from  here!

diff --git a/site/index.html b/site/index.html index a8bfb5b5..fad7a838 100644 --- a/site/index.html +++ b/site/index.html @@ -3,14 +3,14 @@ - termscp is a terminal file transfer and explorer for SCP/SFTP/FTP/S3/SMB/WebDAV | termscp + termscp is a terminal file transfer and explorer for SCP/SFTP/FTP/Kube/S3/WebDAV/SMB/WebDAV | termscp + content="a WinSCP alternative for Linux and MacOS with support for SCP/SFTP/FTP/Kube/S3/WebDAV/SMB/WebDAV. Command line file transfer with user interface compatible with all the operating systems." /> + content="a WinSCP alternative for Linux and MacOS with support for SCP/SFTP/FTP/Kube/S3/WebDAV/SMB/WebDAV. Command line file transfer with user interface compatible with all the operating systems." /> + content="termscp is a terminal file transfer and explorer for SCP/SFTP/FTP/Kube/S3/WebDAV/SMB/WebDAV | termscp" /> diff --git a/site/lang/en.json b/site/lang/en.json index b1815bce..67f6e9d0 100644 --- a/site/lang/en.json +++ b/site/lang/en.json @@ -10,7 +10,7 @@ "support": "Support me" }, "intro": { - "caption": "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/S3", + "caption": "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "Get started →", "versionAlert": "termscp 0.13.0 is NOW out! Download it from", "here": "here", diff --git a/site/lang/es.json b/site/lang/es.json index aedbda3c..81ed3528 100644 --- a/site/lang/es.json +++ b/site/lang/es.json @@ -10,7 +10,7 @@ "support": "Apoyame" }, "intro": { - "caption": "Un explorador y transferencia de archivos de terminal rico en funciones, con apoyo para SCP/SFTP/FTP/S3", + "caption": "Un explorador y transferencia de archivos de terminal rico en funciones, con apoyo para SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "Para iniciar →", "versionAlert": "termscp 0.13.0 ya está disponible! Descárgalo desde", "here": "aquì", diff --git a/site/lang/fr.json b/site/lang/fr.json index 6a9327e2..3912539d 100644 --- a/site/lang/fr.json +++ b/site/lang/fr.json @@ -10,7 +10,7 @@ "support": "Me soutenir" }, "intro": { - "caption": "Un file transfer et navigateur de terminal riche en fonctionnalités avec support pour SCP/SFTP/FTP/S3", + "caption": "Un file transfer et navigateur de terminal riche en fonctionnalités avec support pour SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "Pour commencer →", "versionAlert": "termscp 0.13.0 est maintenant sorti! Télécharge-le depuis", "here": "ici", diff --git a/site/lang/zh-CN.json b/site/lang/zh-CN.json index 5a1b194b..a69a71f6 100644 --- a/site/lang/zh-CN.json +++ b/site/lang/zh-CN.json @@ -10,7 +10,7 @@ "support": "支持我" }, "intro": { - "caption": "功能丰富的终端 UI 文件传输和浏览器,支持 SCP/SFTP/FTP/S3", + "caption": "功能丰富的终端 UI 文件传输和浏览器,支持 SCP/SFTP/FTP/Kube/S3/WebDAV", "getStarted": "开始 →", "versionAlert": "termscp 0.13.0 现已发布! 从下载", "here": "这里", diff --git a/src/config/bookmarks.rs b/src/config/bookmarks.rs index 4e71e66f..53161d0d 100644 --- a/src/config/bookmarks.rs +++ b/src/config/bookmarks.rs @@ -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, /// local folder to open at startup pub local_path: Option, + /// Kube params; optional. When used other fields are empty for sure + pub kube: Option, /// S3 params; optional. When used other fields are empty for sure pub s3: Option, /// SMB params; optional. Extra params required for SMB protocol pub smb: Option, } -/// Connection parameters for Aws s3 protocol -#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)] -pub struct S3Params { - pub bucket: String, - pub region: Option, - pub endpoint: Option, - pub profile: Option, - pub access_key: Option, - pub secret_access_key: Option, - /// NOTE: there are no session token and security token since they are always temporary - pub new_path_style: Option, -} - -/// Extra Connection parameters for SMB protocol -#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)] -pub struct SmbParams { - pub share: String, - pub workgroup: Option, -} - // -- impls impl From for Bookmark { @@ -87,6 +76,7 @@ impl From for Bookmark { password: params.password, remote_path, local_path, + kube: None, s3: None, smb: None, }, @@ -98,9 +88,22 @@ impl From 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 for Bookmark { password: params.password, remote_path, local_path, + kube: None, s3: None, }, ProtocolParams::WebDAV(parms) => Self { @@ -123,6 +127,7 @@ impl From for Bookmark { password: Some(parms.password), remote_path, local_path, + kube: None, s3: None, smb: None, }, @@ -149,6 +154,11 @@ impl From 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 for FileTransferParams { } } -impl From 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 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 for SmbParams { - fn from(params: TransferSmbParams) -> Self { - Self { - share: params.share, - workgroup: params.workgroup, - } - } -} - -#[cfg(windows)] -impl From for SmbParams { - fn from(params: TransferSmbParams) -> Self { - Self { - share: params.share, - workgroup: None, - } - } -} - fn deserialize_protocol<'de, D>(deserializer: D) -> Result 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, diff --git a/src/config/bookmarks/aws_s3.rs b/src/config/bookmarks/aws_s3.rs new file mode 100644 index 00000000..9522b991 --- /dev/null +++ b/src/config/bookmarks/aws_s3.rs @@ -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, + pub endpoint: Option, + pub profile: Option, + pub access_key: Option, + pub secret_access_key: Option, + /// NOTE: there are no session token and security token since they are always temporary + pub new_path_style: Option, +} + +impl From 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 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)) + } +} diff --git a/src/config/bookmarks/kube.rs b/src/config/bookmarks/kube.rs new file mode 100644 index 00000000..ed26c2a4 --- /dev/null +++ b/src/config/bookmarks/kube.rs @@ -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, + pub cluster_url: Option, + pub username: Option, + pub client_cert: Option, + pub client_key: Option, +} + +impl From 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 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, + } + } +} diff --git a/src/config/bookmarks/smb.rs b/src/config/bookmarks/smb.rs new file mode 100644 index 00000000..bf92e5ec --- /dev/null +++ b/src/config/bookmarks/smb.rs @@ -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, +} + +#[cfg(unix)] +impl From for SmbParams { + fn from(params: TransferSmbParams) -> Self { + Self { + share: params.share, + workgroup: params.workgroup, + } + } +} + +#[cfg(windows)] +impl From for SmbParams { + fn from(params: TransferSmbParams) -> Self { + Self { + share: params.share, + workgroup: None, + } + } +} diff --git a/src/config/serialization.rs b/src/config/serialization.rs index 662a9784..8a824384 100644 --- a/src/config/serialization.rs +++ b/src/config/serialization.rs @@ -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 = 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" diff --git a/src/filetransfer/builder.rs b/src/filetransfer/builder.rs index 94e8abd7..041683f9 100644 --- a/src/filetransfer/builder.rs +++ b/src/filetransfer/builder.rs @@ -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(¶ms.pod, ¶ms.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( diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index ab9ddabd..4eabe82d 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -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")); } } diff --git a/src/filetransfer/params.rs b/src/filetransfer/params.rs index c87f9158..bcd1eabd 100644 --- a/src/filetransfer/params.rs +++ b/src/filetransfer/params.rs @@ -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, } -/// Connection parameters for AWS S3 protocol -#[derive(Debug, Clone)] -pub struct AwsS3Params { - pub bucket_name: String, - pub region: Option, - pub endpoint: Option, - pub profile: Option, - pub access_key: Option, - pub secret_access_key: Option, - pub security_token: Option, - pub session_token: Option, - 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, - pub password: Option, - #[cfg(unix)] - pub workgroup: Option, -} - 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>(bucket: S, region: Option, profile: Option) -> 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>(mut self, endpoint: Option) -> Self { - self.endpoint = endpoint.map(|x| x.as_ref().to_string()); - self - } - - /// Construct aws s3 params with provided access key - pub fn access_key>(mut self, key: Option) -> 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>(mut self, key: Option) -> 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>(mut self, key: Option) -> 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>(mut self, key: Option) -> 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>(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) -> Self { - self.username = username.map(|x| x.to_string()); - self - } - - pub fn password(mut self, password: Option) -> Self { - self.password = password.map(|x| x.to_string()); - self - } - - #[cfg(unix)] - pub fn workgroup(mut self, workgroup: Option) -> 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!(¶ms.address, "localhost"); - - #[cfg(unix)] - assert_eq!(params.port, 445); - assert_eq!(¶ms.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!(¶ms.address, "localhost"); - assert_eq!(params.port, 3456); - assert_eq!(¶ms.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!(¶ms.address, "localhost"); - assert_eq!(¶ms.share, "temp"); - assert_eq!(params.username.as_deref().unwrap(), "foo"); - assert_eq!(params.password.as_deref().unwrap(), "bar"); - } - #[test] fn references() { let mut params = diff --git a/src/filetransfer/params/aws_s3.rs b/src/filetransfer/params/aws_s3.rs new file mode 100644 index 00000000..d053703b --- /dev/null +++ b/src/filetransfer/params/aws_s3.rs @@ -0,0 +1,121 @@ +/// Connection parameters for AWS S3 protocol +#[derive(Debug, Clone)] +pub struct AwsS3Params { + pub bucket_name: String, + pub region: Option, + pub endpoint: Option, + pub profile: Option, + pub access_key: Option, + pub secret_access_key: Option, + pub security_token: Option, + pub session_token: Option, + pub new_path_style: bool, +} + +// -- S3 params + +impl AwsS3Params { + /// Instantiates a new `AwsS3Params` struct + pub fn new>(bucket: S, region: Option, profile: Option) -> 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>(mut self, endpoint: Option) -> Self { + self.endpoint = endpoint.map(|x| x.as_ref().to_string()); + self + } + + /// Construct aws s3 params with provided access key + pub fn access_key>(mut self, key: Option) -> 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>(mut self, key: Option) -> 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>(mut self, key: Option) -> 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>(mut self, key: Option) -> 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); + } +} diff --git a/src/filetransfer/params/kube.rs b/src/filetransfer/params/kube.rs new file mode 100644 index 00000000..ce2aa13b --- /dev/null +++ b/src/filetransfer/params/kube.rs @@ -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, + pub cluster_url: Option, + pub username: Option, + pub client_cert: Option, + pub client_key: Option, +} + +impl KubeProtocolParams { + pub fn set_default_secret(&mut self, _secret: String) {} + + pub fn password_missing(&self) -> bool { + false + } + + pub fn config(self) -> Option { + 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 + } + } +} diff --git a/src/filetransfer/params/smb.rs b/src/filetransfer/params/smb.rs new file mode 100644 index 00000000..da53bb52 --- /dev/null +++ b/src/filetransfer/params/smb.rs @@ -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, + pub password: Option, + #[cfg(unix)] + pub workgroup: Option, +} + +// -- SMB params + +impl SmbParams { + /// Instantiates a new `AwsS3Params` struct + pub fn new>(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) -> Self { + self.username = username.map(|x| x.to_string()); + self + } + + pub fn password(mut self, password: Option) -> Self { + self.password = password.map(|x| x.to_string()); + self + } + + #[cfg(unix)] + pub fn workgroup(mut self, workgroup: Option) -> 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!(¶ms.address, "localhost"); + + #[cfg(unix)] + assert_eq!(params.port, 445); + assert_eq!(¶ms.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!(¶ms.address, "localhost"); + assert_eq!(params.port, 3456); + assert_eq!(¶ms.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!(¶ms.address, "localhost"); + assert_eq!(¶ms.share, "temp"); + assert_eq!(params.username.as_deref().unwrap(), "foo"); + assert_eq!(params.password.as_deref().unwrap(), "bar"); + } +} diff --git a/src/filetransfer/params/webdav.rs b/src/filetransfer/params/webdav.rs new file mode 100644 index 00000000..9c8bec18 --- /dev/null +++ b/src/filetransfer/params/webdav.rs @@ -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() + } +} diff --git a/src/ui/activities/auth/bookmarks.rs b/src/ui/activities/auth/bookmarks.rs index e970aa3a..67f9d1aa 100644 --- a/src/ui/activities/auth/bookmarks.rs +++ b/src/ui/activities/auth/bookmarks.rs @@ -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(¶ms.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)] diff --git a/src/ui/activities/auth/components/form.rs b/src/ui/activities/auth/components/form.rs index 5ffd349c..b813c2ee 100644 --- a/src/ui/activities/auth/components/form.rs +++ b/src/ui/activities/auth/components/form.rs @@ -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 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 for InputKubePodName { + fn on(&mut self, ev: Event) -> Option { + 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 for InputKubeNamespace { + fn on(&mut self, ev: Event) -> Option { + 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 for InputKubeClusterUrl { + fn on(&mut self, ev: Event) -> Option { + 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 for InputKubeContainer { + fn on(&mut self, ev: Event) -> Option { + 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 for InputKubeUsername { + fn on(&mut self, ev: Event) -> Option { + 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 for InputKubeClientCert { + fn on(&mut self, ev: Event) -> Option { + 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 for InputKubeClientKey { + fn on(&mut self, ev: Event) -> Option { + handle_input_ev( + self, + ev, + Msg::Ui(UiMsg::KubeClientKeyBlurDown), + Msg::Ui(UiMsg::KubeClientKeyBlurUp), + ) + } +} diff --git a/src/ui/activities/auth/components/mod.rs b/src/ui/activities/auth/components/mod.rs index 37139cae..2d875f0a 100644 --- a/src/ui/activities/auth/components/mod.rs +++ b/src/ui/activities/auth/components/mod.rs @@ -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, diff --git a/src/ui/activities/auth/misc.rs b/src/ui/activities/auth/misc.rs index 35e8e5b9..c3b97904 100644 --- a/src/ui/activities/auth/misc.rs +++ b/src/ui/activities/auth/misc.rs @@ -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 { 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 { + 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 { let params = self.get_smb_params_input(); if params.address.is_empty() { diff --git a/src/ui/activities/auth/mod.rs b/src/ui/activities/auth/mod.rs index 7c24e836..de095ca3 100644 --- a/src/ui/activities/auth/mod.rs +++ b/src/ui/activities/auth/mod.rs @@ -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, } diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index eb02b0df..930b7aa9 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -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, }) diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index a680606b..b75c5339 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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() { diff --git a/src/ui/activities/filetransfer/misc.rs b/src/ui/activities/filetransfer/misc.rs index caddd348..e61613e9 100644 --- a/src/ui/activities/filetransfer/misc.rs +++ b/src/ui/activities/filetransfer/misc.rs @@ -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 {}:{}", diff --git a/src/ui/activities/setup/components/config.rs b/src/ui/activities/setup/components/config.rs index 525f4dc8..0ca498f2 100644 --- a/src/ui/activities/setup/components/config.rs +++ b/src/ui/activities/setup/components/config.rs @@ -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, diff --git a/src/ui/activities/setup/mod.rs b/src/ui/activities/setup/mod.rs index 86c960d2..fc1d4dfe 100644 --- a/src/ui/activities/setup/mod.rs +++ b/src/ui/activities/setup/mod.rs @@ -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)] diff --git a/src/ui/activities/setup/view/setup.rs b/src/ui/activities/setup/view/setup.rs index a264943a..393acde0 100644 --- a/src/ui/activities/setup/view/setup.rs +++ b/src/ui/activities/setup/view/setup.rs @@ -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, diff --git a/src/utils/parser.rs b/src/utils/parser.rs index 9b41e124..7c9b2011 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -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 = lazy_regex!( * - group 4: Some(path) | None */ static REMOTE_WEBDAV_OPT_REGEX: Lazy = - 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 = lazy_regex!(r"(?:(.+[^@])@)(?:([^/]+))(?:(.+))?"); /** * Regex matches: @@ -162,6 +170,7 @@ pub fn parse_remote_opt(s: &str) -> Result { // 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 { } } +fn parse_kube_remote_opt(s: &str) -> Result { + 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 = + 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 { @@ -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() {