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.
@@ -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
+ ![Static Badge](https://img.shields.io/badge/cci%20tagger%20workflow-8AD6F6)
26
+ ![GitHub Release](https://img.shields.io/github/v/release/cedadev/cci-os-worker)
27
+ [![PyPI version](https://badge.fury.io/py/cci-os-worker.svg)](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
+ ![CCI Opensearch Workflow](https://github.com/cedadev/cci-os-worker/blob/main/images/CCI_Workflow.png)
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
+ ![Static Badge](https://img.shields.io/badge/cci%20tagger%20workflow-8AD6F6)
4
+ ![GitHub Release](https://img.shields.io/github/v/release/cedadev/cci-os-worker)
5
+ [![PyPI version](https://badge.fury.io/py/cci-os-worker.svg)](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
+ ![CCI Opensearch Workflow](https://github.com/cedadev/cci-os-worker/blob/main/images/CCI_Workflow.png)
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,8 @@
1
+ # Logger setup
2
+ import logging
3
+
4
+ logging.basicConfig(level=logging.DEBUG)
5
+ logstream = logging.StreamHandler()
6
+
7
+ formatter = logging.Formatter('%(levelname)s [%(name)s]: %(message)s')
8
+ logstream.setFormatter(formatter)
@@ -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()