Skip to content

Commit 92be60e

Browse files
committed
Container start works, stop doesn't....
1 parent edb060a commit 92be60e

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

examples/lading-container.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
generator:
2+
- container:
3+
repository: ghcr.io/scottopell/randomreader
4+
tag: latest
5+
args: [ "--buffer-size-mb", "10" ]
6+
7+
blackhole:
8+
- http:
9+
binding_addr: "0.0.0.0:8080"

lading/src/generator.rs

+13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use tracing::{error, warn};
1515

1616
use crate::target::TargetPidReceiver;
1717

18+
pub mod container;
1819
pub mod file_gen;
1920
pub mod file_tree;
2021
pub mod grpc;
@@ -67,6 +68,9 @@ pub enum Error {
6768
/// See [`crate::generator::procfs::Error`] for details.
6869
#[error(transparent)]
6970
ProcFs(#[from] procfs::Error),
71+
/// See [`crate::generator::container::Error`] for details.
72+
#[error(transparent)]
73+
Container(#[from] container::Error),
7074
}
7175

7276
#[derive(Debug, Deserialize, Serialize, PartialEq)]
@@ -120,6 +124,8 @@ pub enum Inner {
120124
ProcessTree(process_tree::Config),
121125
/// See [`crate::generator::procfs::Config`] for details.
122126
ProcFs(procfs::Config),
127+
/// See [`crate::generator::container::Config`] for details.
128+
Container(container::Config),
123129
}
124130

125131
#[derive(Debug)]
@@ -152,6 +158,8 @@ pub enum Server {
152158
ProcessTree(process_tree::ProcessTree),
153159
/// See [`crate::generator::procfs::Procfs`] for details.
154160
ProcFs(procfs::ProcFs),
161+
/// See [`crate::generator::container::Container`] for details.
162+
Container(container::Container),
155163
}
156164

157165
impl Server {
@@ -204,6 +212,9 @@ impl Server {
204212
Self::ProcessTree(process_tree::ProcessTree::new(&conf, shutdown)?)
205213
}
206214
Inner::ProcFs(conf) => Self::ProcFs(procfs::ProcFs::new(&conf, shutdown)?),
215+
Inner::Container(conf) => {
216+
Self::Container(container::Container::new(config.general, &conf, shutdown)?)
217+
}
207218
};
208219
Ok(srv)
209220
}
@@ -237,6 +248,8 @@ impl Server {
237248
Server::PassthruFile(inner) => inner.spin().await?,
238249
Server::ProcessTree(inner) => inner.spin().await?,
239250
Server::ProcFs(inner) => inner.spin().await?,
251+
// Run the container generator
252+
Server::Container(inner) => inner.spin().await?,
240253
};
241254

242255
Ok(())

lading/src/generator/container.rs

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
//! The container generator
2+
//!
3+
//! This generator is meant to spin up a container from a configured image. For now,
4+
//! it does not actually do anything beyond logging that it's running and then waiting
5+
//! for a shutdown signal.
6+
//!
7+
//! ## Future Work
8+
//! - Pull and run a specified container image
9+
//! - Possibly support metrics about container lifecycle
10+
11+
use bollard::container::{
12+
Config as ContainerConfig, CreateContainerOptions, RemoveContainerOptions,
13+
StartContainerOptions, StopContainerOptions,
14+
};
15+
use bollard::image::CreateImageOptions;
16+
use bollard::secret::ContainerCreateResponse;
17+
use bollard::Docker;
18+
use serde::{Deserialize, Serialize};
19+
use tokio::pin;
20+
use tokio_stream::StreamExt;
21+
use tracing::{info, warn};
22+
use uuid::Uuid;
23+
24+
use super::General;
25+
26+
#[derive(Debug, Deserialize, Serialize, PartialEq)]
27+
#[serde(deny_unknown_fields)]
28+
/// Configuration of the container generator.
29+
pub struct Config {
30+
/// The seed for random operations (not currently used, but included for parity)
31+
//pub seed: [u8; 32],
32+
/// The container repository (e.g. "library/nginx")
33+
pub repository: String,
34+
/// The container image tag (e.g. "latest")
35+
pub tag: String,
36+
/// Arguments to provide to the container (docker calls this args, but that's a dumb name)
37+
pub args: Option<Vec<String>>,
38+
}
39+
40+
/// Errors produced by the `Container` generator.
41+
#[derive(thiserror::Error, Debug)]
42+
pub enum Error {
43+
/// Generic error produced by the container generator.
44+
#[error("Generic container error: {0}")]
45+
Generic(String),
46+
/// Error produced by the Bollard Docker client.
47+
#[error("Bollard/Docker error: {0}")]
48+
Bollard(#[from] bollard::errors::Error),
49+
}
50+
51+
#[derive(Debug)]
52+
/// Represents a container that can be spun up from a configured image.
53+
pub struct Container {
54+
image: String,
55+
tag: String,
56+
args: Option<Vec<String>>,
57+
shutdown: lading_signal::Watcher,
58+
}
59+
60+
impl Container {
61+
/// Create a new `Container` instance
62+
///
63+
/// # Errors
64+
///
65+
/// Will return an error if config parsing fails or runtime setup fails
66+
/// in the future. For now, always succeeds.
67+
pub fn new(
68+
general: General,
69+
config: &Config,
70+
shutdown: lading_signal::Watcher,
71+
) -> Result<Self, Error> {
72+
Ok(Self {
73+
image: config.repository.clone(),
74+
tag: config.tag.clone(),
75+
args: config.args.clone(),
76+
shutdown,
77+
})
78+
}
79+
80+
/// Run the `Container` generator.
81+
///
82+
/// # Errors
83+
///
84+
/// Will return an error if Docker connection fails, image pulling fails,
85+
/// container creation fails, container start fails, or container removal fails.
86+
///
87+
/// Steps:
88+
/// 1. Connect to Docker.
89+
/// 2. Pull the specified image (if not available).
90+
/// 3. Create and start the container.
91+
/// 4. Wait for shutdown signal.
92+
/// 5. On shutdown, stop and remove the container.
93+
pub async fn spin(self) -> Result<(), Error> {
94+
info!("Container generator running: {}:{}", self.image, self.tag);
95+
96+
let docker = Docker::connect_with_local_defaults()?;
97+
98+
let full_image = format!("{}:{}", self.image, self.tag);
99+
info!("Ensuring image is available: {}", full_image);
100+
101+
// Pull the image
102+
let mut pull_stream = docker.create_image(
103+
Some(CreateImageOptions::<String> {
104+
from_image: full_image.clone(),
105+
..Default::default()
106+
}),
107+
None,
108+
None,
109+
);
110+
111+
while let Some(item) = pull_stream.next().await {
112+
match item {
113+
Ok(status) => {
114+
if let Some(progress) = status.progress {
115+
info!("Pull progress: {}", progress);
116+
}
117+
}
118+
Err(e) => {
119+
warn!("Pull error: {}", e);
120+
return Err(e.into());
121+
}
122+
}
123+
}
124+
125+
let container_name = format!("lading_container_{}", Uuid::new_v4());
126+
info!("Creating container: {}", container_name);
127+
128+
let container = docker
129+
.create_container(
130+
Some(CreateContainerOptions {
131+
name: &container_name,
132+
platform: None,
133+
}),
134+
ContainerConfig {
135+
image: Some(full_image.as_str()),
136+
tty: Some(true),
137+
cmd: self
138+
.args
139+
.as_ref()
140+
.map(|args| args.iter().map(String::as_str).collect()),
141+
..Default::default()
142+
},
143+
)
144+
.await?;
145+
146+
info!("Created container with id: {}", container.id);
147+
for warning in &container.warnings {
148+
warn!("Container warning: {}", warning);
149+
}
150+
151+
docker
152+
.start_container(&container.id, None::<StartContainerOptions<String>>)
153+
.await?;
154+
155+
info!("Started container: {}", container.id);
156+
157+
// Wait for shutdown signal
158+
let shutdown_wait = self.shutdown.recv();
159+
tokio::pin!(shutdown_wait);
160+
tokio::select! {
161+
() = &mut shutdown_wait => {
162+
info!("shutdown signal received");
163+
Self::stop_and_remove_container(&docker, &container).await?;
164+
165+
Ok(())
166+
}
167+
}
168+
}
169+
170+
async fn stop_and_remove_container(
171+
docker: &Docker,
172+
container: &ContainerCreateResponse,
173+
) -> Result<(), Error> {
174+
info!("Stopping container: {}", container.id);
175+
if let Err(e) = docker
176+
.stop_container(&container.id, Some(StopContainerOptions { t: 5 }))
177+
.await
178+
{
179+
warn!("Error stopping container {}: {}", container.id, e);
180+
}
181+
182+
info!("Removing container: {}", container.id);
183+
docker
184+
.remove_container(
185+
&container.id,
186+
Some(RemoveContainerOptions {
187+
force: true,
188+
..Default::default()
189+
}),
190+
)
191+
.await?;
192+
193+
info!("Removed container: {}", container.id);
194+
Ok(())
195+
}
196+
}

0 commit comments

Comments
 (0)