From 9d88017e1f82145316d304ecb5f0908de8a134cf Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 21 Aug 2023 11:21:18 +0100 Subject: [PATCH] Add Download Deployment Function --- app/controllers/api/functions.php | 87 +++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 5fef4ddbc2..fa904074f5 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -760,6 +760,93 @@ App::put('/v1/functions/:functionId') $response->dynamic($function, Response::MODEL_FUNCTION); }); +App::get('/v1/functions/:functionId/deployments/:deploymentId/download') + ->groups(['api', 'functions']) + ->desc('Download Deployment') + ->label('scope', 'functions.read') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'functions') + ->label('sdk.method', 'downloadDeployment') + ->label('sdk.description', '/docs/references/functions/download-deployment.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', '*/*') + ->label('sdk.methodType', 'location') + ->param('functionId', '', new UID(), 'Function ID.') + ->param('deploymentId', '', new UID(), 'Deployment ID.') + ->inject('response') + ->inject('request') + ->inject('dbForProject') + ->inject('deviceFunctions') + ->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceFunctions) { + + $function = $dbForProject->getDocument('functions', $functionId); + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $deployment = $dbForProject->getDocument('deployments', $deploymentId); + if ($deployment->isEmpty()) { + throw new Exception(Exception::DEPLOYMENT_NOT_FOUND); + } + + if ($deployment->getAttribute('resourceId') !== $function->getId()) { + throw new Exception(Exception::DEPLOYMENT_NOT_FOUND); + } + + $path = $deployment->getAttribute('path', ''); + if (!$deviceFunctions->exists($path)) { + throw new Exception(Exception::DEPLOYMENT_NOT_FOUND); + } + + $response + ->setContentType('application/gzip') + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('X-Peak', \memory_get_peak_usage()) + ->addHeader('Content-Disposition', 'attachment; filename="'.$deploymentId.'.tar.gz"') + ; + + $size = $deviceFunctions->getFileSize($path); + $rangeHeader = $request->getHeader('range'); + + if (!empty($rangeHeader)) { + $start = $request->getRangeStart(); + $end = $request->getRangeEnd(); + $unit = $request->getRangeUnit(); + + if ($end === null) { + $end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1)); + } + + if ($unit !== 'bytes' || $start >= $end || $end >= $size) { + throw new Exception(Exception::STORAGE_INVALID_RANGE); + } + + $response + ->addHeader('Accept-Ranges', 'bytes') + ->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size) + ->addHeader('Content-Length', $end - $start + 1) + ->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT); + + $response->send($deviceFunctions->read($path, $start, ($end - $start + 1))); + } + + if ($size > APP_STORAGE_READ_BUFFER) { + $response->addHeader('Content-Length', $deviceFunctions->getFileSize($path)); + for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) { + $response->chunk( + $deviceFunctions->read( + $path, + ($i * MAX_OUTPUT_CHUNK_SIZE), + min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE)) + ), + (($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size + ); + } + } else { + $response->send($deviceFunctions->read($path)); + } + }); + App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Update Function Deployment')