From bc0388e6abefb0a2ea2bead02b85c0a1e428de43 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Wed, 30 Sep 2026 09:46:31 +0200 Subject: [PATCH] chore(doc) #32 Add missing documentations: harmonize and complete reference pages for every Task (renamed to snake_case), add SFTP import, SFTP export and remote cleanup cookbooks. Harmonize and fix existing documentation. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + docs/cookbooks/remote_cleanup.md | 66 ++++++++++++ docs/cookbooks/sftp_export.md | 70 +++++++++++++ docs/cookbooks/sftp_import.md | 102 ++++++++++++++++++ docs/index.md | 62 +++++++---- docs/reference/tasks/01-FileFetchTask.md | 76 -------------- docs/reference/tasks/02-ListContentTask.md | 42 -------- docs/reference/tasks/03-RemoveFileTask.md | 64 ------------ docs/reference/tasks/_template.md | 33 +++--- docs/reference/tasks/file_fetch_task.md | 115 +++++++++++++++++++++ docs/reference/tasks/list_content_task.md | 86 +++++++++++++++ docs/reference/tasks/remove_file_task.md | 72 +++++++++++++ docs/troubleshooting.md | 4 +- 13 files changed, 576 insertions(+), 217 deletions(-) create mode 100644 docs/cookbooks/remote_cleanup.md create mode 100644 docs/cookbooks/sftp_export.md create mode 100644 docs/cookbooks/sftp_import.md delete mode 100644 docs/reference/tasks/01-FileFetchTask.md delete mode 100644 docs/reference/tasks/02-ListContentTask.md delete mode 100644 docs/reference/tasks/03-RemoveFileTask.md create mode 100644 docs/reference/tasks/file_fetch_task.md create mode 100644 docs/reference/tasks/list_content_task.md create mode 100644 docs/reference/tasks/remove_file_task.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d1202..4e4c7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Latest ### Changes * [#30](https://github.com/cleverage/flysystem-process-bundle/issues/30) Update quality stack: use Rector `withComposerBased()` sets (removed `SYMFONY_64` / `PHPUNIT_100` sets), declare used Symfony packages and PHPUnit range in composer.json, apply quality tools fixes +* [#32](https://github.com/cleverage/flysystem-process-bundle/issues/32) Add missing documentations: harmonize and complete reference pages for every Task (renamed to snake_case), add SFTP import, SFTP export and remote cleanup cookbooks. Harmonize and fix existing documentation. v3.0 ------ diff --git a/docs/cookbooks/remote_cleanup.md b/docs/cookbooks/remote_cleanup.md new file mode 100644 index 0000000..3d79682 --- /dev/null +++ b/docs/cookbooks/remote_cleanup.md @@ -0,0 +1,66 @@ +Remote cleanup +============== + +This recipe describes how to purge files from a remote storage, e.g. the archives of the [SFTP import](sftp_import.md) +cookbook, while logging each deleted file. + +```yaml +clever_age_process: + configurations: + app.remote_cleanup: + description: 'Purge the archived CSV files of a given year' + help: "bin/console cleverage:process:execute app.remote_cleanup -c year:2025" + tasks: + list: + service: '@CleverAge\FlysystemProcessBundle\Task\ListContentTask' + options: + filesystem: 'remote.storage.archive' + file_pattern: '/^catalog_{{ year }}.*\.csv$/' + outputs: [only_files] + + only_files: + service: '@CleverAge\ProcessBundle\Task\FilterTask' + options: + match: + type: 'file' + outputs: [get_path] + + get_path: + service: '@CleverAge\ProcessBundle\Task\PropertyGetterTask' + options: + property: 'path' + outputs: [remove] + + remove: + service: '@CleverAge\FlysystemProcessBundle\Task\RemoveFileTask' + options: + filesystem: 'remote.storage.archive' +``` + +How it works: +- The [ListContentTask](../reference/tasks/list_content_task.md) iterates over the items at the root of + `remote.storage.archive` whose path matches `file_pattern`; the `{{ year }}` placeholder is replaced by the `year` + context value given on the command line. Each item is a `League\Flysystem\StorageAttributes` object. +- `ListContentTask` also lists directories: the + [FilterTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/filter_task.md) keeps only the + items whose `type` is `file`. +- The [PropertyGetterTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/property_getter_task.md) + extracts the `path` of the file, relative to the storage root. +- The [RemoveFileTask](../reference/tasks/remove_file_task.md) deletes the file given as input, and logs it. A deletion + failure is logged as a warning and does not stop the process. + +When you do not need to act on each file (log, filter on other attributes, etc.), a single +[RemoveFileTask](../reference/tasks/remove_file_task.md) with a `file_pattern` option does the same (it only +considers files): + +```yaml +clever_age_process: + configurations: + app.remote_cleanup_pattern: + tasks: + remove: + service: '@CleverAge\FlysystemProcessBundle\Task\RemoveFileTask' + options: + filesystem: 'remote.storage.archive' + file_pattern: '/^catalog_{{ year }}.*\.csv$/' +``` diff --git a/docs/cookbooks/sftp_export.md b/docs/cookbooks/sftp_export.md new file mode 100644 index 0000000..17a03d0 --- /dev/null +++ b/docs/cookbooks/sftp_export.md @@ -0,0 +1,70 @@ +SFTP export +=========== + +This recipe describes a typical export flow to a remote SFTP server: write a CSV file locally, then upload it to the +remote server and remove the local file. + +It uses the `local.storage` storage (whose directory is the `local.storage.dir` parameter) described in the +[SFTP import](sftp_import.md) cookbook, and a `remote.storage.outgoing` SFTP storage. + +```yaml +clever_age_process: + configurations: + app.sftp_export: + description: 'Export the catalog to the SFTP server' + tasks: + data: + service: '@CleverAge\ProcessBundle\Task\ConstantIterableOutputTask' # Replace with your own reader + options: + output: + - { sku: 'SKU-001', name: 'Product 1', price: '12.50' } + - { sku: 'SKU-002', name: 'Product 2', price: '8.90' } + outputs: [write] + + write: + service: '@CleverAge\ProcessBundle\Task\File\Csv\CsvWriterTask' + options: + file_path: '%local.storage.dir%/catalog_{date_time}.csv' + headers: [sku, name, price] + outputs: [relative_path] + + relative_path: + service: '@CleverAge\ProcessBundle\Task\TransformerTask' + options: + transformers: + callback: + callback: basename + outputs: [upload] + + upload: + service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' + options: + source_filesystem: 'local.storage' + destination_filesystem: 'remote.storage.outgoing' + remove_source: true + outputs: [log_upload] + + log_upload: + service: '@CleverAge\ProcessBundle\Task\Reporting\LoggerTask' + options: + level: info + message: 'File uploaded' +``` + +How it works: +- The [ConstantIterableOutputTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/constant_iterable_output_task.md) + stands for your own reader (database, API...): it outputs the lines one at a time. +- The [CsvWriterTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/csv_writer_task.md) + is blocking: it writes every line in a file located in the `local.storage` directory, then outputs the absolute path + of this file once all lines have been written. +- [FileFetchTask](../reference/tasks/file_fetch_task.md) expects a path relative to the root of its source storage: + the [TransformerTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/transformer_task.md) + keeps only the file name with the + [callback](https://github.com/cleverage/process-bundle/blob/main/docs/reference/transformers/callback_transformer.md) + transformer (`basename`). +- The [FileFetchTask](../reference/tasks/file_fetch_task.md) copies the file from `local.storage` to + `remote.storage.outgoing`, then deletes the local file (`remove_source: true`), and outputs the file name to the + [LoggerTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/logger_task.md). + +The same flow can target any Flysystem adapter (FTP, Amazon S3, Azure...): only the `remote.storage.outgoing` storage +configuration changes. diff --git a/docs/cookbooks/sftp_import.md b/docs/cookbooks/sftp_import.md new file mode 100644 index 0000000..8c508a5 --- /dev/null +++ b/docs/cookbooks/sftp_import.md @@ -0,0 +1,102 @@ +SFTP import +=========== + +This recipe describes a typical import flow from a remote SFTP server: download every CSV file of an `incoming` +directory to a local storage, read each file line by line, then archive the remote file into an `archive` directory +and delete the local copy. + +It uses the following storages: + +```yaml +# config/packages/flysystem.yaml +parameters: + local.storage.dir: '%kernel.project_dir%/var/storage/local' + +flysystem: + storages: + local.storage: + adapter: 'local' + options: + directory: '%local.storage.dir%' + + remote.storage.incoming: + adapter: 'sftp' + options: + host: '%env(string:SFTP_HOST)%' + username: '%env(string:SFTP_USERNAME)%' + password: '%env(string:SFTP_PASSWORD)%' + root: '%env(string:SFTP_ROOT)%/incoming' + + remote.storage.archive: + adapter: 'sftp' + options: + host: '%env(string:SFTP_HOST)%' + username: '%env(string:SFTP_USERNAME)%' + password: '%env(string:SFTP_PASSWORD)%' + root: '%env(string:SFTP_ROOT)%/archive' +``` + +```yaml +clever_age_process: + configurations: + app.sftp_import: + description: 'Import the CSV files dropped on the SFTP server' + tasks: + fetch: + service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' + options: + source_filesystem: 'remote.storage.incoming' + destination_filesystem: 'local.storage' + file_pattern: '/\.csv$/' + outputs: [read, archive] # "archive" is executed once "read" has read the whole file + + read: + service: '@CleverAge\ProcessBundle\Task\File\Csv\InputCsvReaderTask' + options: + base_path: '%local.storage.dir%' + delimiter: ';' + outputs: [import_line] + + import_line: + service: '@CleverAge\ProcessBundle\Task\Reporting\LoggerTask' # Replace with your own transformation and writer tasks + options: + level: info + message: 'Imported line' + + archive: + service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' + options: + source_filesystem: 'remote.storage.incoming' + destination_filesystem: 'remote.storage.archive' + remove_source: true + outputs: [cleanup_local] + + cleanup_local: + service: '@CleverAge\FlysystemProcessBundle\Task\RemoveFileTask' + options: + filesystem: 'local.storage' +``` + +How it works: +- The first [FileFetchTask](../reference/tasks/file_fetch_task.md) (`fetch`) is iterable: it copies the files matching + `file_pattern` one at a time from `remote.storage.incoming` to `local.storage`, and outputs the path of each file + (e.g. `products.csv`) relative to the storage root. Each file goes through all the following tasks before the next + one is downloaded. As `ignore_missing` defaults to `true`, the process ends without error when there is no file to + import; set it to `false` to make the process fail instead. +- The [InputCsvReaderTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/input_csv_reader_task.md) + reads the local copy: its `base_path` option is the directory of `local.storage`, so that the relative path becomes a + local path. It iterates over the lines of the file, sent to the + [LoggerTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/logger_task.md): replace it + with your own tasks (e.g. a + [TransformerTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/transformer_task.md) + and a writer). +- Outputs are executed depth-first, in order: once the whole file has been read, the second + [FileFetchTask](../reference/tasks/file_fetch_task.md) (`archive`) receives the same path as input and moves the + remote file from `remote.storage.incoming` to `remote.storage.archive` (`remove_source: true`). Using two storages + with different roots on the same server allows moving files between remote directories. +- The [RemoveFileTask](../reference/tasks/remove_file_task.md) finally deletes the local copy, whose path is the + output of `archive`. + +Note that if a line cannot be imported with the default `stop` error strategy, the process stops before the file is +archived: it will be imported again on the next execution. If your import takes a long time, the SFTP server may +close the idle connection used by `fetch`: see [SFTP stale connection](../troubleshooting.md). diff --git a/docs/index.md b/docs/index.md index 0c25277..180eb8b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ ## Prerequisite -CleverAge/ProcessBundle must be [installed](https://github.com/cleverage/process-bundle/blob/main/docs/01-quick_start.md#installation. +CleverAge/ProcessBundle must be [installed](https://github.com/cleverage/process-bundle/blob/main/docs/01-quick_start.md#installation). ## Installation @@ -19,28 +19,54 @@ Remember to add the following line to config/bundles.php (not required if Symfon CleverAge\FlysystemProcessBundle\CleverAgeFlysystemProcessBundle::class => ['all' => true], ``` -Configure at least one flysytem/storage into `config/packages/flysytem.yaml` +This bundle relies on [league/flysystem-bundle](https://github.com/thephpleague/flysystem-bundle), installed as a +dependency: the bundle `League\FlysystemBundle\FlysystemBundle` must be enabled as well. Adapters other than `local` +require their own package, e.g. `composer require league/flysystem-sftp-v3` for SFTP. + +## Configuration + +The tasks of this bundle work with the Flysystem **storages** configured in `config/packages/flysystem.yaml`: their +`filesystem`, `source_filesystem` and `destination_filesystem` options take the name of a storage (the key under +`flysystem.storages`). Configure at least one storage: ```yaml -#config/packages/flysytem.yaml +# config/packages/flysystem.yaml flysystem: - storages: - storage.source: # This is the identifier of flysytem/storage - adapter: 'local' - options: - directory: '%kernel.project_dir%/var/storage/source' -``` - -See https://github.com/thephpleague/flysystem-bundle?tab=readme-ov-file for more sample configuration (sftp, ftp, amazon s3 ...) + storages: + local.storage: # Name of the storage, used in the task options + adapter: 'local' + options: + directory: '%kernel.project_dir%/var/storage/local' + remote.storage: + adapter: 'sftp' + options: + host: '%env(string:SFTP_HOST)%' + port: 22 + username: '%env(string:SFTP_USERNAME)%' + password: '%env(string:SFTP_PASSWORD)%' + root: '%env(string:SFTP_ROOT)%' +``` -## Reference +Paths used and returned by the tasks are relative to the root of the storage (`directory` or `root` option). +Listings (`file_pattern` options, [ListContentTask](reference/tasks/list_content_task.md)) are not recursive: to work +in a sub-directory, configure a dedicated storage whose root is this directory. -- Tasks - - [FileFetchTask](reference/tasks/01-FileFetchTask.md) - - [ListContentTask](reference/tasks/02-ListContentTask.md) - - [RemoveFileTask](reference/tasks/03-RemoveFileTask.md) +See the [flysystem-bundle documentation](https://github.com/thephpleague/flysystem-bundle?tab=readme-ov-file) +for the configuration of other adapters (FTP, Amazon S3, Azure, Google Cloud Storage...). -## Troubleshooting +## Documentation -- [SFTP stale connection on long-running processes](troubleshooting.md) (`Got packet type` / `Connection closed prematurely`) +- Cookbooks + - [SFTP import](cookbooks/sftp_import.md) + - [SFTP export](cookbooks/sftp_export.md) + - [Remote cleanup](cookbooks/remote_cleanup.md) +- Reference + - Tasks + - [FileFetchTask](reference/tasks/file_fetch_task.md) + - [ListContentTask](reference/tasks/list_content_task.md) + - [RemoveFileTask](reference/tasks/remove_file_task.md) +- [Troubleshooting](troubleshooting.md) + - [SFTP stale connection on long-running processes](troubleshooting.md#sftp-long-running-process-fails-with-got-packet-type--connection-closed-prematurely) + (`Got packet type` / `Connection closed prematurely`) +- [CleverAge/ProcessBundle documentation](https://github.com/cleverage/process-bundle/blob/main/docs/index.md) diff --git a/docs/reference/tasks/01-FileFetchTask.md b/docs/reference/tasks/01-FileFetchTask.md deleted file mode 100644 index 4ad65e3..0000000 --- a/docs/reference/tasks/01-FileFetchTask.md +++ /dev/null @@ -1,76 +0,0 @@ -FileFetchTask -======== - -Perform copy between 2 flysystems storage - -Task reference --------------- - -* **Service**: [`CleverAge\FlysystemProcessBundle\Task\FileFetchTask`](../src/Task/FileFetchTask.php) - -Accepted inputs ---------------- - -The filename or filenames to copy from. - -If the option `file_pattern` is not set the input is used as strict filename(s) to match. - -If input is set but not corresponding as any file into `source_filesystem` task failed with UnableToReadFile exception. - -If FileFetchTask is the first task of you process and you want to use input, don't forgive to set the `entry_point` task name at process level - -Possible outputs ----------------- - -Filename of copied file. - -Options -------- - -| Code | Type | Required | Default | Description | -|--------------------------|:--------:|:--------:|:-------:|----------------------------------------------------------------------------------------------------------------------------------------------| -| `source_filesystem` | `string` | **X** | | The source flysystem/storage.
See config/packages/flysystem.yaml to see configured flysystem/storages. | -| `destination_filesystem` | `string` | **X** | | The source flysystem/storage.
See config/packages/flysystem.yaml to see configured flysystem/storages. | -| `file_pattern` | `string` | | null | The file_pattern used in preg_match to match into `source_filesystem` list of files. If not set try to use input as strict filename to match | -| `remove_source` | `bool` | | false | If true delete source file after copy | -| `ignore_missing` | `bool` | | true | Ignore property accessor errors for this source | - - -Examples --------- - -* Simple fetch task configuration - - See config/packages/flysystem.yaml to see configured flysystems/storages. - - copy all .csv files from 'storage.source' to 'storage.destination' - - remove .csv from 'storage.source' after copy - - output will be filename of copied files - - throw Exception when file(s) not found -```yaml -# Task configuration level -code: - service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' - options: - source_filesystem: 'storage.source' - destination_filesystem: 'storage.destination' - file_pattern: '/.csv$/' - remove_source: true - ignore_missing: false -``` - -* Simple fetch process configuration to cipy a specific file from --input option via
```bin/console cleverage:process:execute my_custom_process --input=foobar.csv -vv``` - - See config/packages/flysystem.yaml to see configured flysystems/storages. - - copy input file from 'storage.source' to 'storage.destination' - - remove .csv from 'storage.source' after copy - - output will be filename of copied file -```yaml -# Full process configuration to use input as filename with the following call -my_custom_process: - entry_point: copy_from_input - tasks: - copy_from_input: - service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' - options: - source_filesystem: 'storage.source' - destination_filesystem: 'storage.destination' - remove_source: true -``` diff --git a/docs/reference/tasks/02-ListContentTask.md b/docs/reference/tasks/02-ListContentTask.md deleted file mode 100644 index 036c0c3..0000000 --- a/docs/reference/tasks/02-ListContentTask.md +++ /dev/null @@ -1,42 +0,0 @@ -ListContentTask -======== - -List files of a flysystem storage - -Task reference --------------- - -* **Service**: [`CleverAge\FlysystemProcessBundle\Task\ListContentTask`](../src/Task/ListContentTask.php) - -Accepted inputs ---------------- - -Input is ignored - -Possible outputs ----------------- - -League\Flysystem\StorageAttributes - -Options -------- - -| Code | Type | Required | Default | Description | -|----------------|:----------:|:---------:|:---------:|------------------------------------------------------------------------------------------------------------| -| `filesystem` | `string` | **X** | | The source flysystem/storage.
See config/packages/flysystem.yaml to see configured flysystem/storages. | -| `file_pattern` | `string` | | | The file_parttern used in preg_match to match into `filesystem` | - -Examples --------- -* Simple list task configuration from a filesystem - - see config/packages/flysystem.yaml to see configured flysystems/storages. - - list all .csv files from 'storage.source' - - output will be League\Flysystem\StorageAttributes representation of copied files -```yaml -# Task configuration level -code: - service: '@CleverAge\FlysystemProcessBundle\Task\ListContentTask' - options: - filesystem: 'storage.source' - file_pattern: '/.csv$/' -``` diff --git a/docs/reference/tasks/03-RemoveFileTask.md b/docs/reference/tasks/03-RemoveFileTask.md deleted file mode 100644 index 8e1b6c4..0000000 --- a/docs/reference/tasks/03-RemoveFileTask.md +++ /dev/null @@ -1,64 +0,0 @@ -RemoveFileTask -======== - -Remove a file from a flysystem storage - -Task reference --------------- - -* **Service**: [`CleverAge\FlysystemProcessBundle\Task\RemoveFileTask`](../src/Task/RemoveFileTask.php) - -Accepted inputs ---------------- - -The filename of the file to remove on `filesystem`. - -If the option `file_pattern` is not set the input is used as strict filename(s) to match. - -When filename is deleted add a info log. - -If filename not found or cannot be deleted on `filesystem` add a warning log. - -Possible outputs ----------------- - -None - -Options -------- - -| Code | Type | Required | Default | Description | -|----------------|:----------:|:--------:|:---------:|----------------------------------------------------------------------------------------------------------------------------------------------| -| `filesystem` | `string` | **X** | | The source flysystem/storage.
See config/packages/flysystem.yaml to see configured flysystem/storages. | -| `file_pattern` | `string` | | null | The file_pattern used in preg_match to match into `source_filesystem` list of files. If not set try to use input as strict filename to match | - - -Examples --------- -* Simple process to remove a file on 'storage.source' via
```bin/console cleverage:process:execute input_process --input=foobar.csv -vv``` - - see config/packages/flysystem.yaml to see configured flysystems/storages. - - remove file with name passed as input -```yaml -# -input_process: - entry_point: remove_from_input - tasks: - remove_from_input: - service: '@CleverAge\FlysystemProcessBundle\Task\RemoveFileTask' - options: - filesystem: 'storage.source' -``` - -* Simple process to remove a file on 'storage.source' via
```bin/console cleverage:process:execute pattern_process -vv``` - - see config/packages/flysystem.yaml to see configured flysystems/storages. - - remove files filtered by file_pattern -```yaml -# -pattern_process: - tasks: - remove_from_file_pattern: - service: '@CleverAge\FlysystemProcessBundle\Task\RemoveFileTask' - options: - filesystem: 'storage.source' - file_pattern: '/.csv$/' -``` diff --git a/docs/reference/tasks/_template.md b/docs/reference/tasks/_template.md index ed1d4a5..919390a 100644 --- a/docs/reference/tasks/_template.md +++ b/docs/reference/tasks/_template.md @@ -1,44 +1,47 @@ TaskName ======== -_Describe main goal an use cases of the task_ +_Describe the main goal and use cases of the task._ Task reference -------------- -* **Service**: `ClassName` +* **Service**: `Fully\Qualified\ClassName` +* **Iterable task** _(only if it implements `IterableTaskInterface`)_ +* **Blocking task** _(only if it implements `BlockingTaskInterface`)_ +* **Flushable task** _(only if it implements `FlushableTaskInterface`)_ Accepted inputs --------------- -_Description of allowed types_ +_Description of allowed types, or "Input is ignored"._ Possible outputs ---------------- -_Description of possible types_ +_Description of possible types._ Options ------- -| Code | Type | Required | Default | Description | -| ---- | ---- | :------: | ------- | ----------- | -| `code` | `type` | **X** _or nothing_ | `default value` _if available_ | _description_ | +| Code | Type | Required | Default | Description | +|--------|--------|:--------:|-----------------|---------------| +| `code` | `type` | **X** | `default value` | _description_ | + +_If the task has no option, replace the table with "This task has no option."._ Examples -------- -_YAML samples and explanations_ - * Example 1 - details - - details - + ```yaml # Task configuration level code: - service: '@service_ref' - options: - a: 1 - b: 2 + service: '@Fully\Qualified\ClassName' + options: + a: 1 + b: 2 + outputs: [next_task] ``` diff --git a/docs/reference/tasks/file_fetch_task.md b/docs/reference/tasks/file_fetch_task.md new file mode 100644 index 0000000..504d45c --- /dev/null +++ b/docs/reference/tasks/file_fetch_task.md @@ -0,0 +1,115 @@ +FileFetchTask +============= + +Copies (or moves) files from one Flysystem storage to another, and iterates over the copied files, outputting each +file path. Files are either selected with a regular expression on the source storage, or given as input. + +Typical use cases: download files from a remote SFTP/FTP/S3 storage to a local storage before reading them, upload +generated files to a remote storage, or archive processed files into another storage. + +Task reference +-------------- + +* **Service**: `CleverAge\FlysystemProcessBundle\Task\FileFetchTask` +* **Iterable task** + +Accepted inputs +--------------- + +* When `file_pattern` is set, the input is ignored. +* Otherwise, `string|array`: path, or list of paths, of the file(s) to copy, relative to the root of the + `source_filesystem` storage. An empty input throws an `\UnexpectedValueException` + (`No pattern neither input provided for the Task`). + +If the task is the first task of the process and takes its files from the input, set the process `entry_point` to +this task (e.g. `bin/console cleverage:process:execute my_process --input=foobar.csv`). + +Possible outputs +---------------- + +`string`: for each copied file, its path relative to the storage root (the same path is used in the +`source_filesystem` and in the `destination_filesystem`). + +When no file matches `file_pattern` (and `ignore_missing` is `true`), or when every received file has already been +copied during this process execution, no output is produced and the task is skipped. + +Options +------- + +| Code | Type | Required | Default | Description | +|--------------------------|----------------|:--------:|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `source_filesystem` | `string` | **X** | | Name of the Flysystem storage to read files from, as configured under `flysystem.storages` (see [configuration](../../index.md#configuration)) | +| `destination_filesystem` | `string` | **X** | | Name of the Flysystem storage to write files to, as configured under `flysystem.storages` | +| `file_pattern` | `string\|null` | | `null` | Regular expression (see [preg_match](https://www.php.net/manual/en/function.preg-match.php)) tested on the path of each file at the root of `source_filesystem`. If `null` (or empty), the file path(s) are taken from the input | +| `remove_source` | `bool` | | `false` | Delete the file from `source_filesystem` after the copy (move instead of copy) | +| `ignore_missing` | `bool` | | `true` | Only used with `file_pattern`: if `false`, throw an `\UnexpectedValueException` (`File(s) not found in source filesystem`) when no file matches the pattern. If `true`, the task is skipped | + +Examples +-------- + +* Move all CSV files from a remote storage to a local one + - each file is copied then deleted from `remote.storage` + - the output is the path of each copied file, e.g. `products.csv` + - an exception is thrown if no CSV file is found + +```yaml +# Task configuration level +fetch: + service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' + options: + source_filesystem: 'remote.storage' + destination_filesystem: 'local.storage' + file_pattern: '/\.csv$/' + remove_source: true + ignore_missing: false + outputs: [read] +``` + +* Copy a file given as input, with contextualized storages + - `bin/console cleverage:process:execute my_process --input=foobar.csv -c source:remote.storage -c destination:local.storage` + +```yaml +# Process configuration level +my_process: + entry_point: copy_from_input + tasks: + copy_from_input: + service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' + options: + source_filesystem: '{{ source }}' + destination_filesystem: '{{ destination }}' +``` + +* Archive a processed file into another storage + - the input is the path of a file previously fetched from `remote.storage.incoming` + +```yaml +# Task configuration level +archive: + service: '@CleverAge\FlysystemProcessBundle\Task\FileFetchTask' + options: + source_filesystem: 'remote.storage.incoming' + destination_filesystem: 'remote.storage.archive' + remove_source: true +``` + +Notes +----- + +* Both storages are resolved when the task is initialized, at the start of the process: an unknown storage name makes + the process fail before any task is executed. +* `file_pattern` is only tested on the files located at the root of `source_filesystem` (the listing is not + recursive and directories are ignored). The pattern is tested on the path relative to the storage root: to target + a sub-directory, configure a dedicated storage whose root is this directory. +* The source storage is listed again before each iteration: files added to the source during the iteration are also + copied. With an SFTP storage and long-running downstream tasks, see + [SFTP stale connection](../../troubleshooting.md). +* A given file is copied only once per process execution: already copied paths are remembered, so receiving the same + path again (or a new input while using `file_pattern`) skips the task. +* The file is written to `destination_filesystem` with the same path, overwriting any existing file. A file given as + input that does not exist in `source_filesystem` throws a `League\Flysystem\UnableToReadFile` exception, whatever + the value of `ignore_missing`. +* To read a copied file with a core task (e.g. + [InputCsvReaderTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/input_csv_reader_task.md)), + prefix the output with the directory of the local destination storage (e.g. with the `base_path` option). +* See the [SFTP import](../../cookbooks/sftp_import.md) and [SFTP export](../../cookbooks/sftp_export.md) cookbooks. diff --git a/docs/reference/tasks/list_content_task.md b/docs/reference/tasks/list_content_task.md new file mode 100644 index 0000000..1db9737 --- /dev/null +++ b/docs/reference/tasks/list_content_task.md @@ -0,0 +1,86 @@ +ListContentTask +=============== + +Lists the content (files and directories) at the root of a Flysystem storage and iterates over it, outputting each +item as a `League\Flysystem\StorageAttributes` object. Items can be filtered with a regular expression on their path. + +Task reference +-------------- + +* **Service**: `CleverAge\FlysystemProcessBundle\Task\ListContentTask` +* **Iterable task** + +Accepted inputs +--------------- + +Input is ignored + +Possible outputs +---------------- + +`League\Flysystem\StorageAttributes`: for each listed item, a `League\Flysystem\FileAttributes` (file) or a +`League\Flysystem\DirectoryAttributes` (directory) object. Useful properties are `path` (relative to the storage +root), `type` (`file` or `dir`), `lastModified`, and for files `fileSize` and `mimeType` +(see [Flysystem directory listings](https://flysystem.thephpleague.com/docs/usage/directory-listings/)). + +Use [PropertyGetterTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/property_getter_task.md) +with `property: path` to get the path, as expected by [FileFetchTask](file_fetch_task.md) or +[RemoveFileTask](remove_file_task.md). + +When the storage is empty, or when no item matches `file_pattern`, no output is produced and the task is skipped. + +Options +------- + +| Code | Type | Required | Default | Description | +|----------------|----------------|:--------:|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `filesystem` | `string` | **X** | | Name of the Flysystem storage to list, as configured under `flysystem.storages` (see [configuration](../../index.md#configuration)) | +| `file_pattern` | `string\|null` | | `null` | Regular expression (see [preg_match](https://www.php.net/manual/en/function.preg-match.php)) tested on the path of each item. If `null`, every item (files and directories) is output | + +Examples +-------- + +* List CSV files of a storage and output their path + +```yaml +# Task configuration level +list: + service: '@CleverAge\FlysystemProcessBundle\Task\ListContentTask' + options: + filesystem: 'remote.storage' + file_pattern: '/\.csv$/' + outputs: [get_path] +get_path: + service: '@CleverAge\ProcessBundle\Task\PropertyGetterTask' + options: + property: 'path' + outputs: [log] +``` + +* Keep only files (exclude directories) with a + [FilterTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/filter_task.md) + +```yaml +# Task configuration level +list: + service: '@CleverAge\FlysystemProcessBundle\Task\ListContentTask' + options: + filesystem: 'remote.storage' + outputs: [only_files] +only_files: + service: '@CleverAge\ProcessBundle\Task\FilterTask' + options: + match: + type: 'file' + outputs: [get_path] +``` + +Notes +----- + +* The listing is not recursive: only the items located at the root of the storage are listed. To list a + sub-directory, configure a dedicated storage whose root is this directory. +* Unlike [FileFetchTask](file_fetch_task.md) and [RemoveFileTask](remove_file_task.md), directories are not excluded: + make `file_pattern` specific enough (e.g. on the extension) or filter on `type`. +* The storage is listed once, when the iteration starts; the listing is done again on the next execution of the task, + once the previous iteration is over. diff --git a/docs/reference/tasks/remove_file_task.md b/docs/reference/tasks/remove_file_task.md new file mode 100644 index 0000000..c57aedb --- /dev/null +++ b/docs/reference/tasks/remove_file_task.md @@ -0,0 +1,72 @@ +RemoveFileTask +============== + +Deletes files from a Flysystem storage: either every file matching a regular expression, or the file given as input. + +Task reference +-------------- + +* **Service**: `CleverAge\FlysystemProcessBundle\Task\RemoveFileTask` + +Accepted inputs +--------------- + +* When `file_pattern` is set, the input is ignored (but the deletion is run again each time the task is executed). +* Otherwise, `string`: path of the file to delete, relative to the root of the `filesystem` storage. An empty input + throws an `\UnexpectedValueException` (`No pattern neither input provided for the Task`). A list of paths is not + supported: iterate over it first (e.g. with + [InputIteratorTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/input_iterator_task.md)). + A `StorageAttributes` output of [ListContentTask](list_content_task.md) must be converted to its `path` first. + +Possible outputs +---------------- + +No output is set. + +Options +------- + +| Code | Type | Required | Default | Description | +|----------------|----------------|:--------:|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `filesystem` | `string` | **X** | | Name of the Flysystem storage to delete files from, as configured under `flysystem.storages` (see [configuration](../../index.md#configuration)) | +| `file_pattern` | `string\|null` | | `null` | Regular expression (see [preg_match](https://www.php.net/manual/en/function.preg-match.php)) tested on the path of each file at the root of `filesystem`: every matching file is deleted. If `null` (or empty), the file path is taken from the input | + +Examples +-------- + +* Delete the file given as input + - `bin/console cleverage:process:execute my_process --input=foobar.csv` + +```yaml +# Process configuration level +my_process: + entry_point: remove_from_input + tasks: + remove_from_input: + service: '@CleverAge\FlysystemProcessBundle\Task\RemoveFileTask' + options: + filesystem: 'remote.storage' +``` + +* Delete every CSV file of a storage + +```yaml +# Task configuration level +purge: + service: '@CleverAge\FlysystemProcessBundle\Task\RemoveFileTask' + options: + filesystem: 'remote.storage' + file_pattern: '/\.csv$/' +``` + +Notes +----- + +* `file_pattern` is only tested on the files located at the root of the storage (the listing is not recursive and + directories are ignored). +* Deletion errors do not stop the process: each deleted file is logged with the `info` level (`Deleted input file`), + and a deletion failure (`League\Flysystem\FilesystemException`) is logged with the `warning` level, with the file + path in the log context. Deleting a file that does not exist is not an error for most adapters (e.g. `local`, + `sftp`): it is logged as deleted. +* The storage is resolved on each execution: an unknown storage name makes the task fail when it is executed. +* See the [Remote cleanup](../../cookbooks/remote_cleanup.md) cookbook. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b60c821..4f8ad85 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -4,8 +4,8 @@ ### Symptom -A long-running process that uses an SFTP storage (e.g. `FileFetchTask`, `ListContentTask`, -`RemoveFileTask`) fails on a **later** task with one of: +A long-running process that uses an SFTP storage (e.g. [FileFetchTask](reference/tasks/file_fetch_task.md), +[ListContentTask](reference/tasks/list_content_task.md), [RemoveFileTask](reference/tasks/remove_file_task.md)) fails on a **later** task with one of: ``` Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. Got packet type: