rucio-clients 35.7.0__py3-none-any.whl → 37.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of rucio-clients might be problematic. Click here for more details.

Files changed (85) hide show
  1. rucio/alembicrevision.py +1 -1
  2. rucio/cli/__init__.py +14 -0
  3. rucio/cli/account.py +216 -0
  4. rucio/cli/bin_legacy/__init__.py +13 -0
  5. rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
  6. rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
  7. rucio/cli/command.py +272 -0
  8. rucio/cli/config.py +72 -0
  9. rucio/cli/did.py +191 -0
  10. rucio/cli/download.py +128 -0
  11. rucio/cli/lifetime_exception.py +33 -0
  12. rucio/cli/replica.py +162 -0
  13. rucio/cli/rse.py +293 -0
  14. rucio/cli/rule.py +158 -0
  15. rucio/cli/scope.py +40 -0
  16. rucio/cli/subscription.py +73 -0
  17. rucio/cli/upload.py +60 -0
  18. rucio/cli/utils.py +226 -0
  19. rucio/client/accountclient.py +0 -1
  20. rucio/client/baseclient.py +33 -24
  21. rucio/client/client.py +45 -1
  22. rucio/client/didclient.py +5 -3
  23. rucio/client/downloadclient.py +6 -8
  24. rucio/client/replicaclient.py +0 -2
  25. rucio/client/richclient.py +317 -0
  26. rucio/client/rseclient.py +4 -4
  27. rucio/client/uploadclient.py +26 -12
  28. rucio/common/bittorrent.py +234 -0
  29. rucio/common/cache.py +66 -29
  30. rucio/common/checksum.py +168 -0
  31. rucio/common/client.py +122 -0
  32. rucio/common/config.py +22 -35
  33. rucio/common/constants.py +61 -3
  34. rucio/common/didtype.py +72 -24
  35. rucio/common/exception.py +65 -8
  36. rucio/common/extra.py +5 -10
  37. rucio/common/logging.py +13 -13
  38. rucio/common/pcache.py +8 -7
  39. rucio/common/plugins.py +59 -27
  40. rucio/common/policy.py +12 -3
  41. rucio/common/schema/__init__.py +84 -34
  42. rucio/common/schema/generic.py +0 -17
  43. rucio/common/schema/generic_multi_vo.py +0 -17
  44. rucio/common/test_rucio_server.py +12 -6
  45. rucio/common/types.py +132 -52
  46. rucio/common/utils.py +93 -643
  47. rucio/rse/__init__.py +3 -3
  48. rucio/rse/protocols/bittorrent.py +11 -1
  49. rucio/rse/protocols/cache.py +0 -11
  50. rucio/rse/protocols/dummy.py +0 -11
  51. rucio/rse/protocols/gfal.py +14 -9
  52. rucio/rse/protocols/globus.py +1 -1
  53. rucio/rse/protocols/http_cache.py +1 -1
  54. rucio/rse/protocols/posix.py +2 -2
  55. rucio/rse/protocols/protocol.py +84 -317
  56. rucio/rse/protocols/rclone.py +2 -1
  57. rucio/rse/protocols/rfio.py +10 -1
  58. rucio/rse/protocols/ssh.py +2 -1
  59. rucio/rse/protocols/storm.py +2 -13
  60. rucio/rse/protocols/webdav.py +74 -30
  61. rucio/rse/protocols/xrootd.py +2 -1
  62. rucio/rse/rsemanager.py +170 -53
  63. rucio/rse/translation.py +260 -0
  64. rucio/vcsversion.py +4 -4
  65. rucio/version.py +7 -0
  66. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
  67. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rucio.cfg.template +3 -19
  68. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/requirements.client.txt +11 -7
  69. rucio_clients-37.0.0.data/scripts/rucio +133 -0
  70. rucio_clients-37.0.0.data/scripts/rucio-admin +97 -0
  71. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/METADATA +18 -14
  72. rucio_clients-37.0.0.dist-info/RECORD +104 -0
  73. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/licenses/AUTHORS.rst +3 -0
  74. rucio/common/schema/atlas.py +0 -413
  75. rucio/common/schema/belleii.py +0 -408
  76. rucio/common/schema/domatpc.py +0 -401
  77. rucio/common/schema/escape.py +0 -426
  78. rucio/common/schema/icecube.py +0 -406
  79. rucio/rse/protocols/gsiftp.py +0 -92
  80. rucio_clients-35.7.0.dist-info/RECORD +0 -88
  81. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rse-accounts.cfg.template +0 -0
  82. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/rucio_client/merge_rucio_configs.py +0 -0
  83. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/WHEEL +0 -0
  84. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/licenses/LICENSE +0 -0
  85. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- #!python
1
+ #!/usr/bin/env python
2
2
  # Copyright European Organization for Nuclear Research (CERN) since 2012
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,46 +15,33 @@
15
15
 
16
16
  import argparse
17
17
  import datetime
18
- import errno
19
18
  import json
20
- import logging
21
19
  import math
22
20
  import os
23
21
  import signal
24
22
  import sys
25
23
  import time
26
- import traceback
27
- from configparser import NoOptionError, NoSectionError
28
- from functools import wraps
29
24
  from textwrap import dedent
30
25
 
26
+ from rich.console import Console
27
+ from rich.padding import Padding
28
+ from rich.status import Status
29
+ from rich.text import Text
30
+ from rich.theme import Theme
31
+ from rich.traceback import install
32
+ from rich.tree import Tree
31
33
  from tabulate import tabulate
32
34
 
33
35
  from rucio import version
34
- from rucio.client import Client
35
- from rucio.common.config import config_get
36
+ from rucio.cli.utils import exception_handler, get_client, setup_gfal2_logger, signal_handler
37
+ from rucio.client.richclient import MAX_TRACEBACK_WIDTH, MIN_CONSOLE_WIDTH, CLITheme, generate_table, get_cli_config, get_pager, print_output, setup_rich_logger
36
38
  from rucio.common.constants import RseAttr
37
39
  from rucio.common.exception import (
38
- AccessDenied,
39
- AccountNotFound,
40
- CannotAuthenticate,
41
- ConfigNotFound,
42
- DataIdentifierAlreadyExists,
43
- DataIdentifierNotFound,
44
- Duplicate,
45
- DuplicateContent,
46
- InputValidationError,
47
- InvalidObject,
48
- InvalidRSEExpression,
49
- ReplicaIsLocked,
50
40
  ReplicaNotFound,
51
- RSENotFound,
52
41
  RSEOperationNotSupported,
53
- RuleNotFound,
54
- ScopeNotFound,
55
42
  )
56
43
  from rucio.common.extra import import_extras
57
- from rucio.common.utils import StoreAndDeprecateWarningAction, chunks, clean_pfns, construct_non_deterministic_pfn, extract_scope, get_bytes_value_from_string, parse_response, render_json, sizefmt
44
+ from rucio.common.utils import StoreAndDeprecateWarningAction, chunks, clean_pfns, construct_non_deterministic_pfn, extract_scope, get_bytes_value_from_string, parse_response, render_json, setup_logger, sizefmt
58
45
  from rucio.rse import rsemanager as rsemgr
59
46
 
60
47
  EXTRA_MODULES = import_extras(['argcomplete'])
@@ -71,170 +58,9 @@ SUCCESS = 0
71
58
  FAILURE = 1
72
59
  DEFAULT_PORT = 443
73
60
 
74
- logger = logging.getLogger("user")
75
61
 
76
62
  tablefmt = 'psql'
