rucio-clients 37.3.0__py3-none-any.whl → 37.4.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 (38) hide show
  1. rucio/cli/rule.py +1 -1
  2. rucio/client/accountclient.py +205 -60
  3. rucio/client/accountlimitclient.py +84 -25
  4. rucio/client/baseclient.py +85 -48
  5. rucio/client/client.py +49 -41
  6. rucio/client/configclient.py +36 -13
  7. rucio/client/credentialclient.py +16 -6
  8. rucio/client/didclient.py +321 -133
  9. rucio/client/diracclient.py +13 -6
  10. rucio/client/downloadclient.py +435 -165
  11. rucio/client/exportclient.py +8 -2
  12. rucio/client/fileclient.py +10 -3
  13. rucio/client/importclient.py +4 -1
  14. rucio/client/lifetimeclient.py +48 -31
  15. rucio/client/lockclient.py +22 -7
  16. rucio/client/metaconventionsclient.py +59 -21
  17. rucio/client/pingclient.py +3 -1
  18. rucio/client/replicaclient.py +213 -96
  19. rucio/client/requestclient.py +123 -16
  20. rucio/client/rseclient.py +385 -160
  21. rucio/client/ruleclient.py +147 -51
  22. rucio/client/scopeclient.py +35 -10
  23. rucio/client/subscriptionclient.py +60 -27
  24. rucio/client/touchclient.py +16 -7
  25. rucio/vcsversion.py +3 -3
  26. {rucio_clients-37.3.0.data → rucio_clients-37.4.0.data}/data/etc/rucio.cfg.template +0 -1
  27. {rucio_clients-37.3.0.dist-info → rucio_clients-37.4.0.dist-info}/METADATA +1 -1
  28. {rucio_clients-37.3.0.dist-info → rucio_clients-37.4.0.dist-info}/RECORD +38 -38
  29. {rucio_clients-37.3.0.dist-info → rucio_clients-37.4.0.dist-info}/WHEEL +1 -1
  30. {rucio_clients-37.3.0.data → rucio_clients-37.4.0.data}/data/etc/rse-accounts.cfg.template +0 -0
  31. {rucio_clients-37.3.0.data → rucio_clients-37.4.0.data}/data/etc/rucio.cfg.atlas.client.template +0 -0
  32. {rucio_clients-37.3.0.data → rucio_clients-37.4.0.data}/data/requirements.client.txt +0 -0
  33. {rucio_clients-37.3.0.data → rucio_clients-37.4.0.data}/data/rucio_client/merge_rucio_configs.py +0 -0
  34. {rucio_clients-37.3.0.data → rucio_clients-37.4.0.data}/scripts/rucio +0 -0
  35. {rucio_clients-37.3.0.data → rucio_clients-37.4.0.data}/scripts/rucio-admin +0 -0
  36. {rucio_clients-37.3.0.dist-info → rucio_clients-37.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
  37. {rucio_clients-37.3.0.dist-info → rucio_clients-37.4.0.dist-info}/licenses/LICENSE +0 -0
  38. {rucio_clients-37.3.0.dist-info → rucio_clients-37.4.0.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,7 @@ import secrets
23
23
  import sys
24
24
  import time
25
25
  from configparser import NoOptionError, NoSectionError
26
- from os import environ, fdopen, geteuid, makedirs, path
26
+ from os import environ, fdopen, geteuid, makedirs
27
27
  from shutil import move
28
28
  from tempfile import mkstemp
29
29
  from typing import TYPE_CHECKING, Any, Optional
@@ -37,7 +37,7 @@ from requests.status_codes import codes
37
37
 
38
38
  from rucio import version
39
39
  from rucio.common import exception
40
- from rucio.common.config import config_get, config_get_bool, config_get_int
40
+ from rucio.common.config import config_get, config_get_bool, config_get_int, config_has_section
41
41
  from rucio.common.exception import CannotAuthenticate, ClientProtocolNotFound, ClientProtocolNotSupported, ConfigNotFound, MissingClientParameter, MissingModuleException, NoAuthInformation, ServerConnectionException
42
42
  from rucio.common.extra import import_extras
43
43
  from rucio.common.utils import build_url, get_tmp_dir, my_key_generator, parse_response, setup_logger, ssh_sign
@@ -73,6 +73,14 @@ def choice(hosts):
73
73
  return secrets.choice(hosts)
74
74
 
75
75
 
76
+ def _expand_path(path: str) -> str:
77
+ """Fully expand path, including ~ and env variables"""
78
+ path = path.strip()
79
+ if path == '':
80
+ return ''
81
+ return os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
82
+
83
+
76
84
  class BaseClient:
77
85
 
78
86
  """Main client class for accessing Rucio resources. Handles the authentication."""
@@ -95,15 +103,29 @@ class BaseClient:
95
103
  logger: 'Logger' = LOG) -> None:
96
104
  """
97
105
  Constructor of the BaseClient.
98
- :param rucio_host: The address of the rucio server, if None it is read from the config file.
99
- :param auth_host: The address of the rucio authentication server, if None it is read from the config file.
100
- :param account: The account to authenticate to rucio.
101
- :param ca_cert: The path to the rucio server certificate.
102
- :param auth_type: The type of authentication (e.g.: 'userpass', 'kerberos' ...)
103
- :param creds: Dictionary with credentials needed for authentication.
104
- :param user_agent: Indicates the client.
105
- :param vo: The VO to authenticate into.
106
- :param logger: Logger object to use. If None, use the default LOG created by the module
106
+
107
+ Parameters
108
+ ----------
109
+ rucio_host :
110
+ The address of the rucio server, if None it is read from the config file.
111
+ auth_host :
112
+ The address of the rucio authentication server, if None it is read from the config file.
113
+ account :
114
+ The account to authenticate to rucio.
115
+ ca_cert :
116
+ The path to the rucio server certificate.
117
+ auth_type :
118
+ The type of authentication (e.g.: 'userpass', 'kerberos' ...)
119
+ creds :
120
+ Dictionary with credentials needed for authentication.
121
+ timeout :
122
+ Timeout for requests.
123
+ user_agent :
124
+ Indicates the client.
125
+ vo :
126
+ The VO to authenticate into.
127
+ logger :
128
+ Logger object to use. If None, use the default LOG created by the module.
107
129
  """
108
130
 
109
131
  self.logger = logger
@@ -173,7 +195,7 @@ class BaseClient:
173
195
  if self.ca_cert is None:
174
196
  self.logger.debug('HTTPS is required, but no ca_cert was passed and X509_CERT_DIR is not defined. Trying to get it from the config file.')
175
197
  try:
176
- self.ca_cert = path.expandvars(config_get('client', 'ca_cert'))
198
+ self.ca_cert = _expand_path(config_get('client', 'ca_cert'))
177
199
  except (NoOptionError, NoSectionError):
178
200
  self.logger.debug('No ca_cert found in configuration. Falling back to Mozilla default CA bundle (certifi).')
179
201
  self.ca_cert = True
@@ -289,46 +311,61 @@ class BaseClient:
289
311
  creds['client_cert'] = environ["RUCIO_CLIENT_CERT"]
290
312
  else:
291
313
  creds['client_cert'] = config_get('client', 'client_cert')
292
- creds['client_cert'] = path.abspath(path.expanduser(path.expandvars(creds['client_cert'])))
293
- if not path.exists(creds['client_cert']):
294
- raise MissingClientParameter('X.509 client certificate not found: %s' % creds['client_cert'])
314
+
315
+ creds['client_cert'] = _expand_path(creds['client_cert'])
316
+
317
+ if not os.path.exists(creds['client_cert']):
318
+ raise MissingClientParameter('X.509 client certificate not found: %r' % creds['client_cert'])
295
319
 
296
320
  if 'client_key' not in creds or creds['client_key'] is None:
297
321
  if "RUCIO_CLIENT_KEY" in environ:
298
322
  creds['client_key'] = environ["RUCIO_CLIENT_KEY"]
299
323
  else:
300
324
  creds['client_key'] = config_get('client', 'client_key')
301
- creds['client_key'] = path.abspath(path.expanduser(path.expandvars(creds['client_key'])))
302
- if not path.exists(creds['client_key']):
303
- raise MissingClientParameter('X.509 client key not found: %s' % creds['client_key'])
304
- else:
305
- perms = oct(os.stat(creds['client_key']).st_mode)[-3:]
306
- if perms not in ['400', '600']:
307
- raise CannotAuthenticate('X.509 authentication selected, but private key (%s) permissions are liberal (required: 400 or 600, found: %s)' % (creds['client_key'], perms))
325
+
326
+ creds['client_key'] = _expand_path(creds['client_key'])
327
+ if not os.path.exists(creds['client_key']):
328
+ raise MissingClientParameter('X.509 client key not found: %r' % creds['client_key'])
329
+
330
+ perms = oct(os.stat(creds['client_key']).st_mode)[-3:]
331
+ if perms not in ['400', '600']:
332
+ raise CannotAuthenticate('X.509 authentication selected, but private key (%s) permissions are liberal (required: 400 or 600, found: %s)' % (creds['client_key'], perms))
308
333
 
309
334
  elif self.auth_type == 'x509_proxy':
335
+ # rucio specific configuration takes precedence over GSI logic
336
+ # environment variables take precedence over config values
337
+ # So we check in order:
338
+ # RUCIO_CLIENT_PROXY env variable
339
+ # client.client_x509_proxy rucio cfg variable
340
+ # X509_USER_PROXY env variable
341
+ # /tmp/x509up_u`id -u` if exists
342
+
343
+ gsi_proxy_path = '/tmp/x509up_u%d' % geteuid()
310
344
  if 'client_proxy' not in creds or creds['client_proxy'] is None:
311
- try:
312
- creds['client_proxy'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'client_x509_proxy'))))
313
- except NoOptionError:
314
- # Recreate the classic GSI logic for locating the proxy:
315
- # - $X509_USER_PROXY, if it is set.
316
- # - /tmp/x509up_u`id -u` otherwise.
317
- # If neither exists (at this point, we don't care if it exists but is invalid), then rethrow
318
- if 'X509_USER_PROXY' in environ:
319
- creds['client_proxy'] = environ['X509_USER_PROXY']
320
- else:
321
- fname = '/tmp/x509up_u%d' % geteuid()
322
- if path.exists(fname):
323
- creds['client_proxy'] = fname
324
- else:
325
- raise MissingClientParameter(
326
- 'Cannot find a valid X509 proxy; not in %s, $X509_USER_PROXY not set, and '
327
- '\'x509_proxy\' not set in the configuration file.' % fname)
345
+ if 'RUCIO_CLIENT_PROXY' in environ:
346
+ creds['client_proxy'] = environ['RUCIO_CLIENT_PROXY']
347
+ elif config_has_section('client') and config_get('client', 'client_x509_proxy', default='') != '':
348
+ creds['client_proxy'] = config_get('client', 'client_x509_proxy')
349
+ elif 'X509_USER_PROXY' in environ:
350
+ creds['client_proxy'] = environ['X509_USER_PROXY']
351
+ elif os.path.isfile(gsi_proxy_path):
352
+ creds['client_proxy'] = gsi_proxy_path
353
+
354
+ creds['client_proxy'] = _expand_path(creds['client_proxy'])
355
+
356
+ if not os.path.isfile(creds['client_proxy']):
357
+ raise MissingClientParameter(
358
+ 'Cannot find a valid X509 proxy; checked $RUCIO_CLIENT_PROXY, $X509_USER_PROXY'
359
+ 'client/client_x509_proxy config and default path: %r' % gsi_proxy_path
360
+ )
328
361
 
329
362
  elif self.auth_type == 'ssh':
330
363
  if 'ssh_private_key' not in creds or creds['ssh_private_key'] is None:
331
- creds['ssh_private_key'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'ssh_private_key'))))
364
+ creds['ssh_private_key'] = config_get('client', 'ssh_private_key')
365
+
366
+ creds['ssh_private_key'] = _expand_path(creds['ssh_private_key'])
367
+ if not os.path.isfile(creds["ssh_private_key"]):
368
+ raise CannotAuthenticate('Provided ssh private key %r does not exist' % creds['ssh_private_key'])
332
369
 
333
370
  except (NoOptionError, NoSectionError) as error:
334
371
  if error.args[0] != 'client_key':
@@ -535,11 +572,11 @@ class BaseClient:
535
572
 
536
573
  if not self.auth_oidc_refresh_active:
537
574
  return False
538
- if path.exists(self.token_exp_epoch_file):
575
+ if os.path.exists(self.token_exp_epoch_file):
539
576
  with open(self.token_exp_epoch_file, 'r') as token_epoch_file:
540
577
  try:
541
578
  self.token_exp_epoch = int(token_epoch_file.readline())
542
- except:
579
+ except Exception:
543
580
  self.token_exp_epoch = None
544
581
 
545
582
  if self.token_exp_epoch is None:
@@ -718,10 +755,10 @@ class BaseClient:
718
755
  url = build_url(self.auth_host, path='auth/x509_proxy')
719
756
  client_cert = self.creds['client_proxy']
720
757
 
721
- if (client_cert is not None) and not (path.exists(client_cert)):
758
+ if (client_cert is not None) and not (os.path.exists(client_cert)):
722
759
  self.logger.error('given client cert (%s) doesn\'t exist' % client_cert)
723
760
  return False
724
- if client_key is not None and not path.exists(client_key):
761
+ if client_key is not None and not os.path.exists(client_key):
725
762
  self.logger.error('given client key (%s) doesn\'t exist' % client_key)
726
763
 
727
764
  if client_key is None:
@@ -755,10 +792,10 @@ class BaseClient:
755
792
  headers = {}
756
793
 
757
794
  private_key_path = self.creds['ssh_private_key']
758
- if not path.exists(private_key_path):
795
+ if not os.path.exists(private_key_path):
759
796
  self.logger.error('given private key (%s) doesn\'t exist' % private_key_path)
760
797
  return False
761
- if private_key_path is not None and not path.exists(private_key_path):
798
+ if private_key_path is not None and not os.path.exists(private_key_path):
762
799
  self.logger.error('given private key (%s) doesn\'t exist' % private_key_path)
763
800
  return False
764
801
 
@@ -906,7 +943,7 @@ class BaseClient:
906
943
 
907
944
  :return: True if a token could be read. False if no file exists.
908
945
  """
909
- if not path.exists(self.token_file):
946
+ if not os.path.exists(self.token_file):
910
947
  return False
911
948
 
912
949
  try:
@@ -927,7 +964,7 @@ class BaseClient:
927
964
  Write the current auth_token to the local token file.
928
965
  """
929
966
  # check if rucio temp directory is there. If not create it with permissions only for the current user
930
- if not path.isdir(self.token_path):
967
+ if not os.path.isdir(self.token_path):
931
968
  try:
932
969
  self.logger.debug('rucio token folder \'%s\' not found. Create it.' % self.token_path)
933
970
  makedirs(self.token_path, 0o700)
rucio/client/client.py CHANGED
@@ -58,63 +58,71 @@ class Client(AccountClient,
58
58
  LifetimeClient):
59
59
 
60
60
  """
61
- Main client class for accessing Rucio resources. Handles the authentication.
61
+ Main client class for accessing Rucio resources. Handles the authentication.
62
62
 
63
- Note:
64
- Used to access all client methods. Each entity client *can* be used to access methods, but using the main client class is recommended for ease of use.
63
+ Note:
64
+ ------
65
+ Used to access all client methods. Each entity client *can* be used to access methods, but using the main client class is recommended for ease of use.
65
66
 
66
- For using general methods -
67
67
 
68
+ Example:
69
+ -------
70
+ from rucio.client import Client
68
71
 
69
- ```
70
- from rucio.client import Client
72
+ client = Client() # authenticate with config or environ settings
73
+ client.add_replication_rule(...)
71
74
 
72
- client = Client() # authenticate with config or environ settings
73
- client.add_replication_rule(...)
75
+ client = Client(
76
+ rucio_host = "my_host",
77
+ auth_host = "my_auth_host",
78
+ account = "jdoe12345",
79
+ auth_type = "userpass",
80
+ creds = {
81
+ "username": "jdoe12345",
82
+ "password": "******",
83
+ }
84
+ ) # authenticate with kwargs
85
+ client.list_replicas(...)
74
86
 
75
- client = Client(
76
- rucio_host = "my_host",
77
- auth_host = "my_auth_host",
78
- account = "jdoe12345",
79
- auth_type = "userpass",
80
- creds = {
81
- "username": "jdoe12345",
82
- "password": "******",
83
- }
84
- ) # authenticate with kwargs
85
- client.list_replicas(...)
86
- ```
87
87
 
88
- For using the upload and download clients -
88
+ # For using the upload and download clients
89
89
 
90
- ```
91
- from rucio.client import Client
92
- from rucio.client.uploadclient import UploadClient
93
- from rucio.client.downloadclient import DownloadClient
90
+ from rucio.client import Client
91
+ from rucio.client.uploadclient import UploadClient
92
+ from rucio.client.downloadclient import DownloadClient
94
93
 
95
- client = Client(...) # Initialize a client using your preferred method
94
+ client = Client(...) # Initialize a client using your preferred method
96
95
 
97
- upload_client = UploadClient(client) # Pass forward the initialized client
98
- upload_client.upload(items=...)
99
-
100
- download_client = DownloadClient(client)
101
- download_client.download_dids(items=...)
102
- ```
96
+ upload_client = UploadClient(client) # Pass forward the initialized client
97
+ upload_client.upload(items=...)
103
98
 
99
+ download_client = DownloadClient(client)
100
+ download_client.download_dids(items=...)
104
101
  """
105
102
 
106
103
  def __init__(self, **args):
