Skip to content

Commit

Permalink
feat: initial composer setup
Browse files Browse the repository at this point in the history
  • Loading branch information
merklefruit committed Oct 7, 2023
1 parent f160592 commit 0d65a6c
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 0 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions crates/composer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "op-composer"
description = "A Docker Compose-like tool for managing Op Stack components"

version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true

[dependencies]
op-primitives = { path = "../primitives" }

strum.workspace = true
inquire.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_regex.workspace = true
enum-variants-strings.workspace = true

eyre.workspace = true
tracing.workspace = true

hex-literal.workspace = true
once_cell.workspace = true

bollard = "0.15.0"
tokio = { version = "1.11.0", features = ["full"] }

[dev-dependencies]
pretty_assertions = "1"
Empty file added crates/composer/README.md
Empty file.
145 changes: 145 additions & 0 deletions crates/composer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use std::collections::HashMap;

use bollard::{
container::{
Config, CreateContainerOptions, ListContainersOptions, RemoveContainerOptions,
StartContainerOptions, StopContainerOptions,
},
service::ContainerSummary,
Docker,
};
use eyre::Result;

/// Placeholder config struct.
/// TODO: Use the actual parsed component TOML file struct instead of this placeholder
pub struct ComponentConfig<'a> {
name: &'a str,
}

/// The Composer is responsible for managing the OP-UP docker containers.
pub struct Composer {
pub daemon: Docker,
}

impl Composer {
/// Create a new instance of the Composer.
pub async fn new() -> Self {
let daemon = Docker::connect_with_local_defaults().expect(
"Failed to connect to Docker daemon.
Please check that Docker is installed and running on your machine",
);

Self { daemon }
}

/// List all the OP-UP docker containers existing on the host.
///
/// The containers are filtered by the label `com.docker.compose.project=op-up`.
///
/// This method allows optional filtering by container status:
/// `created`|`restarting`|`running`|`removing`|`paused`|`exited`|`dead`
pub async fn list_opup_containers(
&self,
status: Option<&str>,
) -> Result<Vec<ContainerSummary>> {
let mut filters = HashMap::new();
filters.insert("label", vec!["com.docker.compose.project=op-up"]);

if let Some(status) = status {
filters.insert("status", vec![status]);
}

let list_options = ListContainersOptions {
all: true,
limit: None,
size: false,
filters,
};

self.daemon
.list_containers(Some(list_options))
.await
.map_err(Into::into)
}

/// Create a Docker container for the specified OP Stack component
///
/// The container will be created from the options specified in the component TOML file.
pub async fn create_container(&self, component: ComponentConfig<'_>) -> Result<()> {
let create_options = CreateContainerOptions {
name: component.name,
platform: None,
};

let mut labels = HashMap::new();
labels.insert("com.docker.compose.project", "op-up");

let config = Config {
image: None,
labels: Some(labels),
..Default::default()
};

let res = self
.daemon
.create_container(Some(create_options), config)
.await?;

tracing::debug!(
"Created docker container {} with ID: {}",
component.name,
res.id
);

Ok(())
}

/// Start the specified OP Stack component container.
pub async fn start_container(&self, name: &str) -> Result<()> {
self.daemon
.start_container(name, None::<StartContainerOptions<&str>>)
.await?;

Ok(())
}

/// Stop all OP-UP docker containers at once.
pub async fn stop_all_opup_containers(&self) -> Result<()> {
let running_containers = self.list_opup_containers(Some("running")).await?;

let names = running_containers
.iter()
.filter_map(|container| container.names.as_ref().and_then(|names| names.first()))
.collect::<Vec<_>>();

tracing::info!("Stopping containers: {:?}", names);

for name in names {
self.daemon
.stop_container(name, None::<StopContainerOptions>)
.await?;

tracing::debug!("Successfully stopped container: {}", name);
}

Ok(())
}

pub async fn purge_all_opup_containers(&self) -> Result<()> {
let containers = self.list_opup_containers(None).await?;

for container in containers {
let name = container
.names
.as_ref()
.and_then(|names| names.first())
.ok_or_else(|| eyre::eyre!("Container name not found"))?;

self.daemon
.remove_container(name, None::<RemoveContainerOptions>)
.await?;
}

Ok(())
}
}

0 comments on commit 0d65a6c

Please sign in to comment.