backups: support Cloudflare R2 by listing uploaded parts from Wings

This commit is contained in:
Matthew Penner 2022-09-26 11:15:52 -06:00
parent 0dc77aec25
commit 2dcc46ecd6
No known key found for this signature in database
GPG key ID: 31311906AD4CF6D6
2 changed files with 21 additions and 6 deletions

View file

@ -66,7 +66,7 @@ class BackupStatusController extends Controller
// being completed in S3 correctly. // being completed in S3 correctly.
$adapter = $this->backupManager->adapter(); $adapter = $this->backupManager->adapter();
if ($adapter instanceof AwsS3Adapter) { 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 * @throws \Throwable
*/ */
public function restore(Request $request, string $backup) public function restore(Request $request, string $backup): JsonResponse
{ {
/** @var \Pterodactyl\Models\Backup $model */ /** @var \Pterodactyl\Models\Backup $model */
$model = Backup::query()->where('uuid', $backup)->firstOrFail(); $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. * the given backup.
* *
* @throws \Exception * @throws \Exception
* @throws \Pterodactyl\Exceptions\DisplayException * @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 // 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. // wildly fun error messaging. Just stop the process right here.
if (empty($backup->upload_id)) { 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 // an error before we even start the upload. AWS gives you tooling to clear these failed
// multipart uploads as needed too. // multipart uploads as needed too.
if (!$successful) { if (!$successful) {
return; return;
} }
throw new DisplayException('Cannot complete backup request: no upload_id present on model.'); 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. // Otherwise send a CompleteMultipartUpload request.
$params['MultipartUpload'] = [ $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)); $client->execute($client->getCommand('CompleteMultipartUpload', $params));
} }
} }

View file

@ -16,6 +16,9 @@ class ReportBackupCompleteRequest extends FormRequest
'checksum' => 'nullable|string|required_if:successful,true', 'checksum' => 'nullable|string|required_if:successful,true',
'checksum_type' => 'nullable|string|required_if:successful,true', 'checksum_type' => 'nullable|string|required_if:successful,true',
'size' => 'nullable|numeric|required_if:successful,true', 'size' => 'nullable|numeric|required_if:successful,true',
'parts' => 'nullable|array',
'parts.*.etag' => 'required|string',
'parts.*.part_number' => 'required|numeric',
]; ];
} }
} }