Skip to content

Commit

Permalink
ExApp init, disable apps actions if daemon not accessible (#96)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Borysenko <[email protected]>
  • Loading branch information
andrey18106 authored Oct 19, 2023
1 parent c0e2651 commit e409b7e
Show file tree
Hide file tree
Showing 16 changed files with 304 additions and 73 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [1.1.0 - 2023-10-20]

### Added

- Added ExApp initialization progress
- Added disabled state of app management actions if Deploy daemon is not accessible
- Added support for new fileActions registration (Nextcloud 28)

## [1.0.1 - 2023-10-06]

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
['name' => 'ExAppsPage#listApps', 'url' => '/apps/list', 'verb' => 'GET' , 'root' => ''],
['name' => 'ExAppsPage#enableApp', 'url' => '/apps/enable/{appId}', 'verb' => 'GET' , 'root' => ''],
['name' => 'ExAppsPage#enableApp', 'url' => '/apps/enable/{appId}', 'verb' => 'POST' , 'root' => ''],
['name' => 'ExAppsPage#getAppStatus', 'url' => '/apps/status/{appId}', 'verb' => 'GET' , 'root' => ''],
['name' => 'ExAppsPage#enableApps', 'url' => '/apps/enable', 'verb' => 'POST' , 'root' => ''],
['name' => 'ExAppsPage#disableApp', 'url' => '/apps/disable/{appId}', 'verb' => 'GET' , 'root' => ''],
['name' => 'ExAppsPage#disableApps', 'url' => '/apps/disable', 'verb' => 'POST' , 'root' => ''],
Expand All @@ -34,6 +35,7 @@
['name' => 'OCSApi#log', 'url' => '/api/v1/log', 'verb' => 'POST'],

['name' => 'OCSApi#getNCUsersList', 'url' => '/api/v1/users', 'verb' => 'GET'],
['name' => 'OCSApi#setAppProgress', 'url' => '/apps/status/{appId}', 'verb' => 'PUT'],

// ExApps
['name' => 'OCSExApp#getExAppsList', 'url' => '/api/v1/ex-app/{list}', 'verb' => 'GET'],
Expand Down
4 changes: 4 additions & 0 deletions css/settings.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.app-version {
color: var(--color-text-maxcontrast);
}

.app-level span {
color: var(--color-text-maxcontrast);
background-color: rgba(0, 0, 0, 0);
Expand Down
24 changes: 13 additions & 11 deletions lib/Command/ExApp/Register.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ protected function configure() {
$this->addArgument('appid', InputArgument::REQUIRED);
$this->addArgument('daemon-config-name', InputArgument::OPTIONAL);

$this->addOption('enabled', 'e', InputOption::VALUE_NONE, 'Enable ExApp after registration');
$this->addOption('force-scopes', null, InputOption::VALUE_NONE, 'Force scopes approval');
$this->addOption('info-xml', null, InputOption::VALUE_REQUIRED, '[required] Path to ExApp info.xml file (url or local absolute path)');
$this->addOption('json-info', null, InputOption::VALUE_REQUIRED, 'ExApp JSON deploy info');
Expand Down Expand Up @@ -191,16 +190,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->registerExAppScopes($output, $exApp, $requestedExAppScopeGroups['optional'], 'optional');
}

$enabled = (bool) $input->getOption('enabled');
if ($enabled) {
if ($this->service->enableExApp($exApp)) {
$output->writeln(sprintf('ExApp %s successfully enabled.', $appId));
} else {
$output->writeln(sprintf('Failed to enable ExApp %s.', $appId));
// Fallback unregistering ExApp
$this->service->unregisterExApp($exApp->getAppid());
return 1;
}
$this->service->dispatchExAppInit($exApp);

if ($daemonConfig->getAcceptsDeployId() === $this->manualActions->getAcceptsDeployId()) {
// Wait until ExApp initialized in case of manual-install type
do {
$exApp = $this->service->getExApp($appId);
$status = json_decode($exApp->getStatus(), true);
if (isset($status['error'])) {
$output->writeln(sprintf('ExApp %s initialization step failed. Error: %s', $appId, $status['error']));
return 1;
}
usleep(100000); // 0.1s
} while (isset($status['progress']));
}

$output->writeln(sprintf('ExApp %s successfully registered.', $appId));
Expand Down
11 changes: 2 additions & 9 deletions lib/Command/ExApp/Update.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ protected function configure() {
$this->addOption('info-xml', null, InputOption::VALUE_REQUIRED, '[required] Path to ExApp info.xml file (url or local absolute path)');
$this->addOption('force-update', null, InputOption::VALUE_NONE, 'Force ExApp update approval');
$this->addOption('force-scopes', null, InputOption::VALUE_NONE, 'Force new ExApp scopes approval');
$this->addOption('enabled', 'e', InputOption::VALUE_NONE, 'Enable ExApp after update');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
Expand Down Expand Up @@ -223,14 +222,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return 1;
}

$enabled = (bool) $input->getOption('enabled');
if ($enabled) {
if ($this->service->enableExApp($exApp)) {
$output->writeln(sprintf('ExApp %s successfully enabled.', $appId));
} else {
$output->writeln(sprintf('Failed to enable ExApp %s.', $appId));
}
}
$this->service->setAppInitProgress($appId, 0, '', true);
$this->service->dispatchExAppInit($exApp);

$output->writeln(sprintf('ExApp %s successfully updated.', $appId));
return 0;
Expand Down
64 changes: 51 additions & 13 deletions lib/Controller/ExAppsPageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,16 @@ public function viewApps(): TemplateResponse {
$appInitialData = [
'appstoreEnabled' => $this->config->getSystemValueBool('appstoreenabled', true),
'updateCount' => count($this->getExAppsWithUpdates()),
'default_daemon_config' => $defaultDaemonConfigName,
'docker_socket_accessible' => $this->dockerActions->isDockerSocketAvailable(),
];

if ($defaultDaemonConfigName !== '') {
$daemonConfig = $this->daemonConfigService->getDaemonConfigByName($defaultDaemonConfigName);
$appInitialData['daemon_config'] = $daemonConfig;
$this->dockerActions->initGuzzleClient($daemonConfig);
$daemonConfigAccessible = $this->dockerActions->ping($this->dockerActions->buildDockerUrl($daemonConfig));
$appInitialData['daemon_config_accessible'] = $daemonConfigAccessible;
if (!$daemonConfigAccessible) {
$this->logger->error(sprintf('Deploy daemon "%s" is not accessible by Nextcloud. Please verify its configuration', $daemonConfig->getName()));
}
}

$this->initialStateService->provideInitialState('apps', $appInitialData);
Expand Down Expand Up @@ -256,6 +259,7 @@ private function getAppsForCategory(string $requestedCategory = ''): array {
'daemon' => $daemon,
'systemApp' => $exApp !== null && $this->exAppUsersService->exAppUserExists($exApp->getAppid(), ''),
'exAppUrl' => $exApp !== null ? AppAPIService::getExAppUrl($exApp->getProtocol(), $exApp->getHost(), $exApp->getPort()) : '',
'status' => $exApp !== null ? json_decode($exApp->getStatus(), true) : [],
];
}

Expand Down Expand Up @@ -398,6 +402,7 @@ private function buildLocalAppsList(array $apps, array $exApps): array {
'exAppUrl' => AppAPIService::getExAppUrl($exApp->getProtocol(), $exApp->getHost(), $exApp->getPort()),
'releases' => [],
'update' => null,
'status' => json_decode($exApp->getStatus(), true),
];
}
}
Expand Down Expand Up @@ -447,26 +452,31 @@ public function enableApps(array $appIds, array $groups = []): JSONResponse {
// 2. Register ExApp (container must be already initialized successfully)
if (!$this->registerExApp($appId, $infoXml, $daemonConfig)) {
$this->service->unregisterExApp($appId); // Fallback unregister if failure
return new JSONResponse(['data' => ['message' => $this->l10n->t('Failed to register ExApp')]], Http::STATUS_INTERNAL_SERVER_ERROR);
}
} else {
$this->logger->error('Failed to Deploy ExApp');
$this->logger->error(sprintf('Failed to Deploy %s ExApp', $appId));
return new JSONResponse([
'data' => [
'message' => 'Failed to Deploy ExApp',
'message' => $this->l10n->t('Failed to Deploy ExApp'),
]
], Http::STATUS_INTERNAL_SERVER_ERROR);
}

$exApp = $this->service->getExApp($appId);
if (!$this->service->enableExApp($exApp)) {
return new JSONResponse(['data' => ['message' => 'Failed to enable ExApp']], Http::STATUS_INTERNAL_SERVER_ERROR);
}

// Start ExApp initialization step (to download dynamic content, e.g. models)
$this->service->dispatchExAppInit($exApp);

//if (!$this->service->enableExApp($exApp)) {
// return new JSONResponse(['data' => ['message' => $this->l10n->t('Failed to enable ExApp')]], Http::STATUS_INTERNAL_SERVER_ERROR);
//}
return new JSONResponse([
'data' => [
'update_required' => $updateRequired,
'daemon_config' => $daemonConfig,
'systemApp' => $this->exAppUsersService->exAppUserExists($exApp->getAppid(), ''),
'exAppUrl' => AppAPIService::getExAppUrl($exApp->getProtocol(), $exApp->getHost(), $exApp->getPort()),
'status' => json_decode($exApp->getStatus(), true),
]
]);
}
Expand All @@ -481,7 +491,7 @@ public function enableApps(array $appIds, array $groups = []): JSONResponse {
}