77
-
78
-
79
- def setup_logger(logger):
80
- logger.setLevel(logging.DEBUG)
81
- hdlr = logging.StreamHandler()
82
-
83
- def emit_decorator(fcn):
84
- def func(*args):
85
- formatter = logging.Formatter("%(message)s")
86
- hdlr.setFormatter(formatter)
87
- return fcn(*args)
88
- return func
89
- hdlr.emit = emit_decorator(hdlr.emit)
90
- logger.addHandler(hdlr)
91
-
92
-
93
- setup_logger(logger)
94
-
95
-
96
- def signal_handler(signal, frame):
97
- logger.warning('You pressed Ctrl+C! Exiting gracefully')
98
- sys.exit(1)
99
-
100
-
101
- signal.signal(signal.SIGINT, signal_handler)
102
-
103
-
104
- def exception_handler(function):
105
- @wraps(function)
106
- def new_funct(*args, **kwargs):
107
- try:
108
- return function(*args, **kwargs)
109
- except InvalidObject as error:
110
- logger.error(error)
111
- return error.error_code
112
- except DataIdentifierNotFound as error:
113
- logger.error(error)
114
- logger.debug('This means that the Data IDentifier you provided is not known by Rucio.')
115
- return error.error_code
116
- except AccessDenied as error:
117
- logger.error(error)
118
- logger.debug('This error is a permission issue. You cannot run this command with your account.')
119
- return error.error_code
120
- except DataIdentifierAlreadyExists as error:
121
- logger.error(error)
122
- logger.debug('This means that the data IDentifier you try to add is already registered in Rucio.')
123
- return error.error_code
124
- except RSENotFound as error:
125
- logger.error(error)
126
- logger.debug('This means that the Rucio Storage Element you provided is not known by Rucio.')
127
- return error.error_code
128
- except InvalidRSEExpression as error:
129
- logger.error(error)
130
- logger.debug('This means the RSE expression you provided is not syntactically correct.')
131
- return error.error_code
132
- except DuplicateContent as error:
133
- logger.error(error)
134
- logger.debug('This means that the DID you want to attach is already in the target DID.')
135
- return error.error_code
136
- except TypeError as error:
137
- logger.error(error)
138
- logger.debug('This means the parameter you passed has a wrong type.')
139
- return FAILURE
140
- except RuleNotFound as error:
141
- logger.error(error)
142
- logger.debug('This means the rule you specified does not exist.')
143
- return error.error_code
144
- except AccountNotFound as error:
145
- logger.error(error)
146
- logger.debug('This means that the specified account cannot be found.')
147
- return error.error_code
148
- except NotImplementedError as error:
149
- logger.error(error)
150
- logger.debug('This means that the method is not implemented yet.')
151
- return FAILURE
152
- except Duplicate as error:
153
- logger.error(error)
154
- logger.debug('This means that you are trying to add something that already exists.')
155
- return error.error_code
156
- except ReplicaIsLocked as error:
157
- logger.error(error)
158
- logger.error('This means that the replica has a lock and is therefore protected.')
159
- return FAILURE
160
- except ReplicaNotFound as error:
161
- logger.error(error)
162
- logger.error('This means that the replica does not exist.')
163
- return FAILURE
164
- except ConfigNotFound as error:
165
- logger.error(error)
166
- logger.error('This means that the configuration section you are looking for does not exist.')
167
- return FAILURE
168
- except ScopeNotFound as error:
169
- logger.error(error)
170
- logger.error('This means that no scopes were found for the specified account ID.')
171
- return FAILURE
172
- except InputValidationError as error:
173
- logger.error(error)
174
- logger.error('This means that the input you provided did not meet all the requirements.')
175
- return FAILURE
176
- except Exception as error:
177
- if isinstance(error, IOError) and getattr(error, 'errno', None) == errno.EPIPE:
178
- # Ignore Broken Pipe
179
- # While in python3 we can directly catch 'BrokenPipeError', in python2 it doesn't exist.
180
-
181
- # Python flushes standard streams on exit; redirect remaining output
182
- # to devnull to avoid another BrokenPipeError at shutdown
183
- devnull = os.open(os.devnull, os.O_WRONLY)
184
- os.dup2(devnull, sys.stdout.fileno())
185
- return SUCCESS
186
- logger.error(error)
187
- logger.error('Rucio exited with an unexpected/unknown error, please provide the traceback below to the developers.')
188
- logger.debug(traceback.format_exc())
189
- return FAILURE
190
- return new_funct
191
-
192
-
193
- def get_client(args):
194
- """
195
- Returns a new client object.
196
- """
197
- if not args.auth_strategy:
198
- if 'RUCIO_AUTH_TYPE' in os.environ:
199
- auth_type = os.environ['RUCIO_AUTH_TYPE'].lower()
200
- else:
201
- try:
202
- auth_type = config_get('client', 'auth_type').lower()
203
- except (NoOptionError, NoSectionError):
204
- logger.error('Cannot get AUTH_TYPE')
205
- sys.exit(FAILURE)
206
- else:
207
- auth_type = args.auth_strategy.lower()
208
-
209
- if auth_type in ['userpass', 'saml'] and args.username is not None and args.password is not None:
210
- creds = {'username': args.username, 'password': args.password}
211
- elif auth_type == 'oidc':
212
- if args.oidc_issuer:
213
- args.oidc_issuer = args.oidc_issuer.lower()
214
- creds = {'oidc_auto': args.oidc_auto,
215
- 'oidc_scope': args.oidc_scope,
216
- 'oidc_audience': args.oidc_audience,
217
- 'oidc_polling': args.oidc_polling,
218
- 'oidc_refresh_lifetime': args.oidc_refresh_lifetime,
219
- 'oidc_issuer': args.oidc_issuer,
220
- 'oidc_username': args.oidc_username,
221
- 'oidc_password': args.oidc_password}
222
- else:
223
- creds = None
224
-
225
- try:
226
- client = Client(rucio_host=args.host, auth_host=args.auth_host,
227
- account=args.issuer,
228
- auth_type=auth_type, creds=creds,
229
- ca_cert=args.ca_certificate, timeout=args.timeout, vo=args.vo)
230
- except CannotAuthenticate as error:
231
- logger.error(error)
232
- if 'alert certificate expired' in str(error):
233
- logger.error('The server certificate expired.')
234
- elif auth_type.lower() == 'x509_proxy':
235
- logger.error('Please verify that your proxy is still valid and renew it if needed.')
236
- sys.exit(FAILURE)
237
- return client
63
+ cli_config = get_cli_config()
238
64
 
239
65
 
240
66
  def get_scope(did, client):
@@ -245,135 +71,145 @@ def get_scope(did, client):
245
71
  scopes = client.list_scopes()
246
72
  scope, name = extract_scope(did, scopes)
247
73
  return scope, name
248
- return None, did
249
74
 
250
75
 
251
76
  @exception_handler
252
- def add_account(args):
77
+ def add_account(args, client, logger, console, spinner):
253
78
  """
254
79
  %(prog)s add [options] <field1=value1 field2=value2 ...>
255
80
 
256
81
  Adds a new account. Specify metadata fields as arguments.
257
82
 
258
83
  """
259
- client = get_client(args)
260
- client.add_account(account=args.account, type_=args.accounttype, email=args.accountemail)
84
+ client.add_account(account=args.account, type_=args.account_type, email=args.email)
261
85
  print('Added new account: %s' % args.account)
262
86
  return SUCCESS
263
87
 
264
88
 
265
89
  @exception_handler
266
- def delete_account(args):
90
+ def delete_account(args, client, logger, console, spinner):
267
91
  """
268
92
  %(prog)s disable [options] <field1=value1 field2=value2 ...>
269
93
 
270
94
  Delete account.
271
95
 
272
96
  """
273
- client = get_client(args)
274
- client.delete_account(args.acnt)
275
- print('Deleted account: %s' % args.acnt)
97
+ client.delete_account(args.account)
98
+ print('Deleted account: %s' % args.account)
276
99
  return SUCCESS
277
100
 
278
101
 
279
102
  @exception_handler
280
- def update_account(args):
103
+ def update_account(args, client, logger, console, spinner):
281
104
  """
282
105
  %(prog)s update [options] <field1=value1 field2=value2 ...>
283
106
 
284
107
  Update an account.
285
108
 
286
109
  """
287
- client = get_client(args)
288
110
  client.update_account(account=args.account, key=args.key, value=args.value)
289
111
  print('%s of account %s changed to %s' % (args.key, args.account, args.value))
290
112
  return SUCCESS
291
113
 
292
114
 
293
115
  @exception_handler
294
- def ban_account(args):
116
+ def ban_account(args, client, logger, console, spinner):
295
117
  """
296
118
  %(prog)s ban [options] <field1=value1 field2=value2 ...>
297
119
 
298
120
  Ban an account.
299
121
 
300
122
  """
301
- client = get_client(args)
302
123
  client.update_account(account=args.account, key='status', value='SUSPENDED')
303
124
  print('Account %s banned' % args.account)
304
125
  return SUCCESS
305
126
 
306
127
 
307
128
  @exception_handler
308
- def unban_account(args):
129
+ def unban_account(args, client, logger, console, spinner):
309
130
  """
310
131
  %(prog)s unban [options] <field1=value1 field2=value2 ...>
311
132
 
312
133
  Unban a banned account.
313
134
 
314
135
  """
315
- client = get_client(args)
316
136
  client.update_account(account=args.account, key='status', value='ACTIVE')
317
137
  print('Account %s unbanned' % args.account)
318
138
  return SUCCESS
319
139
 
320
140
 
321
141
  @exception_handler
322
- def list_accounts(args):
142
+ def list_accounts(args, client, logger, console, spinner):
323
143
  """
324
144
  %(prog)s list [options] <field1=value1 field2=value2 ...>
325
145
 
326
146
  List accounts.
327
147
 
328
148
  """
329
- client = get_client(args)
330
149
  filters = {}
331
150
  if args.filters:
332
151
  for key, value in [(_.split('=')[0], _.split('=')[1]) for _ in args.filters.split(',')]:
333
152
  filters[key] = value
334
153
  accounts = client.list_accounts(identity=args.identity, account_type=args.account_type, filters=filters)
335
- for account in accounts:
336
- print(account['account'])
154
+ if args.csv:
155
+ print(*(account['account'] for account in accounts), sep=',')
156
+ elif cli_config == 'rich':
157
+ table = generate_table([[account['account']] for account in accounts], headers=['ACCOUNT'], col_alignments=['left'])
158
+ spinner.stop()
159
+ print_output(table, console=console, no_pager=args.no_pager)
160
+ else:
161
+ for account in accounts:
162
+ print(account['account'])
337
163
  return SUCCESS
338
164
 
339
165
 
340
166
  @exception_handler
341
- def info_account(args):
167
+ def info_account(args, client, logger, console, spinner):
342
168
  """
343
169
  %(prog)s show [options] <field1=value1 field2=value2 ...>
344
170
 
345
171
  Show extended information of a given account
346
172
 
347
173
  """
348
- client = get_client(args)
349
174
  info = client.get_account(account=args.account)