107
104
  """
108
105
  Constructor for the Rucio main client class.
109
106
 
110
- :param rucio_host: the host of the rucio system.
111
- :param auth_host: the host of the rucio authentication server.
112
- :param account: the rucio account that should be used to interact with the rucio system.
113
- :param ca_cert: the certificate to verify the server.
114
- :param auth_type: the type of authentication to use (e.g. userpass, x509 ...)
115
- :param creds: credentials needed for authentication.
116
- :param timeout: Float describes the timeout of the request (in seconds).
117
- :param vo: The vo that the client will interact with.
118
- :param logger: Logger instance to use (optional)
107
+ Parameters
108
+ ----------
109
+ rucio_host :
110
+ The host of the rucio system.
111
+ auth_host :
112
+ The host of the rucio authentication server.
113
+ account :
114
+ The rucio account that should be used to interact with the rucio system.
115
+ ca_cert :
116
+ The certificate to verify the server.
117
+ auth_type :
118
+ The type of authentication to use (e.g. userpass, x509 ...).
119
+ creds :
120
+ Credentials needed for authentication.
121
+ timeout :
122
+ Describes the timeout of the request (in seconds).
123
+ vo :
124
+ The vo that the client will interact with.
125
+ logger :
126
+ Logger instance to use.
119
127
  """
120
128
  super(Client, self).__init__(**args)
@@ -35,9 +35,12 @@ class ConfigClient(BaseClient):
35
35
  """
36
36
  Sends the request to get the matching configuration.
37
37
 
38
- :param section: the optional name of the section.
39
- :param option: the optional option within the section.
40
- :return: dictionary containing the configuration.
38
+ Parameters
39
+ ----------
40
+ section :
41
+ The name of the section.
42
+ option :
43
+ The option within the section.
41
44
  """
42
45
 
43
46
  if section is None and option is not None:
@@ -68,18 +71,30 @@ class ConfigClient(BaseClient):
68
71
  """
69
72
  Sends the request to create or set an option within a section. Missing sections will be created.
70
73
 
71
- :param section: the name of the section.
72
- :param option: the name of the option.
73
- :param value: the value to set on the config option
74
- :param use_body_for_params: send parameters in a json-encoded request body instead of url-encoded
75
- TODO: remove this parameter
74
+ Parameters
75
+ ----------
76
+ section :
77
+ The name of the section.
78
+ option :
79
+ The name of the option.
80
+ value :
81
+ The value to set on the config option.
82
+ use_body_for_params :
83
+ Send parameters in a json-encoded request body instead of url-encoded.
84
+
85
+ Returns
86
+ -------
87
+ bool
88
+ True if option was set successfully.
89
+
90
+ Note:
91
+ ------
76
92
  The format of the /config endpoint was recently changed. We migrated from performing a PUT on
77
93
  "/config/<section>/<option>/<value>" to sending the parameters using a json-encoded body.
78
94
  This was done to fix multiple un-wanted side effects related to how the middleware treats
79
95
  values encoded in a path.
80
96
  For a smooth transition, we allow both cases for now, but we should migrate to only passing
81
97
  values via the request body.
82
- :return: True if option was removed successfully. False otherwise.
83
98
  """
84
99
 
85
100
  if use_body_for_params:
@@ -107,11 +122,19 @@ class ConfigClient(BaseClient):
107
122
  option: str
108
123
  ) -> bool:
109
124
  """
110
- Sends the request to remove an option from a section
125
+ Sends the request to remove an option from a section.
111
126
 
112
- :param section: the name of the section.
113
- :param option: the name of the option.
114
- :return: True if option was removed successfully. False otherwise.
127
+ Parameters
128
+ ----------
129
+ section :
130
+ The name of the section.
131
+ option :
132
+ The name of the option.
133
+
134
+ Returns
135
+ -------
136
+
137
+ True if option was removed successfully.
115
138
  """
116
139
 
117
140
  path = '/'.join([self.CONFIG_BASEURL, section, option])
@@ -34,13 +34,23 @@ class CredentialClient(BaseClient):
34
34
  """
35
35
  Return a signed version of the given URL for the given operation.
36
36
 
37
- :param rse: The name of the RSE the URL points to.
38
- :param service: The service the URL points to (gcs, s3, swift)
39
- :param operation: The desired operation (read, write, delete)
40
- :param url: The URL to sign
41
- :param lifetime: The desired lifetime of the URL in seconds
37
+ Parameters
38
+ ----------
39
+ rse :
40
+ The name of the RSE the URL points to.
41
+ service :
42
+ The service the URL points to (gcs, s3, swift)
43
+ operation :
44
+ The desired operation (read, write, delete)
45
+ url :
46
+ The URL to sign
47
+ lifetime :
48
+ The desired lifetime of the URL in seconds, by default 3600
42
49
 
43
- :return: The signed URL string
50
+ Returns
51
+ -------
52
+
53
+ The signed URL string
44
54
  """
45
55
  path = '/'.join([self.CREDENTIAL_BASEURL, 'signurl'])
46
56
  params = {}