Completed
Pull Request — master (#17)
by Jindun
02:49
created

DockerComposeService::filesInitialized()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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