Skip to main content

ironflow_ops_docker/containers/
create.rs

1//! Container creation.
2
3use async_trait::async_trait;
4use bollard::Docker;
5use bollard::models::ContainerCreateBody;
6use bollard::query_parameters::CreateContainerOptions;
7use ironflow_core::error::OperationError;
8use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::containers::DockerRef;
13use crate::helpers::{docker_error, to_value};
14
15/// Output of a container creation.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ContainerCreateOutput {
18    /// The created container ID.
19    pub id: String,
20    /// Warnings from the Docker daemon.
21    pub warnings: Vec<String>,
22}
23
24/// Create a new container.
25///
26/// # Examples
27///
28/// ```no_run
29/// use ironflow_ops_docker::containers::ContainerCreate;
30/// use ironflow_ops_docker::DockerClient;
31/// use ironflow_core::operation::Operation;
32///
33/// let client = DockerClient::connect_local().unwrap();
34/// let op = ContainerCreate::new(&client, "my-container", "alpine:latest");
35/// assert_eq!(op.kind(), "docker");
36/// ```
37pub struct ContainerCreate {
38    docker: Docker,
39    name: String,
40    image: String,
41    cmd: Option<Vec<String>>,
42    env: Option<Vec<String>>,
43    exposed_ports: Option<Vec<String>>,
44}
45
46impl ContainerCreate {
47    /// Create a new container-create operation.
48    pub fn new(
49        client: impl Into<DockerRef>,
50        name: impl Into<String>,
51        image: impl Into<String>,
52    ) -> Self {
53        Self {
54            docker: client.into().0,
55            name: name.into(),
56            image: image.into(),
57            cmd: None,
58            env: None,
59            exposed_ports: None,
60        }
61    }
62
63    /// Set the command to run in the container.
64    pub fn cmd(mut self, cmd: Vec<String>) -> Self {
65        self.cmd = Some(cmd);
66        self
67    }
68
69    /// Set environment variables for the container.
70    pub fn env(mut self, env: Vec<String>) -> Self {
71        self.env = Some(env);
72        self
73    }
74
75    /// Set exposed ports for the container.
76    pub fn exposed_ports(mut self, ports: Vec<String>) -> Self {
77        self.exposed_ports = Some(ports);
78        self
79    }
80
81    /// Execute and return a typed result.
82    ///
83    /// # Errors
84    ///
85    /// Returns [`OperationError::External`] if the Docker daemon rejects the
86    /// creation request.
87    pub async fn run(
88        &self,
89        _ctx: &OperationContext,
90    ) -> Result<ContainerCreateOutput, OperationError> {
91        let options = CreateContainerOptions {
92            name: Some(self.name.clone()),
93            platform: String::new(),
94        };
95        let config = ContainerCreateBody {
96            image: Some(self.image.clone()),
97            cmd: self.cmd.clone(),
98            env: self.env.clone(),
99            exposed_ports: self.exposed_ports.clone(),
100            ..Default::default()
101        };
102        let response = self
103            .docker
104            .create_container(Some(options), config)
105            .await
106            .map_err(docker_error)?;
107        Ok(ContainerCreateOutput {
108            id: response.id,
109            warnings: response.warnings,
110        })
111    }
112}
113
114#[async_trait]
115impl Operation for ContainerCreate {
116    fn kind(&self) -> &str {
117        "docker"
118    }
119
120    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
121        to_value(&self.run(ctx).await?)
122    }
123
124    fn input(&self) -> Option<Value> {
125        Some(serde_json::json!({
126            "operation": "container_create",
127            "name": self.name,
128            "image": self.image,
129        }))
130    }
131}
132
133impl TypedOperation for ContainerCreate {
134    type Output = ContainerCreateOutput;
135}