350
- for k in info:
351
- print(k.ljust(10) + ' : ' + str(info[k]))
175
+ if cli_config == 'rich':
176
+ keyword_style = {**CLITheme.ACCOUNT_STATUS, **CLITheme.ACCOUNT_TYPE}
177
+ table_data = [(k, Text(str(v), style=keyword_style.get(str(v), 'default'))) for k, v in sorted(info.items())]
178
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
179
+ print_output(table, console=console, no_pager=args.no_pager)
180
+ else:
181
+ for k in info:
182
+ print(k.ljust(10) + ' : ' + str(info[k]))
352
183
  return SUCCESS
353
184
 
354
185
 
355
186
  @exception_handler
356
- def list_identities(args):
187
+ def list_identities(args, client, logger, console, spinner):
357
188
  """
358
189
  %(prog)s list-identities [options] <field1=value1 field2=value2 ...>
359
190
 
360
191
  List all identities on an account.
361
192
  """
362
- client = get_client(args)
193
+ table_data = []
363
194
  identities = client.list_identities(account=args.account)
364
195
  for identity in identities:
365
- print('Identity: %(identity)s,\ttype: %(type)s' % identity)
196
+ if cli_config == 'rich':
197
+ table_data.append([identity['identity'], identity['type']])
198
+ else:
199
+ print('Identity: %(identity)s,\ttype: %(type)s' % identity)
200
+ if cli_config == 'rich':
201
+ table = generate_table(table_data, headers=['IDENTITY', 'TYPE'], col_alignments=['left', 'left'])
202
+ print_output(table, console=console, no_pager=args.no_pager)
366
203
  return SUCCESS
367
204
 
368
205
 
369
206
  @exception_handler
370
- def set_limits(args):
207
+ def set_limits(args, client, logger, console, spinner):
371
208
  """
372
209
  %(prog)s set [options] <field1=value1 field2=value2 ...>
373
210
 
374
211
  Set account limit for an account and rse.
375
212
  """
376
- client = get_client(args)
377
213
  locality = args.locality.lower()
378
214
  byte_limit = None
379
215
  limit_input = args.bytes.lower()
@@ -395,14 +231,13 @@ def set_limits(args):
395
231
 
396
232
 
397
233
  @exception_handler
398
- def get_limits(args):
234
+ def get_limits(args, client, logger, console, spinner):
399
235
  """
400
236
  %(prog)s get-limits [options] <field1=value1 field2=value2 ...>
401
237
 
402
238
  Grant an identity access to an account.
403
239
 
404
240
  """
405
- client = get_client(args)
406
241
  locality = args.locality.lower()
407
242
  limits = client.get_account_limits(account=args.account, rse_expression=args.rse, locality=locality)
408
243
  for rse in limits:
@@ -411,27 +246,25 @@ def get_limits(args):
411
246
 
412
247
 
413
248
  @exception_handler
414
- def delete_limits(args):
249
+ def delete_limits(args, client, logger, console, spinner):
415
250
  """
416
251
  %(prog)s delete [options] <field1=value1 field2=value2 ...>
417
252
 
418
253
  Delete account limit for an account and rse.
419
254
  """
420
- client = get_client(args)
421
255
  client.delete_account_limit(account=args.account, rse=args.rse, locality=args.locality)
422
256
  print('Deleted account limit for account %s and RSE %s' % (args.account, args.rse))
423
257
  return SUCCESS
424
258
 
425
259
 
426
260
  @exception_handler
427
- def identity_add(args):
261
+ def identity_add(args, client, logger, console, spinner):
428
262
  """
429
263
  %(prog)s del [options] <field1=value1 field2=value2 ...>
430
264
 
431
265
  Grant an identity access to an account.
432
266
 
433
267
  """
434
- client = get_client(args)
435
268
  if args.email == "":
436
269
  logger.error('Error: --email argument can\'t be an empty string. Failed to grant an identity access to an account')
437
270
  return FAILURE
@@ -446,63 +279,71 @@ def identity_add(args):
446
279
 
447
280
 
448
281
  @exception_handler
449
- def identity_delete(args):
282
+ def identity_delete(args, client, logger, console, spinner):
450
283
  """
451
284
  %(prog)s delete [options] <field1=value1 field2=value2 ...>
452
285
 
453
286
  Revoke an identity's access to an account.
454
287
 
455
288
  """
456
- client = get_client(args)
457
289
  client.del_identity(args.account, args.identity, authtype=args.authtype)
458
290
  print('Deleted identity: %s' % args.identity)
459
291
  return SUCCESS
460
292
 
461
293
 
462
294
  @exception_handler
463
- def add_rse(args):
295
+ def add_rse(args, client, logger, console, spinner):
464
296
  """
465
297
  %(prog)s add [options] <field1=value1 field2=value2 ...>
466
298
 
467
299
  Adds a new RSE. Specify metadata fields as arguments.
468
300
 
469
301
  """
470
- client = get_client(args)
471
302
  client.add_rse(args.rse, deterministic=not args.non_deterministic)
472
303
  print('Added new %sdeterministic RSE: %s' % ('non-' if args.non_deterministic else '', args.rse))
473
304
  return SUCCESS
474
305
 
475
306
 
476
307
  @exception_handler
477
- def disable_rse(args):
308
+ def disable_rse(args, client, logger, console, spinner):
478
309
  """
479
310
  %(prog)s del [options] <field1=value1 field2=value2 ...>
480
311
 
481
312
  Disable RSE.
482
313
 
483
314
  """
484
- client = get_client(args)
485
315
  client.delete_rse(args.rse)
486
316
  return SUCCESS
487
317
 
488
318
 
489
319
  @exception_handler
490
- def list_rses(args):
320
+ def list_rses(args, client, logger, console, spinner):
491
321
  """
492
322
  %(prog)s list [options] <field1=value1 field2=value2 ...>
493
323
 
494
324
  List RSEs.
495
325
 
496
326
  """
497
- client = get_client(args)
327
+ if cli_config == 'rich':
328
+ spinner.update(status='Fetching RSEs')
329
+ spinner.start()
330
+
498
331
  rses = client.list_rses()
499
- for rse in rses:
500
- print('%(rse)s' % rse)
332
+ if args.csv:
333
+ print(*(rse['rse'] for rse in rses), sep='\n')
334
+ elif cli_config == 'rich':
335
+ table_data = [[rse['rse']] for rse in sorted(rses, key=lambda elem: elem['rse'])]
336
+ table = generate_table(table_data, headers=['RSE'], col_alignments=['left'])
337
+ spinner.stop()
338
+ print_output(table, console=console, no_pager=args.no_pager)
339
+ else:
340
+ for rse in rses:
341
+ print(rse['rse'])
501
342
  return SUCCESS
502
343
 
503
344
 
504
345
  @exception_handler
505
- def update_rse(args):
346
+ def update_rse(args, client, logger, console, spinner):
506
347
  """
507
348
  %(prog)s update [options] <field1=value1 field2=value2 ...>
508
349
 
@@ -514,7 +355,6 @@ def update_rse(args):
514
355
  Use '', 'None' or 'null' to wipe the value of following RSE settings:
515
356
  qos_class
516
357
  """
517
- client = get_client(args)
518
358
  if args.value in ['true', 'True', 'TRUE', '1']:
519
359
  args.value = True
520
360
  if args.value in ['false', 'False', 'FALSE', '0']:
@@ -530,102 +370,192 @@ def update_rse(args):
530
370
 
531
371
 
532
372
  @exception_handler
533
- def info_rse(args):
373
+ def info_rse(args, client, logger, console, spinner):
534
374
  """
535
375
  %(prog)s info [options] <field1=value1 field2=value2 ...>
536
376
 
537
377
  Show extended information of a given RSE
538
378
 
539
379
  """
540
- client = get_client(args)
380
+ if cli_config == 'rich':
381
+ spinner.update(status='Fetching RSE info')
382
+ spinner.start()
383
+
541
384
  rseinfo = client.get_rse(rse=args.rse)
542
385
  attributes = client.list_rse_attributes(rse=args.rse)
543
386
  usage = client.get_rse_usage(rse=args.rse)
544
387
  rse_limits = client.get_rse_limits(args.rse)
545
- print('Settings:')
546
- print('=========')
547
- for key in sorted(rseinfo):
548
- if key != 'protocols':
549
- print(' ' + key + ': ' + str(rseinfo[key]))
550
- print('Attributes:')
551
- print('===========')
552
- for attribute in sorted(attributes):
553
- print(' ' + attribute + ': ' + str(attributes[attribute]))
554
- print('Protocols:')
555
- print('==========')
556
- for protocol in sorted(rseinfo['protocols'], key=lambda x: x['scheme']):
557
- print(' ' + protocol['scheme'])
388
+
389
+ if cli_config == 'rich':
390
+ keyword_styles = {**CLITheme.BOOLEAN, **CLITheme.RSE_TYPE}
391
+ output = []
392
+ table_data = []
393
+ else:
394
+ print('Settings:')
395
+ print('=========')
396
+ for i, key in enumerate(sorted(rseinfo)):
397
+ if cli_config == 'rich':
398
+ if i == 0:
399
+ output.append('[b]Settings:[/]')
400
+ if key != 'protocols':
401
+ table_data.append([key, Text(str(rseinfo[key]), style=keyword_styles.get(str(rseinfo[key]), 'default'))])
402
+ else:
403
+ if key != 'protocols':
404
+ print(' ' + key + ': ' + str(rseinfo[key]))
405
+
406
+ if cli_config == 'rich':
407
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
408
+ output.append(table)
409
+ table_data = []
410
+ else:
411
+ print('Attributes:')
412
+ print('===========')
413
+ for i, attribute in enumerate(sorted(attributes)):
414
+ if cli_config == 'rich':
415
+ if i == 0:
416
+ output.append('\n[b]Attributes:[/]')
417
+ table_data.append([attribute, Text(str(attributes[attribute]), style=keyword_styles.get(str(attributes[attribute]), 'default'))])
418
+ else:
419
+ print(' ' + attribute + ': ' + str(attributes[attribute]))
420
+
421
+ if cli_config == 'rich':
422
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
423
+ output.append(table)
424
+ else:
425
+ print('Protocols:')
426
+ print('==========')
427
+ for i, protocol in enumerate(sorted(rseinfo['protocols'], key=lambda x: x['scheme'])):
428
+ if cli_config == 'rich':
429
+ if i == 0:
430
+ output.append('\n[b]Protocols:[/]')
431
+ output.append(Padding.indent(Text(protocol['scheme'], style=CLITheme.SUBHEADER_HIGHLIGHT), 2))
432
+ else:
433
+ print(' ' + protocol['scheme'])
434
+
435
+ table_data = []
558
436
  for item in sorted(protocol):
