cci-os-worker 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cci_os_worker-0.5.0/LICENSE +31 -0
- cci_os_worker-0.5.0/PKG-INFO +122 -0
- cci_os_worker-0.5.0/README.md +99 -0
- cci_os_worker-0.5.0/cci_os_worker/README.md +32 -0
- cci_os_worker-0.5.0/cci_os_worker/__init__.py +8 -0
- cci_os_worker-0.5.0/cci_os_worker/all_facets.py +211 -0
- cci_os_worker-0.5.0/cci_os_worker/deleter.py +115 -0
- cci_os_worker-0.5.0/cci_os_worker/directory.py +387 -0
- cci_os_worker-0.5.0/cci_os_worker/errors.py +31 -0
- cci_os_worker-0.5.0/cci_os_worker/filehandlers/__init__.py +2 -0
- cci_os_worker-0.5.0/cci_os_worker/filehandlers/generic_file.py +135 -0
- cci_os_worker-0.5.0/cci_os_worker/filehandlers/geojson.py +314 -0
- cci_os_worker-0.5.0/cci_os_worker/filehandlers/netcdf_file.py +311 -0
- cci_os_worker-0.5.0/cci_os_worker/filehandlers/util.py +679 -0
- cci_os_worker-0.5.0/cci_os_worker/log_status.py +134 -0
- cci_os_worker-0.5.0/cci_os_worker/path_tools.py +422 -0
- cci_os_worker-0.5.0/cci_os_worker/tests/__init__.py +0 -0
- cci_os_worker-0.5.0/cci_os_worker/tests/test_consistency.py +30 -0
- cci_os_worker-0.5.0/cci_os_worker/utils.py +178 -0
- cci_os_worker-0.5.0/pyproject.toml +39 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2024, Centre of Environmental Data Analysis Developers,
|
|
4
|
+
Scientific and Technical Facilities Council (STFC),
|
|
5
|
+
UK Research and Innovation (UKRI).
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
Redistribution and use in source and binary forms, with or without
|
|
9
|
+
modification, are permitted provided that the following conditions are met:
|
|
10
|
+
|
|
11
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
12
|
+
list of conditions and the following disclaimer.
|
|
13
|
+
|
|
14
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this
|
|
15
|
+
list of conditions and the following disclaimer in the documentation and/or other
|
|
16
|
+
materials provided with the distribution.
|
|
17
|
+
|
|
18
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
19
|
+
may be used to endorse or promote products derived from this software without
|
|
20
|
+
specific prior written permission.
|
|
21
|
+
|
|
22
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
|
|
23
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
24
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
25
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
26
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
27
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
28
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
29
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
30
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
31
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: cci-os-worker
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Worker repo to execute the steps for CCI Opensearch Ingestion, namely facet and tag scanning and updates to the CEDA FBI
|
|
5
|
+
License: BSD 3 - See License file
|
|
6
|
+
Author: Daniel Westwood
|
|
7
|
+
Author-email: daniel.westwood@stfc.ac.uk
|
|
8
|
+
Requires-Python: >=3.9,<4
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
|
15
|
+
Requires-Dist: cci-facet-scanner (>=0.3.1,<0.4.0)
|
|
16
|
+
Requires-Dist: elasticsearch (>=7,<8)
|
|
17
|
+
Requires-Dist: ldap3 (>=2.9,<3.0)
|
|
18
|
+
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
19
|
+
Requires-Dist: python-magic (>=0.4.22,<0.5.0)
|
|
20
|
+
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# CCI Opensearch Worker repository
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+

