-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/http api new approach #348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b11b392
c7f4f57
063701d
26007b4
c0ed960
01b1a6e
9cf4925
60a3048
556d590
5fe5d9e
68466a6
b21327f
496bf78
7d2077c
2f362a8
7f3f58c
d3c90a3
efdf20c
aabf00d
0ecbe2a
06c5220
6737e30
3c89aa2
ec0d8eb
126882b
8e12bf3
d19a710
a94806d
0ab447a
8a08a07
29ecb3d
c16b06e
a3321b4
45f6920
e0a3bae
0fc0165
ab85ca0
c60a25d
1553b51
5696b15
1bc8d5e
bcdaa6a
3df3f44
2920f41
59d9a35
acd7420
a5e95a3
8990562
3a4c34a
beb1697
12373f5
9e34b52
1f4b181
0f7edcd
ac8fc2d
c57b2cb
6b52714
7f456b0
0e7e520
980b2fe
b92d959
624c432
e77113f
180a9c2
dea16fc
eb5da26
035e282
69905f0
4ca5e45
03a6143
5d2552d
fe3dd2f
1b1347d
9ee787f
5cad9b2
bbde73d
efbcd6c
cd5f3fe
22e569a
e5208f2
1e3c5b5
b95b58b
658959f
7d4736b
c3067f3
81195be
9c9ace0
8c4d74f
2dd28b7
276edbb
33cc849
db71dcf
5e4c3f4
a6b655b
8f9cd1a
372f77b
6e86689
6118ca7
14780e9
baa1a97
f73db5a
20b4344
e96da9b
055b26c
55be924
f766bf9
e9e6ace
95ac12d
997eb44
9585bc4
01c5bfe
42c099d
e7e780d
6202bce
28900b6
20a155c
13b61f7
c832308
fe8bfe7
784480d
a99837e
b21d269
dacd2c9
206408f
e2a1073
416d28a
759aadf
3978487
6e9ddb5
85ab301
4083ea8
a3dfd92
4d400f0
6891cb4
3b9390c
0b23052
33a9e38
80b45cd
bd85f8f
09e06ee
aee2eaa
3cee7e2
1191aed
5d2a744
af8d7f5
5b66701
9f21561
9376563
7bbf13e
01a8519
3db3180
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| <?php | ||
|
|
||
| /* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */ | ||
|
|
||
| namespace Icinga\Module\Notifications\Clicommands; | ||
|
|
||
| use FilesystemIterator; | ||
| use Icinga\Application\Icinga; | ||
| use Icinga\Cli\Command; | ||
| use Icinga\Module\Notifications\Api\OpenApiPreprocessor\AddGlobal401Response; | ||
| use Icinga\Module\Notifications\Common\PsrLogger; | ||
| use OpenApi\Generator; | ||
| use RecursiveDirectoryIterator; | ||
| use RecursiveIteratorIterator; | ||
| use RuntimeException; | ||
| use SplFileInfo; | ||
| use Throwable; | ||
|
|
||
| class OpenapiCommand extends Command | ||
| { | ||
| /** | ||
| * Generate an OpenAPI JSON file from PHP attributes. | ||
| * | ||
| * This command scans a directory for PHP files and generates an OpenAPI JSON file using swagger-php. | ||
| * The paths are relative to the notifications module directory. | ||
| * | ||
| * USAGE: | ||
| * | ||
| * icingacli notifications openapi generate [OPTIONS] | ||
| * | ||
| * OPTIONS | ||
| * | ||
| * --dir <path/> Set the path to the directory to scan for PHP files. | ||
| * Default: /library/Notifications/Api/ | ||
| * | ||
| * --exclude <comma seperated strings> Exclude files matching these strings. Wildcard is `*` | ||
| * | ||
| * --include <comma seperated strings> Include files matching these strings. Wildcard is `*` | ||
| * | ||
| * --output <path/> Set the path to the output file. | ||
| * Default: /doc/api/api-v1-public.json | ||
| * | ||
| * --api-version <version string> Set the API version. | ||
| * Default: v1 | ||
| * If the output path is set the --api-version option is ignored. | ||
| * | ||
| * --oad-version <version string> Set the OpenAPI version. | ||
| * Default: 3.1.0 | ||
| */ | ||
| public function generateAction(): void | ||
| { | ||
| $directoryInNotifications = $this->params->get('dir', '/library/Notifications/Api/'); | ||
| $exclude = $this->params->get('exclude'); | ||
| $include = $this->params->get('include'); | ||
| $outputPath = $this->params->get('output'); | ||
| $apiVersion = $this->params->get('api-version', 'v1'); | ||
| $oadVersion = $this->params->get('oad-version', '3.1.0'); | ||
|
|
||
| $notificationsPath = Icinga::app()->getModuleManager()->getModule('notifications')->getBaseDir(); | ||
| $directory = $notificationsPath . $directoryInNotifications; | ||
|
Check failure on line 60 in application/clicommands/OpenapiCommand.php
|
||
|
|
||
| $baseDirectory = realpath($directory); | ||
| if ($baseDirectory === false || ! is_dir($baseDirectory)) { | ||
| throw new RuntimeException("Invalid directory: {$directory}"); | ||
| } | ||
|
|
||
| $exclude = isset($exclude) ? array_map('trim', explode(',', $exclude)) : []; | ||
|
Check failure on line 67 in application/clicommands/OpenapiCommand.php
|
||
| $include = isset($include) ? array_map('trim', explode(',', $include)) : []; | ||
|
Check failure on line 68 in application/clicommands/OpenapiCommand.php
|
||
| $outputPath = $notificationsPath . ($outputPath ?? '/doc/api/api-' . $apiVersion . '-public.json'); | ||
|
Check failure on line 69 in application/clicommands/OpenapiCommand.php
|
||
|
|
||
| $files = $this->collectPhpFiles($baseDirectory, $exclude, $include); | ||
|
|
||
| echo "→ Scanning directory: $baseDirectory\n"; | ||
| echo "→ Found " . count($files) . " PHP files\n"; | ||
|
|
||
| $generator = new Generator(new PsrLogger()); | ||
| $generator->setVersion($oadVersion); | ||
|
Check failure on line 77 in application/clicommands/OpenapiCommand.php
|
||
| $generator->getProcessorPipeline()->add(new AddGlobal401Response()); | ||
|
|
||
| try { | ||
| $openapi = $generator->generate($files); | ||
|
|
||
| $json = $openapi->toJson( | ||
|
Check failure on line 83 in application/clicommands/OpenapiCommand.php
|
||
| JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_IGNORE | JSON_PRETTY_PRINT | ||
| ); | ||
|
|
||
| $dir = dirname($outputPath); | ||
| if (! is_dir($dir)) { | ||
| mkdir($dir, 0755, true); | ||
| } | ||
|
|
||
| file_put_contents($outputPath, $json); | ||
|
|
||
| echo "OpenAPI documentation written to: $outputPath\n"; | ||
| } catch (Throwable $e) { | ||
| fwrite(STDERR, "Error generating OpenAPI: " . $e->getMessage() . "\n"); | ||
| exit(1); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Recursively scan a directory for PHP files. | ||
| */ | ||
| protected function collectPhpFiles(string $baseDirectory, array $exclude, array $include): array | ||
|
Check failure on line 104 in application/clicommands/OpenapiCommand.php
|
||
| { | ||
| $baseDirectory = rtrim($baseDirectory, '/') . '/'; | ||
| if (! is_dir($baseDirectory)) { | ||
| throw new RuntimeException("Directory $baseDirectory does not exist"); | ||
| } | ||
| if (! is_readable($baseDirectory)) { | ||
| throw new RuntimeException("Directory $baseDirectory is not readable"); | ||
| } | ||
|
|
||
| $files = []; | ||
| $iterator = new RecursiveIteratorIterator( | ||
| new RecursiveDirectoryIterator($baseDirectory, FilesystemIterator::SKIP_DOTS) | ||
| ); | ||
|
|
||
| /** @var SplFileInfo $file */ | ||
| foreach ($iterator as $file) { | ||
| if (! $file->isFile() || $file->getExtension() !== 'php') { | ||
| continue; | ||
| } | ||
|
|
||
| $path = $file->getPathname(); | ||
|
|
||
| if ($exclude !== [] && $this->matchesAnyPattern($path, $exclude)) { | ||
| continue; | ||
| } | ||
|
|
||
| if ($include !== [] && ! $this->matchesAnyPattern($path, $include)) { | ||
| continue; | ||
| } | ||
|
|
||
| $files[] = $path; | ||
| } | ||
|
|
||
| if (empty($files)) { | ||
| throw new RuntimeException("No PHP files found in $baseDirectory"); | ||
| } | ||
|
|
||
| return $files; | ||
| } | ||
|
|
||
| protected function matchesAnyPattern(string $string, array $patterns): bool | ||
| { | ||
| foreach ($patterns as $pattern) { | ||
| // Escape regex special chars except for '*' | ||
| $regex = '/^' . str_replace('\*', '.*', preg_quote($pattern, '/')) . '$/'; | ||
| if (preg_match($regex, $string)) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| <?php | ||
|
|
||
| /* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */ | ||
|
|
||
| namespace Icinga\Module\Notifications\Controllers; | ||
|
|
||
| use Exception; | ||
| use Icinga\Exception\Http\HttpBadRequestException; | ||
| use Icinga\Module\Notifications\Api\Middleware\DispatchMiddleware; | ||
| use Icinga\Module\Notifications\Api\Middleware\EndpointExecutionMiddleware; | ||
| use Icinga\Module\Notifications\Api\Middleware\ErrorHandlingMiddleware; | ||
| use Icinga\Module\Notifications\Api\Middleware\LegacyRequestConversionMiddleware; | ||
| use Icinga\Module\Notifications\Api\Middleware\MiddlewarePipeline; | ||
| use Icinga\Module\Notifications\Api\Middleware\RoutingMiddleware; | ||
| use Icinga\Module\Notifications\Api\Middleware\ValidationMiddleware; | ||
| use Icinga\Security\SecurityException; | ||
| use Icinga\Web\Request; | ||
| use ipl\Web\Compat\CompatController; | ||
| use Psr\Http\Message\ResponseInterface; | ||
|
|
||
| class ApiController extends CompatController | ||
| { | ||
| /** | ||
| * Handle API requests and route them to the appropriate endpoint class. | ||
| * | ||
| * Processes API requests for the Notifications module, serving as the main entry point for all API interactions. | ||
| * | ||
| * @return never | ||
| * @throws SecurityException | ||
| */ | ||
| public function indexAction(): never | ||
| { | ||
| $this->assertPermission('notifications/api'); | ||
|
|
||
| $pipeline = new MiddlewarePipeline([ | ||
| new ErrorHandlingMiddleware(), | ||
| new LegacyRequestConversionMiddleware($this->getRequest()), | ||
| new RoutingMiddleware(), | ||
| new DispatchMiddleware(), | ||
| new ValidationMiddleware(), | ||
| new EndpointExecutionMiddleware(), | ||
| ]); | ||
|
|
||
| $this->emitResponse($pipeline->execute()); | ||
|
|
||
| exit; | ||
| } | ||
|
|
||
| /** | ||
| * Emit the HTTP response to the client. | ||
| * | ||
| * @param ResponseInterface $response The response object to emit. | ||
| * | ||
| * @return void | ||
| */ | ||
| protected function emitResponse(ResponseInterface $response): void | ||
| { | ||
| do { | ||
| ob_end_clean(); | ||
| } while (ob_get_level() > 0); | ||
|
|
||
| http_response_code($response->getStatusCode()); | ||
|
|
||
| foreach ($response->getHeaders() as $name => $values) { | ||
| foreach ($values as $value) { | ||
| header(sprintf('%s: %s', $name, $value), false); | ||
| } | ||
| } | ||
| header('Content-Type: application/json'); | ||
|
|
||
| $body = $response->getBody(); | ||
| while (! $body->eof()) { | ||
| echo $body->read(8192); | ||
| } | ||
| } | ||
| } |
nilmerg marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # REST API | ||
|
|
||
| Icinga Notifications Web provides a REST API that allows you to manage notification-related resources programmatically. | ||
|
|
||
| With this API, you can: | ||
| - Manage **contacts** and **contact groups** | ||
| - Read available **notification channels** | ||
|
|
||
| This API enables easy integration with external tools, automation workflows, and configuration management systems. | ||
|
|
||
| ## API Versioning | ||
|
|
||
| The API follows a **versioned** structure to ensure backward compatibility and predictable upgrades. | ||
|
|
||
| The current and first stable version is: /icingaweb2/notifications/api/v1 | ||
|
|
||
| Future versions will be accessible under corresponding paths (for example, `/api/v2`), allowing you to migrate at your own pace. | ||
|
|
||
| ## API Description | ||
|
|
||
| The complete API reference for version `v1` is available in [`api/v1.md`](api/v1.md). | ||
|
|
||
| It contains an OpenAPI v3.1 description with detailed information about all endpoints, including: | ||
| - Request and response schemas | ||
| - Example payloads | ||
| - Authentication requirements | ||
| - Error handling |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing license header. It seems, not only this file is missing it, please make sure all PHP files have one.