Skip to content

Commit a7fef8b

Browse files
committed
Correctly handle backups that fail without an upload_id attached to them
1 parent 952715f commit a7fef8b

File tree

1 file changed

+49
-23
lines changed

1 file changed

+49
-23
lines changed

app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Http\JsonResponse;
88
use League\Flysystem\AwsS3v3\AwsS3Adapter;
99
use Pterodactyl\Http\Controllers\Controller;
10+
use Pterodactyl\Exceptions\DisplayException;
1011
use Pterodactyl\Extensions\Backups\BackupManager;
1112
use Pterodactyl\Repositories\Eloquent\BackupRepository;
1213
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -41,16 +42,14 @@ public function __construct(BackupRepository $repository, BackupManager $backupM
4142
*
4243
* @param \Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest $request
4344
* @param string $backup
44-
*
4545
* @return \Illuminate\Http\JsonResponse
4646
*
47-
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
4847
* @throws \Exception
4948
*/
5049
public function __invoke(ReportBackupCompleteRequest $request, string $backup)
5150
{
5251
/** @var \Pterodactyl\Models\Backup $model */
53-
$model = $this->repository->findFirstWhere([[ 'uuid', '=', $backup ]]);
52+
$model = Backup::query()->where('uuid', $backup)->firstOrFail();
5453

5554
if (! is_null($model->completed_at)) {
5655
throw new BadRequestHttpException(
@@ -60,39 +59,66 @@ public function __invoke(ReportBackupCompleteRequest $request, string $backup)
6059

6160
$successful = $request->input('successful') ? true : false;
6261

63-
$model->forceFill([
62+
$model->fill([
6463
'is_successful' => $successful,
6564
'checksum' => $successful ? ($request->input('checksum_type') . ':' . $request->input('checksum')) : null,
6665
'bytes' => $successful ? $request->input('size') : 0,
6766
'completed_at' => CarbonImmutable::now(),
6867
])->save();
6968

70-
// Check if we are using the s3 backup adapter.
69+
// Check if we are using the s3 backup adapter. If so, make sure we mark the backup as
70+
// being completed in S3 correctly.
7171
$adapter = $this->backupManager->adapter();
7272
if ($adapter instanceof AwsS3Adapter) {
73-
/** @var \Pterodactyl\Models\Backup $backup */
74-
$backup = Backup::query()->where('uuid', $backup)->firstOrFail();
75-
76-
$client = $adapter->getClient();
73+
$this->completeMultipartUpload($model, $adapter, $successful);
74+
}
7775

78-
$params = [
79-
'Bucket' => $adapter->getBucket(),
80-
'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid),
81-
'UploadId' => $backup->upload_id,
82-
];
76+
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
77+
}
8378

84-
// If the backup was not successful, send an AbortMultipartUpload request.
79+
/**
80+
* Marks a multipart upload in a given S3-compatiable instance as failed or successful for
81+
* the given backup.
82+
*
83+
* @param \Pterodactyl\Models\Backup $backup
84+
* @param \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter
85+
* @param bool $successful
86+
*
87+
* @throws \Exception
88+
* @throws \Pterodactyl\Exceptions\DisplayException
89+
*/
90+
protected function completeMultipartUpload(Backup $backup, AwsS3Adapter $adapter, bool $successful)
91+
{
92+
// This should never really happen, but if it does don't let us fall victim to Amazon's
93+
// wildly fun error messaging. Just stop the process right here.
94+
if (empty($backup->upload_id)) {
95+
// A failed backup doesn't need to error here, this can happen if the backup encouters
96+
// an error before we even start the upload. AWS gives you tooling to clear these failed
97+
// multipart uploads as needed too.
8598
if (! $successful) {
86-
$client->execute($client->getCommand('AbortMultipartUpload', $params));
87-
} else {
88-
// Otherwise send a CompleteMultipartUpload request.
89-
$params['MultipartUpload'] = [
90-
'Parts' => $client->execute($client->getCommand('ListParts', $params))['Parts'],
91-
];
92-
$client->execute($client->getCommand('CompleteMultipartUpload', $params));
99+
return;
93100
}
101+
throw new DisplayException('Cannot complete backup request: no upload_id present on model.');
94102
}
95103

96-
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
104+
$params = [
105+
'Bucket' => $adapter->getBucket(),
106+
'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid),
107+
'UploadId' => $backup->upload_id,
108+
];
109+
110+
$client = $adapter->getClient();
111+
if (! $successful) {
112+
$client->execute($client->getCommand('AbortMultipartUpload', $params));
113+
114+
return;
115+
}
116+
117+
// Otherwise send a CompleteMultipartUpload request.
118+
$params['MultipartUpload'] = [
119+
'Parts' => $client->execute($client->getCommand('ListParts', $params))['Parts'],
120+
];
121+
122+
$client->execute($client->getCommand('CompleteMultipartUpload', $params));
97123
}
98124
}

0 commit comments

Comments
 (0)