if (!$this->service->enableExApp($exApp)) {
return new JSONResponse(['data' => ['message' => 'Failed to enable ExApp']], Http::STATUS_INTERNAL_SERVER_ERROR);
return new JSONResponse(['data' => ['message' => $this->l10n->t('Failed to enable ExApp')]], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}

Expand Down Expand Up @@ -587,7 +597,7 @@ public function disableApps(array $appIds): JSONResponse {
foreach ($appIds as $appId) {
$exApp = $this->service->getExApp($appId);
if (!$this->service->disableExApp($exApp)) {
return new JSONResponse(['data' => ['message' => 'Failed to disable ExApp']], Http::STATUS_INTERNAL_SERVER_ERROR);
return new JSONResponse(['data' => ['message' => $this->l10n->t('Failed to disable ExApp')]], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
return new JSONResponse([]);
Expand Down Expand Up @@ -650,19 +660,37 @@ public function updateApp(string $appId): JSONResponse {
'host' => $exAppInfo['host'],
'port' => (int) $exAppInfo['port'],
])) {
// 6. Enable ExApp
$this->service->enableExApp($exApp);
// 6. Set initialization progress to start
$this->service->setAppInitProgress($appId, 0, '', true);
// 7. Dispatch init step on ExApp side
$this->service->dispatchExAppInit($exApp);
}

return new JSONResponse([
'data' => [
'appid' => $appId,
'status' => ['progress' => 0],
'systemApp' => filter_var($exAppInfo['system_app'], FILTER_VALIDATE_BOOLEAN),
'exAppUrl' => AppAPIService::getExAppUrl($exAppInfo['protocol'], $exAppInfo['host'], (int) $exAppInfo['port']),
]
]);
}

public function enableExApp(string $appId): JSONResponse {
$exApp = $this->service->getExApp($appId);
if (!$this->service->enableExApp($exApp)) {
return new JSONResponse(['data' => ['message' => $this->l10n->t('Failed to enable ExApp')]]);
}

return new JSONResponse([
'data' => [
'appid' => $appId,
'systemApp' => $this->exAppUsersService->exAppUserExists($exApp->getAppid(), ''),
'exAppUrl' => AppAPIService::getExAppUrl($exApp->getProtocol(), $exApp->getHost(), $exApp->getPort()),
]
]);
}

/**
* @param ExApp $exApp
* @param \SimpleXMLElement $infoXml
Expand Down Expand Up @@ -729,6 +757,16 @@ public function listCategories(): JSONResponse {
return new JSONResponse($this->getAllCategories());
}

/**
* Get ExApp status, that includes initialization information
*
* @return JSONResponse
*/
public function getAppStatus(string $appId): JSONResponse {
$exApp = $this->service->getExApp($appId);
return new JSONResponse(json_decode($exApp->getStatus(), true));
}

/**
* Using default methods to fetch App Store categories as they are the same for ExApps
*
Expand Down
16 changes: 16 additions & 0 deletions lib/Controller/OCSApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,20 @@ public function log(int $level, string $message): DataResponse {
public function getNCUsersList(): DataResponse {
return new DataResponse($this->service->getNCUsersList(), Http::STATUS_OK);
}

/**
* @PublicPage
* @NoCSRFRequired
*
* Get ExApp status, that required during initialization step with progress information
*
* @return DataResponse
*/
#[AppAPIAuth]
#[PublicPage]
#[NoCSRFRequired]
public function setAppProgress(string $appId, int $progress, string $error = ''): DataResponse {
$this->service->setAppInitProgress($appId, $progress, $error);
return new DataResponse();
}
}
Loading

0 comments on commit e409b7e

Please sign in to comment.