|
|
27
|
+
[](https://pypi.python.org/pypi/cci-os-worker/)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
**See release notes for change history**
|
|
31
|
+
|
|
32
|
+
This package serves as a wrapper for the CCI Opensearch Workflow, which involves several independent packages with multiple dependencies. Primarily the CCI Tagger (cci-tag-scanner) and Facet scanner (cci-facet-scanner) are combined, with elements from the CEDA FBS (ceda-fbs-cci) package to create the components for Opensearch records in Elasticsearch.
|
|
33
|
+
|
|
34
|
+
**NOTE:** When publishing a new tagged release of this package, please make sure to rebuild the corresponding Docker image, in the CEDA gitlab repository `cci_opensearch_base`. This repository has a single build-image step that should be rerun (following the steps found there) to ensure any changes to this package are picked up by the OS worker deployment.
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
## 1. Installation
|
|
39
|
+
|
|
40
|
+
This package can be cloned directly or used as a dependency in a pyproject file.
|
|
41
|
+
|
|
42
|
+
Set up a python virtual environment:
|
|
43
|
+
```
|
|
44
|
+
$ python -m venv .venv
|
|
45
|
+
$ source .venv/bin/activate
|
|
46
|
+
$ pip install cci-os-worker
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
NOTE: As of 22nd Jan 2025 the `cci-os-worker` repository has been upgraded for use with Poetry version 2. The temporary solution to use a `requirements_fix.txt` file has been removed as this package is now on Pypi.
|
|
50
|
+
|
|
51
|
+
### 1.1. Use in other packages
|
|
52
|
+
|
|
53
|
+
**Poetry 1.8.5 and older**
|
|
54
|
+
For use in another package as a dependency, use the following in your pyproject `[tool.poetry.dependencies]`:
|
|
55
|
+
```
|
|
56
|
+
cci-os-worker = { git = "https://github.com/cedadev/cci-os-worker.git", tag="v0.3.1"}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Poetry 2.0.1 and later**
|
|
60
|
+
This package is now a pip-installable published package as of 11th April 2025! That means for packages using Poetry 2 or higher, the cci-os-worker can be added via Poetry at version 0.5.0 or higher.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
poetry add cci-os-worker^0.5.0
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 2. Usage
|
|
67
|
+
|
|
68
|
+
## 2.1 Find datasets
|
|
69
|
+
|
|
70
|
+
Determining the set of files to operate over can be done in two ways using built-in scripts here, or indeed by any other means. If the intention is to submit to a rabbit queue however, this script is required with the additional `-R` parameter to submit to a queue, and the configuration for the queue given by a yaml file provided as `--conf`.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
rescan_dir path/to/json/directory/ --extension nc -l 1 -o path/to/dataset/filelist.txt
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**NOTE**: As of v0.5.0 this changed from `fbi_rescan_dir` to simply `rescan_dir`.
|
|
77
|
+
|
|
78
|
+
In the above command:
|
|
79
|
+
- `r` represents a recursive look through identified directories.
|
|
80
|
+
- `l` means the scan level. Scan level 1 will involve finding all the JSON files and expanding each `datasets` path into a list.
|
|
81
|
+
- `o` is the output file to send the list of datasets.
|
|
82
|
+
- `--extension` applies to the files identified and added to the output file. `nc` is the default value so is redundant here.
|
|
83
|
+
- `--file-regex` alternative to supplying just the extension, if a valid regex pattern can be matched to identify specific files it can be submitted here.
|
|
84
|
+
|
|
85
|
+
This command can also be run for a known directory to expand into a list of datasets:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
rescan_dir my/datasets/path/ -l 2 -o path/to/dataset/filelist.txt
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
In this case we specify `l` as 2 since there are no JSON files involved. The extension/file_regex options can also be added here, but as the `nc` option is a default value we have omitted it here.
|
|
92
|
+
|
|
93
|
+
## 2.2 Run the facet scan workflow
|
|
94
|
+
|
|
95
|
+
The facet scanner workflow utilises both the facet and tag scanners to produce the set of facets under `project.opensearch` in the resulting opensearch records. This workflow can be run using the `facetscan` entrypoint script installed with this package.
|
|
96
|
+
|
|
97
|
+
The environment variable `JSON_TAGGER_ROOT` should be set, which should be the path to the top-level directory under which all JSON files are placed. These JSON files provide defaults and mappings to values placed in the opensearch records - supplementary material to aid facet scanning or replace found values.
|
|
98
|
+
|
|
99
|
+
As of v0.5.0 the two workflows (facet and FBI) have been combined into one singular workflow to generate all portions of the Opensearch records. This can be run with the following command:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
$ cci_os_update path/to/dataset/filelist.txt path/to/config/file.yaml
|
|
103
|
+
```
|
|
104
|
+
(Note: Verbose flag -v can be added to the above command.)
|
|
105
|
+
|
|
106
|
+
Where the yaml file should look something like this:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
elasticsearch:
|
|
110
|
+
# Fill in with key value
|
|
111
|
+
x-api-key: ""
|
|
112
|
+
facet_files_index:
|
|
113
|
+
name: facet-index-staging
|
|
114
|
+
facet_files_test_index:
|
|
115
|
+
name: facet-index-staging
|
|
116
|
+
ldap_configuration:
|
|
117
|
+
hosts:
|
|
118
|
+
- ldap://homer.esc.rl.ac.uk
|
|
119
|
+
- ldap://marge.esc.rl.ac.uk
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# CCI Opensearch Worker repository
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
[](https://pypi.python.org/pypi/cci-os-worker/)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
**See release notes for change history**
|
|
9
|
+
|
|
10
|
+
This package serves as a wrapper for the CCI Opensearch Workflow, which involves several independent packages with multiple dependencies. Primarily the CCI Tagger (cci-tag-scanner) and Facet scanner (cci-facet-scanner) are combined, with elements from the CEDA FBS (ceda-fbs-cci) package to create the components for Opensearch records in Elasticsearch.
|
|
11
|
+
|
|
12
|
+
**NOTE:** When publishing a new tagged release of this package, please make sure to rebuild the corresponding Docker image, in the CEDA gitlab repository `cci_opensearch_base`. This repository has a single build-image step that should be rerun (following the steps found there) to ensure any changes to this package are picked up by the OS worker deployment.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
## 1. Installation
|
|
17
|
+
|
|
18
|
+
This package can be cloned directly or used as a dependency in a pyproject file.
|
|
19
|
+
|
|
20
|
+
Set up a python virtual environment:
|
|
21
|
+
```
|
|
22
|
+
$ python -m venv .venv
|
|
23
|
+
$ source .venv/bin/activate
|
|
24
|
+
$ pip install cci-os-worker
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
NOTE: As of 22nd Jan 2025 the `cci-os-worker` repository has been upgraded for use with Poetry version 2. The temporary solution to use a `requirements_fix.txt` file has been removed as this package is now on Pypi.
|
|
28
|
+
|
|
29
|
+
### 1.1. Use in other packages
|
|
30
|
+
|
|
31
|
+
**Poetry 1.8.5 and older**
|
|
32
|
+
For use in another package as a dependency, use the following in your pyproject `[tool.poetry.dependencies]`:
|
|
33
|
+
```
|
|
34
|
+
cci-os-worker = { git = "https://github.com/cedadev/cci-os-worker.git", tag="v0.3.1"}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Poetry 2.0.1 and later**
|
|
38
|
+
This package is now a pip-installable published package as of 11th April 2025! That means for packages using Poetry 2 or higher, the cci-os-worker can be added via Poetry at version 0.5.0 or higher.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
poetry add cci-os-worker^0.5.0
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 2. Usage
|
|
45
|
+
|
|
46
|
+
## 2.1 Find datasets
|
|
47
|
+
|
|
48
|
+
Determining the set of files to operate over can be done in two ways using built-in scripts here, or indeed by any other means. If the intention is to submit to a rabbit queue however, this script is required with the additional `-R` parameter to submit to a queue, and the configuration for the queue given by a yaml file provided as `--conf`.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
rescan_dir path/to/json/directory/ --extension nc -l 1 -o path/to/dataset/filelist.txt
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**NOTE**: As of v0.5.0 this changed from `fbi_rescan_dir` to simply `rescan_dir`.
|
|
55
|
+
|
|
56
|
+
In the above command:
|
|
57
|
+
- `r` represents a recursive look through identified directories.
|
|
58
|
+
- `l` means the scan level. Scan level 1 will involve finding all the JSON files and expanding each `datasets` path into a list.
|
|
59
|
+
- `o` is the output file to send the list of datasets.
|
|
60
|
+
- `--extension` applies to the files identified and added to the output file. `nc` is the default value so is redundant here.
|
|
61
|
+
- `--file-regex` alternative to supplying just the extension, if a valid regex pattern can be matched to identify specific files it can be submitted here.
|
|
62
|
+
|
|
63
|
+
This command can also be run for a known directory to expand into a list of datasets:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
rescan_dir my/datasets/path/ -l 2 -o path/to/dataset/filelist.txt
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
In this case we specify `l` as 2 since there are no JSON files involved. The extension/file_regex options can also be added here, but as the `nc` option is a default value we have omitted it here.
|
|
70
|
+
|
|
71
|
+
## 2.2 Run the facet scan workflow
|
|
72
|
+
|
|
73
|
+
The facet scanner workflow utilises both the facet and tag scanners to produce the set of facets under `project.opensearch` in the resulting opensearch records. This workflow can be run using the `facetscan` entrypoint script installed with this package.
|
|
74
|
+
|
|
75
|
+
The environment variable `JSON_TAGGER_ROOT` should be set, which should be the path to the top-level directory under which all JSON files are placed. These JSON files provide defaults and mappings to values placed in the opensearch records - supplementary material to aid facet scanning or replace found values.
|
|
76
|
+
|
|
77
|
+
As of v0.5.0 the two workflows (facet and FBI) have been combined into one singular workflow to generate all portions of the Opensearch records. This can be run with the following command:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
$ cci_os_update path/to/dataset/filelist.txt path/to/config/file.yaml
|
|
81
|
+
```
|
|
82
|
+
(Note: Verbose flag -v can be added to the above command.)
|
|
83
|
+
|
|
84
|
+
Where the yaml file should look something like this:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
elasticsearch:
|
|
88
|
+
# Fill in with key value
|
|
89
|
+
x-api-key: ""
|
|
90
|
+
facet_files_index:
|
|
91
|
+
name: facet-index-staging
|
|
92
|
+
facet_files_test_index:
|
|
93
|
+
name: facet-index-staging
|
|
94
|
+
ldap_configuration:
|
|
95
|
+
hosts:
|
|
96
|
+
- ldap://homer.esc.rl.ac.uk
|
|
97
|
+
- ldap://marge.esc.rl.ac.uk
|
|
98
|
+
```
|
|
99
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CCI OS Worker Source
|
|
2
|
+
|
|
3
|
+
This package has now combined elements from the following packages, which have been dropped as dependencies:
|
|
4
|
+
- CEDA FBS
|
|
5
|
+
- Nappy
|
|
6
|
+
|
|
7
|
+
The source code provided here should accomplish all tasks related to these dependency packages. Contact [Daniel Westwood](daniel.westwood@stfc.ac.uk) for more details
|
|
8
|
+
|
|
9
|
+
## Entrypoint Scripts
|
|
10
|
+
It is recommended to use the entrypoint scripts to interface with this package once installed:
|
|
11
|
+
- facetscan
|
|
12
|
+
- fbi_update
|
|
13
|
+
|
|
14
|
+
NOTE: In a future update, these two scripts will be combined into a single workflow which generates the entire record for one dataset as a single process, rather than generating half the record in each case.
|
|
15
|
+
|
|
16
|
+
### Facetscan
|
|
17
|
+
|
|
18
|
+
Use this entrypoint to perform a facet scan given a set of dataset files and the worker config. This is automatically configured to create records in the elasticsearch index given by `facet_files_index` in the worker config.
|
|
19
|
+
- `datafile_path`: A path to a `datasets.txt` list of files.
|
|
20
|
+
- `conf`: Path to the worker config file.
|
|
21
|
+
- [OPTIONAL] `output`: Outputs failed datasets to a text file.
|
|
22
|
+
|
|
23
|
+
Use `facetscan --help` for more information.
|
|
24
|
+
|
|
25
|
+
### FBI Update
|
|
26
|
+
|
|
27
|
+
This entrypoint completes the second part of the Opensearch record for each dataset, with the same configuration options as above:
|
|
28
|
+
- `datafile_path`: A path to a `datasets.txt` list of files.
|
|
29
|
+
- `conf`: Path to the worker config file.
|
|
30
|
+
- [OPTIONAL] `output`: Outputs failed datasets to a text file.
|
|
31
|
+
|
|
32
|
+
Use `fbi_update --help` for more information.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
__author__ = 'Daniel Westwood'
|
|
3
|
+
__date__ = '05 Nov 2024'
|
|
4
|
+
__copyright__ = 'Copyright 2024 United Kingdom Research and Innovation'
|
|
5
|
+
__license__ = 'BSD - see LICENSE file in top-level package directory'
|
|
6
|
+
__contact__ = 'daniel.westwood@stfc.ac.uk'
|
|
7
|
+
|
|
8
|
+
# Opens the file provided in the datasets folder.
|
|
9
|
+
# Iterate over all provided dataset paths.
|
|
10
|
+
|
|
11
|
+
# facet_scanner.get_handler('filepath')
|
|
12
|
+
# facets = handler.get_facets('filepath')
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import hashlib
|
|
16
|
+
import argparse
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
|
|
21
|
+
from cci_facet_scanner.core.facet_scanner import FacetScanner
|
|
22
|
+
from elasticsearch import Elasticsearch
|
|
23
|
+
|
|
24
|
+
from cci_os_worker.filehandlers.util import LDAPIdentifier
|
|
25
|
+
from cci_os_worker.filehandlers import NetCdfFile, GenericFile
|
|
26
|
+
from cci_os_worker import logstream
|
|
27
|
+
|
|
28
|
+
from .path_tools import PathTools
|
|
29
|
+
from .directory import check_timeout
|
|
30
|
+
from .utils import load_config, UpdateHandler, set_verbose
|
|
31
|
+
from .errors import HandlerError, DocMetadataError
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
logger.addHandler(logstream)
|
|
35
|
+
logger.propagate = False
|
|
36
|
+
|
|
37
|
+
class FacetUpdateHandler(UpdateHandler):
|
|
38
|
+
|
|
39
|
+
def __init__(self, conf: dict, dryrun: bool = False, test: bool = False):
|
|
40
|
+
"""
|
|
41
|
+
Initialise this class with the correct connections to
|
|
42
|
+
establish an elasticsearch client.
|
|
43
|
+
"""
|
|
44
|
+
logger.info('Loading Facet Updater')
|
|
45
|
+
|
|
46
|
+
super().__init__(conf, dryrun=dryrun, test=test)
|
|
47
|
+
|
|
48
|
+
self.facet_scanner = FacetScanner()
|
|
49
|
+
|
|
50
|
+
ldap_hosts = self._conf['ldap_configuration']['hosts']
|
|
51
|
+
self.ldap_interface = LDAPIdentifier(server=ldap_hosts, auto_bind=True)
|
|
52
|
+
|
|
53
|
+
self._spot_file = conf.get('spot_file',None)
|
|
54
|
+
self.pt = PathTools(spot_file=self._spot_file)
|
|
55
|
+
|
|
56
|
+
def _get_project_info(self, path):
|
|
57
|
+
"""
|
|
58
|
+
Get project info for a specific path
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
extension = os.path.splitext(path.split('/')[-1])[-1]
|
|
62
|
+
extension = extension.lower()
|
|
63
|
+
|
|
64
|
+
if extension == '.nc':
|
|
65
|
+
handler = NetCdfFile
|
|
66
|
+
else:
|
|
67
|
+
handler = GenericFile
|
|
68
|
+
|
|
69
|
+
calculate_md5 = self._conf.get('calculate_md5',False)
|
|
70
|
+
|
|
71
|
+
if handler is None:
|
|
72
|
+
raise HandlerError(filename=path)
|
|
73
|
+
|
|
74
|
+
handler_instance = handler(path, 3, calculate_md5=calculate_md5)
|
|
75
|
+
|
|
76
|
+
# FutureDetail: Remove manifest from 'doc' if unneeded (no indexing required.)
|
|
77
|
+
doc, phenomena, spatial = handler_instance.get_metadata()
|
|
78
|
+
|
|
79
|
+
if doc is None:
|
|
80
|
+
raise DocMetadataError(filename=path)
|
|
81
|
+
if len(doc) > 1:
|
|
82
|
+
doc = doc[0]
|
|
83
|
+
|
|
84
|
+
if phenomena:
|
|
85
|
+
doc['info']['phenomena'] = phenomena
|
|
86
|
+
if spatial:
|
|
87
|
+
doc['info']['spatial'] = spatial
|
|
88
|
+
|
|
89
|
+
spot = self.pt.spots.get_spot(path)
|
|
90
|
+
|
|
91
|
+
if spot is not None:
|
|
92
|
+
doc['info']['spot_name'] = spot
|
|
93
|
+
|
|
94
|
+
# Replace the UID and GID with name and group
|
|
95
|
+
uid = doc['info']['user']
|
|
96
|
+
gid = doc['info']['group']
|
|
97
|
+
|
|
98
|
+
doc['info']['user'] = self.ldap_interface.get_user(uid)
|
|
99
|
+
doc['info']['group'] = self.ldap_interface.get_group(gid)
|
|
100
|
+
|
|
101
|
+
return doc['info']
|
|
102
|
+
|
|
103
|
+
def _single_process_file(self, filepath: str, index: int = None, total: int = None):
|
|
104
|
+
"""
|
|
105
|
+
Perform facet scanning for a specific filepath
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
logger.info('--------------------------------')
|
|
109
|
+
if index is None:
|
|
110
|
+
logger.info(f'Processing {filepath.split("/")[-1]}')
|
|
111
|
+
else:
|
|
112
|
+
logger.info(f'Processing {filepath.split("/")[-1]} ({index}/{total})')
|
|
113
|
+
|
|
114
|
+
# Get the handler for this filepath
|
|
115
|
+
handler = self.facet_scanner.get_handler(filepath, json_files=None)
|
|
116
|
+
|
|
117
|
+
# Extract the facets
|
|
118
|
+
facets = handler.get_facets(filepath)
|
|
119
|
+
|
|
120
|
+
# Build the project dictionary using the handlers project name attr
|
|
121
|
+
project = {
|
|
122
|
+
'info': self._get_project_info(filepath),
|
|
123
|
+
'projects': {
|
|
124
|
+
handler.project_name: facets
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if self._test:
|
|
129
|
+
index = self._conf['facet_files_test_index']['name']
|
|
130
|
+
else:
|
|
131
|
+
index = self._conf['facet_files_index']['name']
|
|
132
|
+
|
|
133
|
+
id = hashlib.sha1(filepath.encode(errors="ignore")).hexdigest()
|
|
134
|
+
|
|
135
|
+
# Send facets to elasticsearch
|
|
136
|
+
if not self._dryrun:
|
|
137
|
+
self.es.update(
|
|
138
|
+
index=index,
|
|
139
|
+
id=id,
|
|
140
|
+
body={'doc': project, 'doc_as_upsert': True}
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
logger.info(f'DRYRUN: Skipped updating for {filepath.split("/")[-1]}')
|
|
144
|
+
|
|
145
|
+
self._local_cache(
|
|
146
|
+
filename=f'cache/{filepath.split("/")[-1]}-cache.json',
|
|
147
|
+
contents=project,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _get_command_line_args():
|
|
151
|
+
"""
|
|
152
|
+
Get the command line arguments for the facet scan
|
|
153
|
+
"""
|
|
154
|
+
parser = argparse.ArgumentParser(description='Entrypoint for the CCI OS Worker on the CMD Line')
|
|
155
|
+
parser.add_argument('datafile_path', type=str, help='Path to the "datasets.txt" file')
|
|
156
|
+
parser.add_argument('conf', type=str, help='Path to Yaml config file for Elasticsearch')
|
|
157
|
+
|
|
158
|
+
parser.add_argument('-d','--dryrun', dest='dryrun', action='store_true', help='Perform in dryrun mode')
|
|
159
|
+
parser.add_argument('-t','--test', dest='test', action='store_true', help='Perform in test/staging mode')
|
|
160
|
+
parser.add_argument('-p','--prefix', dest='prefix', default='', help='Prefix to apply to all filenames')
|
|
161
|
+
parser.add_argument('-v','--verbose', action='count', default=2, help='Set level of verbosity for logs')
|
|
162
|
+
parser.add_argument('-f','--file-count', dest='file_count', type=int, help='Add limit to number of files to process.')
|
|
163
|
+
parser.add_argument('-o','--output', dest='output', default=None, help='Send fail list to an output file')
|
|
164
|
+
|
|
165
|
+
args = parser.parse_args()
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
'datafile_path': args.datafile_path,
|
|
169
|
+
'conf': args.conf,
|
|
170
|
+
'dryrun': args.dryrun,
|
|
171
|
+
'test': args.test,
|
|
172
|
+
'prefix': args.prefix,
|
|
173
|
+
'verbose': args.verbose-1,
|
|
174
|
+
'file_count': args.file_count,
|
|
175
|
+
'output': args.output
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
def main(args: dict = None):
|
|
179
|
+
if args is None:
|
|
180
|
+
args = _get_command_line_args()
|
|
181
|
+
if isinstance(args['conf'], str):
|
|
182
|
+
conf = load_config(args['conf'])
|
|
183
|
+
|
|
184
|
+
if conf is None:
|
|
185
|
+
logger.error('Config file could not be loaded')
|
|
186
|
+
return
|
|
187
|
+
if not os.path.isfile(args['datafile_path']):
|
|
188
|
+
logger.error(f'Inaccessible Datafile - {args["datafile_path"]}')
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
if check_timeout():
|
|
192
|
+
logger.error('Check-timeout failed')
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
file_limit = conf.get('file_limit', None) or args.get('file_limit', None)
|
|
196
|
+
|
|
197
|
+
set_verbose(args['verbose'])
|
|
198
|
+
|
|
199
|
+
fs = FacetUpdateHandler(conf, dryrun=args['dryrun'], test=args['test'])
|
|
200
|
+
fail_list = fs.process_deposits(args['datafile_path'], args['prefix'], file_limit=file_limit)
|
|
201
|
+
|
|
202
|
+
logger.info('Failed items:')
|
|
203
|
+
for f in fail_list:
|
|
204
|
+
logger.info(f)
|
|
205
|
+
|
|
206
|
+
if args['output'] is not None and fail_list != []:
|
|
207
|
+
with open(args['output'],'w') as f:
|
|
208
|
+
f.write('\n'.join(fail_list))
|
|
209
|
+
|
|
210
|
+
if __name__ == '__main__':
|
|
211
|
+
main()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
__author__ = 'Daniel Westwood'
|
|
3
|
+
__date__ = '05 Nov 2024'
|
|
4
|
+
__copyright__ = 'Copyright 2024 United Kingdom Research and Innovation'
|
|
5
|
+
__license__ = 'BSD - see LICENSE file in top-level package directory'
|
|
6
|
+
__contact__ = 'daniel.westwood@stfc.ac.uk'
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import hashlib
|
|
10
|
+
import argparse
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
from typing import Union
|
|
14
|
+
|
|
15
|
+
from cci_facet_scanner.core.facet_scanner import FacetScanner
|
|
16
|
+
from ceda_elasticsearch_tools.elasticsearch import CEDAElasticsearchClient
|
|
17
|
+
from fbi_directory_check.utils import check_timeout
|
|
18
|
+
|
|
19
|
+
from .utils import load_config, UpdateHandler, set_verbose
|
|
20
|
+
|
|
21
|
+
from cci_os_worker import logstream
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
logger.addHandler(logstream)
|
|
25
|
+
logger.propagate = False
|
|
26
|
+
|
|
27
|
+
class ElasticsearchDeleter(UpdateHandler):
|
|
28
|
+
|
|
29
|
+
def __init__(self, conf: dict, dryrun: bool = False, test: bool = False):
|
|
30
|
+
"""
|
|
31
|
+
Initialise this class with the correct connections to
|
|
32
|
+
establish an elasticsearch client.
|
|
33
|
+
"""
|
|
34
|
+
logger.info('Loading ES Deleter')
|
|
35
|
+
|
|
36
|
+
super().__init__(conf, dryrun=dryrun, test=test)
|
|
37
|
+
|
|
38
|
+
def _remove_file(self, filepath):
|
|
39
|
+
"""
|
|
40
|
+
Delete all records for a specific dataset
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
index = self._conf['facet_files_index']['name']
|
|
44
|
+
|
|
45
|
+
id = hashlib.sha1(filepath.encode(errors="ignore")).hexdigest()
|
|
46
|
+
|
|
47
|
+
if not self._dryrun:
|
|
48
|
+
try:
|
|
49
|
+
self.es.delete(
|
|
50
|
+
index=index,
|
|
51
|
+
id=id,
|
|
52
|
+
)
|
|
53
|
+
return 0
|
|
54
|
+
except:
|
|
55
|
+
return 1
|
|
56
|
+
else:
|
|
57
|
+
logger.info(f'DRYRUN: Skipped deleting {filepath.split("/")[-1]}')
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
def _get_command_line_args():
|
|
61
|
+
"""
|
|
62
|
+
Get the command line arguments for the facet scan
|
|
63
|
+
"""
|
|
64
|
+
parser = argparse.ArgumentParser(description='Entrypoint for the CCI OS Worker on the CMD Line')
|
|
65
|
+
parser.add_argument('datafile_path', type=str, help='Path to the "datasets.txt" file')
|
|
66
|
+
parser.add_argument('conf', type=str, help='Path to Yaml config file for Elasticsearch')
|
|
67
|
+
|
|
68
|
+
parser.add_argument('-d','--dryrun', dest='dryrun', action='store_true', help='Perform in dryrun mode')
|
|
69
|
+
parser.add_argument('-t','--test', dest='test', action='store_true', help='Perform in test/staging mode')
|
|
70
|
+
parser.add_argument('-v','--verbose', action='count', default=2, help='Set level of verbosity for logs')
|
|
71
|
+
parser.add_argument('-o','--output', dest='output', default=None, help='Send fail list to an output file')
|
|
72
|
+
|
|
73
|
+
args = parser.parse_args()
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
'datafile_path': args.datafile_path,
|
|
77
|
+
'conf': args.conf,
|
|
78
|
+
'dryrun': args.dryrun,
|
|
79
|
+
'test': args.test,
|
|
80
|
+
'verbose': args.verbose-1,
|
|
81
|
+
'output': args.output
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def main(args: dict = None):
|
|
85
|
+
if args is None:
|
|
86
|
+
args = _get_command_line_args()
|
|
87
|
+
if isinstance(args['conf'], str):
|
|
88
|
+
conf = load_config(args['conf'])
|
|
89
|
+
|
|
90
|
+
if conf is None:
|
|
91
|
+
logger.error('Config file could not be loaded')
|
|
92
|
+
return
|
|
93
|
+
if not os.path.isfile(args['datafile_path']):
|
|
94
|
+
logger.error(f'Inaccessible Datafile - {args["datafile_path"]}')
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
if check_timeout():
|
|
98
|
+
logger.error('Check-timeout failed')
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
set_verbose(args['verbose'])
|
|
102
|
+
|
|
103
|
+
deleter = ElasticsearchDeleter(conf, dryrun=args['dryrun'], test=args['test'])
|
|
104
|
+
fail_list = deleter.process_removals(args['datafile_path'])
|
|
105
|
+
|
|
106
|
+
logger.info('Failed items:')
|
|
107
|
+
for f in fail_list:
|
|
108
|
+
logger.info(f)
|
|
109
|
+
|
|
110
|
+
if args['output'] is not None and fail_list != []:
|
|
111
|
+
with open(args['output'],'w') as f:
|
|
112
|
+
f.write('\n'.join(fail_list))
|
|
113
|
+
|
|
114
|
+
if __name__ == '__main__':
|
|
115
|
+
main()
|