diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 16bd9cc97..48d95c905 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1282,12 +1282,13 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, GitHub $github, Event $events) use ($redeployVcs) { $function = $dbForProject->getDocument('functions', $functionId); - $deployment = $dbForProject->getDocument('deployments', $deploymentId); 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); } @@ -1298,11 +1299,22 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') throw new Exception(Exception::BUILD_NOT_FOUND); } - // TODO: Somehow set commit SHA & ownerName & repoName for git deployments, and file path for manual. Redeploy should use exact same source code + $deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([ + '$id' => ID::unique(), + 'buildId' => '', + 'buildInternalId' => '', + 'entrypoint' => $function->getAttribute('entrypoint'), + 'commands' => $function->getAttribute('commands', ''), + ])); - $installation = $dbForConsole->getDocument('installations', $deployment->getAttribute('installationId', '')); + $buildEvent = new Build(); - $redeployVcs($request, $function, $project, $installation, $dbForProject, new Document([]), $github); + $buildEvent + ->setType(BUILD_TYPE_DEPLOYMENT) + ->setResource($function) + ->setDeployment($deployment) + ->setProject($project) + ->trigger(); $events ->setParam('functionId', $function->getId()) @@ -1513,13 +1525,13 @@ App::post('/v1/functions/:functionId/executions') $executionResponse = $executor->createExecution( projectId: $project->getId(), deploymentId: $deployment->getId(), - version: $function->getAttribute('version'), body: \strlen($body) > 0 ? $body : null, variables: $vars, timeout: $function->getAttribute('timeout', 0), image: $runtime['image'], source: $build->getAttribute('path', ''), entrypoint: $deployment->getAttribute('entrypoint', ''), + version: $function->getAttribute('version'), path: $path, method: $method, headers: $headers, diff --git a/app/workers/builds.php b/app/workers/builds.php index be303b449..220e3b9cf 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -113,11 +113,10 @@ class BuildsV1 extends Worker $durationStart = \microtime(true); $buildId = $deployment->getAttribute('buildId', ''); - $build = null; $isNewBuild = empty($buildId); - if (empty($buildId)) { + if ($isNewBuild) { $buildId = ID::unique(); $build = $dbForProject->createDocument('builds', new Document([ '$id' => $buildId, @@ -150,135 +149,131 @@ class BuildsV1 extends Worker $isVcsEnabled = $providerRepositoryId ? true : false; $owner = ''; $repositoryName = ''; - $branchName = ''; + + if ($isVcsEnabled) { + $installation = $dbForConsole->getDocument('installations', $installationId); + $providerInstallationId = $installation->getAttribute('providerInstallationId'); + $privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); + $githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID'); + + $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); + } try { - if ($isNewBuild) { - if ($isVcsEnabled) { - $installation = $dbForConsole->getDocument('installations', $installationId); - $providerInstallationId = $installation->getAttribute('providerInstallationId'); + if ($isNewBuild && $isVcsEnabled) { + $tmpDirectory = '/tmp/builds/' . $buildId . '/code'; + $rootDirectory = $function->getAttribute('providerRootDirectory', ''); + $rootDirectory = \rtrim($rootDirectory, '/'); + $rootDirectory = \ltrim($rootDirectory, '.'); + $rootDirectory = \ltrim($rootDirectory, '/'); - $privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); - $githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID'); + $owner = $github->getOwnerName($providerInstallationId); + $repositoryName = $github->getRepositoryName($providerRepositoryId); - $tmpDirectory = '/tmp/builds/' . $buildId . '/code'; - $rootDirectory = $function->getAttribute('providerRootDirectory', ''); - $rootDirectory = \rtrim($rootDirectory, '/'); - $rootDirectory = \ltrim($rootDirectory, '.'); - $rootDirectory = \ltrim($rootDirectory, '/'); + $cloneOwner = $deployment->getAttribute('providerRepositoryOwner', $owner); + $cloneRepository = $deployment->getAttribute('providerRepositoryName', $repositoryName); - $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); + $branchName = $deployment->getAttribute('providerBranch'); + $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $branchName, $tmpDirectory, $rootDirectory); + $stdout = ''; + $stderr = ''; + Console::execute('mkdir -p /tmp/builds/' . $buildId, '', $stdout, $stderr); + $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr); - $owner = $github->getOwnerName($providerInstallationId); - $repositoryName = $github->getRepositoryName($providerRepositoryId); + if ($exit !== 0) { + throw new \Exception('Unable to clone code repository: ' . $stderr); + } - $cloneOwner = $deployment->getAttribute('providerRepositoryOwner', $owner); - $cloneRepository = $deployment->getAttribute('providerRepositoryName', $repositoryName); + // Build from template + $templateRepositoryName = $template->getAttribute('repositoryName', ''); + $templateOwnerName = $template->getAttribute('ownerName', ''); + $templateBranch = $template->getAttribute('branch', ''); - $branchName = $deployment->getAttribute('providerBranch'); - $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $branchName, $tmpDirectory, $rootDirectory); - $stdout = ''; - $stderr = ''; - Console::execute('mkdir -p /tmp/builds/' . $buildId, '', $stdout, $stderr); - $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr); + $templateRootDirectory = $template->getAttribute('rootDirectory', ''); + $templateRootDirectory = \rtrim($templateRootDirectory, '/'); + $templateRootDirectory = \ltrim($templateRootDirectory, '.'); + $templateRootDirectory = \ltrim($templateRootDirectory, '/'); + + if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateBranch)) { + // Clone template repo + $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template'; + $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateBranch, $tmpTemplateDirectory, $templateRootDirectory); + $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); if ($exit !== 0) { throw new \Exception('Unable to clone code repository: ' . $stderr); } - // Build from template - $templateRepositoryName = $template->getAttribute('repositoryName', ''); - $templateOwnerName = $template->getAttribute('ownerName', ''); - $templateBranch = $template->getAttribute('branch', ''); + // Ensure directories + Console::execute('mkdir -p ' . $tmpTemplateDirectory . '/' . $templateRootDirectory, '', $stdout, $stderr); + Console::execute('mkdir -p ' . $tmpDirectory . '/' . $rootDirectory, '', $stdout, $stderr); - $templateRootDirectory = $template->getAttribute('rootDirectory', ''); - $templateRootDirectory = \rtrim($templateRootDirectory, '/'); - $templateRootDirectory = \ltrim($templateRootDirectory, '.'); - $templateRootDirectory = \ltrim($templateRootDirectory, '/'); + // Merge template into user repo + Console::execute('cp -rfn ' . $tmpTemplateDirectory . '/' . $templateRootDirectory . '/* ' . $tmpDirectory . '/' . $rootDirectory, '', $stdout, $stderr); - if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateBranch)) { - // Clone template repo - $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template'; - $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateBranch, $tmpTemplateDirectory, $templateRootDirectory); - $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); + // Commit and push + $exit = Console::execute('git config --global user.email "security@appwrite.io" && git config --global user.name "Appwrite" && cd ' . $tmpDirectory . ' && git add . && git commit -m "Create \'' . $function->getAttribute('name', '') . '\' function" && git push origin ' . $branchName, '', $stdout, $stderr); - if ($exit !== 0) { - throw new \Exception('Unable to clone template repository: ' . $stderr); - } - - // Ensure directories - Console::execute('mkdir -p ' . $tmpTemplateDirectory . '/' . $templateRootDirectory, '', $stdout, $stderr); - Console::execute('mkdir -p ' . $tmpDirectory . '/' . $rootDirectory, '', $stdout, $stderr); - - // Merge template into user repo - Console::execute('cp -rfn ' . $tmpTemplateDirectory . '/' . $templateRootDirectory . '/* ' . $tmpDirectory . '/' . $rootDirectory, '', $stdout, $stderr); - - // Commit and push - $exit = Console::execute('git config --global user.email "security@appwrite.io" && git config --global user.name "Appwrite" && cd ' . $tmpDirectory . ' && git add . && git commit -m "Create \'' . $function->getAttribute('name', '') . '\' function" && git push origin ' . $branchName, '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to push code repository: ' . $stderr); - } - - $exit = Console::execute('cd ' . $tmpDirectory . ' && git rev-parse HEAD', '', $stdout, $stderr); - - if ($exit !== 0) { - throw new \Exception('Unable to get vcs commit SHA: ' . $stderr); - } - - $providerCommitHash = \trim($stdout); - $authorUrl = "https://github.com/$cloneOwner"; - - $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); - $deployment->setAttribute('providerCommitAuthorUrl', $authorUrl); - $deployment->setAttribute('providerCommitAuthor', 'Appwrite'); - $deployment->setAttribute('providerCommitMessage', "Create '" . $function->getAttribute('name', '') . "' function"); - $deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash"); - $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); - - /** - * Send realtime Event - */ - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $build, - project: $project - ); - Realtime::send( - projectId: 'console', - payload: $build->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); + if ($exit !== 0) { + throw new \Exception('Unable to push code repository: ' . $stderr); } - Console::execute('tar --exclude code.tar.gz -czf /tmp/builds/' . $buildId . '/code.tar.gz -C /tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory) . ' .', '', $stdout, $stderr); + $exit = Console::execute('cd ' . $tmpDirectory . ' && git rev-parse HEAD', '', $stdout, $stderr); - $deviceFunctions = $this->getFunctionsDevice($project->getId()); - - $fileName = 'code.tar.gz'; - $fileTmpName = '/tmp/builds/' . $buildId . '/code.tar.gz'; - - $path = $deviceFunctions->getPath($deployment->getId() . '.' . \pathinfo($fileName, PATHINFO_EXTENSION)); - - $result = $deviceFunctions->move($fileTmpName, $path); - - if (!$result) { - throw new \Exception("Unable to move file"); + if ($exit !== 0) { + throw new \Exception('Unable to get vcs commit SHA: ' . $stderr); } - Console::execute('rm -rf /tmp/builds/' . $buildId, '', $stdout, $stderr); + $providerCommitHash = \trim($stdout); + $authorUrl = "https://github.com/$cloneOwner"; - $source = $path; + $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); + $deployment->setAttribute('providerCommitAuthorUrl', $authorUrl); + $deployment->setAttribute('providerCommitAuthor', 'Appwrite'); + $deployment->setAttribute('providerCommitMessage', "Create '" . $function->getAttribute('name', '') . "' function"); + $deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash"); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); - $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); - - if ($isVcsEnabled) { - $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole); - } + /** + * Send realtime Event + */ + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); + Realtime::send( + projectId: 'console', + payload: $build->getArrayCopy(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'] + ); } + + Console::execute('tar --exclude code.tar.gz -czf /tmp/builds/' . $buildId . '/code.tar.gz -C /tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory) . ' .', '', $stdout, $stderr); + + $deviceFunctions = $this->getFunctionsDevice($project->getId()); + + $fileName = 'code.tar.gz'; + $fileTmpName = '/tmp/builds/' . $buildId . '/code.tar.gz'; + + $path = $deviceFunctions->getPath($deployment->getId() . '.' . \pathinfo($fileName, PATHINFO_EXTENSION)); + + $result = $deviceFunctions->move($fileTmpName, $path); + + if (!$result) { + throw new \Exception("Unable to move file"); + } + + Console::execute('rm -rf /tmp/builds/' . $buildId, '', $stdout, $stderr); + + $source = $path; + + $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); + + $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole); } /** Request the executor to build the code... */ @@ -369,11 +364,11 @@ class BuildsV1 extends Worker Co\go(function () use (&$response, $project, $deployment, $source, $function, $runtime, $vars, $command, &$err) { try { $response = $this->executor->createRuntime( - projectId: $project->getId(), deploymentId: $deployment->getId(), + projectId: $project->getId(), source: $source, - version: $function->getAttribute('version'), image: $runtime['image'], + version: $function->getAttribute('version'), remove: true, entrypoint: $deployment->getAttribute('entrypoint'), destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}", @@ -387,8 +382,8 @@ class BuildsV1 extends Worker Co\go(function () use ($project, $deployment, &$response, &$build, $dbForProject, $allEvents, &$err) { try { $this->executor->getLogs( - projectId: $project->getId(), deploymentId: $deployment->getId(), + projectId: $project->getId(), callback: function ($logs) use (&$response, &$build, $dbForProject, $allEvents, $project) { if ($response === null) { $build = $build->setAttribute('logs', $build->getAttribute('logs', '') . $logs); @@ -472,6 +467,8 @@ class BuildsV1 extends Worker $build->setAttribute('status', 'failed'); $build->setAttribute('logs', $th->getMessage()); Console::error($th->getMessage()); + Console::error($th->getFile() . ':' . $th->getLine()); + Console::error($th->getTraceAsString()); if ($isVcsEnabled) { $this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole); @@ -510,7 +507,7 @@ class BuildsV1 extends Worker ->trigger(); } - protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $function, string $deploymentId, Database $dbForProject, Database $dbForConsole) + protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $function, string $deploymentId, Database $dbForProject, Database $dbForConsole): void { if ($function->getAttribute('providerSilentMode', false) === true) { return; @@ -551,7 +548,7 @@ class BuildsV1 extends Worker if (!empty($commentId)) { $retries = 0; - while ($retries < 10) { + while (true) { $retries++; try { @@ -563,27 +560,20 @@ class BuildsV1 extends Worker if ($retries >= 9) { throw $err; } - } - \sleep(1); + \sleep(1); + } } - // Wrap in try/catch to ensure lock file gets deleted - $error = null; + // Wrap in try/finally to ensure lock file gets deleted try { $comment = new Comment(); $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); $comment->addBuild($project, $function, $status, $deployment->getId(), ['type' => 'logs']); $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); - } catch (\Exception $e) { - $error = $e; } finally { $dbForConsole->deleteDocument('vcsCommentLocks', $commentId); } - - if (!empty($error)) { - throw $error; - } } }