1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package: chapi |
4
|
|
|
* |
5
|
|
|
* @author: msiebeneicher |
6
|
|
|
* @since: 2015-07-29 |
7
|
|
|
* |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace Chapi\Service\JobRepository; |
11
|
|
|
|
12
|
|
|
use Chapi\Component\Cache\CacheInterface; |
13
|
|
|
use Chapi\Entity\Chronos\ChronosJobEntity; |
14
|
|
|
use Chapi\Entity\JobEntityInterface; |
15
|
|
|
use Chapi\Entity\Marathon\MarathonAppEntity; |
16
|
|
|
use Chapi\Exception\JobLoadException; |
17
|
|
|
use Symfony\Component\Filesystem\Filesystem; |
18
|
|
|
use Webmozart\Glob\Glob; |
19
|
|
|
|
20
|
|
|
class BridgeFileSystem implements BridgeInterface |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* @var Filesystem |
24
|
|
|
*/ |
25
|
|
|
private $oFileSystemService; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var CacheInterface |
29
|
|
|
*/ |
30
|
|
|
private $oCache; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var string |
34
|
|
|
*/ |
35
|
|
|
private $sRepositoryDir = ''; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var string[] |
39
|
|
|
*/ |
40
|
|
|
private $aDirectorySeparators = ['.', ':', '-', '\\']; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var array |
44
|
|
|
*/ |
45
|
|
|
private $aJobFileMap = []; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var array |
49
|
|
|
*/ |
50
|
|
|
private $aGroupedApps = []; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param Filesystem $oFileSystemService |
54
|
|
|
* @param CacheInterface $oCache |
55
|
|
|
* @param string $sRepositoryDir |
56
|
|
|
*/ |
57
|
9 |
|
public function __construct( |
58
|
|
|
Filesystem $oFileSystemService, |
59
|
|
|
CacheInterface $oCache, |
60
|
|
|
$sRepositoryDir |
61
|
|
|
) |
62
|
|
|
{ |
63
|
9 |
|
$this->oFileSystemService = $oFileSystemService; |
64
|
9 |
|
$this->oCache = $oCache; |
65
|
9 |
|
$this->sRepositoryDir = $sRepositoryDir; |
66
|
9 |
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @return JobEntityInterface[] |
70
|
|
|
*/ |
71
|
8 |
|
public function getJobs() |
72
|
|
|
{ |
73
|
8 |
|
if (empty($this->aJobFileMap)) |
74
|
8 |
|
{ |
75
|
8 |
|
$_aJobFiles = $this->getJobFilesFromFileSystem($this->sRepositoryDir); |
76
|
8 |
|
return $this->loadJobsFromFileContent($_aJobFiles, true); |
77
|
|
|
} |
78
|
4 |
|
return $this->loadJobsFromFileContent($this->aJobFileMap, false); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @param ChronosJobEntity|JobEntityInterface $oJobEntity |
83
|
|
|
* @return bool |
84
|
|
|
* @throws JobLoadException |
85
|
|
|
*/ |
86
|
2 |
|
public function addJob(JobEntityInterface $oJobEntity) |
87
|
|
|
{ |
88
|
|
|
// generate job file path by name |
89
|
2 |
|
$_sJobFile = $this->generateJobFilePath($oJobEntity); |
90
|
|
|
|
91
|
2 |
|
if ($this->hasDumpFile($_sJobFile, $oJobEntity)) |
92
|
2 |
|
{ |
93
|
2 |
|
$this->setJobFileToMap($oJobEntity->getKey(), $_sJobFile); |
94
|
2 |
|
return true; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
return false; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @param JobEntityInterface $oJobEntity |
102
|
|
|
* @return bool |
103
|
|
|
*/ |
104
|
6 |
|
public function updateJob(JobEntityInterface $oJobEntity) |
105
|
|
|
{ |
106
|
3 |
|
if (in_array($oJobEntity->getKey(), $this->aGroupedApps)) |
107
|
3 |
|
{ |
108
|
|
|
// marathon's group case where app belongs to a group file |
109
|
1 |
|
return $this->dumpFileWithGroup( |
110
|
1 |
|
$this->getJobFileFromMap($oJobEntity->getKey()), |
111
|
|
|
$oJobEntity |
112
|
6 |
|
); |
113
|
|
|
} |
114
|
2 |
|
return $this->hasDumpFile( |
115
|
2 |
|
$this->getJobFileFromMap($oJobEntity->getKey()), |
116
|
|
|
$oJobEntity |
117
|
2 |
|
); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* @param ChronosJobEntity|JobEntityInterface $oJobEntity |
122
|
|
|
* @return bool |
123
|
|
|
*/ |
124
|
3 |
|
public function removeJob(JobEntityInterface $oJobEntity) |
125
|
|
|
{ |
126
|
3 |
|
if (in_array($oJobEntity->getKey(), $this->aGroupedApps)) |
127
|
3 |
|
{ |
128
|
1 |
|
$_sJobFile = $this->getJobFileFromMap($oJobEntity->getKey()); |
129
|
1 |
|
$this->dumpFileWithGroup( |
130
|
1 |
|
$_sJobFile, |
131
|
1 |
|
$oJobEntity, |
132
|
|
|
false |
133
|
1 |
|
); |
134
|
|
|
|
135
|
1 |
|
unset($this->aJobFileMap[$oJobEntity->getKey()]); |
136
|
1 |
|
return true; |
137
|
|
|
} |
138
|
|
|
|
139
|
2 |
|
$_sJobFile = $this->getJobFileFromMap($oJobEntity->getKey()); |
140
|
2 |
|
$this->oFileSystemService->remove($_sJobFile); |
141
|
|
|
|
142
|
2 |
|
return $this->hasUnsetJobFileFromMap($oJobEntity->getKey(), $_sJobFile); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @param JobEntityInterface $oJobEntity |
147
|
|
|
* @return string |
148
|
|
|
*/ |
149
|
2 |
|
private function generateJobFilePath(JobEntityInterface $oJobEntity) |
150
|
|
|
{ |
151
|
2 |
|
if ($oJobEntity->getEntityType() == JobEntityInterface::CHRONOS_TYPE) |
152
|
2 |
|
{ |
153
|
1 |
|
$_sJobPath = str_replace( |
154
|
1 |
|
$this->aDirectorySeparators, |
155
|
1 |
|
DIRECTORY_SEPARATOR, |
156
|
1 |
|
$oJobEntity->getKey() |
157
|
1 |
|
); |
158
|
1 |
|
} |
159
|
|
|
else |
160
|
|
|
{ |
161
|
1 |
|
$_sJobPath = $oJobEntity->getKey(); |
162
|
|
|
} |
163
|
|
|
|
164
|
2 |
|
return $this->sRepositoryDir . DIRECTORY_SEPARATOR . $_sJobPath . '.json'; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @param string $sPath |
169
|
|
|
* @param array $aJobFiles |
170
|
|
|
* @return array |
171
|
|
|
*/ |
172
|
8 |
|
private function getJobFilesFromFileSystem($sPath, array &$aJobFiles = []) |
173
|
|
|
{ |
174
|
8 |
|
if (!is_dir($sPath)) |
175
|
8 |
|
{ |
176
|
|
|
throw new \RuntimeException(sprintf('Path "%s" is not valid', $sPath)); |
177
|
|
|
} |
178
|
|
|
|
179
|
8 |
|
$_aTemp = Glob::glob(rtrim($sPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*'); |
180
|
|
|
|
181
|
8 |
|
foreach ($_aTemp as $_sPath) |
182
|
|
|
{ |
183
|
7 |
|
if (is_file($_sPath) && preg_match('~\.json~i', $_sPath)) |
184
|
7 |
|
{ |
185
|
6 |
|
$aJobFiles[] = $_sPath; |
186
|
7 |
|
} elseif (is_dir($_sPath)) |
187
|
|
|
{ |
188
|
6 |
|
$this->getJobFilesFromFileSystem($_sPath, $aJobFiles); |
189
|
6 |
|
} |
190
|
8 |
|
} |
191
|
|
|
|
192
|
8 |
|
return $aJobFiles; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @param string $sJobName |
197
|
|
|
* @param string $sJobFile |
198
|
|
|
* @throws JobLoadException |
199
|
|
|
*/ |
200
|
7 |
|
private function setJobFileToMap($sJobName, $sJobFile) |
201
|
|
|
{ |
202
|
|
|
// set path to job file map |
203
|
7 |
|
if (isset($this->aJobFileMap[$sJobName])) |
204
|
7 |
|
{ |
205
|
1 |
|
throw new JobLoadException( |
206
|
1 |
|
sprintf('The jobname "%s" already exists. Jobnames have to be unique - Please check your local jobfiles for duplicate entries.', $sJobName), |
207
|
|
|
JobLoadException::ERROR_CODE_DUPLICATE_JOB_ID |
208
|
1 |
|
); |
209
|
|
|
} |
210
|
|
|
|
211
|
7 |
|
$this->aJobFileMap[$sJobName] = $sJobFile; |
212
|
7 |
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* @param string $sJobName |
216
|
|
|
* @return string |
217
|
|
|
* @throws \RuntimeException |
218
|
|
|
*/ |
219
|
4 |
|
private function getJobFileFromMap($sJobName) |
220
|
|
|
{ |
221
|
4 |
|
if (!isset($this->aJobFileMap[$sJobName])) |
222
|
4 |
|
{ |
223
|
|
|
throw new \RuntimeException(sprintf('Can\'t find file for job "%s"', $sJobName)); |
224
|
|
|
} |
225
|
4 |
|
return $this->aJobFileMap[$sJobName]; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @param string $sJobName |
230
|
|
|
* @param string $sJobFile |
231
|
|
|
* @return bool |
232
|
|
|
* @throws \RuntimeException |
233
|
|
|
*/ |
234
|
2 |
|
private function hasUnsetJobFileFromMap($sJobName, $sJobFile = '') |
235
|
|
|
{ |
236
|
2 |
|
$_sJobFile = (!empty($sJobFile)) ? $sJobFile : $this->getJobFileFromMap($sJobName); |
237
|
2 |
|
if (file_exists($_sJobFile)) |
238
|
2 |
|
{ |
239
|
|
|
throw new \RuntimeException(sprintf('Job file "%s" for job "%s" still exists.', $_sJobFile, $sJobName)); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
// unset path from job file map |
243
|
2 |
|
unset($this->aJobFileMap[$sJobName]); |
244
|
2 |
|
return true; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* @param array $aJobFiles |
249
|
|
|
* @param bool $bSetToFileMap |
250
|
|
|
* @return JobEntityInterface[] |
251
|
|
|
* @throws JobLoadException |
252
|
|
|
*/ |
253
|
8 |
|
private function loadJobsFromFileContent(array $aJobFiles, $bSetToFileMap) |
254
|
|
|
{ |
255
|
8 |
|
$_aJobs = []; |
256
|
|
|
|
257
|
8 |
|
foreach ($aJobFiles as $_sJobFilePath) |
258
|
|
|
{ |
259
|
8 |
|
$_aJobEntities = []; |
260
|
|
|
// remove comment blocks |
261
|
8 |
|
$_aTemp = json_decode( |
262
|
8 |
|
preg_replace( |
263
|
8 |
|
'~\/\*(.*?)\*\/~mis', |
264
|
8 |
|
'', |
265
|
8 |
|
file_get_contents($_sJobFilePath) |
266
|
8 |
|
) |
267
|
8 |
|
); |
268
|
|
|
|
269
|
|
|
if ($_aTemp) |
270
|
8 |
|
{ |
271
|
7 |
|
if (property_exists($_aTemp, 'name')) // chronos |
272
|
7 |
|
{ |
273
|
4 |
|
$_aJobEntities[] = new ChronosJobEntity($_aTemp); |
274
|
|
|
|
275
|
7 |
|
} else if (property_exists($_aTemp, 'id')) //marathon |
276
|
4 |
|
{ |
277
|
4 |
|
foreach ($this->getMarathonEntitiesForConfig($_aTemp) as $_oApp) |
278
|
|
|
{ |
279
|
4 |
|
$_aJobEntities[] = $_oApp; |
280
|
4 |
|
} |
281
|
4 |
|
} else { |
282
|
|
|
throw new JobLoadException( |
283
|
|
|
'Could not distinguish job as either chronos or marathon', |
284
|
|
|
JobLoadException::ERROR_CODE_UNKNOWN_ENTITY_TYPE |
285
|
|
|
); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** @var JobEntityInterface $_oJobEntity */ |
289
|
7 |
|
foreach ($_aJobEntities as $_oJobEntity) |
290
|
|
|
{ |
291
|
|
|
if ($bSetToFileMap) |
292
|
7 |
|
{ |
293
|
|
|
// set path to job file map |
294
|
5 |
|
$this->setJobFileToMap($_oJobEntity->getKey(), $_sJobFilePath); |
295
|
5 |
|
} |
296
|
|
|
|
297
|
7 |
|
$_aJobs[] = $_oJobEntity; |
298
|
7 |
|
} |
299
|
|
|
|
300
|
7 |
|
} |
301
|
|
|
else |
302
|
|
|
{ |
303
|
1 |
|
throw new JobLoadException( |
304
|
1 |
|
sprintf('Unable to load json job data from "%s". Please check if the json is valid.', $_sJobFilePath), |
305
|
|
|
JobLoadException::ERROR_CODE_NO_VALID_JSON |
306
|
1 |
|
); |
307
|
|
|
} |
308
|
7 |
|
} |
309
|
|
|
|
310
|
6 |
|
return $_aJobs; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
|
314
|
4 |
|
private function getMarathonEntitiesForConfig($aEntityData) |
315
|
|
|
{ |
316
|
4 |
|
$_aRet = []; |
317
|
4 |
|
if (property_exists($aEntityData, 'apps')) |
318
|
4 |
|
{ |
319
|
|
|
// store individual apps like single apps |
320
|
2 |
|
foreach ($aEntityData->apps as $_oApp) |
321
|
|
|
{ |
322
|
2 |
|
$_oGroupEntity = new MarathonAppEntity($_oApp); |
323
|
2 |
|
$this->aGroupedApps[] = $_oApp->id; |
324
|
2 |
|
$_aRet[] = $_oGroupEntity; |
325
|
2 |
|
} |
326
|
2 |
|
} |
327
|
|
|
else |
328
|
|
|
{ |
329
|
2 |
|
$_aRet[] = new MarathonAppEntity($aEntityData); |
330
|
|
|
} |
331
|
4 |
|
return $_aRet; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* @param string $sJobFile |
336
|
|
|
* @param JobEntityInterface $oJobEntity |
337
|
|
|
* @return bool |
338
|
|
|
*/ |
339
|
2 |
|
private function hasDumpFile($sJobFile, JobEntityInterface $oJobEntity) |
340
|
|
|
{ |
341
|
2 |
|
$this->oFileSystemService->dumpFile( |
342
|
2 |
|
$sJobFile, |
343
|
2 |
|
json_encode($oJobEntity, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) |
344
|
2 |
|
); |
345
|
|
|
|
346
|
2 |
|
return (file_exists($sJobFile)); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
|
350
|
2 |
|
private function dumpFileWithGroup($sJobFile, JobEntityInterface $oJobEntity, $bAdd = true) |
|
|
|
|
351
|
|
|
{ |
352
|
2 |
|
$_sGroupConfig = file_get_contents($sJobFile); |
353
|
|
|
|
354
|
2 |
|
$_oDecodedConfig = json_decode(preg_replace( |
355
|
2 |
|
'~\/\*(.*?)\*\/~mis', |
356
|
2 |
|
'', |
357
|
|
|
$_sGroupConfig |
358
|
2 |
|
)); |
359
|
|
|
|
360
|
2 |
|
if (!property_exists($_oDecodedConfig, 'apps')) |
361
|
2 |
|
{ |
362
|
|
|
throw new \RuntimeException(sprintf( |
363
|
|
|
'Job file %s does not contain group configuration. But, "%s" belongs to group %s', |
364
|
|
|
$sJobFile, |
365
|
|
|
$oJobEntity->getKey(), |
366
|
|
|
$_oDecodedConfig->id |
367
|
|
|
)); |
368
|
|
|
} |
369
|
|
|
|
370
|
2 |
|
$_bAppFound = false; |
371
|
2 |
|
foreach ($_oDecodedConfig->apps as $key => $_oApp) |
372
|
|
|
{ |
373
|
2 |
|
if ($_oApp->id == $oJobEntity->getKey()) |
374
|
2 |
|
{ |
375
|
2 |
|
if (!$bAdd) |
376
|
2 |
|
{ |
377
|
1 |
|
array_splice($_oDecodedConfig->apps, $key, 1); |
378
|
1 |
|
if (count($_oDecodedConfig->apps) == 0) |
379
|
1 |
|
{ |
380
|
|
|
$this->oFileSystemService->remove($sJobFile); |
381
|
|
|
$iIndex = array_search($oJobEntity->getKey(), $this->aGroupedApps); |
382
|
|
|
if ($iIndex) |
383
|
|
|
{ |
384
|
|
|
unset($this->aGroupedApps[$iIndex]); |
385
|
|
|
} |
386
|
|
|
return false; |
387
|
|
|
} |
388
|
1 |
|
} else { |
389
|
1 |
|
$_oDecodedConfig->apps[$key] = $oJobEntity; |
390
|
|
|
} |
391
|
2 |
|
$_bAppFound = true; |
392
|
2 |
|
} |
393
|
2 |
|
} |
394
|
|
|
|
395
|
2 |
|
if (!$_bAppFound) |
396
|
2 |
|
{ |
397
|
|
|
throw new \RuntimeException(sprintf( |
398
|
|
|
'Could update job. job %s could not be found in the group file %s.', |
399
|
|
|
$oJobEntity->getKey(), |
400
|
|
|
$sJobFile |
401
|
|
|
)); |
402
|
|
|
} |
403
|
|
|
|
404
|
2 |
|
$_sUpdatedConfig = json_encode($_oDecodedConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
405
|
|
|
|
406
|
2 |
|
$this->oFileSystemService->dumpFile( |
407
|
2 |
|
$sJobFile, |
408
|
|
|
$_sUpdatedConfig |
409
|
2 |
|
); |
410
|
|
|
|
411
|
2 |
|
return (file_exists($sJobFile)); |
412
|
|
|
} |
413
|
|
|
} |
This check examines a number of code elements and verifies that they conform to the given naming conventions.
You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.