559
- if item == 'domains':
560
- print(' ' + item + ': \'' + json.dumps(protocol[item]) + '\'')
437
+ if cli_config == 'rich':
438
+ if item == 'domains':
439
+ tree = Tree('')
440
+ for domain, values in protocol[item].items():
441
+ branch = tree.add(f'[{CLITheme.JSON_STR}]{domain}')
442
+ for k, v in values.items():
443
+ branch.add(f'[{CLITheme.JSON_STR}]{k}[/]: [{CLITheme.JSON_NUM}]{v}[/]')
444
+ table_data.append([item, tree])
445
+ else:
446
+ table_data.append([item, Text(str(protocol[item]), style=keyword_styles.get(protocol[item], 'default'))])
561
447
  else:
562
- print(' ' + item + ': ' + str(protocol[item]))
563
- print('Usage:')
564
- print('======')
565
- for elem in sorted(usage, key=lambda x: x['source']):
566
- print(' ' + elem['source'])
567
- for item in sorted(elem):
568
- print(' ' + item + ': ' + str(elem[item]))
569
- print('RSE limits:')
570
- print('===========')
571
- for limit in rse_limits:
572
- print(' ' + limit + ': ' + str(rse_limits[limit]) + ' B')
448
+ if item == 'domains':
449
+ print(' ' + item + ': \'' + json.dumps(protocol[item]) + '\'')
450
+ else:
451
+ print(' ' + item + ': ' + str(protocol[item]))
452
+
453
+ if cli_config == 'rich':
454
+ table = generate_table(table_data, col_alignments=['left', 'left'], row_styles=['none'])
455
+ output.append(Padding.indent(table, 2))
573
456
 
457
+ if cli_config == 'rich':
458
+ header = ['SOURCE', 'USED', 'FILES', 'FREE', 'TOTAL', 'UPDATED AT']
459
+ key2id = {header[i].lower().replace(' ', '_'): i for i in range(len(header))}
460
+ table_data = []
461
+ else:
462
+ print('Usage:')
463
+ print('======')
464
+ for i, elem in enumerate(sorted(usage, key=lambda x: x['source'])):
465
+ if cli_config == 'rich':
466
+ if i == 0:
467
+ output.append('\n[b]Usage:[/]')
468
+
469
+ row = [''] * len(header)
470
+ row[0] = elem['source']
471
+ for item in sorted(elem):
472
+ if item in ['used', 'free', 'total']:
473
+ row[key2id[item]] = sizefmt(elem[item], True)
474
+ elif item != 'source' and item in key2id:
475
+ row[key2id[item]] = str(elem[item])
476
+ table_data.append(row)
477
+ else:
478
+ print(' ' + elem['source'])
479
+ for item in sorted(elem):
480
+ print(' ' + item + ': ' + str(elem[item]))
481
+
482
+ if cli_config == 'rich':
483
+ if len(table_data) > 0:
484
+ usage_table = generate_table(table_data, headers=header, col_alignments=['left', 'right', 'right', 'right', 'right', 'left'])
485
+ output.append(usage_table)
486
+ table_data = []
487
+ else:
488
+ print('RSE limits:')
489
+ print('===========')
490
+ for i, limit in enumerate(rse_limits):
491
+ if cli_config == 'rich':
492
+ if i == 0:
493
+ output.append('\n[b]RSE limits:[/]')
494
+ table_data.append([limit, str(rse_limits[limit]) + ' B'])
495
+ else:
496
+ print(' ' + limit + ': ' + str(rse_limits[limit]) + ' B')
497
+
498
+ if cli_config == 'rich':
499
+ if len(table_data) > 0:
500
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'right'])
501
+ output.append(table)
502
+ spinner.stop()
503
+ print_output(*output, console=console, no_pager=args.no_pager)
574
504
  return SUCCESS
575
505
 
576
506
 
577
507
  @exception_handler
578
- def set_attribute_rse(args):
508
+ def set_attribute_rse(args, client, logger, console, spinner):
579
509
  """
580
510
  %(prog)s set-attribute [options] <field1=value1 field2=value2 ...>
581
511
 
582
512
  Set RSE attributes.
583
513
 
584
514
  """
585
- client = get_client(args)
586
515
  client.add_rse_attribute(rse=args.rse, key=args.key, value=args.value)
587
516
  print('Added new RSE attribute for %s: %s-%s ' % (args.rse, args.key, args.value))
588
517
  return SUCCESS
589
518
 
590
519
 
591
520
  @exception_handler
592
- def get_attribute_rse(args):
521
+ def get_attribute_rse(args, client, logger, console, spinner):
593
522
  """
594
523
  %(prog)s get-attribute [options] <field1=value1 field2=value2 ...>
595
524
 
596
525
  Get RSE attributes.
597
526
 
598
527
  """
599
- client = get_client(args)
600
528
  attributes = client.list_rse_attributes(rse=args.rse)
601
- for k in attributes:
602
- print(k + ': ' + str(attributes[k]))
603
-
529
+ if cli_config == 'rich':
530
+ table_data = [(k, Text(str(v), style=CLITheme.BOOLEAN.get(str(v), 'default'))) for k, v in sorted(attributes.items())]
531
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
532
+ print_output(table, console=console, no_pager=args.no_pager)
533
+ else:
534
+ for k in attributes:
535
+ print(k + ': ' + str(attributes[k]))
604
536
  return SUCCESS
605
537
 
606
538
 
607
539
  @exception_handler
608
- def delete_attribute_rse(args):
540
+ def delete_attribute_rse(args, client, logger, console, spinner):
609
541
  """
610
542
  %(prog)s delete-attribute [options] <field1=value1 field2=value2 ...>
611
543
 
612
544
  Delete RSE attributes.
613
545
 
614
546
  """
615
- client = get_client(args)
616
547
  client.delete_rse_attribute(rse=args.rse, key=args.key)
617
548
  print('Deleted RSE attribute for %s: %s-%s ' % (args.rse, args.key, args.value))
618
549
  return SUCCESS
619
550
 
620
551
 
621
552
  @exception_handler
622
- def add_distance_rses(args):
553
+ def add_distance_rses(args, client, logger, console, spinner):
623
554
  """
624
555
  %(prog)s add-distance [options] SOURCE_RSE DEST_RSE
625
556
 
626
557
  Set the distance between two RSEs.
627
558
  """
628
- client = get_client(args)
629
559
  params = {'distance': args.distance}
630
560
  client.add_distance(args.source, args.destination, params)
631
561
  print('Set distance from %s to %s to %d' % (args.source, args.destination, args.distance))
@@ -633,29 +563,35 @@ def add_distance_rses(args):
633
563
 
634
564
 
635
565
  @exception_handler
636
- def get_distance_rses(args):
566
+ def get_distance_rses(args, client, logger, console, spinner):
637
567
  """
638
568
  %(prog)s get-distance SOURCE_RSE DEST_RSE
639
569
 
640
570
  Retrieve the existing distance information between two RSEs.
641
571
  """
642
- client = get_client(args)
643
572
  distance_info = client.get_distance(args.source, args.destination)
644
- if distance_info:
645
- print('Distance information from %s to %s: distance=%d' % (args.source, args.destination, distance_info[0]['distance']))
573
+ if cli_config == 'rich':
574
+ if distance_info:
575
+ table = generate_table([[args.source, args.destination, str(distance_info[0]['distance'])]], headers=['SOURCE', 'DESTINATION', 'DISTANCE'],
576
+ col_alignments=['left', 'left', 'right'])
577
+ print_output(table, console=console, no_pager=args.no_pager)
578
+ else:
579
+ print(f"No distance set from {args.source} to {args.destination}")
646
580
  else:
647
- print("No distance set from %s to %s" % (args.source, args.destination))
581
+ if distance_info:
582
+ print('Distance information from %s to %s: distance=%d' % (args.source, args.destination, distance_info[0]['distance']))
583
+ else:
584
+ print("No distance set from %s to %s" % (args.source, args.destination))
648
585
  return SUCCESS
649
586
 
650
587
 
651
588
  @exception_handler
652
- def update_distance_rses(args):
589
+ def update_distance_rses(args, client, logger, console, spinner):
653
590
  """
654
591
  %(prog)s update-distance [options] SOURCE_RSE DEST_RSE
655
592
 
656
593
  Update the existing distance entry between two RSEs.
657
594
  """
658
- client = get_client(args)
659
595
  params = {}
