1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace TheAentMachine\Service; |
4
|
|
|
|
5
|
|
|
use Opis\JsonSchema\ValidationError; |
6
|
|
|
use Opis\JsonSchema\Validator; |
7
|
|
|
use TheAentMachine\Service\Enum\EnvVariableTypeEnum; |
8
|
|
|
use TheAentMachine\Service\Enum\VolumeTypeEnum; |
9
|
|
|
use TheAentMachine\Service\Environment\EnvVariable; |
10
|
|
|
use TheAentMachine\Service\Exception\ServiceException; |
11
|
|
|
use TheAentMachine\Service\Volume\BindVolume; |
12
|
|
|
use TheAentMachine\Service\Volume\NamedVolume; |
13
|
|
|
use TheAentMachine\Service\Volume\TmpfsVolume; |
14
|
|
|
|
15
|
|
|
class Service implements \JsonSerializable |
16
|
|
|
{ |
17
|
|
|
/** @var string */ |
18
|
|
|
private $serviceName = ''; |
19
|
|
|
/** @var string|null */ |
20
|
|
|
private $image = null; |
21
|
|
|
/** @var string[] */ |
22
|
|
|
private $command = []; |
23
|
|
|
/** @var int[] */ |
24
|
|
|
private $internalPorts = []; |
25
|
|
|
/** @var string[] */ |
26
|
|
|
private $dependsOn = []; |
27
|
|
|
/** @var mixed[] */ |
28
|
|
|
private $ports = []; |
29
|
|
|
/** @var mixed[] */ |
30
|
|
|
private $labels = []; |
31
|
|
|
/** @var mixed[] */ |
32
|
|
|
private $environment = []; |
33
|
|
|
/** @var mixed[] */ |
34
|
|
|
private $volumes = []; |
35
|
|
|
/** @var \stdClass */ |
36
|
|
|
private $validatorSchema; |
37
|
|
|
/** @var string[] */ |
38
|
|
|
private $dockerfileCommands = []; |
39
|
|
|
/** @var string */ |
40
|
|
|
private $requestMemory = ''; |
41
|
|
|
/** @var string */ |
42
|
|
|
private $requestCpu = ''; |
43
|
|
|
/** @var string */ |
44
|
|
|
private $limitMemory = ''; |
45
|
|
|
/** @var string */ |
46
|
|
|
private $limitCpu = ''; |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Service constructor. |
51
|
|
|
* @throws ServiceException |
52
|
|
|
*/ |
53
|
|
|
public function __construct() |
54
|
|
|
{ |
55
|
|
|
$jsonSchemaPathname = __DIR__ . '/ServiceJsonSchema.json'; |
56
|
|
|
$jsonSchema = file_get_contents($jsonSchemaPathname); |
57
|
|
|
if ($jsonSchema === false) { |
58
|
|
|
throw ServiceException::jsonSchemaNotFound($jsonSchemaPathname); |
59
|
|
|
} |
60
|
|
|
$this->validatorSchema = \GuzzleHttp\json_decode($jsonSchema, false); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param mixed[] $payload |
65
|
|
|
* @return Service |
66
|
|
|
* @throws ServiceException |
67
|
|
|
*/ |
68
|
|
|
public static function parsePayload(array $payload): Service |
69
|
|
|
{ |
70
|
|
|
$service = new self(); |
71
|
|
|
$service->checkValidity($payload); |
72
|
|
|
$service->serviceName = $payload['serviceName'] ?? ''; |
73
|
|
|
$s = $payload['service'] ?? []; |
74
|
|
|
if (!empty($s)) { |
75
|
|
|
$service->image = $s['image'] ?? null; |
76
|
|
|
$service->command = $s['command'] ?? []; |
77
|
|
|
$service->internalPorts = $s['internalPorts'] ?? []; |
78
|
|
|
$service->dependsOn = $s['dependsOn'] ?? []; |
79
|
|
|
$service->ports = $s['ports'] ?? []; |
80
|
|
|
$service->labels = $s['labels'] ?? []; |
81
|
|
|
if (!empty($s['environment'])) { |
82
|
|
|
foreach ($s['environment'] as $key => $env) { |
83
|
|
|
$service->addEnvVar($key, $env['value'], $env['type']); |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
if (!empty($s['volumes'])) { |
87
|
|
|
foreach ($s['volumes'] as $vol) { |
88
|
|
|
$service->addVolume($vol['type'], $vol['source'], $vol['target'] ?? '', $vol['readOnly'] ?? false); |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
$service->dockerfileCommands = $payload['dockerfileCommands'] ?? ''; |
|
|
|
|
93
|
|
|
|
94
|
|
|
$service->requestMemory = $payload['requestMemory'] ?? ''; |
95
|
|
|
$service->requestCpu = $payload['requestCpu'] ?? ''; |
96
|
|
|
$service->limitMemory = $payload['limitMemory'] ?? ''; |
97
|
|
|
$service->limitCpu = $payload['limitCpu'] ?? ''; |
98
|
|
|
return $service; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Specify data which should be serialized to JSON |
103
|
|
|
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php |
104
|
|
|
* @return array data which can be serialized by <b>json_encode</b>, |
105
|
|
|
* which is a value of any type other than a resource. |
106
|
|
|
* @since 5.4.0 |
107
|
|
|
* @throws ServiceException |
108
|
|
|
*/ |
109
|
|
|
public function jsonSerialize(): array |
110
|
|
|
{ |
111
|
|
|
$jsonSerializeMap = function (\JsonSerializable $obj): array { |
112
|
|
|
return $obj->jsonSerialize(); |
113
|
|
|
}; |
114
|
|
|
|
115
|
|
|
$json = array( |
116
|
|
|
'serviceName' => $this->serviceName, |
117
|
|
|
); |
118
|
|
|
|
119
|
|
|
$service = array_filter([ |
120
|
|
|
'image' => $this->image, |
121
|
|
|
'command' => $this->command, |
122
|
|
|
'internalPorts' => $this->internalPorts, |
123
|
|
|
'dependsOn' => $this->dependsOn, |
124
|
|
|
'ports' => $this->ports, |
125
|
|
|
'labels' => $this->labels, |
126
|
|
|
'environment' => array_map($jsonSerializeMap, $this->environment), |
127
|
|
|
'volumes' => array_map($jsonSerializeMap, $this->volumes), |
128
|
|
|
]); |
129
|
|
|
|
130
|
|
|
if (!empty($service)) { |
131
|
|
|
$json['service'] = $service; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
if (!empty($this->dockerfileCommands)) { |
135
|
|
|
$json['dockerfileCommands'] = $this->dockerfileCommands; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
$resources = array_filter([ |
139
|
|
|
'requestMemory' => $this->requestMemory, |
140
|
|
|
'requestCpu' => $this->requestCpu, |
141
|
|
|
'limitMemory' => $this->limitMemory, |
142
|
|
|
'limitCpu' => $this->limitCpu |
143
|
|
|
]); |
144
|
|
|
|
145
|
|
|
if (!empty($resources)) { |
146
|
|
|
$json = array_merge($json, $resources); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
$this->checkValidity($json); |
150
|
|
|
return $json; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** @return mixed[] */ |
154
|
|
|
public function imageJsonSerialize(): array |
155
|
|
|
{ |
156
|
|
|
$dockerfileCommands = []; |
157
|
|
|
$dockerfileCommands[] = 'FROM ' . $this->image; |
158
|
|
|
foreach ($this->environment as $key => $env) { |
159
|
|
|
if ($env->getType() === EnvVariableTypeEnum::IMAGE_ENV_VARIABLE) { |
160
|
|
|
$dockerfileCommands[] = "ENV $key" . '=' . $env->getValue(); |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
foreach ($this->volumes as $volume) { |
164
|
|
|
if ($volume->getType() === VolumeTypeEnum::BIND_VOLUME) { |
165
|
|
|
$dockerfileCommands[] = 'COPY ' . $volume->getSource() . ' ' . $volume->getTarget(); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
if (!empty($this->command)) { |
170
|
|
|
$dockerfileCommands[] = 'CMD ' . implode(' ', $this->command); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
$dockerfileCommands = array_merge($dockerfileCommands, $this->dockerfileCommands); |
174
|
|
|
|
175
|
|
|
return [ |
176
|
|
|
'serviceName' => $this->serviceName, |
177
|
|
|
'dockerfileCommands' => $dockerfileCommands, |
178
|
|
|
]; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @param \stdClass|array|string $data |
183
|
|
|
* @return bool |
184
|
|
|
* @throws ServiceException |
185
|
|
|
*/ |
186
|
|
|
private function checkValidity($data): bool |
187
|
|
|
{ |
188
|
|
|
if (\is_array($data)) { |
189
|
|
|
$data = \GuzzleHttp\json_decode(\GuzzleHttp\json_encode($data), false); |
190
|
|
|
} |
191
|
|
|
$validator = new Validator(); |
192
|
|
|
$result = $validator->dataValidation($data, $this->validatorSchema); |
193
|
|
|
if (!$result->isValid()) { |
194
|
|
|
/** @var ValidationError $vError */ |
195
|
|
|
$vError = $result->getFirstError(); |
196
|
|
|
throw ServiceException::invalidServiceData($vError); |
197
|
|
|
} |
198
|
|
|
return $result->isValid(); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
public function getServiceName(): string |
202
|
|
|
{ |
203
|
|
|
return $this->serviceName; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
public function getImage(): ?string |
207
|
|
|
{ |
208
|
|
|
return $this->image; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** @return string[] */ |
212
|
|
|
public function getCommand(): array |
213
|
|
|
{ |
214
|
|
|
return $this->command; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** @return int[] */ |
218
|
|
|
public function getInternalPorts(): array |
219
|
|
|
{ |
220
|
|
|
return $this->internalPorts; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** @return string[] */ |
224
|
|
|
public function getDependsOn(): array |
225
|
|
|
{ |
226
|
|
|
return $this->dependsOn; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** @return mixed[] */ |
230
|
|
|
public function getPorts(): array |
231
|
|
|
{ |
232
|
|
|
return $this->ports; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** @return mixed[] */ |
236
|
|
|
public function getLabels(): array |
237
|
|
|
{ |
238
|
|
|
return $this->labels; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** @return mixed[] */ |
242
|
|
|
public function getEnvironment(): array |
243
|
|
|
{ |
244
|
|
|
return $this->environment; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** @return mixed[] */ |
248
|
|
|
public function getVolumes(): array |
249
|
|
|
{ |
250
|
|
|
return $this->volumes; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** @return string[] */ |
254
|
|
|
public function getDockerfileCommands(): array |
255
|
|
|
{ |
256
|
|
|
return $this->dockerfileCommands; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
public function getRequestMemory(): string |
260
|
|
|
{ |
261
|
|
|
return $this->requestMemory; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
public function getRequestCpu(): string |
265
|
|
|
{ |
266
|
|
|
return $this->requestCpu; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
public function getLimitMemory(): string |
270
|
|
|
{ |
271
|
|
|
return $this->limitMemory; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
public function getLimitCpu(): string |
275
|
|
|
{ |
276
|
|
|
return $this->limitCpu; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
public function setServiceName(string $serviceName): void |
280
|
|
|
{ |
281
|
|
|
$this->serviceName = $serviceName; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
public function setImage(?string $image): void |
285
|
|
|
{ |
286
|
|
|
$this->image = $image; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** @param string[] $command */ |
290
|
|
|
public function setCommand(array $command): void |
291
|
|
|
{ |
292
|
|
|
$this->command = $command; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** @param int[] $internalPorts */ |
296
|
|
|
public function setInternalPorts(array $internalPorts): void |
297
|
|
|
{ |
298
|
|
|
$this->internalPorts = $internalPorts; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** @param string[] $dependsOn */ |
302
|
|
|
public function setDependsOn(array $dependsOn): void |
303
|
|
|
{ |
304
|
|
|
$this->dependsOn = $dependsOn; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
public function setRequestMemory(string $requestMemory): void |
308
|
|
|
{ |
309
|
|
|
$this->requestMemory = $requestMemory; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
public function setRequestCpu(string $requestCpu): void |
313
|
|
|
{ |
314
|
|
|
$this->requestCpu = $requestCpu; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
public function setLimitMemory(string $limitMemory): void |
318
|
|
|
{ |
319
|
|
|
$this->limitMemory = $limitMemory; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
public function setLimitCpu(string $limitCpu): void |
323
|
|
|
{ |
324
|
|
|
$this->limitCpu = $limitCpu; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
public function addCommand(string $command): void |
328
|
|
|
{ |
329
|
|
|
$this->command[] = $command; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
public function addInternalPort(int $internalPort): void |
333
|
|
|
{ |
334
|
|
|
$this->internalPorts[] = $internalPort; |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
public function addDependsOn(string $dependsOn): void |
338
|
|
|
{ |
339
|
|
|
$this->dependsOn[] = $dependsOn; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
public function addPort(int $source, int $target): void |
343
|
|
|
{ |
344
|
|
|
$this->ports[] = array( |
345
|
|
|
'source' => $source, |
346
|
|
|
'target' => $target, |
347
|
|
|
); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
public function addLabel(string $key, string $value): void |
351
|
|
|
{ |
352
|
|
|
$this->labels[$key] = array( |
353
|
|
|
'value' => $value, |
354
|
|
|
); |
355
|
|
|
} |
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
|
|
|
public function addImageEnvVariable(string $key, string $value): void |
389
|
|
|
{ |
390
|
|
|
$this->environment[$key] = new EnvVariable($value, 'imageEnvVariable'); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
public function addContainerEnvVariable(string $key, string $value): void |
394
|
|
|
{ |
395
|
|
|
$this->environment[$key] = new EnvVariable($value, 'containerEnvVariable'); |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
/** @throws ServiceException */ |
399
|
|
|
private function addVolume(string $type, string $source, string $target = '', bool $readOnly = false): void |
400
|
|
|
{ |
401
|
|
|
switch ($type) { |
402
|
|
|
case VolumeTypeEnum::NAMED_VOLUME: |
403
|
|
|
$this->addNamedVolume($source, $target, $readOnly); |
404
|
|
|
break; |
405
|
|
|
case VolumeTypeEnum::BIND_VOLUME: |
406
|
|
|
$this->addBindVolume($source, $target, $readOnly); |
407
|
|
|
break; |
408
|
|
|
case VolumeTypeEnum::TMPFS_VOLUME: |
409
|
|
|
$this->addTmpfsVolume($source); |
410
|
|
|
break; |
411
|
|
|
default: |
412
|
|
|
throw ServiceException::unknownVolumeType($type); |
413
|
|
|
} |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
public function addNamedVolume(string $source, string $target, bool $readOnly = false): void |
417
|
|
|
{ |
418
|
|
|
$this->volumes[] = new NamedVolume($source, $target, $readOnly); |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
public function addBindVolume(string $source, string $target, bool $readOnly = false): void |
422
|
|
|
{ |
423
|
|
|
$this->volumes[] = new BindVolume($source, $target, $readOnly); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
public function addTmpfsVolume(string $source): void |
427
|
|
|
{ |
428
|
|
|
$this->volumes[] = new TmpfsVolume($source); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
public function addDockerfileCommand(string $dockerfileCommand): void |
432
|
|
|
{ |
433
|
|
|
$this->dockerfileCommands[] = $dockerfileCommand; |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.