Skip to content

Commit

Permalink
feat: multi pod (#1)
Browse files Browse the repository at this point in the history
* feat: multi pod client

* fix: tests and fixes

* fix: version

* fix: changelog
  • Loading branch information
veeso authored Sep 29, 2024
1 parent a843013 commit 362122f
Show file tree
Hide file tree
Showing 7 changed files with 1,891 additions and 61 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# Changelog

- [Changelog](#changelog)
- [0.3.0](#030)
- [0.2.0](#020)
- [0.1.0](#010)

---

## 0.3.0

Released on 29/09/2024

- Added `KubeMultiPodFs` to operate on multiple pod and containers at the same time. See docs for details.
- **BREAKING ‼️** Renamed `KubeFs` to `KubeContainerFs`.

## 0.2.0

Released on 17/07/2024
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ license = "MIT"
name = "remotefs-kube"
readme = "README.md"
repository = "https://github.com/veeso/remotefs-rs-kube"
version = "0.2.0"
version = "0.3.0"

[dependencies]
chrono = "^0.4"
Expand All @@ -28,6 +28,7 @@ tokio-util = "0.7"

[dev-dependencies]
env_logger = "^0.11"
k8s-openapi = { version = "0.22", features = ["v1_30"] }
kube = { version = "0.92", features = ["client", "config", "runtime", "ws"] }
pretty_assertions = "1"
rand = "^0.8.4"
Expand Down
67 changes: 60 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<p align="center">~ Remotefs kube client ~</p>

<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Current version: 0.2.0 (17/07/2024)</p>
<p align="center">Current version: 0.3.0 (29/09/2024)</p>

<p align="center">
<a href="https://opensource.org/licenses/MIT"
Expand Down Expand Up @@ -49,23 +49,76 @@ First of all you need to add **remotefs** and the client to your project depende

```toml
remotefs = "^0.2"
remotefs-kube = "^0.2"
remotefs-kube = "^0.3"
```

these features are supported:

- `find`: enable `find()` method for RemoteFs. (*enabled by default*)
- `no-log`: disable logging. By default, this library will log via the `log` crate.

### Kube client
The library provides two different clients:

Here is a basic usage example, with the `Kube` client, which is very similiar to the `Scp` client.
- **KubeMultiPodFs** client
- **KubeContainerFs** client

```rust,ignore
### Kube multi pod client

The MultiPod client gives access to all the pods with their own containers in a namespace.

This client creates an abstract file system with the following structure

- / (root)
- pod-a
- container-a
- / (container-a root)
- /bin
- /home
- ...
- container-b
- / (container-b root)
- ...
- pod-b
- container-c
- / (container-c root)
- ...

So paths have the following structure: `/pod-name/container-name/path/to/file`.

```rust

// import remotefs trait and client
use remotefs::RemoteFs;
use remotefs_kube::KubeMultiPodFs;
use std::path::Path;

let rt = Arc::new(
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap(),
);
let mut client: KubeMultiPodFs = KubeMultiPodFs::new(&rt);

// connect
assert!(client.connect().is_ok());
// get working directory
println!("Wrkdir: {}", client.pwd().ok().unwrap().display());
// change working directory
assert!(client.change_dir(Path::new("/my-pod/alpine/tmp")).is_ok());
// disconnect
assert!(client.disconnect().is_ok());
```

### Kube container client

Here is a basic usage example, with the `KubeContainerFs` client, which is used to connect and interact with a single container on a certain pod. This client gives the entire access to the container file system.

```rust

// import remotefs trait and client
use remotefs::RemoteFs;
use remotefs_ssh::{SshConfigParseRule, SftpFs, SshOpts};
use remotefs_kube::KubeContainerFs;
use std::path::Path;

let rt = Arc::new(
Expand All @@ -74,7 +127,7 @@ let rt = Arc::new(
.build()
.unwrap(),
);
let mut client: KubeFs = KubeFs::new("my-pod", &rt);
let mut client: KubeContainerFs = KubeContainerFs::new("my-pod", "container-name", &rt);

// connect
assert!(client.connect().is_ok());
Expand Down
75 changes: 30 additions & 45 deletions src/client.rs → src/kube_container_fs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! ## SCP
//! ## Kube Container FS
//!
//! Scp remote fs implementation
//! The `KubeContainerFs` client is a client that allows you to interact with a container in a pod.
use std::ops::Range;
use std::path::{Path, PathBuf};
Expand All @@ -27,17 +27,17 @@ static LS_RE: Lazy<Regex> = lazy_regex!(
r#"^([\-ld])([\-rwxsStT]{9})\s+(\d+)\s+(.+)\s+(.+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#
);

/// Kube "filesystem" client
pub struct KubeFs {
config: Option<Config>,
container: String,
pod_name: String,
pods: Option<Api<Pod>>,
/// Kube "filesystem" client to interact with a container in a pod
pub struct KubeContainerFs {
pub(crate) config: Option<Config>,
pub(crate) container: String,
pub(crate) pod_name: String,
pub(crate) pods: Option<Api<Pod>>,
runtime: Arc<Runtime>,
wrkdir: PathBuf,
pub(crate) wrkdir: PathBuf,
}

impl KubeFs {
impl KubeContainerFs {
/// Creates a new `KubeFs`
///
/// If `config()` is not called then, it will try to use the configuration from the default kubeconfig file
Expand Down Expand Up @@ -321,7 +321,7 @@ impl KubeFs {
}
}

impl RemoteFs for KubeFs {
impl RemoteFs for KubeContainerFs {
fn connect(&mut self) -> RemoteResult<Welcome> {
debug!("Initializing Kube connection...");
let api = self.runtime.block_on(async {
Expand Down Expand Up @@ -863,7 +863,7 @@ mod test {
.build()
.unwrap(),
);
let mut client = KubeFs::new("test", "test", &rt);
let mut client = KubeContainerFs::new("test", "test", &rt);
assert!(client.config.is_none());
assert_eq!(client.is_connected(), false);
}
Expand All @@ -876,7 +876,7 @@ mod test {
.build()
.unwrap(),
);
let mut client = KubeFs::new("aaaaaa", "test", &rt);
let mut client = KubeContainerFs::new("aaaaaa", "test", &rt);
assert!(client.connect().is_err());
}

Expand Down Expand Up @@ -1490,7 +1490,7 @@ mod test {
.build()
.unwrap(),
);
let client = KubeFs::new("test", "test", &rt);
let client = KubeContainerFs::new("test", "test", &rt);
assert_eq!(
client.get_name_and_link("Cargo.toml"),
(String::from("Cargo.toml"), None)
Expand All @@ -1509,7 +1509,7 @@ mod test {
.build()
.unwrap(),
);
let client = KubeFs::new("test", "test", &rt);
let client = KubeContainerFs::new("test", "test", &rt);
// File
let entry = client
.parse_ls_output(
Expand Down Expand Up @@ -1550,7 +1550,7 @@ mod test {
.build()
.unwrap(),
);
let client = KubeFs::new("test", "test", &rt);
let client = KubeContainerFs::new("test", "test", &rt);
// Directory
let entry = client
.parse_ls_output(
Expand Down Expand Up @@ -1595,7 +1595,7 @@ mod test {
.build()
.unwrap(),
);
let client = KubeFs::new("test", "test", &rt);
let client = KubeContainerFs::new("test", "test", &rt);
// File
let entry = client
.parse_ls_output(
Expand Down Expand Up @@ -1623,7 +1623,7 @@ mod test {
.build()
.unwrap(),
);
let client = KubeFs::new("test", "test", &rt);
let client = KubeContainerFs::new("test", "test", &rt);
assert!(client
.parse_ls_output(
Path::new("/tmp"),
Expand Down Expand Up @@ -1660,7 +1660,7 @@ mod test {
.build()
.unwrap(),
);
let mut client = KubeFs::new("test", "test", &rt);
let mut client = KubeContainerFs::new("test", "test", &rt);
assert!(client.change_dir(Path::new("/tmp")).is_err());
assert!(client
.copy(Path::new("/nowhere"), PathBuf::from("/culonia").as_path())
Expand Down Expand Up @@ -1693,7 +1693,7 @@ mod test {
// -- test utils

#[cfg(feature = "integration-tests")]
fn setup_client() -> (Api<Pod>, KubeFs) {
fn setup_client() -> (Api<Pod>, KubeContainerFs) {
// setup pod with random name

use kube::api::PostParams;
Expand Down Expand Up @@ -1784,7 +1784,7 @@ mod test {
pods
});

let mut client = KubeFs::new(&pod_name, "alpine", &runtime).config(config.clone());
let mut client = KubeContainerFs::new(&pod_name, "alpine", &runtime).config(config.clone());
client.connect().expect("connection failed");
// Create wrkdir
let tempdir = PathBuf::from(generate_tempdir());
Expand All @@ -1799,37 +1799,22 @@ mod test {
}

#[cfg(feature = "integration-tests")]
fn finalize_client(pods: Api<Pod>, mut client: KubeFs) {
// Get working directory

use kube::api::DeleteParams;
use kube::ResourceExt as _;
let wrkdir = client.pwd().ok().unwrap();
// Remove directory
assert!(client.remove_dir_all(wrkdir.as_path()).is_ok());
fn finalize_client(_pods: Api<Pod>, mut client: KubeContainerFs) {
assert!(client.disconnect().is_ok());

// cleanup pods
let pod_name = client.pod_name;
client.runtime.block_on(async {
let dp = DeleteParams::default();
pods.delete(&pod_name, &dp).await.unwrap().map_left(|pdel| {
info!("Deleting {pod_name} pod started: {:?}", pdel);
assert_eq!(pdel.name_any(), pod_name);
});
})
}

#[cfg(feature = "integration-tests")]
fn generate_pod_name() -> String {
use rand::distributions::{Alphanumeric, DistString};
use rand::thread_rng;
let random_string: String = Alphanumeric
.sample_string(&mut thread_rng(), 8)
.chars()
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng as _};

let mut rng = thread_rng();
let random_string: String = std::iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.filter(|c| c.is_alphabetic())
.map(|c| c.to_ascii_lowercase())
.take(8)
.take(12)
.collect();

format!("test-{}", random_string)
Expand Down
Loading

0 comments on commit 362122f

Please sign in to comment.