660
596
  if args.distance is not None:
661
597
  params['distance'] = args.distance
@@ -669,26 +605,24 @@ def update_distance_rses(args):
669
605
 
670
606
 
671
607
  @exception_handler
672
- def delete_distance_rses(args):
608
+ def delete_distance_rses(args, client, logger, console, spinner):
673
609
  """
674
610
  %(prog)s delete-distance [options] SOURCE_RSE DEST_RSE
675
611
 
676
612
  Update the existing distance entry between two RSEs.
677
613
  """
678
- client = get_client(args)
679
614
  client.delete_distance(args.source, args.destination)
680
615
  print('Deleted distance information from %s to %s.' % (args.source, args.destination))
681
616
  return SUCCESS
682
617
 
683
618
 
684
619
  @exception_handler
685
- def add_protocol_rse(args):
620
+ def add_protocol_rse(args, client, logger, console, spinner):
686
621
  """
687
622
  %(prog)s add-protocol-rse [options] <rse>
688
623
 
689
624
  Add a new protocol handler for an RSE
690
625
  """
691
- client = get_client(args)
692
626
  proto = {'hostname': args.hostname,
693
627
  'scheme': args.scheme,
694
628
  'port': args.port,
@@ -714,13 +648,12 @@ def add_protocol_rse(args):
714
648
 
715
649
 
716
650
  @exception_handler
717
- def del_protocol_rse(args):
651
+ def del_protocol_rse(args, client, logger, console, spinner):
718
652
  """
719
653
  %(prog)s delete-protocol-rse [options] <rse>
720
654
 
721
655
  Remove a protocol handler for a RSE
722
656
  """
723
- client = get_client(args)
724
657
  kwargs = {}
725
658
  if args.port:
726
659
  kwargs['port'] = args.port
@@ -730,53 +663,59 @@ def del_protocol_rse(args):
730
663
 
731
664
 
732
665
  @exception_handler
733
- def add_qos_policy(args):
666
+ def add_qos_policy(args, client, logger, console, spinner):
734
667
  """
735
668
  %(prog)s add-qos-policy <rse> <qos_policy>
736
669
 
737
670
  Add a QoS policy to an RSE.
738
671
  """
739
- client = get_client(args)
740
672
  client.add_qos_policy(args.rse, args.qos_policy)
741
673
  print('Added QoS policy to RSE %s: %s' % (args.rse, args.qos_policy))
742
674
  return SUCCESS
743
675
 
744
676
 
745
677
  @exception_handler
746
- def delete_qos_policy(args):
678
+ def delete_qos_policy(args, client, logger, console, spinner):
747
679
  """
748
680
  %(prog)s delete-qos-policy <rse> <qos_policy>
749
681
 
750
682
  Delete a QoS policy from an RSE.
751
683
  """
752
- client = get_client(args)
753
684
  client.delete_qos_policy(args.rse, args.qos_policy)
754
685
  print('Deleted QoS policy from RSE %s: %s' % (args.rse, args.qos_policy))
755
686
  return SUCCESS
756
687
 
757
688
 
758
689
  @exception_handler
759
- def list_qos_policies(args):
690
+ def list_qos_policies(args, client, logger, console, spinner):
760
691
  """
761
692
  %(prog)s list-qos-policies <rse>
762
693
 
763
694
  List all QoS policies of an RSE.
764
695
  """
765
- client = get_client(args)
696
+ if cli_config == 'rich':
697
+ spinner.update(status='Fetching QoS policies')
698
+ spinner.start()
699
+
766
700
  qos_policies = client.list_qos_policies(args.rse)
767
- for qos_policy in sorted(qos_policies):
768
- print(qos_policy)
701
+ if cli_config == 'rich':
702
+ qos_policies = [[qos_policy] for qos_policy in sorted(qos_policies)]
703
+ table = generate_table(qos_policies, headers=['QOS POLICY'], col_alignments=['left'])
704
+ spinner.stop()
705
+ print_output(table, console=console, no_pager=args.no_pager)
706
+ else:
707
+ for qos_policy in sorted(qos_policies):
708
+ print(qos_policy)
769
709
  return SUCCESS
770
710
 
771
711
 
772
712
  @exception_handler
773
- def set_limit_rse(args):
713
+ def set_limit_rse(args, client, logger, console, spinner):
774
714
  """
775
715
  %(prog)s set-limit <rse> <name> <value>
776
716
 
777
717
  Set the RSE limit given the rse name and the name and value of the limit
778
718
  """
779
- client = get_client(args)
780
719
  try:
781
720
  args.value = int(args.value)
782
721
  if client.set_rse_limits(args.rse, args.name, args.value):
@@ -788,13 +727,12 @@ def set_limit_rse(args):
788
727
 
789
728
 
790
729
  @exception_handler
791
- def delete_limit_rse(args):
730
+ def delete_limit_rse(args, client, logger, console, spinner):
792
731
  """
793
732
  %(prog)s delete-limit <rse> <name>
794
733
 
795
734
  Delete the RSE limit given the rse name and the name of the limit
796
735
  """
797
- client = get_client(args)
798
736
  limits = client.get_rse_limits(args.rse)
799
737
  if args.name not in limits.keys():
800
738
  logger.error('Limit %s not defined in RSE %s' % (args.name, args.rse))
@@ -806,46 +744,53 @@ def delete_limit_rse(args):
806
744
 
807
745
 
808
746
  @exception_handler
809
- def add_scope(args):
747
+ def add_scope(args, client, logger, console, spinner):
810
748
  """
811
749
  %(prog)s add [options] <field1=value1 field2=value2 ...>
812
750
 
813
751
  Add scope.
814
752
 
815
753
  """
816
- client = get_client(args)
817
754
  client.add_scope(account=args.account, scope=args.scope)
818
755
  print('Added new scope to account: %s-%s' % (args.scope, args.account))
819
756
  return SUCCESS
820
757
 
821
758
 
822
759
  @exception_handler
823
- def list_scopes(args):
760
+ def list_scopes(args, client, logger, console, spinner):
824
761
  """
825
762
  %(prog)s list [options] <field1=value1 field2=value2 ...>
826
763
 
827
764
  List scopes.
828
765
 
829
766
  """
830
- client = get_client(args)
767
+ if cli_config == 'rich':
768
+ spinner.update(status='Fetching scopes')
769
+ spinner.start()
770
+
831
771
  if args.account:
832
772
  scopes = client.list_scopes_for_account(args.account)
833
773
  else:
834
774
  scopes = client.list_scopes()
835
- for scope in scopes:
836
- if 'mock' not in scope:
837
- print(scope)
775
+ if cli_config == 'rich':
776
+ scopes = [[scope] for scope in sorted(scopes) if 'mock' not in scope]
777
+ table = generate_table(scopes, headers=['SCOPE'], col_alignments=['left'])
778
+ spinner.stop()
779
+ print_output(table, console=console, no_pager=args.no_pager)
780
+ else:
781
+ for scope in scopes:
782
+ if 'mock' not in scope:
783
+ print(scope)
838
784
  return SUCCESS
839
785
 
840
786
 
841
787
  @exception_handler
842
- def get_config(args):
788
+ def get_config(args, client, logger, console, spinner):
843
789
  """
844
790
  %(prog)s get [options] <field1=value1 field2=value2 ...>
845
791
 
846
792
  Get the configuration. Either everything, or matching the given section/option.
847
793
  """
848
- client = get_client(args)
849
794
  res = client.get_config(section=args.section, option=args.option)
850
795
  if not isinstance(res, dict):
851
796
  print('[%s]\n%s=%s' % (args.section, args.option, str(res)))
@@ -867,26 +812,24 @@ def get_config(args):
867
812
 
868
813
 
869
814
  @exception_handler
870
- def set_config_option(args):
815
+ def set_config_option(args, client, logger, console, spinner):
871
816
  """
872
817
  %(prog)s set [options] <field1=value1 field2=value2 ...>
873
818
 
874
819
  Set the configuration value for a matching section/option. Missing section/option will be created.
875
820
  """
876
- client = get_client(args)
877
821
  client.set_config_option(section=args.section, option=args.option, value=args.value)
878
822
  print('Set configuration: %s.%s=%s' % (args.section, args.option, args.value))
879
823
  return SUCCESS
880
824
 
881
825
 
882
826
  @exception_handler
883
- def delete_config_option(args):
827
+ def delete_config_option(args, client, logger, console, spinner):
884
828
  """
885
829
  %(prog)s delete [options] <field1=value1 field2=value2 ...>
886
830
 
887
831
  Delete a configuration option from a section
888
832
  """
889
- client = get_client(args)
890
833
  if client.delete_config_option(section=args.section, option=args.option):
891
834
  print('Deleted section \'%s\' option \'%s\'' % (args.section, args.option))
892
835
  else:
@@ -895,14 +838,13 @@ def delete_config_option(args):
895
838
 
896
839
 
897
840
  @exception_handler
898
- def add_subscription(args):
841
+ def add_subscription(args, client, logger, console, spinner):
899
842
  """
900
843
  %(prog)s add [options] name Filter replication_rules
901
844
 
902
845
  Add subscription.
903
846
 
904
847
  """
905
- client = get_client(args)
906
848
  if args.subs_account:
907
849
  account = args.subs_account
908
850
  elif args.issuer:
@@ -916,39 +858,85 @@ def add_subscription(args):
916
858
 
917
859
 
918
860
  @exception_handler
919
- def list_subscriptions(args):
861
+ def list_subscriptions(args, client, logger, console, spinner):
920
862
  """
921
863
  %(prog)s list [options] [name]
922
864
 
923
865
  List subscriptions.
924
866
 
925
867
  """
926
- client = get_client(args)
927
868
  if args.subs_account:
928
869
  account = args.subs_account
929
870
  elif args.issuer:
930
871
  account = args.issuer
931
872
  else:
932
873
  account = client.account
874
+
875
+ if cli_config == 'rich':
876
+ spinner.update(status='Fetching subscriptions')
877
+ spinner.start()
878
+ keyword_styles = {**CLITheme.SUBSCRIPTION_STATE, **CLITheme.BOOLEAN}
879
+
933
880
  subs = client.list_subscriptions(name=args.name, account=account)
934
881
  for sub in subs:
882
+ table_data = []
935
883
  if args.long:
936
- print('\n'.join('%s: %s' % (str(k), str(v)) for (k, v) in list(sub.items())))
937
- print()
884
+ if cli_config == 'rich':
885
+ for k, v in sorted(sub.items()):
886
+ if k == 'filter':
887
+ filter_tree = Tree('')
888
+ for filter, values in json.loads(sub['filter']).items():
889
+ values_str = ', '.join(values)
890
+ filter_tree.add(f'[{CLITheme.JSON_STR}]{filter}[/]: {values_str}')
891
+ table_data.append(['filter', filter_tree])
892
+ elif k == 'replication_rules':
893
+ rule_tree = Tree('')
894
+ for i, rule in enumerate(json.loads(sub['replication_rules'])):
895
+ branch = rule_tree.add(Text('rule:', style='default'))
896
+ for k, v in rule.items():
897
+ branch.add(f'[{CLITheme.JSON_STR}]{k}[/]: {v}')
898
+ table_data.append(['replication_rules', rule_tree])
899
+ else:
900
+ table_data.append([str(k), Text(str(v), style=keyword_styles.get(str(v), 'default'))])
901
+ else:
902
+ print('\n'.join('%s: %s' % (str(k), str(v)) for (k, v) in list(sub.items())))
903
+ print()
938
904
  else:
939
- print("%s: %s %s\n priority: %s\n filter: %s\n rules: %s\n comments: %s" % (sub['account'], sub['name'], sub['state'], sub['policyid'], sub['filter'], sub['replication_rules'], sub.get('comments', '')))
905
+ if cli_config == 'rich':
906
+ table_data.append(['account', sub['account']])
907
+ table_data.append(['comments', sub.get('comments', '')])
908
+ filter_tree = Tree('')
909
+ for filter, values in json.loads(sub['filter']).items():
910
+ values_str = ', '.join(values)
911
+ filter_tree.add(f'[green]{filter}[/]: {values_str}')
912
+ table_data.append(['filter', filter_tree])
913
+ table_data.append(['name', sub['name']])
914
+ table_data.append(['policyid', str(sub['policyid'])])
915
+ rule_tree = Tree('')
916
+ for i, rule in enumerate(json.loads(sub['replication_rules'])):
917
+ branch = rule_tree.add(Text('rule:', style='default'))
918
+ for k, v in rule.items():
919
+ branch.add(f'[{CLITheme.JSON_STR}]{k}[/]: {v}')
920
+ table_data.append(['replication_rules', rule_tree])
921
+ table_data.append(['state', Text(str(sub['state']), keyword_styles.get(str(sub['state']), 'default'))])
922
+ else:
923
+ print("%s: %s %s\n priority: %s\n filter: %s\n rules: %s\n comments: %s" % (sub['account'], sub['name'], sub['state'], sub['policyid'], sub['filter'], sub['replication_rules'], sub.get('comments', '')))
924
+
925
+ if cli_config == 'rich':
926
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
927
+ spinner.stop()
928
+ print_output(table, console=console, no_pager=args.no_pager)
940
929
  return SUCCESS
941
930
 
942
931
 
943
932
  @exception_handler
944
- def update_subscription(args):
933
+ def update_subscription(args, client, logger, console, spinner):
945
934
  """
946
935
  %(prog)s update [options] name filter replication_rules
947
936
 
948
937
  Update a subscription.
949
938
 
950
939
  """
951
- client = get_client(args)
952
940
  if args.subs_account:
953
941
  account = args.subs_account
954
942
  elif args.issuer:
@@ -961,14 +949,13 @@ def update_subscription(args):
961
949
 
962
950
 
963
951
  @exception_handler
964
- def reevaluate_did_for_subscription(args):
952
+ def reevaluate_did_for_subscription(args, client, logger, console, spinner):
965
953
  """
966
954
  %(prog)s reevaulate [options] dids
967
955
 
968
956
  Reevaluate a list of DIDs against all active subscriptions.
969
957
 
970
958
  """
971
- client = get_client(args)
972
959
  for did in args.dids.split(','):
973
960
  scope, name = get_scope(did, client)
974
961
  client.set_metadata(scope, name, 'is_new', True)
@@ -976,56 +963,57 @@ def reevaluate_did_for_subscription(args):
976
963
 
977
964
 
978
965
  @exception_handler
979
- def list_account_attributes(args):
966
+ def list_account_attributes(args, client, logger, console, spinner):
980
967
  """
981
968
  %(prog)s show [options] <field1=value1 field2=value2 ...>
982
969
 
983
970
  List the attributes for an account.
984
971
 
985
972
  """
986
- client = get_client(args)
987
973
  account = args.account or client.account
988
974
  attributes = next(client.list_account_attributes(account))
989
- table = []
975
+ table_data = []
990
976
  for attr in attributes:
991
- table.append([attr['key'], attr['value']])
992
- print(tabulate(table, tablefmt=tablefmt, headers=['Key', 'Value']))
977
+ table_data.append([attr['key'], attr['value']])
978
+
979
+ if cli_config == 'rich':
980
+ table = generate_table(table_data, headers=['Key', 'Value'], col_alignments=['left', 'left'])
981
+ print_output(table, console=console, no_pager=args.no_pager)
982
+ else:
983
+ print(tabulate(table_data, tablefmt=tablefmt, headers=['Key', 'Value']))
993
984
  return SUCCESS
994
985
 
995
986
 
996
987
  @exception_handler
997
- def add_account_attribute(args):
988
+ def add_account_attribute(args, client, logger, console, spinner):
998
989
  """
999
990
  %(prog)s show [options] <field1=value1 field2=value2 ...>
1000
991
 
1001
992
  Add attribute for an account.
1002
993
 
1003
994
  """
1004
- client = get_client(args)
1005
995
  client.add_account_attribute(account=args.account, key=args.key, value=args.value)
1006
996
  return SUCCESS
1007
997
 
1008
998
 
1009
999
  @exception_handler
1010
- def delete_account_attribute(args):
1000
+ def delete_account_attribute(args, client, logger, console, spinner):
1011
1001
  """
1012
1002
  %(prog)s show [options] <field1=value1 field2=value2 ...>
1013
1003
 
1014
1004
  Delete attribute for an account.
1015
1005
 
1016
1006
  """
1017
- client = get_client(args)
1018
1007
  client.delete_account_attribute(account=args.account, key=args.key)
1019
1008
  return SUCCESS
1020
1009
 
1021
1010
 
1022
1011
  @exception_handler
1023
- def quarantine_replicas(args):
1012
+ def quarantine_replicas(args, client, logger, console, spinner):
1024
1013
  """
1025
1014
  %(prog)s quarantine --rse <rse> (--paths <file with replica paths>|<path> ...)
1026
1015
  Quarantine replicas
1027
1016
  """
1028
- client = get_client(args)
1029
1017
  chunk = []
1030
1018
 
1031
1019
  # send requests in chunks
@@ -1049,7 +1037,7 @@ def quarantine_replicas(args):
1049
1037
  return SUCCESS
1050
1038
 
1051
1039
 
1052
- def __declare_bad_file_replicas_by_lfns(args: object) -> object:
1040
+ def __declare_bad_file_replicas_by_lfns(args: object, client) -> object:
1053
1041
  """
1054
1042
  Declare a list of bad replicas using RSE name, scope and list of LFNs.
1055
1043
  """
@@ -1059,7 +1047,6 @@ def __declare_bad_file_replicas_by_lfns(args: object) -> object:
1059
1047
  reason = args.reason
1060
1048
  scope = args.scope
1061
1049
  rse = args.rse
1062
- client = get_client(args)
1063
1050
  replicas = []
1064
1051
 
1065
1052
  # send requests in chunks
@@ -1084,7 +1071,7 @@ def __declare_bad_file_replicas_by_lfns(args: object) -> object:
1084
1071
 
1085
1072
 
1086
1073
  @exception_handler
1087
- def declare_bad_file_replicas(args):
1074
+ def declare_bad_file_replicas(args, client, logger, console, spinner):
1088
1075
  """
1089
1076
 
1090
1077
  Declare replicas as bad.
@@ -1092,9 +1079,7 @@ def declare_bad_file_replicas(args):
1092
1079
  """
1093
1080
 
1094
1081
  if args.lfns:
1095
- return __declare_bad_file_replicas_by_lfns(args)
1096
-
1097
- client = get_client(args)
1082
+ return __declare_bad_file_replicas_by_lfns(args, client)
1098
1083
 
1099
1084
  if args.inputfile:
1100
1085
  with open(args.inputfile) as infile:
@@ -1175,14 +1160,13 @@ def declare_bad_file_replicas(args):
1175
1160
 
1176
1161
 
1177
1162
  @exception_handler
1178
- def declare_temporary_unavailable_replicas(args):
1163
+ def declare_temporary_unavailable_replicas(args, client, logger, console, spinner):
1179
1164
  """
1180
1165
  %(prog)s show [options] <field1=value1 field2=value2 ...>
1181
1166
 
1182
1167
  Declare a list of temporary unavailable replicas.
1183
1168
 
1184
1169
  """
1185
- client = get_client(args)
1186
1170
  bad_files = []
1187
1171
  if args.inputfile:
1188
1172
  with open(args.inputfile) as infile:
@@ -1218,14 +1202,13 @@ def declare_temporary_unavailable_replicas(args):
1218
1202
 
1219
1203
 
1220
1204
  @exception_handler
1221
- def list_pfns(args):
1205
+ def list_pfns(args, client, logger, console, spinner):
1222
1206
  """
1223
1207
  %(prog)s list [options] <field1=value1 field2=value2 ...>
1224
1208
 
1225
1209
  List the possible PFN for a file at a site.
1226
1210
 
1227
1211
  """
1228
- client = get_client(args)
1229
1212
  dids = args.dids.split(',')
1230
1213
  rse = args.rse
1231
1214
  protocol = args.protocol
@@ -1271,71 +1254,104 @@ def list_pfns(args):
1271
1254
 
1272
1255
 
1273
1256
  @exception_handler
1274
- def import_data(args):
1257
+ def import_data(args, client, logger, console, spinner):
1275
1258
  """
1276
1259
  %(prog)s list [options] <field1=value1 field2=value2 ...>
1277
1260
 
1278
1261
  Import data from JSON file to Rucio.
1279
1262
 
1280
1263
  """
1281
- client = get_client(args)
1282
1264
  import_file_path = args.file_path
1283
1265
  data = None
1284
- print('Start reading file.')
1266
+ if cli_config == 'rich':
1267
+ spinner.update(status='Reading file')
1268
+ spinner.start()
1269
+
1285
1270
  try:
1286
1271
  with open(import_file_path) as import_file:
1287
1272
  data_string = import_file.read()
1288
1273
  data = parse_response(data_string)
1289
1274
  except ValueError as error:
1290
- print('There was problem with decoding your file.')
1291
- print(error)
1275
+ if cli_config == 'rich':
1276
+ spinner.stop()
1277
+ print_output(f'{CLITheme.FAILURE_ICON} There was problem with decoding your file.', console=console, no_pager=True)
1278
+ logger.error(error)
1279
+ else:
1280
+ print('There was problem with decoding your file.')
1281
+ print(error)
1292
1282
  return FAILURE
1293
1283
  except OSError as error:
1294
- print('There was a problem with reading your file.')
1295
- print(error)
1284
+ if cli_config == 'rich':
1285
+ spinner.stop()
1286
+ print_output(f'{CLITheme.FAILURE_ICON} There was a problem with reading your file.', console=console, no_pager=True)
1287
+ logger.error(error)
1288
+ else:
1289
+ print('There was a problem with reading your file.')
1290
+ print(error)
1296
1291
  return FAILURE
1297
1292
 
1298
1293
  if data:
1299
1294
  client.import_data(data)
1300
- print('Data successfully imported.')
1295
+ if cli_config == 'rich':
1296
+ spinner.stop()
1297
+ print_output(f'{CLITheme.SUCCESS_ICON} Data successfully imported.', console=console, no_pager=True)
1298
+ else:
1299
+ print('Data successfully imported.')
1301
1300
  return SUCCESS
1302
1301
  else:
1303
- print('Nothing to import.')
1302
+ if cli_config == 'rich':
1303
+ spinner.stop()
1304
+ print_output('Nothing to import.', console=console, no_pager=True)
1305
+ else:
1306
+ print('Nothing to import.')
1304
1307
  return FAILURE
1305
1308
 
1306
1309
 
1307
1310
  @exception_handler
1308
- def export_data(args):
1311
+ def export_data(args, client, logger, console, spinner):
1309
1312
  """
1310
1313
  %(prog)s list [options] <field1=value1 field2=value2 ...>
1311
1314
 
1312
1315
  Export data from Rucio to JSON file.
1313
1316
 
1314
1317
  """
1315
- client = get_client(args)
1316
1318
  destination_file_path = args.file_path
1317
- print('Start querying data.')
1319
+ if cli_config == 'rich':
1320
+ spinner.update(status='Querying data')
1321
+ spinner.start()
1322
+ else:
1323
+ print('Start querying data.')
1324
+
1318
1325
  data = client.export_data()
1319
1326
  try:
1320
1327
  with open(destination_file_path, 'w+') as destination_file:
1321
1328
  destination_file.write(render_json(**data))
1322
- print('File successfully written.')
1323
- print('Data successfully exported to %s' % args.file_path)
1329
+ if cli_config != 'rich':
1330
+ print('File successfully written.')
1331
+ if cli_config == 'rich':
1332
+ spinner.stop()
1333
+ print_output(f'{CLITheme.SUCCESS_ICON} Data successfully exported to {args.file_path}', console=console, no_pager=True)
1334
+ else:
1335
+ print('Data successfully exported to %s' % args.file_path)
1324
1336
  return SUCCESS
1325
1337
  except OSError as error:
1326
- print('There was a problem with reading your file.')
1327
- print(error)
1338
+ if cli_config == 'rich':
1339
+ spinner.stop()
1340
+ print_output(f'{CLITheme.FAILURE_ICON} There was a problem with reading your file.', console=console, no_pager=True)
1341
+ logger.error(error)
1342
+ else:
1343
+ print('There was a problem with reading your file.')
1344
+ print(error)
1328
1345
  return FAILURE
1329
1346
 
1330
1347
 
1331
1348
  @exception_handler
1332
- def set_tombstone(args):
1349
+ def set_tombstone(args, client, logger, console, spinner):
1333
1350
  """
1334
1351
  %(prog)s list [options] <field1=value1 field2=value2 ...>
1335
1352
 
1336
1353
  Set a tombstone on a list of replicas.
1337
1354
  """
1338
- client = get_client(args)
1339
1355
  dids = args.dids
1340
1356
  rse = args.rse
1341
1357
  dids = [dids] if ',' not in dids else dids.split(',')
@@ -1367,7 +1383,9 @@ def get_parser():
1367
1383
  oparser.add_argument('-a', '--account', dest="issuer", metavar="ACCOUNT", help="Rucio account to use")
1368
1384
  oparser.add_argument('-S', '--auth-strategy', dest="auth_strategy", default=None, help="Authentication strategy (userpass, x509, ssh ...)")
1369
1385
  oparser.add_argument('-T', '--timeout', dest="timeout", type=float, default=None, help="Set all timeout values to SECONDS")
1386
+ oparser.add_argument('--user-agent', '-U', dest="user_agent", default='rucio-clients', action='store', help="Rucio User Agent")
1370
1387
  oparser.add_argument('--vo', dest="vo", metavar="VO", default=None, help="VO to authenticate at. Only used in multi-VO mode.")
1388
+ oparser.add_argument("--no-pager", dest="no_pager", default=False, action='store_true', help=argparse.SUPPRESS)
1371
1389
 
1372
1390
  # Options for the userpass auth_strategy
1373
1391
  oparser.add_argument('-u', '--user', dest='username', default=None, help='username')
@@ -1391,6 +1409,7 @@ def get_parser():
1391
1409
 
1392
1410
  # Options for the x509 auth_strategy
1393
1411
  oparser.add_argument('--certificate', dest='certificate', default=None, help='Client certificate file')
1412
+ oparser.add_argument('--client-key', dest='client_key', default=None, help='Client key for x509 Authentication.')
1394
1413
  oparser.add_argument('--ca-certificate', dest='ca_certificate', default=None, help='CA certificate to verify peer against (SSL)')
1395
1414
 
1396
1415
  # The import export subparser
@@ -1440,6 +1459,7 @@ def get_parser():
1440
1459
  list_account_parser.add_argument('--type', dest='account_type', action='store', help='Account Type (USER, GROUP, SERVICE)')
1441
1460
  list_account_parser.add_argument('--id', dest='identity', action='store', help='Identity (e.g. DN)')
1442
1461
  list_account_parser.add_argument('--filters', dest='filters', action='store', help='Filter arguments in form `key=value,another_key=next_value`')
1462
+ list_account_parser.add_argument("--csv", action='store_true', help='List result as a csv')
1443
1463
  list_account_parser.set_defaults(which='list_accounts')
1444
1464
 
1445
1465
  # The list_account_attributes command
@@ -1508,8 +1528,8 @@ def get_parser():
1508
1528
  '\n')
1509
1529
  add_account_parser.set_defaults(which='add_account')
1510
1530
  add_account_parser.add_argument('account', action='store', help='Account name')
1511
- add_account_parser.add_argument('--type', dest='accounttype', default='USER', help='Account Type (USER, GROUP, SERVICE)')
1512
- add_account_parser.add_argument('--email', dest='accountemail', action='store',
1531
+ add_account_parser.add_argument('--type', dest='account_type', default='USER', help='Account Type (USER, GROUP, SERVICE)')
1532
+ add_account_parser.add_argument('--email', dest='email', action='store',
1513
1533
  help='Email address associated with the account')
1514
1534
 
1515
1535
  # The disable_account command
@@ -1524,7 +1544,7 @@ def get_parser():
1524
1544
  ' Deleted account: jdoe-sister\n'
1525
1545
  '\n')
1526
1546
  delete_account_parser.set_defaults(which='delete_account')
1527
- delete_account_parser.add_argument('acnt', action='store', help='Account name')
1547
+ delete_account_parser.add_argument('account', action='store', help='Account name')
1528
1548
 
1529
1549
  # The info_account command
1530
1550
  info_account_parser = account_subparser.add_parser('info',
@@ -1738,6 +1758,7 @@ def get_parser():
1738
1758
  '\n'
1739
1759
  ' $ rucio list-rses --rses \"tier=2&type=DATADISK\"\n'
1740
1760
  '\n')
1761
+ list_rse_parser.add_argument("--csv", action='store_true', help='Output a list of RSEs as a csv')
1741
1762
  list_rse_parser.set_defaults(which='list_rses')
1742
1763
 
1743
1764
  # The add_rse command
@@ -2368,7 +2389,7 @@ def get_parser():
2368
2389
  return oparser
2369
2390
 
2370
2391
 
2371
- if __name__ == '__main__':
2392
+ def main():
2372
2393
  oparser = get_parser()
2373
2394
  if EXTRA_MODULES['argcomplete']:
2374
2395
  argcomplete.autocomplete(oparser)
@@ -2382,66 +2403,98 @@ if __name__ == '__main__':
2382
2403
  if not hasattr(args, 'which'):
2383
2404
  oparser.print_help()
2384
2405
  sys.exit(FAILURE)
2406
+ else:
2407
+ commands = {'add_account': add_account,
2408
+ 'list_accounts': list_accounts,
2409
+ 'list_account_attributes': list_account_attributes,
2410
+ 'add_account_attribute': add_account_attribute,
2411
+ 'delete_account_attribute': delete_account_attribute,
2412
+ 'delete_account': delete_account,
2413
+ 'info_account': info_account,
2414
+ 'ban_account': ban_account,
2415
+ 'unban_account': unban_account,
2416
+ 'update_account': update_account,
2417
+ 'get_limits': get_limits,
2418
+ 'set_limits': set_limits,
2419
+ 'delete_limits': delete_limits,
2420
+ 'list_identities': list_identities,
2421
+ 'identity_add': identity_add,
2422
+ 'identity_delete': identity_delete,
2423
+ 'add_rse': add_rse,
2424
+ 'update_rse': update_rse,
2425
+ 'set_attribute_rse': set_attribute_rse,
2426
+ 'get_attribute_rse': get_attribute_rse,
2427
+ 'delete_attribute_rse': delete_attribute_rse,
2428
+ 'add_distance_rses': add_distance_rses,
2429
+ 'update_distance_rses': update_distance_rses,
2430
+ 'get_distance_rses': get_distance_rses,
2431
+ 'delete_distance_rses': delete_distance_rses,
2432
+ 'add_protocol_rse': add_protocol_rse,
2433
+ 'del_protocol_rse': del_protocol_rse,
2434
+ 'list_rses': list_rses,
2435
+ 'disable_rse': disable_rse,
2436
+ 'add_qos_policy': add_qos_policy,
2437
+ 'delete_qos_policy': delete_qos_policy,
2438
+ 'list_qos_policies': list_qos_policies,
2439
+ 'add_scope': add_scope,
2440
+ 'list_scopes': list_scopes,
2441
+ 'info_rse': info_rse,
2442
+ 'get_config': get_config,
2443
+ 'set_config_option': set_config_option,
2444
+ 'delete_config_option': delete_config_option,
2445
+ 'add_subscription': add_subscription,
2446
+ 'list_subscriptions': list_subscriptions,
2447
+ 'update_subscription': update_subscription,
2448
+ 'reevaluate_did_for_subscription': reevaluate_did_for_subscription,
2449
+ 'declare_bad_file_replicas': declare_bad_file_replicas,
2450
+ 'quarantine_replicas': quarantine_replicas,
2451
+ 'declare_temporary_unavailable_replicas': declare_temporary_unavailable_replicas,
2452
+ 'list_pfns': list_pfns,
2453
+ 'import': import_data,
2454
+ 'export': export_data,
2455
+ 'set_tombstone': set_tombstone,
2456
+ 'set_limit_rse': set_limit_rse,
2457
+ 'delete_limit_rse': delete_limit_rse,
2458
+ }
2459
+
2460
+ pager = get_pager()
2461
+ console = Console(theme=Theme(CLITheme.LOG_THEMES), soft_wrap=True)
2462
+ console.width = max(MIN_CONSOLE_WIDTH, console.width)
2463
+
2464
+ cli_config = get_cli_config()
2465
+ spinner = Status('Initializing spinner', spinner=CLITheme.SPINNER, spinner_style=CLITheme.SPINNER_STYLE, console=console)
2466
+
2467
+ if cli_config == 'rich':
2468
+ install(console=console, word_wrap=True, width=min(console.width, MAX_TRACEBACK_WIDTH)) # Make rich exception tracebacks the default.
2469
+ logger = setup_rich_logger(module_name=__name__, logger_name='user', verbose=args.verbose, console=console)
2470
+ else:
2471
+ logger = setup_logger(module_name=__name__, logger_name='user', verbose=args.verbose)
2472
+
2473
+ signal.signal(signal.SIGINT, lambda sig, frame: signal_handler(sig, frame, logger))
2474
+ setup_gfal2_logger()
2475
+ start_time = time.time()
2476
+ command = commands.get(args.which)
2477
+ client = get_client(args, logger)
2478
+ result = command(args, client, logger, console, spinner) # type: ignore
2479
+
2480
+ end_time = time.time()
2481
+ if cli_config == 'rich':
2482
+ spinner.stop()
2483
+ if console.is_terminal and not args.no_pager:
2484
+ command_output = console.end_capture()
2485
+ if command_output == '' and args.verbose:
2486
+ print("Completed in %-0.4f sec." % (end_time - start_time))
2487
+ else:
2488
+ if args.verbose:
2489
+ command_output += "Completed in %-0.4f sec." % (end_time - start_time)
2490
+ # Ignore SIGINT during pager execution.
2491
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
2492
+ pager(command_output)
2493
+ else:
2494
+ if args.verbose:
2495
+ print("Completed in %-0.4f sec." % (end_time - start_time))
2496
+ sys.exit(result)
2385
2497
 
2386
- commands = {'add_account': add_account,
2387
- 'list_accounts': list_accounts,
2388
- 'list_account_attributes': list_account_attributes,
2389
- 'add_account_attribute': add_account_attribute,
2390
- 'delete_account_attribute': delete_account_attribute,
2391
- 'delete_account': delete_account,
2392
- 'info_account': info_account,
2393
- 'ban_account': ban_account,
2394
- 'unban_account': unban_account,
2395
- 'update_account': update_account,
2396
- 'get_limits': get_limits,
2397
- 'set_limits': set_limits,
2398
- 'delete_limits': delete_limits,
2399
- 'list_identities': list_identities,
2400
- 'identity_add': identity_add,
2401
- 'identity_delete': identity_delete,
2402
- 'add_rse': add_rse,
2403
- 'update_rse': update_rse,
2404
- 'set_attribute_rse': set_attribute_rse,
2405
- 'get_attribute_rse': get_attribute_rse,
2406
- 'delete_attribute_rse': delete_attribute_rse,
2407
- 'add_distance_rses': add_distance_rses,
2408
- 'update_distance_rses': update_distance_rses,
2409
- 'get_distance_rses': get_distance_rses,
2410
- 'delete_distance_rses': delete_distance_rses,
2411
- 'add_protocol_rse': add_protocol_rse,
2412
- 'del_protocol_rse': del_protocol_rse,
2413
- 'list_rses': list_rses,
2414
- 'disable_rse': disable_rse,
2415
- 'add_qos_policy': add_qos_policy,
2416
- 'delete_qos_policy': delete_qos_policy,
2417
- 'list_qos_policies': list_qos_policies,
2418
- 'add_scope': add_scope,
2419
- 'list_scopes': list_scopes,
2420
- 'info_rse': info_rse,
2421
- 'get_config': get_config,
2422
- 'set_config_option': set_config_option,
2423
- 'delete_config_option': delete_config_option,
2424
- 'add_subscription': add_subscription,
2425
- 'list_subscriptions': list_subscriptions,
2426
- 'update_subscription': update_subscription,
2427
- 'reevaluate_did_for_subscription': reevaluate_did_for_subscription,
2428
- 'declare_bad_file_replicas': declare_bad_file_replicas,
2429
- 'quarantine_replicas': quarantine_replicas,
2430
- 'declare_temporary_unavailable_replicas': declare_temporary_unavailable_replicas,
2431
- 'list_pfns': list_pfns,
2432
- 'import': import_data,
2433
- 'export': export_data,
2434
- 'set_tombstone': set_tombstone,
2435
- 'set_limit_rse': set_limit_rse,
2436
- 'delete_limit_rse': delete_limit_rse,
2437
- }
2438
2498
 
2439
- if args.verbose:
2440
- logger.setLevel(logging.DEBUG)
2441
- start_time = time.time()
2442
- command = commands.get(args.which)
2443
- result = command(args)
2444
- end_time = time.time()
2445
- if args.verbose:
2446
- print("Completed in %-0.4f sec." % (end_time - start_time))
2447
- sys.exit(result)
2499
+ if __name__ == '__main__':
2500
+ main()