Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 153 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
AdministratorApiDeployLogic | |
0.00% |
0 / 153 |
|
0.00% |
0 / 13 |
1806 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProgressFilePath | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getArchiveFilePath | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUploadDirectoryPath | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getExpandDirectoryPath | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProgressSetting | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
setProgressSetting | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
executeStartup | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
30 | |||
executeUpload | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
executePrepare | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
30 | |||
executeUpdate | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
12 | |||
validateImpl | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
210 | |||
executeImpl | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
42 | |||
LocalProgressSetting | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | n/a |
0 / 0 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\App\Models\Domain\Api\AdministratorApi; |
6 | |
7 | use DateInterval; |
8 | use DateTime; |
9 | use Exception; |
10 | use ZipArchive; |
11 | use PeServer\App\Models\AppConfiguration; |
12 | use PeServer\App\Models\AppDatabaseConnection; |
13 | use PeServer\App\Models\AuditLog; |
14 | use PeServer\App\Models\Domain\Api\ApiLogicBase; |
15 | use PeServer\App\Models\Domain\AppArchiver; |
16 | use PeServer\App\Models\ResponseJson; |
17 | use PeServer\App\Models\Setup\SetupRunner; |
18 | use PeServer\Core\Binary; |
19 | use PeServer\Core\Collection\Arr; |
20 | use PeServer\Core\IO\Directory; |
21 | use PeServer\Core\IO\File; |
22 | use PeServer\Core\IO\IOUtility; |
23 | use PeServer\Core\IO\Path; |
24 | use PeServer\Core\Log\ILoggerFactory; |
25 | use PeServer\Core\Mvc\LogicCallMode; |
26 | use PeServer\Core\Mvc\LogicParameter; |
27 | use PeServer\Core\ProgramContext; |
28 | use PeServer\Core\Serialization\BuiltinSerializer; |
29 | use PeServer\Core\Serialization\JsonSerializer; |
30 | use PeServer\Core\Text; |
31 | use PeServer\Core\Throws\InvalidOperationException; |
32 | use PeServer\Core\Throws\NotImplementedException; |
33 | use PeServer\Core\Utc; |
34 | |
35 | class AdministratorApiDeployLogic extends ApiLogicBase |
36 | { |
37 | #region define |
38 | |
39 | private const MODE_STARTUP = 'startup'; |
40 | private const MODE_UPLOAD = 'upload'; |
41 | private const MODE_PREPARE = 'prepare'; |
42 | private const MODE_UPDATE = 'update'; |
43 | |
44 | public const PROGRESS_FILE_NAME = 'deploy.dat'; |
45 | private const ARCHIVE_FILE_NAME = 'deploy.zip'; |
46 | private const ENABLED_RANGE_TIME = 'P1M'; |
47 | |
48 | #endregion |
49 | |
50 | public function __construct( |
51 | LogicParameter $parameter, |
52 | private ProgramContext $programContext, |
53 | private AppConfiguration $appConfig, |
54 | private AppArchiver $appArchiver, |
55 | private AppDatabaseConnection $databaseConnection, |
56 | private ILoggerFactory $loggerFactory |
57 | ) { |
58 | parent::__construct($parameter); |
59 | } |
60 | |
61 | #region function |
62 | |
63 | private function getProgressFilePath(): string |
64 | { |
65 | return Path::combine($this->appConfig->setting->cache->deploy, self::PROGRESS_FILE_NAME); |
66 | } |
67 | |
68 | private function getArchiveFilePath(): string |
69 | { |
70 | return Path::combine($this->appConfig->setting->cache->deploy, self::ARCHIVE_FILE_NAME); |
71 | } |
72 | |
73 | private function getUploadDirectoryPath(): string |
74 | { |
75 | return Path::combine($this->appConfig->setting->cache->deploy, 'upload'); |
76 | } |
77 | |
78 | private function getExpandDirectoryPath(): string |
79 | { |
80 | return Path::combine($this->appConfig->setting->cache->deploy, 'expand'); |
81 | } |
82 | |
83 | private function getProgressSetting(): LocalProgressSetting |
84 | { |
85 | $path = $this->getProgressFilePath(); |
86 | $binary = File::readContent($path); |
87 | $serializer = new BuiltinSerializer(); |
88 | /** @var LocalProgressSetting */ |
89 | return $serializer->load($binary); |
90 | } |
91 | |
92 | private function setProgressSetting(LocalProgressSetting $setting): void |
93 | { |
94 | $path = $this->getProgressFilePath(); |
95 | $serializer = new BuiltinSerializer(); |
96 | $binary = $serializer->save($setting); |
97 | Directory::createParentDirectoryIfNotExists($path); |
98 | File::writeContent($path, $binary); |
99 | } |
100 | |
101 | /** |
102 | * [デプロイ] 開始の合図。 |
103 | * |
104 | * @return array<string,string> |
105 | */ |
106 | private function executeStartup(): array |
107 | { |
108 | $setting = new LocalProgressSetting(); |
109 | $setting->mode = self::MODE_STARTUP; |
110 | $setting->uploadedCount = 0; |
111 | $setting->startupTime = Utc::createDateTime(); |
112 | |
113 | $dirs = [ |
114 | $this->getUploadDirectoryPath(), |
115 | $this->getExpandDirectoryPath(), |
116 | ]; |
117 | foreach ($dirs as $dir) { |
118 | if (Directory::exists($dir)) { |
119 | Directory::removeDirectory($dir, true); |
120 | } |
121 | } |
122 | |
123 | $files = [ |
124 | $this->getArchiveFilePath(), |
125 | ]; |
126 | foreach ($files as $file) { |
127 | if (File::exists($file)) { |
128 | File::removeFile($file); |
129 | } |
130 | } |
131 | |
132 | $this->setProgressSetting($setting); |
133 | |
134 | return []; |
135 | } |
136 | |
137 | /** |
138 | * [デプロイ] アップロード処理。 |
139 | * |
140 | * @return array<string,string> |
141 | */ |
142 | private function executeUpload(): array |
143 | { |
144 | $sequence = $this->getRequest('sequence'); |
145 | if (Text::isNullOrWhiteSpace($sequence)) { |
146 | throw new InvalidOperationException('$sequence'); |
147 | } |
148 | |
149 | $file = $this->getFile('file'); |
150 | $fileName = "upload-$sequence.dat"; |
151 | $filePath = Path::combine($this->getUploadDirectoryPath(), $fileName); |
152 | $file->move($filePath); |
153 | |
154 | $setting = $this->getProgressSetting(); |
155 | $setting->mode = self::MODE_UPLOAD; |
156 | $setting->uploadedCount += 1; |
157 | $this->setProgressSetting($setting); |
158 | |
159 | return [ |
160 | 'sequence' => $sequence, |
161 | ]; |
162 | } |
163 | |
164 | /** |
165 | * [デプロイ] 展開処理。 |
166 | * |
167 | * @return array<string,string> |
168 | */ |
169 | private function executePrepare(): array |
170 | { |
171 | $archiveFilePath = $this->getArchiveFilePath(); |
172 | File::removeFileIfExists($archiveFilePath); |
173 | File::createEmptyFileIfNotExists($archiveFilePath); |
174 | |
175 | $setting = $this->getProgressSetting(); |
176 | for ($i = 0; $i < $setting->uploadedCount; $i++) { |
177 | $uploadFileName = "upload-$i.dat"; |
178 | $uploadFilePath = Path::combine($this->getUploadDirectoryPath(), $uploadFileName); |
179 | $content = File::readContent($uploadFilePath); |
180 | File::appendContent($archiveFilePath, $content); |
181 | } |
182 | |
183 | $archiveFilePath = $this->getArchiveFilePath(); |
184 | if (!File::exists($archiveFilePath)) { |
185 | throw new InvalidOperationException($archiveFilePath); |
186 | } |
187 | |
188 | $expandDirPath = $this->getExpandDirectoryPath(); |
189 | if (Directory::exists($expandDirPath)) { |
190 | Directory::removeDirectory($expandDirPath, true); |
191 | } |
192 | Directory::createDirectory($expandDirPath); |
193 | |
194 | // 展開 |
195 | $archiveFilePath = $this->getArchiveFilePath(); |
196 | $zip = new ZipArchive(); |
197 | $zip->open($archiveFilePath); |
198 | $expandDirPath = $this->getExpandDirectoryPath(); |
199 | $zip->extractTo($expandDirPath); |
200 | $zip->close(); |
201 | |
202 | $expandFilePaths = Directory::getFiles($expandDirPath, true); |
203 | foreach ($expandFilePaths as $expandFilePath) { |
204 | $this->logger->info('{0}: {1}', $expandFilePath, File::getFileSize($expandFilePath)); |
205 | } |
206 | |
207 | $setting->mode = self::MODE_PREPARE; |
208 | $this->setProgressSetting($setting); |
209 | |
210 | return [ |
211 | 'count' => (string)$setting->uploadedCount, |
212 | 'size' => (string)File::getFileSize($archiveFilePath), |
213 | ]; |
214 | } |
215 | |
216 | /** |
217 | * [デプロイ] 最終処理。 |
218 | * |
219 | * @return array<string,string> |
220 | */ |
221 | private function executeUpdate(): array |
222 | { |
223 | $this->logger->info('各種データバックアップ'); |
224 | $this->appArchiver->backup(); |
225 | $this->appArchiver->rotate(); |
226 | |
227 | // 今のソースでDB開いとく(使わんけど) |
228 | $database = $this->openDatabase(); |
229 | |
230 | $this->logger->info('展開ファイルを公開ディレクトリに配置'); |
231 | $dirs = []; |
232 | $expandDirPath = $this->getExpandDirectoryPath(); |
233 | $expandFilePaths = Directory::getFiles($expandDirPath, true); |
234 | foreach ($expandFilePaths as $expandFilePath) { |
235 | $basePath = Text::substring($expandFilePath, Text::getLength($expandDirPath) + 1); |
236 | $toPath = Path::combine($this->programContext->rootDirectory, $basePath); |
237 | |
238 | //$this->logger->info('UPDATE: {0}', $toPath); |
239 | |
240 | $parentPath = Path::getDirectoryPath($toPath); |
241 | if (!isset($dirs[$parentPath])) { |
242 | Directory::createDirectoryIfNotExists($parentPath); |
243 | $dirs[$parentPath] = true; |
244 | } |
245 | File::copy($expandFilePath, $toPath); |
246 | } |
247 | |
248 | $this->logger->info('各種マイグレーション実施'); |
249 | $setupRunner = new SetupRunner( |
250 | $this->databaseConnection, |
251 | $this->appConfig, |
252 | $this->loggerFactory |
253 | ); |
254 | $setupRunner->execute(); |
255 | |
256 | $this->logger->info('デプロイ進捗ファイル破棄'); |
257 | $progressFilePath = Path::combine($this->appConfig->setting->cache->deploy, AdministratorApiDeployLogic::PROGRESS_FILE_NAME); |
258 | File::removeFile($progressFilePath); |
259 | |
260 | return [ |
261 | 'success' => (string)true, |
262 | ]; |
263 | } |
264 | |
265 | #endregion |
266 | |
267 | #region ApiLogicBase |
268 | |
269 | protected function validateImpl(LogicCallMode $callMode): void |
270 | { |
271 | $mode = $this->getRequest('mode'); |
272 | $modeIsEnabled = Arr::in([ |
273 | self::MODE_STARTUP, |
274 | self::MODE_UPLOAD, |
275 | self::MODE_PREPARE, |
276 | self::MODE_UPDATE, |
277 | ], $mode); |
278 | |
279 | if (!$modeIsEnabled) { |
280 | throw new Exception('$mode: ' . $mode); |
281 | } |
282 | |
283 | if (File::exists($this->getProgressFilePath())) { |
284 | $setting = $this->getProgressSetting(); |
285 | $current = Utc::createDateTime(); |
286 | $limit = $setting->startupTime->add(new DateInterval(self::ENABLED_RANGE_TIME)); |
287 | if ($limit < $current) { |
288 | $this->logger->error('進捗状況 時間制限: $current {0} < $limit {1}', Utc::toString($current), Utc::toString($limit)); |
289 | throw new Exception('$limit: ' . Utc::toString($limit)); |
290 | } |
291 | |
292 | switch ($mode) { |
293 | case self::MODE_STARTUP: |
294 | $this->logger->info('進捗ファイルは無視'); |
295 | break; |
296 | |
297 | case self::MODE_UPLOAD: |
298 | if (!($setting->mode === self::MODE_STARTUP || $setting->mode === self::MODE_UPLOAD)) { |
299 | $this->logger->error('進捗状況不整合: {0}', $setting->mode); |
300 | throw new Exception('$setting->mode: ' . $setting->mode); |
301 | } |
302 | break; |
303 | |
304 | case self::MODE_PREPARE: |
305 | if ($setting->mode !== self::MODE_UPLOAD) { |
306 | $this->logger->error('進捗状況不整合: {0}', $setting->mode); |
307 | throw new Exception('$setting->mode: ' . $setting->mode); |
308 | } |
309 | break; |
310 | |
311 | case self::MODE_UPDATE: |
312 | if ($setting->mode !== self::MODE_PREPARE) { |
313 | $this->logger->error('進捗状況不整合: {0}', $setting->mode); |
314 | throw new Exception('$setting->mode: ' . $setting->mode); |
315 | } |
316 | break; |
317 | |
318 | default: |
319 | throw new NotImplementedException(); |
320 | } |
321 | } elseif ($mode !== self::MODE_STARTUP) { |
322 | throw new Exception('$mode: ' . $mode . ', not found progress -> ' . $this->getProgressFilePath()); |
323 | } |
324 | } |
325 | |
326 | protected function executeImpl(LogicCallMode $callMode): void |
327 | { |
328 | $mode = $this->getRequest('mode'); |
329 | |
330 | $result = match ($mode) { |
331 | self::MODE_STARTUP => $this->executeStartup(), |
332 | self::MODE_UPLOAD => $this->executeUpload(), |
333 | self::MODE_PREPARE => $this->executePrepare(), |
334 | self::MODE_UPDATE => $this->executeUpdate(), |
335 | default => throw new Exception() |
336 | }; |
337 | |
338 | $this->setResponseJson(ResponseJson::success($result)); |
339 | } |
340 | |
341 | #endregion |
342 | } |
343 | |
344 | //phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses |
345 | class LocalProgressSetting |
346 | { |
347 | #region variable |
348 | |
349 | public DateTime $startupTime; |
350 | public string $mode; |
351 | public int $uploadedCount; |
352 | |
353 | #endregion |
354 | } |