feat: add container hashmap

This commit is contained in:
Alexander Navarro 2025-06-25 16:14:35 -04:00
commit bb1784f259
12 changed files with 1511 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

8
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

11
.idea/epoch.iml generated Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/epoch.iml" filepath="$PROJECT_DIR$/.idea/epoch.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1349
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

9
Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "epoch"
version = "0.1.0"
edition = "2024"
[dependencies]
bollard = "0.19.1"
thiserror = "2.0.12"
tokio = { version = "1.45.1", features = ["macros", "rt", "rt-multi-thread"] }

View file

@ -0,0 +1,18 @@
name: epoch_postgres_example
services:
db:
image: postgres
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=example
- POSTGRES_PASSWORD=secret
labels:
- epoch.manage=true
- epoch.service.group=databases
volumes:
db_data:

15
src/error.rs Normal file
View file

@ -0,0 +1,15 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, self::Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("Unhandled error: {0}")]
Static(&'static str),
#[error("Unhandled error: {0}")]
Generic(String),
#[error(transparent)]
Docker(#[from] bollard::errors::Error),
}

4
src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod error;
pub mod manager;
pub use error::{Error, Result};

23
src/main.rs Normal file
View file

@ -0,0 +1,23 @@
use bollard::Docker;
use std::collections::HashMap;
use tokio;
#[tokio::main]
async fn main() -> epoch::Result<()> {
let docker = Docker::connect_with_local_defaults()?;
let filters: HashMap<&str, Vec<&str>> = HashMap::from(
[
("label", vec!["epoch.manage=true"]),
],
);
let opts = bollard::query_parameters::ListContainersOptionsBuilder::new()
.filters(&filters).build();
let containers = docker.list_containers(Some(opts)).await?;
epoch::manager::manage(&containers).await?;
Ok(())
}

59
src/manager.rs Normal file
View file

@ -0,0 +1,59 @@
use bollard::models::ContainerSummary;
use std::collections::HashMap;
/// Namespace to manage containers together (like compose projects)
type ServiceGroup = String;
const DEFAULT_GROUP: &str = "NONE";
#[derive(Debug)]
struct Services(HashMap<ServiceGroup, Vec<ContainerSummary>>);
impl From<&Vec<ContainerSummary>> for Services {
fn from(value: &Vec<ContainerSummary>) -> Self {
let mut services = HashMap::new();
for container in value.iter().cloned() {
if container
.mounts
.as_deref()
.is_none_or(|mounts| mounts.is_empty())
{
continue;
}
let project = match &container.labels {
None => DEFAULT_GROUP.to_owned(),
Some(labels) => {
// Returns user provided group if exits
if labels.contains_key("epoch.service.group") {
labels
.get("epoch.service.group")
.and_then(|value| Some(value.to_owned()))
.unwrap()
} else {
labels
// Group by compose hash
.get("com.docker.compose.config-hash")
.and_then(|value| Some(value.to_owned()))
// No group found
.unwrap_or(DEFAULT_GROUP.to_owned())
}
}
};
let list: &mut Vec<ContainerSummary> = services.entry(project).or_default();
list.push(container);
}
Self(services)
}
}
pub async fn manage(containers: &Vec<ContainerSummary>) -> crate::Result<()> {
let services = Services::from(containers);
println!("{:#?}", services);
Ok(())
}