Passed
Pull Request — master (#17)
by Jindun
02:14
created

DockerComposeService   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 196
rs 10
c 0
b 0
f 0
wmc 23

8 Methods

Rating   Name   Duplication   Size   Complexity  
B dockerComposeServiceSerialize() 0 52 6
A __construct() 0 3 1
A mergeContentInDockerComposeFiles() 0 25 5
A mergeContentInDockerComposeFile() 0 3 1
A getDockerComposePathnames() 0 10 3
A createDockerComposeFile() 0 10 1
A checkDockerComposeFileValidity() 0 7 1
A seekFiles() 0 23 5
1
<?php
2
3
namespace TheAentMachine\AentDockerCompose\DockerCompose;
4
5
use Psr\Log\LoggerInterface;
6
use Symfony\Component\Filesystem\Filesystem;
7
use Symfony\Component\Finder\Finder;
8
use Symfony\Component\Process\Process;
9
use Symfony\Component\Yaml\Yaml;
10
use TheAentMachine\AentDockerCompose\Aenthill\Enum\PheromoneEnum;
11
use TheAentMachine\AentDockerCompose\Aenthill\Exception\ContainerProjectDirEnvVariableEmptyException;
12
use TheAentMachine\AentDockerCompose\YamlTools\YamlTools;
13
use TheAentMachine\Service\Enum\VolumeTypeEnum;
14
use TheAentMachine\Service\Environment\EnvVariable;
15
use TheAentMachine\Service\Service;
16
use TheAentMachine\Service\Volume\BindVolume;
17
use TheAentMachine\Service\Volume\NamedVolume;
18
use TheAentMachine\Service\Volume\TmpfsVolume;
19
use TheAentMachine\Service\Volume\Volume;
20
21
class DockerComposeService
22
{
23
    public const VERSION = '3.3';
24
25
    /** @var LoggerInterface */
26
    private $log;
27
28
    /** @var DockerComposeFile[] */
29
    private $files;
30
31
    public function __construct(LoggerInterface $log)
32
    {
33
        $this->log = $log;
34
    }
35
36
    /**
37
     * @throws ContainerProjectDirEnvVariableEmptyException
38
     */
39
    private function seekFiles(): void
40
    {
41
        $containerProjectDir = getenv(PheromoneEnum::PHEROMONE_CONTAINER_PROJECT_DIR);
42
        if (empty($containerProjectDir)) {
43
            throw new ContainerProjectDirEnvVariableEmptyException();
44
        }
45
46
        $finder = new Finder();
47
        $dockerComposeFileFilter = function (\SplFileInfo $file) {
48
            return $file->isFile() && preg_match('/^docker-compose(.)*\.(yaml|yml)$/', $file->getFilename());
49
        };
50
        $finder->files()->filter($dockerComposeFileFilter)->in($containerProjectDir)->depth('== 0');
51
52
        if (!$finder->hasResults()) {
53
            $this->log->info("no docker-compose file found, let's create it");
54
            $this->createDockerComposeFile($containerProjectDir . '/docker-compose.yml');
55
            return;
56
        }
57
58
        /** @var \SplFileInfo $file */
59
        foreach ($finder as $file) {
60
            $this->files[] = new DockerComposeFile($file);
61
            $this->log->info($file->getFilename() . ' has been found');
62
        }
63
64
        /*if (count($this->files) === 1) {
65
            $this->log->info($this->files[0]->getFilename() . ' has been found');
66
            return;
67
        }
68
69
        throw new NotImplementedException("multiple docker-compose files handling is not yet implemented");
70
        */
71
    }
72
73
    /**
74
     * @return string[]
75
     */
76
    public function getDockerComposePathnames(): array
77
    {
78
        if ($this->files === null) {
79
            $this->seekFiles();
80
        }
81
        $pathnames = array();
82
        foreach ($this->files as $file) {
83
            $pathnames[] = $file->getPathname();
84
        }
85
        return $pathnames;
86
    }
87
88
    /**
89
     * @param string $path
90
     */
91
    private function createDockerComposeFile(string $path): void
92
    {
93
        // TODO ask questions about version and so on!
94
        file_put_contents($path, "version: '" . self::VERSION . "'");
95
        chown($path, fileowner(\dirname($path)));
96
        chgrp($path, filegroup(\dirname($path)));
97
98
        $file = new DockerComposeFile(new \SplFileInfo($path));
99
        $this->files[] = $file;
100
        $this->log->info($file->getFilename() . ' was successfully created!');
101
    }
102
103
    /**
104
     * @param Service $service
105
     * @param string $version
106
     * @return mixed[]
107
     */
108
    public static function dockerComposeServiceSerialize(Service $service, string $version = self::VERSION): array
109
    {
110
        $portMap = function (array $port): string {
111
            return $port['source'] . ':' . $port['target'];
112
        };
113
        $labelMap = function (array $label): string {
114
            return $label['value'];
115
        };
116
        $envMap = function (EnvVariable $e): string {
117
            return $e->getValue();
118
        };
119
        /**
120
         * @param NamedVolume|BindVolume|TmpfsVolume $v
121
         * @return array
122
         */
123
        $volumeMap = function ($v): array {
124
            $array = [
125
                'type' => $v->getType(),
126
                'source' => $v->getSource(),
127
            ];
128
            if ($v instanceof NamedVolume || $v instanceof BindVolume) {
129
                $array['target'] = $v->getTarget();
130
                $array['read_only'] = $v->isReadOnly();
131
            }
132
            return $array;
133
        };
134
        $dockerService = [
135
            'version' => $version,
136
            'services' => [
137
                $service->getServiceName() => array_filter([
138
                    'image' => $service->getImage(),
139
                    'command' => $service->getCommand(),
140
                    'depends_on' => $service->getDependsOn(),
141
                    'ports' => array_map($portMap, $service->getPorts()),
142
                    'labels' => array_map($labelMap, $service->getLabels()),
143
                    'environment' => array_map($envMap, $service->getEnvironment()),
144
                    'volumes' => array_map($volumeMap, $service->getVolumes()),
145
                ]),
146
            ],
147
        ];
148
        $namedVolumes = array();
149
        /** @var Volume $volume */
150
        foreach ($service->getVolumes() as $volume) {
151
            if ($volume->getType() === VolumeTypeEnum::NAMED_VOLUME) {
152
                // for now we just add them without any option
153
                $namedVolumes[$volume->getSource()] = null;
154
            }
155
        }
156
        if (!empty($namedVolumes)) {
157
            $dockerService['volumes'] = $namedVolumes;
158
        }
159
        return $dockerService;
160
    }
161
162
    /**
163
     * @param string $pathname
164
     */
165
    public static function checkDockerComposeFileValidity(string $pathname): void
166
    {
167
        $command = ['docker-compose', '-f', $pathname, 'config', '-q'];
168
        $process = new Process($command);
169
        $process->enableOutput();
170
        $process->setTty(true);
171
        $process->mustRun();
172
    }
173
174
175
    /**
176
     * Merge some yaml content into a docker-compose file (and check its validity, by default)
177
     * @param mixed[]|string $content
178
     * @param string $file
179
     * @param bool $checkValidity
180
     */
181
    public static function mergeContentInDockerComposeFile($content, string $file, bool $checkValidity = true): void
182
    {
183
        self::mergeContentInDockerComposeFiles($content, [$file], $checkValidity);
184
    }
185
186
    /**
187
     * Merge some yaml content into multiple docker-compose files (and check their validity, by default)
188
     * @param mixed[]|string $content
189
     * @param array $files
190
     * @param bool $checkValidity
191
     */
192
    public static function mergeContentInDockerComposeFiles($content, array $files, bool $checkValidity = true): void
193
    {
194
        $tmpFile = __DIR__ . '/tmp-merge-content-file.yml';
195
        $tmpMergedFile = __DIR__ . '/tmp-merged-content-file.yml';
196
197
        if (\is_array($content)) {
198
            $content = Yaml::dump($content, 256, 2, Yaml::DUMP_OBJECT_AS_MAP);
199
        }
200
201
        $fileSystem = new Filesystem();
202
        $fileSystem->dumpFile($tmpFile, $content);
203
204
        foreach ($files as $file) {
205
            if ($checkValidity) {
206
                YamlTools::mergeSuccessive([$file, $tmpFile], $tmpMergedFile);
207
                self::checkDockerComposeFileValidity($tmpMergedFile);
208
                $fileSystem->copy($tmpMergedFile, $file);
209
            } else {
210
                YamlTools::mergeTwoFiles($file, $tmpFile);
211
            }
212
        }
213
214
        $fileSystem->remove($tmpFile);
215
        if ($checkValidity) {
216
            $fileSystem->remove($tmpMergedFile);
217
        }
218
    }
219
}
220