• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

diego-ninja / docker / 19601180552

22 Nov 2025 09:03PM UTC coverage: 98.214% (+9.2%) from 88.971%
19601180552

push

github

diego-ninja
docs: facade documentation updated

550 of 560 relevant lines covered (98.21%)

13.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.27
/src/DockerContainerInstance.php
1
<?php
2

3
// ABOUTME: Represents a running Docker container instance with execution capabilities.
4
// ABOUTME: Provides methods to execute commands, manage files, and inspect container state.
5

6
declare(strict_types=1);
7

8
namespace Ninja\Docker;
9

10
use JsonException;
11
use Ninja\Docker\ValueObjects\ContainerPath;
12
use Ninja\Docker\ValueObjects\HostPath;
13
use RuntimeException;
14
use Spatie\Macroable\Macroable;
15
use Symfony\Component\Process\Exception\ProcessFailedException;
16
use Symfony\Component\Process\Process;
17

18
class DockerContainerInstance
19
{
20
    use Macroable;
21

22
    public const string DEFAULT_PATH_AUTHORIZED_KEYS = '/root/.ssh/authorized_keys';
23

24
    public function __construct(
27 ✔
25
        private readonly DockerContainer $config,
26
        private readonly string $dockerIdentifier,
27
        private readonly string $name
28
    ) {}
27 ✔
29

30
    public static function fromExisting(string $name): self
1 ✔
31
    {
32
        return new self(
1 ✔
33
            config: DockerContainer::create(
1 ✔
34
                image: self::getImageFromExistingContainer($name),
1 ✔
35
                name: $name
1 ✔
36
            ),
1 ✔
37
            dockerIdentifier: self::getIdFromExistingContainer($name),
1 ✔
38
            name: $name
1 ✔
39
        );
1 ✔
40
    }
41

42
    public static function isRunning(string $name): bool
5 ✔
43
    {
44
        $process = new Process([
5 ✔
45
            'docker',
5 ✔
46
            'ps',
5 ✔
47
            '-q',
5 ✔
48
            '-f',
5 ✔
49
            "name={$name}",
5 ✔
50
        ]);
5 ✔
51

52
        $process->run();
5 ✔
53

54
        return !empty(trim($process->getOutput()));
5 ✔
55
    }
56

57
    public function __destruct()
20 ✔
58
    {
59
        if ($this->config->stopOnDestruct) {
20 ✔
60
            $this->stop();
5 ✔
61
        }
62
    }
63

64
    public function start(bool $async = false): Process
2 ✔
65
    {
66
        $fullCommand = $this->config->getStartCommand($this->getShortDockerIdentifier());
2 ✔
67
        $process     = new Process($fullCommand);
2 ✔
68

69
        $async ? $process->start() : $process->run();
2 ✔
70

71
        return $process;
2 ✔
72
    }
73

74
    public function stop(bool $async = false): Process
13 ✔
75
    {
76
        $fullCommand = $this->config->getStopCommand($this->getShortDockerIdentifier());
13 ✔
77
        $process     = new Process($fullCommand);
13 ✔
78

79
        $async ? $process->start() : $process->run();
13 ✔
80

81
        return $process;
13 ✔
82
    }
83

84
    public function getName(): string
5 ✔
85
    {
86
        return $this->name;
5 ✔
87
    }
88

89
    public function getConfig(): DockerContainer
1 ✔
90
    {
91
        return $this->config;
1 ✔
92
    }
93

94
    public function getDockerIdentifier(): string
4 ✔
95
    {
96
        return $this->dockerIdentifier;
4 ✔
97
    }
98

99
    public function getShortDockerIdentifier(): string
20 ✔
100
    {
101
        return substr($this->dockerIdentifier, 0, 12);
20 ✔
102
    }
103

104
    public function execute(string $command): string
8 ✔
105
    {
106
        $dockerCommand = array_merge(
8 ✔
107
            $this->config->getBaseCommand(),
8 ✔
108
            ['exec', '--interactive', $this->getShortDockerIdentifier(), $this->config->shell]
8 ✔
109
        );
8 ✔
110

111
        $process = new Process($dockerCommand);
8 ✔
112
        $process->setInput($command);
8 ✔
113
        $process->setTimeout(null);
8 ✔
114
        $process->run();
8 ✔
115

116
        if (!$process->isSuccessful()) {
8 ✔
117
            throw new RuntimeException(
1 ✔
118
                "Command execution failed: {$process->getErrorOutput()}"
1 ✔
119
            );
1 ✔
120
        }
121

122
        return $process->getOutput();
7 ✔
123
    }
124

125
    public function addPublicKey(
5 ✔
126
        HostPath|string $pathToPublicKey,
127
        ContainerPath|string $pathToAuthorizedKeys = self::DEFAULT_PATH_AUTHORIZED_KEYS
128
    ): self {
129
        $publicKeyPath = $pathToPublicKey instanceof HostPath
5 ✔
130
            ? $pathToPublicKey
1 ✔
131
            : HostPath::from($pathToPublicKey);
4 ✔
132

133
        $authorizedKeysPath = $pathToAuthorizedKeys instanceof ContainerPath
4 ✔
134
            ? $pathToAuthorizedKeys
1 ✔
135
            : ContainerPath::from($pathToAuthorizedKeys);
3 ✔
136

137
        $contents = file_get_contents((string)$publicKeyPath);
4 ✔
138
        if ($contents === false) {
4 ✔
139
            throw new RuntimeException(
×
140
                sprintf("Could not read contents of public key at %s", (string)$publicKeyPath)
×
141
            );
×
142
        }
143

144
        $publicKeyContents = trim($contents);
4 ✔
145
        $sshDir            = dirname((string)$authorizedKeysPath);
4 ✔
146

147
        $this->execute("mkdir -p {$sshDir}");
4 ✔
148
        $this->execute("chmod 700 {$sshDir}");
4 ✔
149
        $this->execute("echo '{$publicKeyContents}' >> {$authorizedKeysPath}");
4 ✔
150
        $this->execute("chmod 600 {$authorizedKeysPath}");
4 ✔
151
        $this->execute("chown root:root {$authorizedKeysPath}");
4 ✔
152

153
        return $this;
4 ✔
154
    }
155

156
    /**
157
     * @param string $fileOrDirectoryOnHost
158
     * @param string $pathInContainer
159
     * @return self
160
     * @throws ProcessFailedException
161
     */
162
    public function addFiles(string $fileOrDirectoryOnHost, string $pathInContainer): self
2 ✔
163
    {
164
        $fullCommand = $this->config->getCopyCommand($this->getShortDockerIdentifier(), $fileOrDirectoryOnHost, $pathInContainer);
2 ✔
165

166
        $process = new Process($fullCommand);
2 ✔
167

168
        $process->run();
2 ✔
169

170
        if (!$process->isSuccessful()) {
2 ✔
171
            throw new ProcessFailedException($process);
1 ✔
172
        }
173

174
        return $this;
1 ✔
175
    }
176

177
    /**
178
     * @return list<array<string, mixed>>
179
     * @throws JsonException
180
     */
181
    public function inspect(): array
1 ✔
182
    {
183
        $fullCommand = $this->config->getInspectCommand($this->getShortDockerIdentifier());
1 ✔
184

185
        $process = new Process($fullCommand);
1 ✔
186
        $process->run();
1 ✔
187

188
        $json = trim($process->getOutput());
1 ✔
189

190
        /** @var list<array<string, mixed>> $result */
191
        $result = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
1 ✔
192

193
        return $result;
1 ✔
194
    }
195

196
    private static function getImageFromExistingContainer(string $name): string
1 ✔
197
    {
198
        $process = new Process([
1 ✔
199
            'docker',
1 ✔
200
            'ps',
1 ✔
201
            '--format={{.Image}}',
1 ✔
202
            '-f',
1 ✔
203
            "name={$name}",
1 ✔
204
        ]);
1 ✔
205

206
        $process->run();
1 ✔
207

208
        return trim($process->getOutput());
1 ✔
209
    }
210

211
    private static function getIdFromExistingContainer(string $name): string
1 ✔
212
    {
213
        $process = new Process([
1 ✔
214
            'docker',
1 ✔
215
            'ps',
1 ✔
216
            '-q',
1 ✔
217
            '-f',
1 ✔
218
            "name={$name}",
1 ✔
219
        ]);
1 ✔
220

221
        $process->run();
1 ✔
222

223
        return trim($process->getOutput());
1 ✔
224
    }
225

226
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc