Skip to content

Commit 290cf3b

Browse files
#228 - Only delete the uploaded files written for the execution, keep them while a failed message will be retried, delete them on a rejected HTTP request
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 74dd715 commit 290cf3b

39 files changed

Lines changed: 1162 additions & 16 deletions

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Latest
22
------
33

4+
## Fixes
5+
* [#228](https://github.com/cleverage/ui-process-bundle/issues/228) `ProcessExecuteHandler`, `ProcessExecuteController` (`cleverage_ui_process_http_execute`): only the files written by the launch form or the HTTP API for the execution are deleted (#209), flagged by the new `deleteInput` argument of `ProcessExecuteMessage` (`false` by default): a file of the upload directory given by its path (HTTP API, text input of the launch form, schedule, default input) is no longer deleted. A message failing in a Messenger worker keeps its file while it will be retried or when it is sent to a failure transport (`messenger:failed:retry`), new internal `UploadedFileSubscriber`. The file uploaded with a request rejected by the HTTP API (422) is deleted. The messages queued by 3.4 have no flag: their file is no longer deleted by the worker, it may need a manual cleanup of the upload directory (`cleverage_ui_process.upload_directory`). Update documentation, add tests.
6+
47
v3.4
58
------
69

‎config/services/event_subscriber.yaml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@ services:
99
- '@cleverage_ui_process.monolog_handler.doctrine_process'
1010
- '@cleverage_ui_process.manager.process_execution'
1111
- '@event_dispatcher'
12+
13+
cleverage_ui_process.event_subscriber.uploaded_file:
14+
class: CleverAge\UiProcessBundle\EventSubscriber\UploadedFileSubscriber
15+
public: false
16+
tags:
17+
- { name: 'kernel.event_subscriber' }
18+
arguments:
19+
- '@cleverage_ui_process.manager.uploaded_file'
20+
# Failure transports: set by the UploadedFileSubscriberPass

‎config/services/message.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ services:
1717
- '@cleverage_process.manager.process'
1818
- '@cleverage_ui_process.monolog_handler.process'
1919
- '@cleverage_ui_process.manager.uploaded_file'
20+
- '@cleverage_ui_process.event_subscriber.uploaded_file'
2021
tags: [messenger.message_handler]
2122

‎docs/reference/01-bundle_configuration.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Container parameters
9797
| Parameter | Default | Description |
9898
|-----------------------------------------|-------------------------------------------|-------------|
9999
| `cleverage_ui_process.upload_directory` | `%kernel.project_dir%/var/storage/uploads` | Directory where files uploaded from the launch form or the [HTTP API](06-http_api.md) are stored before being passed as process input. |
100-
| `cleverage_ui_process.delete_uploaded_files` | `true` | Delete the uploaded file once the process has been executed (successfully or not), by the Messenger handler or by the HTTP API when the process is not queued. Only the files of `cleverage_ui_process.upload_directory` are deleted. Set it to `false` to keep them, e.g. when a process stores the path of its input. |
100+
| `cleverage_ui_process.delete_uploaded_files` | `true` | Delete the uploaded file once the process has been executed (successfully or not), by the Messenger handler or by the HTTP API when the process is not queued; the file of a request rejected by the HTTP API is also deleted. Only the files written by the bundle for the execution are deleted, never a file given by its path, even in `cleverage_ui_process.upload_directory`. A message failing in a Messenger worker keeps its file while it will be retried, or when it is sent to a failure transport (see [messenger](08-messenger.md#uploaded-files)). Set it to `false` to keep them, e.g. when a process stores the path of its input. |
101101
| `upload_directory` | `%cleverage_ui_process.upload_directory%` | Deprecated since 3.3, removed in 4.0: use `cleverage_ui_process.upload_directory`. When your application still sets it, its value is used, with a deprecation. If your application has its own `upload_directory` parameter (e.g. for another bundle), also set `cleverage_ui_process.upload_directory`: the bundle then leaves `upload_directory` alone, without deprecation. |
102102

103103
It can be overridden in your application:

‎docs/reference/02-process_ui_options.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ The launch form (`/process/launch?process=<process_code>`) contains:
8686
When `entrypoint_type` is `file`, the uploaded file is saved to the `cleverage_ui_process.upload_directory` directory (see
8787
[bundle configuration](01-bundle_configuration.md#container-parameters)) with a random name keeping the original
8888
extension, and its absolute path is passed as process input. The file is deleted once the process has been executed,
89-
unless `cleverage_ui_process.delete_uploaded_files` is `false`.
89+
unless `cleverage_ui_process.delete_uploaded_files` is `false`. When `entrypoint_type` is `text`, a file path given as
90+
input is never deleted.
9091

9192
Default values
9293
--------------

‎docs/reference/06-http_api.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Parameters can be sent either as a JSON body, or as form data (`application/x-ww
3131
| Parameter | Type | Required | Default | Description |
3232
|-----------|---------------------------|:--------:|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
3333
| `code` | `string` | **X** | | Code of the process. It must exist and be public. |
34-
| `input` | `string` or file | | `null` | Process input. With form data, it can be an uploaded file: the file is saved in the `cleverage_ui_process.upload_directory` directory (see [bundle configuration](01-bundle_configuration.md#container-parameters)) and its path is passed as input; the file is deleted once the process has been executed, unless `cleverage_ui_process.delete_uploaded_files` is `false`. |
34+
| `input` | `string` or file | | `null` | Process input. With form data, it can be an uploaded file: the file is saved in the `cleverage_ui_process.upload_directory` directory (see [bundle configuration](01-bundle_configuration.md#container-parameters)) and its path is passed as input; the file is deleted once the process has been executed, or when the request is rejected (422), unless `cleverage_ui_process.delete_uploaded_files` is `false`. A file given by its path is never deleted. |
3535
| `context` | `object` or JSON `string` | | `{}` | Process context, as key/value pairs. A JSON string must decode to an object or an array (`422` otherwise). With form data, send one field per value: `context[key]=value`. |
3636
| `queue` | `bool` | | `true` | `true`: the process is queued to the `execute_process` transport (see [messenger](08-messenger.md)). `false`: the process is executed during the HTTP request. |
3737

‎docs/reference/08-messenger.md‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Messages
1010

1111
| Message | Transport | Handler |
1212
|--------------------------------------------------------|-------------------|-----------------------------------------------------------------------------------------------------------------|
13-
| `CleverAge\UiProcessBundle\Message\ProcessExecuteMessage` (`code`, `input`, `context`) | `execute_process` | Executes the process with the process manager, like `cleverage:process:execute`. |
13+
| `CleverAge\UiProcessBundle\Message\ProcessExecuteMessage` (`code`, `input`, `context`, `deleteInput`) | `execute_process` | Executes the process with the process manager, like `cleverage:process:execute`. |
1414
| `CleverAge\UiProcessBundle\Message\CronProcessMessage` (`processSchedule`) | `scheduler_cron` | Reads the schedule again, and dispatches a `ProcessExecuteMessage` with its current input and context, unless it has been disabled or deleted. |
1515

1616
You can dispatch a `ProcessExecuteMessage` yourself to queue a process from your code:
@@ -27,6 +27,22 @@ public function importProducts(string $filePath): void
2727
}
2828
```
2929

30+
Uploaded files
31+
--------------
32+
33+
The launch form and the HTTP API set `deleteInput` (`false` by default) when the input is the file they have just
34+
written in the upload directory: the file is deleted once the process has been executed, successfully or not (see the
35+
`cleverage_ui_process.delete_uploaded_files` [parameter](01-bundle_configuration.md#container-parameters)). In a
36+
worker, the file of a failed message is kept while the message will be retried (retry strategy of the transport), or
37+
when it is sent to a failure transport, so that `messenger:failed:retry` executes it with its file. It is deleted once
38+
the message no longer fails, or when it fails again from the failure transport and Symfony drops it (not sent again to
39+
the failure transport it has been received from). A file of a message removed from the failure transport
40+
(`messenger:failed:remove`) is kept. With Symfony 6.4.0 to 6.4.4, which sends a message failing again from the failure
41+
transport back to it, its file is deleted anyway.
42+
43+
The messages queued by the version 3.4 of the bundle have no `deleteInput`: their file is not deleted, you may have to
44+
remove it from the upload directory.
45+
3046
Transports
3147
----------
3248

‎src/CleverAgeUiProcessBundle.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use CleverAge\UiProcessBundle\DependencyInjection\Compiler\CheckDoctrineLazyGhostObjectsPass;
1717
use CleverAge\UiProcessBundle\DependencyInjection\Compiler\UploadDirectoryParameterPass;
18+
use CleverAge\UiProcessBundle\DependencyInjection\Compiler\UploadedFileSubscriberPass;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
1920
use Symfony\Component\HttpKernel\Bundle\Bundle;
2021

@@ -36,5 +37,6 @@ public function build(ContainerBuilder $container): void
3637

3738
$container->addCompilerPass(new CheckDoctrineLazyGhostObjectsPass());
3839
$container->addCompilerPass(new UploadDirectoryParameterPass());
40+
$container->addCompilerPass(new UploadedFileSubscriberPass());
3941
}
4042
}

‎src/Controller/Admin/Process/LaunchAction.php‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,19 @@ public function __invoke(
113113
$form->handleRequest($this->requestStack->getMainRequest());
114114
if ($form->isSubmitted() && $form->isValid()) {
115115
$input = $form->get('input')->getData();
116+
// Only the file written here is deleted once executed, never a file given by its path (text input)
117+
$deleteInput = false;
116118
if ($input instanceof UploadedFile) {
117119
$filename = \sprintf('%s/%s.%s', $uploadDirectory, Uuid::v4(), $input->getClientOriginalExtension());
118120
(new Filesystem())->dumpFile($filename, $input->getContent());
119121
$input = $filename;
122+
$deleteInput = true;
120123
}
121124
/** @var string $processCode */
122125
$processCode = $form->getConfig()->getOption('process_code');
123126
/** @var array<string, mixed> $context */
124127
$context = $form->get('context')->getData();
125-
$this->dispatch($processCode, $input, $context);
128+
$this->dispatch($processCode, $input, $context, $deleteInput);
126129
$this->addFlash(
127130
'success',
128131
'Process has been added to queue. It will start as soon as possible'
@@ -143,15 +146,16 @@ public function __invoke(
143146
/**
144147
* @param array<string, mixed> $context
145148
*/
146-
protected function dispatch(string $processCode, mixed $input = null, array $context = []): void
149+
protected function dispatch(string $processCode, mixed $input = null, array $context = [], bool $deleteInput = false): void
147150
{
148151
$message = new ProcessExecuteMessage(
149152
$processCode,
150153
$input,
151154
array_merge(
152155
['execution_user' => $this->getUser()?->getEmail()],
153156
$context
154-
)
157+
),
158+
$deleteInput
155159
);
156160
$this->messageBus->dispatch($message);
157161
}

‎src/Controller/ProcessExecuteController.php‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
use CleverAge\ProcessBundle\Manager\ProcessManagerInterface;
1717
use CleverAge\UiProcessBundle\Entity\ProcessExecution;
1818
use CleverAge\UiProcessBundle\Http\Model\HttpProcessExecution;
19+
use CleverAge\UiProcessBundle\Http\ValueResolver\HttpProcessExecuteValueResolver;
1920
use CleverAge\UiProcessBundle\Manager\ProcessExecutionManager;
2021
use CleverAge\UiProcessBundle\Manager\UploadedFileManager;
2122
use CleverAge\UiProcessBundle\Message\ProcessExecuteMessage;
2223
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
2324
use Symfony\Component\HttpFoundation\JsonResponse;
25+
use Symfony\Component\HttpFoundation\Request;
2426
use Symfony\Component\HttpFoundation\Response;
2527
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
2628
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
@@ -47,13 +49,18 @@ public function __construct(
4749

4850
public function __invoke(
4951
#[ValueResolver('http_process_execution')] HttpProcessExecution $httpProcessExecution,
52+
Request $request,
5053
): JsonResponse {
54+
// Only the file written by the value resolver is deleted, never a file given by its path
55+
$uploadedInput = $request->attributes->get(HttpProcessExecuteValueResolver::UPLOADED_INPUT_ATTRIBUTE);
56+
$deleteInput = \is_string($uploadedInput) && $uploadedInput === $httpProcessExecution->input;
5157
$violations = $this->validator->validate($httpProcessExecution);
5258
if ($violations->count() > 0) {
5359
$violationsMessages = [];
5460
foreach ($violations as $violation) {
5561
$violationsMessages[] = $violation->getMessage();
5662
}
63+
$this->uploadedFileManager?->deleteInput($uploadedInput);
5764
throw new UnprocessableEntityHttpException(implode('. ', $violationsMessages));
5865
}
5966
/** @var array<string, mixed> $context */
@@ -65,7 +72,8 @@ public function __invoke(
6572
new ProcessExecuteMessage(
6673
$httpProcessExecution->code ?? '',
6774
$httpProcessExecution->input,
68-
$context
75+
$context,
76+
$deleteInput
6977
)
7078
);
7179

@@ -87,7 +95,9 @@ public function __invoke(
8795

8896
return new JsonResponse($message, Response::HTTP_INTERNAL_SERVER_ERROR);
8997
} finally {
90-
$this->uploadedFileManager?->deleteInput($httpProcessExecution->input);
98+
if ($deleteInput) {
99+
$this->uploadedFileManager?->deleteInput($httpProcessExecution->input);
100+
}
91101
}
92102

93103
return new JsonResponse('Process has been proceed well.');

0 commit comments

Comments
 (0)