docker_wrapper/command/
update.rs

1//! Docker update command implementation.
2//!
3//! This module provides the `docker update` command for updating container configurations.
4
5use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use async_trait::async_trait;
8
9/// Docker update command builder
10///
11/// Update configuration of one or more containers.
12///
13/// # Example
14///
15/// ```no_run
16/// use docker_wrapper::UpdateCommand;
17///
18/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
19/// // Update memory limit
20/// let result = UpdateCommand::new("my-container")
21///     .memory("512m")
22///     .run()
23///     .await?;
24///
25/// if result.success() {
26///     println!("Container updated successfully");
27/// }
28/// # Ok(())
29/// # }
30/// ```
31#[derive(Debug, Clone)]
32pub struct UpdateCommand {
33    /// Container names or IDs to update
34    containers: Vec<String>,
35    /// Memory limit
36    memory: Option<String>,
37    /// Memory reservation (soft limit)
38    memory_reservation: Option<String>,
39    /// Memory swap limit
40    memory_swap: Option<String>,
41    /// CPU shares (relative weight)
42    cpu_shares: Option<u64>,
43    /// CPU period
44    cpu_period: Option<u64>,
45    /// CPU quota
46    cpu_quota: Option<i64>,
47    /// CPUs (number of CPUs)
48    cpus: Option<String>,
49    /// CPU set
50    cpuset_cpus: Option<String>,
51    /// Memory nodes
52    cpuset_mems: Option<String>,
53    /// Block IO weight
54    blkio_weight: Option<u16>,
55    /// Kernel memory limit
56    kernel_memory: Option<String>,
57    /// Restart policy
58    restart: Option<String>,
59    /// PID limit
60    pids_limit: Option<i64>,
61    /// Command executor
62    pub executor: CommandExecutor,
63}
64
65impl UpdateCommand {
66    /// Create a new update command for a single container
67    ///
68    /// # Example
69    ///
70    /// ```
71    /// use docker_wrapper::UpdateCommand;
72    ///
73    /// let cmd = UpdateCommand::new("my-container");
74    /// ```
75    #[must_use]
76    pub fn new(container: impl Into<String>) -> Self {
77        Self {
78            containers: vec![container.into()],
79            memory: None,
80            memory_reservation: None,
81            memory_swap: None,
82            cpu_shares: None,
83            cpu_period: None,
84            cpu_quota: None,
85            cpus: None,
86            cpuset_cpus: None,
87            cpuset_mems: None,
88            blkio_weight: None,
89            kernel_memory: None,
90            restart: None,
91            pids_limit: None,
92            executor: CommandExecutor::new(),
93        }
94    }
95
96    /// Create a new update command for multiple containers
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// use docker_wrapper::UpdateCommand;
102    ///
103    /// let cmd = UpdateCommand::new_multiple(vec!["web", "db", "cache"]);
104    /// ```
105    #[must_use]
106    pub fn new_multiple(containers: Vec<impl Into<String>>) -> Self {
107        Self {
108            containers: containers.into_iter().map(Into::into).collect(),
109            memory: None,
110            memory_reservation: None,
111            memory_swap: None,
112            cpu_shares: None,
113            cpu_period: None,
114            cpu_quota: None,
115            cpus: None,
116            cpuset_cpus: None,
117            cpuset_mems: None,
118            blkio_weight: None,
119            kernel_memory: None,
120            restart: None,
121            pids_limit: None,
122            executor: CommandExecutor::new(),
123        }
124    }
125
126    /// Add another container to update
127    #[must_use]
128    pub fn container(mut self, container: impl Into<String>) -> Self {
129        self.containers.push(container.into());
130        self
131    }
132
133    /// Set memory limit
134    ///
135    /// # Example
136    ///
137    /// ```
138    /// use docker_wrapper::UpdateCommand;
139    ///
140    /// let cmd = UpdateCommand::new("my-container")
141    ///     .memory("512m");
142    /// ```
143    #[must_use]
144    pub fn memory(mut self, memory: impl Into<String>) -> Self {
145        self.memory = Some(memory.into());
146        self
147    }
148
149    /// Set memory reservation (soft limit)
150    ///
151    /// # Example
152    ///
153    /// ```
154    /// use docker_wrapper::UpdateCommand;
155    ///
156    /// let cmd = UpdateCommand::new("my-container")
157    ///     .memory_reservation("256m");
158    /// ```
159    #[must_use]
160    pub fn memory_reservation(mut self, memory_reservation: impl Into<String>) -> Self {
161        self.memory_reservation = Some(memory_reservation.into());
162        self
163    }
164
165    /// Set memory swap limit
166    ///
167    /// # Example
168    ///
169    /// ```
170    /// use docker_wrapper::UpdateCommand;
171    ///
172    /// let cmd = UpdateCommand::new("my-container")
173    ///     .memory_swap("1g");
174    /// ```
175    #[must_use]
176    pub fn memory_swap(mut self, memory_swap: impl Into<String>) -> Self {
177        self.memory_swap = Some(memory_swap.into());
178        self
179    }
180
181    /// Set CPU shares (relative weight)
182    ///
183    /// # Example
184    ///
185    /// ```
186    /// use docker_wrapper::UpdateCommand;
187    ///
188    /// let cmd = UpdateCommand::new("my-container")
189    ///     .cpu_shares(512);
190    /// ```
191    #[must_use]
192    pub fn cpu_shares(mut self, cpu_shares: u64) -> Self {
193        self.cpu_shares = Some(cpu_shares);
194        self
195    }
196
197    /// Set CPU period
198    ///
199    /// # Example
200    ///
201    /// ```
202    /// use docker_wrapper::UpdateCommand;
203    ///
204    /// let cmd = UpdateCommand::new("my-container")
205    ///     .cpu_period(100_000);
206    /// ```
207    #[must_use]
208    pub fn cpu_period(mut self, cpu_period: u64) -> Self {
209        self.cpu_period = Some(cpu_period);
210        self
211    }
212
213    /// Set CPU quota
214    ///
215    /// # Example
216    ///
217    /// ```
218    /// use docker_wrapper::UpdateCommand;
219    ///
220    /// let cmd = UpdateCommand::new("my-container")
221    ///     .cpu_quota(50000);
222    /// ```
223    #[must_use]
224    pub fn cpu_quota(mut self, cpu_quota: i64) -> Self {
225        self.cpu_quota = Some(cpu_quota);
226        self
227    }
228
229    /// Set number of CPUs
230    ///
231    /// # Example
232    ///
233    /// ```
234    /// use docker_wrapper::UpdateCommand;
235    ///
236    /// let cmd = UpdateCommand::new("my-container")
237    ///     .cpus("1.5");
238    /// ```
239    #[must_use]
240    pub fn cpus(mut self, cpus: impl Into<String>) -> Self {
241        self.cpus = Some(cpus.into());
242        self
243    }
244
245    /// Set CPU set
246    ///
247    /// # Example
248    ///
249    /// ```
250    /// use docker_wrapper::UpdateCommand;
251    ///
252    /// let cmd = UpdateCommand::new("my-container")
253    ///     .cpuset_cpus("0,1");
254    /// ```
255    #[must_use]
256    pub fn cpuset_cpus(mut self, cpuset_cpus: impl Into<String>) -> Self {
257        self.cpuset_cpus = Some(cpuset_cpus.into());
258        self
259    }
260
261    /// Set memory nodes
262    ///
263    /// # Example
264    ///
265    /// ```
266    /// use docker_wrapper::UpdateCommand;
267    ///
268    /// let cmd = UpdateCommand::new("my-container")
269    ///     .cpuset_mems("0");
270    /// ```
271    #[must_use]
272    pub fn cpuset_mems(mut self, cpuset_mems: impl Into<String>) -> Self {
273        self.cpuset_mems = Some(cpuset_mems.into());
274        self
275    }
276
277    /// Set block IO weight
278    ///
279    /// # Example
280    ///
281    /// ```
282    /// use docker_wrapper::UpdateCommand;
283    ///
284    /// let cmd = UpdateCommand::new("my-container")
285    ///     .blkio_weight(500);
286    /// ```
287    #[must_use]
288    pub fn blkio_weight(mut self, blkio_weight: u16) -> Self {
289        self.blkio_weight = Some(blkio_weight);
290        self
291    }
292
293    /// Set kernel memory limit
294    ///
295    /// # Example
296    ///
297    /// ```
298    /// use docker_wrapper::UpdateCommand;
299    ///
300    /// let cmd = UpdateCommand::new("my-container")
301    ///     .kernel_memory("128m");
302    /// ```
303    #[must_use]
304    pub fn kernel_memory(mut self, kernel_memory: impl Into<String>) -> Self {
305        self.kernel_memory = Some(kernel_memory.into());
306        self
307    }
308
309    /// Set restart policy
310    ///
311    /// # Example
312    ///
313    /// ```
314    /// use docker_wrapper::UpdateCommand;
315    ///
316    /// let cmd = UpdateCommand::new("my-container")
317    ///     .restart("unless-stopped");
318    /// ```
319    #[must_use]
320    pub fn restart(mut self, restart: impl Into<String>) -> Self {
321        self.restart = Some(restart.into());
322        self
323    }
324
325    /// Set PID limit
326    ///
327    /// # Example
328    ///
329    /// ```
330    /// use docker_wrapper::UpdateCommand;
331    ///
332    /// let cmd = UpdateCommand::new("my-container")
333    ///     .pids_limit(100);
334    /// ```
335    #[must_use]
336    pub fn pids_limit(mut self, pids_limit: i64) -> Self {
337        self.pids_limit = Some(pids_limit);
338        self
339    }
340
341    /// Execute the update command
342    ///
343    /// # Errors
344    /// Returns an error if:
345    /// - The Docker daemon is not running
346    /// - Any of the specified containers don't exist
347    /// - Invalid resource limits are specified
348    ///
349    /// # Example
350    ///
351    /// ```no_run
352    /// use docker_wrapper::UpdateCommand;
353    ///
354    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
355    /// let result = UpdateCommand::new("my-container")
356    ///     .memory("1g")
357    ///     .cpu_shares(512)
358    ///     .run()
359    ///     .await?;
360    ///
361    /// if result.success() {
362    ///     println!("Updated containers: {:?}", result.containers());
363    /// }
364    /// # Ok(())
365    /// # }
366    /// ```
367    pub async fn run(&self) -> Result<UpdateResult> {
368        let output = self.execute().await?;
369
370        Ok(UpdateResult {
371            output,
372            containers: self.containers.clone(),
373        })
374    }
375}
376
377#[async_trait]
378impl DockerCommand for UpdateCommand {
379    type Output = CommandOutput;
380
381    fn build_command_args(&self) -> Vec<String> {
382        let mut args = vec!["update".to_string()];
383
384        if let Some(ref memory) = self.memory {
385            args.push("--memory".to_string());
386            args.push(memory.clone());
387        }
388
389        if let Some(ref memory_reservation) = self.memory_reservation {
390            args.push("--memory-reservation".to_string());
391            args.push(memory_reservation.clone());
392        }
393
394        if let Some(ref memory_swap) = self.memory_swap {
395            args.push("--memory-swap".to_string());
396            args.push(memory_swap.clone());
397        }
398
399        if let Some(cpu_shares) = self.cpu_shares {
400            args.push("--cpu-shares".to_string());
401            args.push(cpu_shares.to_string());
402        }
403
404        if let Some(cpu_period) = self.cpu_period {
405            args.push("--cpu-period".to_string());
406            args.push(cpu_period.to_string());
407        }
408
409        if let Some(cpu_quota) = self.cpu_quota {
410            args.push("--cpu-quota".to_string());
411            args.push(cpu_quota.to_string());
412        }
413
414        if let Some(ref cpus) = self.cpus {
415            args.push("--cpus".to_string());
416            args.push(cpus.clone());
417        }
418
419        if let Some(ref cpuset_cpus) = self.cpuset_cpus {
420            args.push("--cpuset-cpus".to_string());
421            args.push(cpuset_cpus.clone());
422        }
423
424        if let Some(ref cpuset_mems) = self.cpuset_mems {
425            args.push("--cpuset-mems".to_string());
426            args.push(cpuset_mems.clone());
427        }
428
429        if let Some(blkio_weight) = self.blkio_weight {
430            args.push("--blkio-weight".to_string());
431            args.push(blkio_weight.to_string());
432        }
433
434        if let Some(ref kernel_memory) = self.kernel_memory {
435            args.push("--kernel-memory".to_string());
436            args.push(kernel_memory.clone());
437        }
438
439        if let Some(ref restart) = self.restart {
440            args.push("--restart".to_string());
441            args.push(restart.clone());
442        }
443
444        if let Some(pids_limit) = self.pids_limit {
445            args.push("--pids-limit".to_string());
446            args.push(pids_limit.to_string());
447        }
448
449        args.extend(self.containers.clone());
450        args.extend(self.executor.raw_args.clone());
451        args
452    }
453
454    fn get_executor(&self) -> &CommandExecutor {
455        &self.executor
456    }
457
458    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
459        &mut self.executor
460    }
461
462    async fn execute(&self) -> Result<Self::Output> {
463        if self.containers.is_empty() {
464            return Err(crate::error::Error::invalid_config(
465                "No containers specified for update",
466            ));
467        }
468
469        let args = self.build_command_args();
470        let command_name = args[0].clone();
471        let command_args = args[1..].to_vec();
472        self.executor
473            .execute_command(&command_name, command_args)
474            .await
475    }
476}
477
478/// Result from the update command
479#[derive(Debug, Clone)]
480pub struct UpdateResult {
481    /// Raw command output
482    pub output: CommandOutput,
483    /// Containers that were updated
484    pub containers: Vec<String>,
485}
486
487impl UpdateResult {
488    /// Check if the update was successful
489    #[must_use]
490    pub fn success(&self) -> bool {
491        self.output.success
492    }
493
494    /// Get the updated container names
495    #[must_use]
496    pub fn containers(&self) -> &[String] {
497        &self.containers
498    }
499
500    /// Get the raw command output
501    #[must_use]
502    pub fn output(&self) -> &CommandOutput {
503        &self.output
504    }
505
506    /// Get container count
507    #[must_use]
508    pub fn container_count(&self) -> usize {
509        self.containers.len()
510    }
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516
517    #[test]
518    fn test_update_single_container() {
519        let cmd = UpdateCommand::new("test-container");
520        let args = cmd.build_command_args();
521        assert_eq!(args, vec!["update", "test-container"]);
522    }
523
524    #[test]
525    fn test_update_multiple_containers() {
526        let cmd = UpdateCommand::new_multiple(vec!["web", "db", "cache"]);
527        let args = cmd.build_command_args();
528        assert_eq!(args, vec!["update", "web", "db", "cache"]);
529    }
530
531    #[test]
532    fn test_update_add_container() {
533        let cmd = UpdateCommand::new("web").container("db").container("cache");
534        let args = cmd.build_command_args();
535        assert_eq!(args, vec!["update", "web", "db", "cache"]);
536    }
537
538    #[test]
539    fn test_update_memory_options() {
540        let cmd = UpdateCommand::new("test-container")
541            .memory("512m")
542            .memory_reservation("256m")
543            .memory_swap("1g");
544        let args = cmd.build_command_args();
545        assert_eq!(
546            args,
547            vec![
548                "update",
549                "--memory",
550                "512m",
551                "--memory-reservation",
552                "256m",
553                "--memory-swap",
554                "1g",
555                "test-container"
556            ]
557        );
558    }
559
560    #[test]
561    fn test_update_cpu_options() {
562        let cmd = UpdateCommand::new("test-container")
563            .cpu_shares(512)
564            .cpu_period(100_000)
565            .cpu_quota(50000)
566            .cpus("1.5")
567            .cpuset_cpus("0,1")
568            .cpuset_mems("0");
569        let args = cmd.build_command_args();
570        assert_eq!(
571            args,
572            vec![
573                "update",
574                "--cpu-shares",
575                "512",
576                "--cpu-period",
577                "100000",
578                "--cpu-quota",
579                "50000",
580                "--cpus",
581                "1.5",
582                "--cpuset-cpus",
583                "0,1",
584                "--cpuset-mems",
585                "0",
586                "test-container"
587            ]
588        );
589    }
590
591    #[test]
592    fn test_update_all_options() {
593        let cmd = UpdateCommand::new("test-container")
594            .memory("1g")
595            .cpu_shares(1024)
596            .blkio_weight(500)
597            .kernel_memory("128m")
598            .restart("unless-stopped")
599            .pids_limit(100);
600        let args = cmd.build_command_args();
601        assert_eq!(
602            args,
603            vec![
604                "update",
605                "--memory",
606                "1g",
607                "--cpu-shares",
608                "1024",
609                "--blkio-weight",
610                "500",
611                "--kernel-memory",
612                "128m",
613                "--restart",
614                "unless-stopped",
615                "--pids-limit",
616                "100",
617                "test-container"
618            ]
619        );
620    }
621
622    #[test]
623    fn test_update_result() {
624        let result = UpdateResult {
625            output: CommandOutput {
626                stdout: "test-container".to_string(),
627                stderr: String::new(),
628                exit_code: 0,
629                success: true,
630            },
631            containers: vec!["test-container".to_string()],
632        };
633
634        assert!(result.success());
635        assert_eq!(result.containers(), &["test-container"]);
636        assert_eq!(result.container_count(), 1);
637    }
638}