1
1
#!/usr/bin/python3
2
2
3
3
import argparse
4
- import netrc
5
- import os
6
- import re
7
4
import sys
8
5
import tempfile
9
- from datetime import datetime
10
- from urllib .parse import urlparse
11
6
12
7
import defusedxml .ElementTree
13
8
import pystac
14
- import requests
15
9
import stactools .sentinel1 .grd .stac
16
10
import stactools .sentinel1 .slc .stac
17
11
import stactools .sentinel2 .stac
18
12
import stactools .sentinel3 .stac
19
13
from stactools .sentinel3 import constants
20
14
import stactools .sentinel5p .stac
21
- import yaml
22
- from requests import Session
23
15
from stactools .sentinel3 .file_extension_updated import FileExtensionUpdated
24
16
from tqdm import tqdm
25
17
26
- import sentinel_stac
18
+ from sentinel_stac import *
27
19
28
20
CONFIG_FILE = "sentinel_config.yml"
29
- ERR_PREFIX = ""
30
21
SUCC_PREFIX = ""
31
22
PRODUCT_ID = None
32
23
COLLECTION = None
@@ -88,39 +79,10 @@ def parse_arguments():
88
79
89
80
args = parser .parse_args ()
90
81
if not args .push and not args .save :
91
- die_with_error ('--push or --save required to take any action' )
82
+ die_with_error (PRODUCT_ID , '--push or --save required to take any action' )
92
83
return args
93
84
94
85
95
- def die_with_error (msg , detailed_msg = "" , code = - 1 ):
96
- """
97
- Before terminating with exception, writes message to error file.
98
- Known HTTP error code should be used, otherwise -1 is used.
99
- """
100
- rundate = datetime .now ().strftime ('%Y-%m-%d' )
101
- err_file = ERR_PREFIX + rundate
102
- create_missing_dir (os .path .dirname (err_file ))
103
- with open (err_file , 'a' ) as f :
104
- f .write (f"{ COLLECTION } ,{ PRODUCT_ID } ,{ code } :{ msg } \n " )
105
- raise Exception ("\n " .join ([f"{ code } : { msg } " , detailed_msg ]))
106
-
107
-
108
- def read_configuration ():
109
- """
110
- Read configuration file.
111
- """
112
- with open (CONFIG_FILE , "r" ) as f :
113
- return yaml .safe_load (f )
114
-
115
-
116
- def create_missing_dir (dir_path ):
117
- """
118
- Creates directory, if it does not exist yet (including all missing directories in the path).
119
- """
120
- if not os .path .exists (dir_path ):
121
- os .makedirs (dir_path , exist_ok = True )
122
-
123
-
124
86
def request_with_progress (url , output_path ):
125
87
"""
126
88
Downloads a file from a URL and saves it to the specified output path, with a progress bar.
@@ -131,7 +93,7 @@ def request_with_progress(url, output_path):
131
93
block_size = 1024 # Size of each block (1 KB)
132
94
133
95
if not response .ok :
134
- die_with_error (f"Request to fetch file { url } failed." , response .text , response .status_code )
96
+ die_with_error (PRODUCT_ID , f"Request to fetch file { url } failed." , response .text , response .status_code )
135
97
136
98
progress_bar = tqdm (total = total_size ,
137
99
unit = 'iB' ,
@@ -170,7 +132,7 @@ def fetch_product_data(sentinel_host, metadata_dir):
170
132
COLLECTION = map_to_collection (title )
171
133
172
134
if not title or not product_url :
173
- die_with_error ("Missing required title or product url for product." )
135
+ die_with_error (PRODUCT_ID , "Missing required title or product url for product." )
174
136
175
137
print (f"Parsed product data for product (UUID { PRODUCT_ID } ):\n "
176
138
f"* Title ID: { title } \n "
@@ -184,46 +146,25 @@ def check_hosts(sentinel_host, stac_host, push):
184
146
"""
185
147
Checks sentinel_host and stac_host variables were resolved and .netrc file contains authentication credentials.
186
148
"""
187
- if not sentinel_host :
188
- die_with_error ("Sentinel host not configured properly!" )
189
- if not stac_host and push :
190
- die_with_error ("STAC host not configured properly!" )
191
-
192
- try :
193
- auth_info = netrc .netrc ()
194
- if not auth_info .authenticators (urlparse (sentinel_host ).netloc ):
195
- die_with_error (
196
- f"Host { urlparse (sentinel_host )} not found in authentication credentials in the .netrc file!" )
197
- if push and not auth_info .authenticators (urlparse (stac_host ).netloc ):
198
- die_with_error (f"Host { urlparse (stac_host )} not found in authentication credentials in the .netrc file!" )
199
- except (FileNotFoundError , netrc .NetrcParseError ) as e :
200
- die_with_error (f"Error parsing authentication file .netrc in the home directory." )
201
-
202
-
203
- def map_to_collection (product_name ):
204
- """
205
- Returns the normalized collection name for a given product.
206
- """
207
- for pattern , collection in sentinel_stac .product_collection_mapping .items ():
208
- if re .match (pattern , product_name ):
209
- return collection
210
- die_with_error ("Could not match product to collection name! Probably missing in the sentinel_stac.py mappings." )
149
+ check_host (PRODUCT_ID , sentinel_host )
150
+ if push :
151
+ check_host (PRODUCT_ID , stac_host )
211
152
212
153
213
154
def fetch_platform_metadata (product_url , metadata_dir , platform ):
214
155
"""
215
156
Fetches metadata from product's /Nodes data and stores them in the metadata directory.
216
157
"""
217
158
if platform .lower () == "s1" :
218
- platform_files = sentinel_stac . s1_files
159
+ platform_files = S1_FILES
219
160
elif platform .lower () == "s2" :
220
- platform_files = sentinel_stac . s2_files
161
+ platform_files = S2_FILES
221
162
elif platform .lower () == "s3" :
222
- platform_files = sentinel_stac . s3_files
163
+ platform_files = S3_FILES
223
164
elif platform .lower () == "s5" :
224
- platform_files = sentinel_stac . s5_files
165
+ platform_files = S5_FILES
225
166
else :
226
- die_with_error (f"Platform { platform } not supported!" )
167
+ die_with_error (PRODUCT_ID , f"Platform { platform } not supported!" )
227
168
for file in platform_files :
228
169
source_url = f"{ product_url } /Nodes('{ file } ')/$value"
229
170
output_file = os .path .join (metadata_dir , file )
@@ -302,39 +243,19 @@ def regenerate_href_links(stacfile_path, metadata_dir, product_url, salt):
302
243
os .replace (new_file , stacfile_path )
303
244
304
245
305
- def get_auth_token (token_url ):
306
- """
307
- Gets token for communication with API from token url.
308
- """
309
- response = requests .get (token_url )
310
- if not response .ok :
311
- die_with_error (f"Could not obtain API token from { token_url } " , response .text , response .status_code )
312
- return response .json ()["token" ]
313
-
314
-
315
- def get_auth_session (token ):
316
- """
317
- Creates session which overwrites the BA credentials set in the ~/.netrc file by auth token.
318
- """
319
- token_session = Session ()
320
- token_session .trust_env = False # need to overwrite the authorization header, otherwise BA is used
321
- token_session .headers .update ({"Authorization" : f"Bearer { token } " })
322
- return token_session
323
-
324
-
325
246
def update_catalogue_entry (stac_host , entry_id , json_data , auth_token = None ):
326
247
"""
327
248
Updates stac entry by fully rewriting it
328
249
"""
329
250
url = f"{ stac_host } /collections/{ COLLECTION } /items/{ entry_id } "
330
251
print (f"Overwriting existing product entry in STAC catalogue." )
331
252
332
- token = auth_token or get_auth_token (f"{ stac_host } /auth" )
253
+ token = auth_token or get_auth_token (f"{ stac_host } /auth" , PRODUCT_ID )
333
254
token_session = get_auth_session (token )
334
255
335
256
response = token_session .put (url , data = json_data )
336
257
if not response .ok :
337
- die_with_error (f"Could not remove existing product from catalogue." , response .text , response .status_code )
258
+ die_with_error (PRODUCT_ID , f"Could not remove existing product from catalogue." , response .text , response .status_code )
338
259
339
260
340
261
def upload_to_catalogue (stac_host , stac_filepath , overwrite = False ):
@@ -345,7 +266,7 @@ def upload_to_catalogue(stac_host, stac_filepath, overwrite=False):
345
266
url = f"{ stac_host } /collections/{ COLLECTION } /items"
346
267
print (f"Uploading STAC data to { url } " )
347
268
348
- token = get_auth_token (f"{ stac_host } /auth" )
269
+ token = get_auth_token (f"{ stac_host } /auth" , PRODUCT_ID )
349
270
350
271
with open (stac_filepath , 'r' ) as file :
351
272
json_data = file .read ()
@@ -361,52 +282,51 @@ def upload_to_catalogue(stac_host, stac_filepath, overwrite=False):
361
282
elif response .status_code == 409 :
362
283
if not overwrite :
363
284
# don't die
364
- err_file = ERR_PREFIX + rundate
365
- create_missing_dir (os .path .dirname (err_file ))
366
- with open (err_file , 'a' ) as f :
285
+ create_missing_dir (os .path .dirname (ERR_FILE ))
286
+ with open (ERR_FILE , 'a' ) as f :
367
287
f .write (f"{ COLLECTION } ,{ PRODUCT_ID } ,0,Skipped existing product\n " )
368
288
print ("Product already registered, skipping." )
369
289
else :
370
290
if response .text and "Feature" in response .text and "ErrorMessage" in response .text :
371
291
stac_product_id = response .json ().get ("ErrorMessage" ).split (" " )[1 ]
372
- update_catalogue_entry (stac_host , COLLECTION , stac_product_id , json_data , token )
292
+ update_catalogue_entry (stac_host , stac_product_id , json_data , token )
373
293
else :
374
- die_with_error ("Cannot update existing entry, feature id expected in response not found." )
294
+ die_with_error (PRODUCT_ID , "Cannot update existing entry, feature id expected in response not found." )
375
295
elif response .status_code == 404 :
376
- die_with_error ("Wrong URL, or collection does not exist." , response .text , response .status_code )
296
+ die_with_error (PRODUCT_ID , "Wrong URL, or collection does not exist." , response .text , response .status_code )
377
297
else :
378
- die_with_error (f"Request to upload STAC file failed" , response .text , response .status_code )
298
+ die_with_error (PRODUCT_ID , f"Request to upload STAC file failed" , response .text , response .status_code )
379
299
380
300
381
301
def main ():
382
302
args = parse_arguments ()
383
- config = read_configuration ()
303
+ config = read_configuration (CONFIG_FILE )
384
304
global PRODUCT_ID
385
305
PRODUCT_ID = args .productId
386
306
387
307
sentinel_host = args .sentinelHost or config .get ("SENTINEL_HOST" )
388
308
stac_host = args .stacHost or config .get ("STAC_HOST" )
389
309
390
310
if args .save and config .get ("LOCAL_DIR" ) is None and args .localDir is None :
391
- die_with_error ("Flag --save was provided, but LOCAL_DIR option not configured and not specified "
311
+ die_with_error (PRODUCT_ID , "Flag --save was provided, but LOCAL_DIR option not configured and not specified "
392
312
"in the --localDir argument!" )
393
313
394
314
stac_storage = args .localDir or os .path .join (config .get ("LOCAL_DIR" ), "register_stac" )
395
315
if stac_storage is not None :
396
316
if not os .path .isabs (stac_storage ):
397
- die_with_error ("Valid path not used for the stac storage argument - expected an absolute directory path!" )
317
+ die_with_error (PRODUCT_ID , "Valid path not used for the stac storage argument - expected an absolute directory path!" )
398
318
create_missing_dir (os .path .dirname (stac_storage ))
399
319
400
320
global SUCC_PREFIX , ERR_PREFIX
401
321
SUCC_PREFIX = config .get ("SUCC_PREFIX" )
402
322
ERR_PREFIX = config .get ("ERR_PREFIX" )
403
323
if args .push and (SUCC_PREFIX is None or ERR_PREFIX is None ):
404
- die_with_error ("Flag --push was provided, but SUCC_PREFIX and ERR_PREFIX need to be set in the configuration "
324
+ die_with_error (PRODUCT_ID , "Flag --push was provided, but SUCC_PREFIX and ERR_PREFIX need to be set in the configuration "
405
325
"file for logging!" )
406
326
407
327
salt = config .get ("SALT" )
408
328
if args .push and not stac_host :
409
- die_with_error ('--push requires --stacHost argument or STAC_HOST configuration option to be set!' )
329
+ die_with_error (PRODUCT_ID , '--push requires --stacHost argument or STAC_HOST configuration option to be set!' )
410
330
411
331
check_hosts (sentinel_host , stac_host , args .push )
412
332
@@ -443,7 +363,7 @@ def main():
443
363
else :
444
364
raise Exception (f"Unknown platform { platform } " )
445
365
except Exception as e :
446
- die_with_error (e .args [0 ] if e .args and len (str (e .args [0 ])) > 5 else str (e ))
366
+ die_with_error (PRODUCT_ID , e .args [0 ] if e .args and len (str (e .args [0 ])) > 5 else str (e ))
447
367
448
368
stac_storage = stac_storage if args .save else metadata_dir
449
369
stac_filepath = os .path .join (stac_storage , "{}.json" .format (item .id ))
0 commit comments