diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php index 7b96f1ba7..93ebc74a8 100644 --- a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -66,7 +66,7 @@ class BackupStatusController extends Controller // being completed in S3 correctly. $adapter = $this->backupManager->adapter(); if ($adapter instanceof AwsS3Adapter) { - $this->completeMultipartUpload($model, $adapter, $successful); + $this->completeMultipartUpload($model, $adapter, $successful, $request->input('parts')); } }); @@ -85,7 +85,7 @@ class BackupStatusController extends Controller * * @throws \Throwable */ - public function restore(Request $request, string $backup) + public function restore(Request $request, string $backup): JsonResponse { /** @var \Pterodactyl\Models\Backup $model */ $model = Backup::query()->where('uuid', $backup)->firstOrFail(); @@ -101,23 +101,24 @@ class BackupStatusController extends Controller } /** - * Marks a multipart upload in a given S3-compatiable instance as failed or successful for + * Marks a multipart upload in a given S3-compatible instance as failed or successful for * the given backup. * * @throws \Exception * @throws \Pterodactyl\Exceptions\DisplayException */ - protected function completeMultipartUpload(Backup $backup, AwsS3Adapter $adapter, bool $successful) + protected function completeMultipartUpload(Backup $backup, AwsS3Adapter $adapter, bool $successful, ?array $parts): void { // This should never really happen, but if it does don't let us fall victim to Amazon's // wildly fun error messaging. Just stop the process right here. if (empty($backup->upload_id)) { - // A failed backup doesn't need to error here, this can happen if the backup encouters + // A failed backup doesn't need to error here, this can happen if the backup encounters // an error before we even start the upload. AWS gives you tooling to clear these failed // multipart uploads as needed too. if (!$successful) { return; } + throw new DisplayException('Cannot complete backup request: no upload_id present on model.'); } @@ -136,9 +137,20 @@ class BackupStatusController extends Controller // Otherwise send a CompleteMultipartUpload request. $params['MultipartUpload'] = [ - 'Parts' => $client->execute($client->getCommand('ListParts', $params))['Parts'], + 'Parts' => [], ]; + if (is_null($parts)) { + $params['MultipartUpload']['Parts'] = $client->execute($client->getCommand('ListParts', $params))['Parts']; + } else { + foreach ($parts as $part) { + $params['MultipartUpload']['Parts'][] = [ + 'ETag' => $part['etag'], + 'PartNumber' => $part['part_number'], + ]; + } + } + $client->execute($client->getCommand('CompleteMultipartUpload', $params)); } } diff --git a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php index 0c96b3f02..dcf7435c7 100644 --- a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php +++ b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php @@ -16,6 +16,9 @@ class ReportBackupCompleteRequest extends FormRequest 'checksum' => 'nullable|string|required_if:successful,true', 'checksum_type' => 'nullable|string|required_if:successful,true', 'size' => 'nullable|numeric|required_if:successful,true', + 'parts' => 'nullable|array', + 'parts.*.etag' => 'required|string', + 'parts.*.part_number' => 'required|numeric', ]; } }