1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! Structures and methods to easily manipulate Docker images, especially for `LanguageTool`
//! applications.

use crate::error::{exit_status_error, Result};
#[cfg(feature = "cli")]
use clap::Parser;
use std::process::{Command, Output, Stdio};

#[cfg_attr(feature = "cli", derive(Parser))]
#[derive(Debug, Clone)]
/// Commands to pull, start and stop a `LanguageTool` container using Docker.
pub struct Docker {
    #[cfg_attr(
        feature = "cli",
        clap(
            default_value = "erikvl87/languagetool",
            env = "LANGUAGETOOL_DOCKER_IMAGE"
        )
    )]
    /// Image or repository from a registry.
    name: String,
    #[cfg_attr(
        feature = "cli",
        clap(
            short = 'b',
            long,
            default_value = "docker",
            env = "LANGUAGETOOL_DOCKER_BIN"
        )
    )]
    /// Path to Docker's binaries.
    bin: String,
    #[cfg_attr(
        feature = "cli",
        clap(long, default_value = "languagetool", env = "LANGUAGETOOL_DOCKER_NAME")
    )]
    /// Name assigned to the container.
    container_name: String,
    #[cfg_attr(
        feature = "cli",
        clap(
            short = 'p',
            long,
            default_value = "8010:8010",
            env = "LANGUAGETOOL_DOCKER_PORT"
        )
    )]
    /// Publish a container's port(s) to the host.
    port: String,
    #[cfg_attr(feature = "cli", clap(subcommand))]
    /// Docker action.
    action: Action,
}

#[cfg_attr(feature = "cli", derive(clap::Subcommand))]
#[derive(Clone, Debug)]
/// Enumerate supported Docker actions.
enum Action {
    /// Pull a docker docker image.
    ///
    /// Alias to `{docker.bin} pull {docker.name}`.
    Pull,
    /// Start a (detached) docker container.
    ///
    /// Alias to `{docker.bin} run --rm -d -p {docker.port} {docker.name}`
    Start,
    /// Stop a docker container.
    ///
    /// Alias to `{docker.bin} kill $({docker.bin} ps -l -f "name={docker.container_name}")`.
    Stop,
}

impl Docker {
    /// Pull a Docker image from the given repository/file/...
    pub fn pull(&self) -> Result<Output> {
        let output = Command::new(&self.bin)
            .args(["pull", &self.name])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .output()?;

        exit_status_error(&output.status)?;

        Ok(output)
    }

    /// Start a Docker container with given specifications.
    pub fn start(&self) -> Result<Output> {
        let output = Command::new(&self.bin)
            .args([
                "run",
                "--rm",
                "--name",
                &self.container_name,
                "-d",
                "-p",
                "8010:8010",
                &self.name,
            ])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .output()?;

        exit_status_error(&output.status)?;

        Ok(output)
    }

    /// Stop the latest Docker container with the given name.
    pub fn stop(&self) -> Result<Output> {
        let output = Command::new(&self.bin)
            .args([
                "ps",
                "-l",
                "-f",
                &format!("name={}", self.container_name),
                "-q",
            ])
            .stderr(Stdio::inherit())
            .output()?;

        exit_status_error(&output.status)?;

        let docker_id: String = String::from_utf8_lossy(&output.stdout)
            .chars()
            .filter(|c| c.is_alphanumeric()) // This avoids newlines
            .collect();

        let output = Command::new(&self.bin)
            .args(["kill", &docker_id])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .output()?;

        exit_status_error(&output.status)?;

        Ok(output)
    }

    /// Run a Docker command according to self.action.
    pub fn run_action(&self) -> Result<Output> {
        match self.action {
            Action::Pull => self.pull(),
            Action::Start => self.start(),
            Action::Stop => self.stop(),
        }
    }
}