Completed
Pull Request — master (#67)
by Jindun
03:05
created

Service::jsonSerialize()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 23
nc 4
nop 0
dl 0
loc 36
rs 9.552
c 0
b 0
f 0
1
<?php
2
3
namespace TheAentMachine\Service;
4
5
use JsonSerializable;
6
use Opis\JsonSchema\ValidationError;
7
use Opis\JsonSchema\Validator;
8
use TheAentMachine\Aenthill\Manifest;
9
use TheAentMachine\Aenthill\CommonMetadata;
10
use TheAentMachine\Service\Enum\EnvVariableTypeEnum;
11
use TheAentMachine\Service\Enum\VolumeTypeEnum;
12
use TheAentMachine\Service\Environment\EnvVariable;
13
use TheAentMachine\Service\Exception\ServiceException;
14
use TheAentMachine\Service\Volume\BindVolume;
15
use TheAentMachine\Service\Volume\NamedVolume;
16
use TheAentMachine\Service\Volume\TmpfsVolume;
17
18
class Service implements JsonSerializable
19
{
20
    /** @var string */
21
    private $serviceName = '';
22
    /** @var string|null */
23
    private $image;
24
    /** @var string[] */
25
    private $command = [];
26
    /** @var int[] */
27
    private $internalPorts = [];
28
    /** @var string[] */
29
    private $dependsOn = [];
30
    /** @var mixed[] */
31
    private $ports = [];
32
    /** @var mixed[] */
33
    private $labels = [];
34
    /** @var mixed[] */
35
    private $environment = [];
36
    /** @var mixed[] */
37
    private $volumes = [];
38
    /** @var null|bool */
39
    private $needVirtualHost;
40
    /** @var null|bool */
41
    private $needBuild;
42
    /** @var null|bool */
43
    private $needDeploy;
44
    /** @var \stdClass */
45
    private $validatorSchema;
46
    /** @var string[] */
47
    private $dockerfileCommands = [];
48
    /** @var string[] */
49
    private $destEnvTypes = []; // empty === all env types
50
51
    public function __construct()
52
    {
53
        $this->validatorSchema = \GuzzleHttp\json_decode((string)file_get_contents(__DIR__ . '/ServiceJsonSchema.json'), false);
54
    }
55
56
    /**
57
     * @param mixed[] $payload
58
     * @return Service
59
     * @throws ServiceException
60
     */
61
    public static function parsePayload(array $payload): Service
62
    {
63
        $service = new self();
64
        $service->checkValidity($payload);
65
        $service->serviceName = $payload['serviceName'] ?? '';
66
        $s = $payload['service'] ?? [];
67
        if (!empty($s)) {
68
            $service->image = $s['image'] ?? null;
69
            $service->command = $s['command'] ?? [];
70
            $service->internalPorts = $s['internalPorts'] ?? [];
71
            $service->dependsOn = $s['dependsOn'] ?? [];
72
            $service->ports = $s['ports'] ?? [];
73
            $service->labels = $s['labels'] ?? [];
74
            if (!empty($s['environment'])) {
75
                foreach ($s['environment'] as $key => $env) {
76
                    $service->addEnvVar($key, $env['value'], $env['type']);
77
                }
78
            }
79
            if (!empty($s['volumes'])) {
80
                foreach ($s['volumes'] as $vol) {
81
                    $service->addVolume($vol['type'], $vol['source'], $vol['target'] ?? '', $vol['readOnly'] ?? false);
82
                }
83
            }
84
            $service->needVirtualHost = $s['needVirtualHost'] ?? null;
85
            $service->needBuild = $s['needBuild'] ?? null;
86
            $service->needDeploy = $s['needDeploy'] ?? null;
87
        }
88
        $service->dockerfileCommands = $payload['dockerfileCommands'] ?? [];
89
        $service->destEnvTypes = $payload['destEnvTypes'] ?? [];
90
        return $service;
91
    }
92
93
    /**
94
     * Specify data which should be serialized to JSON
95
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
96
     * @return array data which can be serialized by <b>json_encode</b>,
97
     * which is a value of any type other than a resource.
98
     * @since 5.4.0
99
     * @throws ServiceException
100
     */
101
    public function jsonSerialize(): array
102
    {
103
        $jsonSerializeMap = function (JsonSerializable $obj): array {
104
            return $obj->jsonSerialize();
105
        };
106
107
        $json = array(
108
            'serviceName' => $this->serviceName,
109
        );
110
111
        $service = array_filter([
112
            'image' => $this->image,
113
            'command' => $this->command,
114
            'internalPorts' => $this->internalPorts,
115
            'dependsOn' => $this->dependsOn,
116
            'ports' => $this->ports,
117
            'labels' => $this->labels,
118
            'environment' => array_map($jsonSerializeMap, $this->environment),
119
            'volumes' => array_map($jsonSerializeMap, $this->volumes),
120
            'needVirtualHost' => $this->needVirtualHost,
121
            'needBuild' => $this->needBuild,
122
            'needDeploy' => $this->needDeploy,
123
        ]);
124
125
        if (!empty($service)) {
126
            $json['service'] = $service;
127
        }
128
129
        if (!empty($this->dockerfileCommands)) {
130
            $json['dockerfileCommands'] = $this->dockerfileCommands;
131
        }
132
133
        $json['destEnvTypes'] = $this->destEnvTypes;
134
135
        $this->checkValidity($json);
136
        return $json;
137
    }
138
139
    /**
140
     * @return mixed[]
141
     */
142
    public function imageJsonSerialize(): array
143
    {
144
        $dockerfileCommands = [];
145
        $dockerfileCommands[] = 'FROM ' . $this->image;
146
        foreach ($this->environment as $key => $env) {
147
            if ($env->getType() === EnvVariableTypeEnum::IMAGE_ENV_VARIABLE) {
148
                $dockerfileCommands[] = "ENV $key" . '='. $env->getValue();
149
            }
150
        }
151
        foreach ($this->volumes as $volume) {
152
            if ($volume->getType() === VolumeTypeEnum::BIND_VOLUME) {
153
                $dockerfileCommands[] = 'COPY ' . $volume->getSource() . ' ' . $volume->getTarget();
154
            }
155
        }
156
157
        if (!empty($this->command)) {
158
            $dockerfileCommands[] = 'CMD ' . implode(' ', $this->command);
159
        }
160
161
        $dockerfileCommands = array_merge($dockerfileCommands, $this->dockerfileCommands);
162
163
        return [
164
            'serviceName' => $this->serviceName,
165
            'dockerfileCommands' => $dockerfileCommands,
166
            'destEnvTypes' => $this->destEnvTypes,
167
        ];
168
    }
169
170
    /**
171
     * @param \stdClass|array|string $data
172
     * @return bool
173
     * @throws ServiceException
174
     */
175
    private function checkValidity($data): bool
176
    {
177
        if (\is_array($data)) {
178
            $data = \GuzzleHttp\json_decode(\GuzzleHttp\json_encode($data), false);
179
        }
180
        $validator = new Validator();
181
        $result = $validator->dataValidation($data, $this->validatorSchema);
182
        if (!$result->isValid()) {
183
            /** @var ValidationError $vError */
184
            $vError = $result->getFirstError();
185
            throw ServiceException::invalidServiceData($vError);
186
        }
187
        return $result->isValid();
188
    }
189
190
191
    /************************ getters **********************/
192
193
    public function getServiceName(): string
194
    {
195
        return $this->serviceName;
196
    }
197
198
    public function getImage(): ?string
199
    {
200
        return $this->image;
201
    }
202
203
    /** @return string[] */
204
    public function getCommand(): array
205
    {
206
        return $this->command;
207
    }
208
209
    /** @return int[] */
210
    public function getInternalPorts(): array
211
    {
212
        return $this->internalPorts;
213
    }
214
215
    /** @return string[] */
216
    public function getDependsOn(): array
217
    {
218
        return $this->dependsOn;
219
    }
220
221
    /** @return mixed[] */
222
    public function getPorts(): array
223
    {
224
        return $this->ports;
225
    }
226
227
    /** @return mixed[] */
228
    public function getLabels(): array
229
    {
230
        return $this->labels;
231
    }
232
233
    /** @return mixed[] */
234
    public function getEnvironment(): array
235
    {
236
        return $this->environment;
237
    }
238
239
    /** @return mixed[] */
240
    public function getVolumes(): array
241
    {
242
        return $this->volumes;
243
    }
244
245
    public function getNeedVirtualHost(): ?bool
246
    {
247
        return $this->needVirtualHost;
248
    }
249
250
    public function getNeedBuild(): ?bool
251
    {
252
        return $this->needBuild;
253
    }
254
255
    public function getNeedDeploy(): ?bool
256
    {
257
        return $this->needDeploy;
258
    }
259
260
261
    /** @return string[] */
262
    public function getDockerfileCommands(): array
263
    {
264
        return $this->dockerfileCommands;
265
    }
266
267
    /**  @return string[] */
268
    public function getDestEnvTypes(): array
269
    {
270
        return $this->destEnvTypes;
271
    }
272
273
274
    /************************ setters **********************/
275
276
    public function setServiceName(string $serviceName): void
277
    {
278
        $this->serviceName = $serviceName;
279
    }
280
281
    public function setImage(?string $image): void
282
    {
283
        $this->image = $image;
284
    }
285
286
    /** @param string[] $command  */
287
    public function setCommand(array $command): void
288
    {
289
        $this->command = $command;
290
    }
291
292
    /**
293
     * @param int[] $internalPorts
294
     */
295
    public function setInternalPorts(array $internalPorts): void
296
    {
297
        $this->internalPorts = $internalPorts;
298
    }
299
300
    /** @param string[] $dependsOn */
301
    public function setDependsOn(array $dependsOn): void
302
    {
303
        $this->dependsOn = $dependsOn;
304
    }
305
306
    public function setNeedVirtualHost(?bool $needVirtualHost): void
307
    {
308
        $this->needVirtualHost = $needVirtualHost;
309
    }
310
311
    public function setNeedBuild(?bool $needBuild): void
312
    {
313
        $this->needBuild = $needBuild;
314
    }
315
316
    public function setNeedDeploy(?bool $needDeploy): void
317
    {
318
        $this->needDeploy = $needDeploy;
319
    }
320
321
322
    /************************ adders **********************/
323
324
    public function addCommand(string $command): void
325
    {
326
        $this->command[] = $command;
327
    }
328
329
    public function addInternalPort(int $internalPort): void
330
    {
331
        $this->internalPorts[] = $internalPort;
332
    }
333
334
    public function addDependsOn(string $dependsOn): void
335
    {
336
        $this->dependsOn[] = $dependsOn;
337
    }
338
339
    public function addPort(int $source, int $target): void
340
    {
341
        $this->ports[] = array(
342
            'source' => $source,
343
            'target' => $target,
344
        );
345
    }
346
347
    public function addLabel(string $key, string $value): void
348
    {
349
        $this->labels[$key] = array(
350
            'value' => $value,
351
        );
352
    }
353
354
355
    /************************ environment adders **********************/
356
357
    /** @throws ServiceException */
358
    private function addEnvVar(string $key, string $value, string $type): void
359
    {
360
        switch ($type) {
361
            case EnvVariableTypeEnum::SHARED_ENV_VARIABLE:
362
                $this->addSharedEnvVariable($key, $value);
363
                break;
364
            case EnvVariableTypeEnum::SHARED_SECRET:
365
                $this->addSharedSecret($key, $value);
366
                break;
367
            case EnvVariableTypeEnum::IMAGE_ENV_VARIABLE:
368
                $this->addImageEnvVariable($key, $value);
369
                break;
370
            case EnvVariableTypeEnum::CONTAINER_ENV_VARIABLE:
371
                $this->addContainerEnvVariable($key, $value);
372
                break;
373
            default:
374
                throw ServiceException::unknownEnvVariableType($type);
375
        }
376
    }
377
378
    public function addSharedEnvVariable(string $key, string $value): void
379
    {
380
        $this->environment[$key] = new EnvVariable($value, 'sharedEnvVariable');
381
    }
382
383
    public function addSharedSecret(string $key, string $value): void
384
    {
385
        $this->environment[$key] = new EnvVariable($value, 'sharedSecret');
386
    }
387
388
389
    /************************ volumes adders **********************/
390
391
    public function addImageEnvVariable(string $key, string $value): void
392
    {
393
        $this->environment[$key] = new EnvVariable($value, 'imageEnvVariable');
394
    }
395
396
    public function addContainerEnvVariable(string $key, string $value): void
397
    {
398
        $this->environment[$key] = new EnvVariable($value, 'containerEnvVariable');
399
    }
400
401
    /** @throws ServiceException */
402
    private function addVolume(string $type, string $source, string $target = '', bool $readOnly = false): void
403
    {
404
        switch ($type) {
405
            case VolumeTypeEnum::NAMED_VOLUME:
406
                $this->addNamedVolume($source, $target, $readOnly);
407
                break;
408
            case VolumeTypeEnum::BIND_VOLUME:
409
                $this->addBindVolume($source, $target, $readOnly);
410
                break;
411
            case VolumeTypeEnum::TMPFS_VOLUME:
412
                $this->addTmpfsVolume($source);
413
                break;
414
            default:
415
                throw ServiceException::unknownVolumeType($type);
416
        }
417
    }
418
419
    public function addNamedVolume(string $source, string $target, bool $readOnly = false): void
420
    {
421
        $this->volumes[] = new NamedVolume($source, $target, $readOnly);
422
    }
423
424
    public function addBindVolume(string $source, string $target, bool $readOnly = false): void
425
    {
426
        $this->volumes[] = new BindVolume($source, $target, $readOnly);
427
    }
428
429
    public function addTmpfsVolume(string $source): void
430
    {
431
        $this->volumes[] = new TmpfsVolume($source);
432
    }
433
434
    public function addDockerfileCommand(string $dockerfileCommand): void
435
    {
436
        $this->dockerfileCommands[] = $dockerfileCommand;
437
    }
438
439
440
    /************************ forSpecificEnvTypes stuffs **********************/
441
442
    public function addDestEnvType(string $envType, bool $keepTheOtherEnvTypes = true): void
443
    {
444
        if (!$keepTheOtherEnvTypes) {
445
            $this->destEnvTypes = [];
446
        }
447
        $this->destEnvTypes[] = $envType;
448
    }
449
450
    public function isForDevEnvType(): bool
451
    {
452
        return empty($this->destEnvTypes) || \in_array(CommonMetadata::ENV_TYPE_DEV, $this->destEnvTypes);
453
    }
454
455
    public function isForTestEnvType(): bool
456
    {
457
        return empty($this->destEnvTypes) || \in_array(CommonMetadata::ENV_TYPE_TEST, $this->destEnvTypes);
458
    }
459
460
    public function isForProdEnvType(): bool
461
    {
462
        return empty($this->destEnvTypes) || \in_array(CommonMetadata::ENV_TYPE_PROD, $this->destEnvTypes);
463
    }
464
465
    public function isForMyEnvType(): bool
466
    {
467
        $myEnvType = Manifest::getMetadata(CommonMetadata::ENV_TYPE_KEY);
468
        return empty($this->destEnvTypes) || \in_array($myEnvType, $this->destEnvTypes, true);
469
    }
470
}
471