rucio-clients 35.7.0__py3-none-any.whl → 37.0.0rc2__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.
- rucio/alembicrevision.py +1 -1
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
- rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/accountclient.py +0 -1
- rucio/client/baseclient.py +33 -24
- rucio/client/client.py +45 -1
- rucio/client/didclient.py +5 -3
- rucio/client/downloadclient.py +6 -8
- rucio/client/replicaclient.py +0 -2
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +4 -4
- rucio/client/uploadclient.py +26 -12
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +66 -29
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +22 -35
- rucio/common/constants.py +61 -3
- rucio/common/didtype.py +72 -24
- rucio/common/exception.py +65 -8
- rucio/common/extra.py +5 -10
- rucio/common/logging.py +13 -13
- rucio/common/pcache.py +8 -7
- rucio/common/plugins.py +59 -27
- rucio/common/policy.py +12 -3
- rucio/common/schema/__init__.py +84 -34
- rucio/common/schema/generic.py +0 -17
- rucio/common/schema/generic_multi_vo.py +0 -17
- rucio/common/stomp_utils.py +383 -119
- rucio/common/test_rucio_server.py +12 -6
- rucio/common/types.py +132 -52
- rucio/common/utils.py +93 -643
- rucio/rse/__init__.py +3 -3
- rucio/rse/protocols/bittorrent.py +11 -1
- rucio/rse/protocols/cache.py +0 -11
- rucio/rse/protocols/dummy.py +0 -11
- rucio/rse/protocols/gfal.py +14 -9
- rucio/rse/protocols/globus.py +1 -1
- rucio/rse/protocols/http_cache.py +1 -1
- rucio/rse/protocols/posix.py +2 -2
- rucio/rse/protocols/protocol.py +84 -317
- rucio/rse/protocols/rclone.py +2 -1
- rucio/rse/protocols/rfio.py +10 -1
- rucio/rse/protocols/ssh.py +2 -1
- rucio/rse/protocols/storm.py +2 -13
- rucio/rse/protocols/webdav.py +74 -30
- rucio/rse/protocols/xrootd.py +2 -1
- rucio/rse/rsemanager.py +170 -53
- rucio/rse/translation.py +260 -0
- rucio/vcsversion.py +4 -4
- rucio/version.py +7 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.template +3 -19
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/requirements.client.txt +11 -7
- rucio_clients-37.0.0rc2.data/scripts/rucio +133 -0
- rucio_clients-37.0.0rc2.data/scripts/rucio-admin +97 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/METADATA +18 -14
- rucio_clients-37.0.0rc2.dist-info/RECORD +104 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/AUTHORS.rst +3 -0
- rucio/common/schema/atlas.py +0 -413
- rucio/common/schema/belleii.py +0 -408
- rucio/common/schema/domatpc.py +0 -401
- rucio/common/schema/escape.py +0 -426
- rucio/common/schema/icecube.py +0 -406
- rucio/rse/protocols/gsiftp.py +0 -92
- rucio_clients-35.7.0.dist-info/RECORD +0 -88
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rse-accounts.cfg.template +0 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/rucio_client/merge_rucio_configs.py +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/WHEEL +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/top_level.txt +0 -0
rucio/cli/command.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import importlib
|
|
15
|
+
import signal
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.status import Status
|
|
21
|
+
from rich.theme import Theme
|
|
22
|
+
from rich.traceback import install
|
|
23
|
+
|
|
24
|
+
from rucio import version
|
|
25
|
+
from rucio.cli.bin_legacy.rucio import ping, test_server, whoami_account
|
|
26
|
+
from rucio.cli.utils import Arguments, exception_handler, get_client, setup_gfal2_logger, signal_handler
|
|
27
|
+
from rucio.client.richclient import MAX_TRACEBACK_WIDTH, MIN_CONSOLE_WIDTH, CLITheme, get_cli_config, get_pager, setup_rich_logger
|
|
28
|
+
from rucio.common.utils import setup_logger
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Taken directly from https://click.palletsprojects.com/en/stable/complex/#defining-the-lazy-group
|
|
32
|
+
class LazyGroup(click.Group):
|
|
33
|
+
def __init__(self, *args, lazy_subcommands=None, **kwargs):
|
|
34
|
+
super().__init__(*args, **kwargs)
|
|
35
|
+
# lazy_subcommands is a map of the form:
|
|
36
|
+
#
|
|
37
|
+
# {command-name} -> {module-name}.{command-object-name}
|
|
38
|
+
#
|
|
39
|
+
self.lazy_subcommands = lazy_subcommands or {}
|
|
40
|
+
|
|
41
|
+
def list_commands(self, ctx):
|
|
42
|
+
base = super().list_commands(ctx)
|
|
43
|
+
lazy = sorted(self.lazy_subcommands.keys())
|
|
44
|
+
return base + lazy
|
|
45
|
+
|
|
46
|
+
def get_command(self, ctx, cmd_name):
|
|
47
|
+
if cmd_name in self.lazy_subcommands:
|
|
48
|
+
return self._lazy_load(cmd_name)
|
|
49
|
+
return super().get_command(ctx, cmd_name)
|
|
50
|
+
|
|
51
|
+
def _lazy_load(self, cmd_name):
|
|
52
|
+
# lazily loading a command, first get the module name and attribute name
|
|
53
|
+
import_path = self.lazy_subcommands[cmd_name]
|
|
54
|
+
modname, cmd_object_name = import_path.rsplit(".", 1)
|
|
55
|
+
# do the import
|
|
56
|
+
mod = importlib.import_module(modname)
|
|
57
|
+
# get the Command object from that module
|
|
58
|
+
cmd_object = getattr(mod, cmd_object_name)
|
|
59
|
+
# check the result to make debugging easier
|
|
60
|
+
if not isinstance(cmd_object, click.BaseCommand):
|
|
61
|
+
raise ValueError(f"Lazy loading of {import_path} failed by returning " "a non-command object")
|
|
62
|
+
return cmd_object
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@click.group(
|
|
66
|
+
cls=LazyGroup,
|
|
67
|
+
lazy_subcommands={
|
|
68
|
+
"account": "rucio.cli.account.account",
|
|
69
|
+
"config": "rucio.cli.config.config",
|
|
70
|
+
"did": "rucio.cli.did.did",
|
|
71
|
+
"download": "rucio.cli.download.download",
|
|
72
|
+
"lifetime-exception": "rucio.cli.lifetime_exception.lifetime_exception",
|
|
73
|
+
"replica": "rucio.cli.replica.replica",
|
|
74
|
+
"rse": "rucio.cli.rse.rse",
|
|
75
|
+
"rule": "rucio.cli.rule.rule",
|
|
76
|
+
"scope": "rucio.cli.scope.scope",
|
|
77
|
+
"subscription": "rucio.cli.subscription.subscription",
|
|
78
|
+
"upload": "rucio.cli.upload.upload_command",
|
|
79
|
+
},
|
|
80
|
+
context_settings={"help_option_names": ["-h", "--help"]}
|
|
81
|
+
) # TODO: Implement https://click.palletsprojects.com/en/stable/options/#dynamic-defaults-for-prompts for args from config or os
|
|
82
|
+
@click.option("--account", "--issuer", "issuer", help="Rucio account to use.")
|
|
83
|
+
@click.option("--auth-host", help="The Rucio Authentication host")
|
|
84
|
+
@click.option("-S", "--auth-strategy", help="Authentication strategy", type=click.Choice(['userpass', 'x509', 'x509_proxy', 'gss', 'ssh', 'saml', 'oidc']))
|
|
85
|
+
# x509 and x509 proxy auth
|
|
86
|
+
@click.option("--ca-certificate", help="CA certificate to verify peer against (SSL)")
|
|
87
|
+
@click.option("--certificate", help="Client certificate file")
|
|
88
|
+
@click.option("--client-key", help="Client key for x509 Authentication")
|
|
89
|
+
@click.option("--config", help="The Rucio configuration file to use")
|
|
90
|
+
@click.option("-H", "--host", help="The Rucio API host")
|
|
91
|
+
# oidc auth
|
|
92
|
+
@click.option("--oidc-user", help="OIDC username")
|
|
93
|
+
@click.option("--oidc-password", help="OIDC password")
|
|
94
|
+
@click.option("--oidc-audience", help="Defines which audience are tokens requested for.")
|
|
95
|
+
@click.option(
|
|
96
|
+
"--oidc-auto",
|
|
97
|
+
is_flag=True,
|
|
98
|
+
default=False,
|
|
99
|
+
help="""
|
|
100
|
+
If not specified, username and password credentials are not required and users will be given a URL to use in their browser.
|
|
101
|
+
If specified, the users explicitly trust Rucio with their IdP credentials"
|
|
102
|
+
""",
|
|
103
|
+
)
|
|
104
|
+
@click.option(
|
|
105
|
+
"--oidc-issuer",
|
|
106
|
+
help="""
|
|
107
|
+
Defines which Identity Provider is going to be used.
|
|
108
|
+
The issuer string must correspond to the keys configured in the /etc/idpsecrets.json auth server configuration file")
|
|
109
|
+
""",
|
|
110
|
+
)
|
|
111
|
+
@click.option(
|
|
112
|
+
"--oidc-polling",
|
|
113
|
+
is_flag=True,
|
|
114
|
+
default=False,
|
|
115
|
+
help="""
|
|
116
|
+
If not specified, user will be asked to enter a code returned by the browser to the command line.
|
|
117
|
+
If --polling is set, Rucio Client should get the token without any further interaction of the user.
|
|
118
|
+
This option is active only if --auto is *not* specified
|
|
119
|
+
""",
|
|
120
|
+
)
|
|
121
|
+
@click.option(
|
|
122
|
+
"--oidc-refresh-lifetime",
|
|
123
|
+
help="""
|
|
124
|
+
Max lifetime in hours for this access token; the token will be refreshed by an asynchronous Rucio daemon.
|
|
125
|
+
If not specified, refresh will be stopped after 4 days.
|
|
126
|
+
This option is effective only if --oidc-scope includes offline_access scope for a refresh token to be granted to Rucio
|
|
127
|
+
""",
|
|
128
|
+
)
|
|
129
|
+
@click.option(
|
|
130
|
+
"--oidc-scope",
|
|
131
|
+
default="openid profile",
|
|
132
|
+
help="""
|
|
133
|
+
Defines which (OIDC) information user will share with Rucio. Rucio requires at least -sc='openid profile'.
|
|
134
|
+
To request refresh token for Rucio, scope must include 'openid offline_access'
|
|
135
|
+
and there must be no active access token saved on the side of the currently used Rucio Client,
|
|
136
|
+
""",
|
|
137
|
+
)
|
|
138
|
+
@click.option("-T", "--timeout", type=float, help="Set all timeout values to seconds")
|
|
139
|
+
@click.option("-U", "--user-agent", default="rucio-clients", help="Rucio User Agent")
|
|
140
|
+
# userpass/gss/saml auth
|
|
141
|
+
@click.option("-u", "--user", help="Username for userpass")
|
|
142
|
+
@click.option("--password", help="Password for userpass")
|
|
143
|
+
@click.option("--vo", help="VO to authenticate at. Only used in multi-VO mode")
|
|
144
|
+
@click.option("-v", "--verbose", default=False, is_flag=True, help="Print more verbose output")
|
|
145
|
+
@click.version_option(version.version_string(), message="%(prog)s %(version)s")
|
|
146
|
+
# Hidden options at the end
|
|
147
|
+
@click.option("--pager", is_flag=True, default=False, hidden=True)
|
|
148
|
+
@exception_handler
|
|
149
|
+
@click.pass_context
|
|
150
|
+
def main(
|
|
151
|
+
ctx,
|
|
152
|
+
config,
|
|
153
|
+
verbose,
|
|
154
|
+
host,
|
|
155
|
+
auth_host,
|
|
156
|
+
issuer,
|
|
157
|
+
auth_strategy,
|
|
158
|
+
timeout,
|
|
159
|
+
user_agent,
|
|
160
|
+
vo,
|
|
161
|
+
pager,
|
|
162
|
+
user,
|
|
163
|
+
password,
|
|
164
|
+
oidc_user,
|
|
165
|
+
oidc_password,
|
|
166
|
+
oidc_scope,
|
|
167
|
+
oidc_audience,
|
|
168
|
+
oidc_auto,
|
|
169
|
+
oidc_polling,
|
|
170
|
+
oidc_refresh_lifetime,
|
|
171
|
+
oidc_issuer,
|
|
172
|
+
certificate,
|
|
173
|
+
client_key,
|
|
174
|
+
ca_certificate,
|
|
175
|
+
):
|
|
176
|
+
ctx.ensure_object(Arguments)
|
|
177
|
+
ctx.obj.start_time = time.time()
|
|
178
|
+
|
|
179
|
+
ctx.obj.no_pager = not pager
|
|
180
|
+
pager = get_pager()
|
|
181
|
+
use_rich = get_cli_config() == "rich"
|
|
182
|
+
|
|
183
|
+
console = Console(theme=Theme(CLITheme.LOG_THEMES), soft_wrap=True)
|
|
184
|
+
console.width = max(MIN_CONSOLE_WIDTH, console.width)
|
|
185
|
+
spinner = Status("Initializing spinner", spinner=CLITheme.SPINNER, spinner_style=CLITheme.SPINNER_STYLE, console=console)
|
|
186
|
+
|
|
187
|
+
ctx.obj.use_rich = use_rich
|
|
188
|
+
ctx.obj.spinner = spinner
|
|
189
|
+
ctx.obj.console = console
|
|
190
|
+
ctx.obj.pager = pager
|
|
191
|
+
|
|
192
|
+
if use_rich:
|
|
193
|
+
install(console=console, word_wrap=True, width=min(console.width, MAX_TRACEBACK_WIDTH)) # Make rich exception tracebacks the default.
|
|
194
|
+
logger = setup_rich_logger(module_name=__name__, logger_name="user", verbose=verbose, console=console)
|
|
195
|
+
else:
|
|
196
|
+
logger = setup_logger(module_name=__name__, logger_name="user", verbose=verbose)
|
|
197
|
+
args = Arguments(
|
|
198
|
+
{
|
|
199
|
+
"config": config,
|
|
200
|
+
"host": host,
|
|
201
|
+
"isser": issuer,
|
|
202
|
+
"auth_host": auth_host,
|
|
203
|
+
"auth_strategy": auth_strategy,
|
|
204
|
+
"timeout": timeout,
|
|
205
|
+
"user_agent": user_agent,
|
|
206
|
+
"VO": vo,
|
|
207
|
+
"username": user,
|
|
208
|
+
"password": password,
|
|
209
|
+
"oidc_username": oidc_user,
|
|
210
|
+
"oidc_password": oidc_password,
|
|
211
|
+
"oidc_scope": oidc_scope,
|
|
212
|
+
"oidc_audience": oidc_audience,
|
|
213
|
+
"oidc_auto": oidc_auto,
|
|
214
|
+
"oidc_polling": oidc_polling,
|
|
215
|
+
"oidc_refresh_lifetime": oidc_refresh_lifetime,
|
|
216
|
+
"oidc_issuer": oidc_issuer,
|
|
217
|
+
"certificate": certificate,
|
|
218
|
+
"client_key": client_key,
|
|
219
|
+
"ca_certificate": ca_certificate,
|
|
220
|
+
}
|
|
221
|
+
) # TODO Future improvement - change `get_client` to take these args directly
|
|
222
|
+
client = get_client(args, logger) # TODO Future improvement - use envvar functionality in click to remove conditionals checking env vars
|
|
223
|
+
|
|
224
|
+
setup_gfal2_logger()
|
|
225
|
+
signal.signal(signal.SIGINT, lambda sig, frame: signal_handler(sig, frame, logger))
|
|
226
|
+
|
|
227
|
+
ctx.obj.client = client
|
|
228
|
+
ctx.obj.logger = logger
|
|
229
|
+
|
|
230
|
+
ctx.call_on_close(_teardown)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@click.pass_context
|
|
234
|
+
def _teardown(ctx):
|
|
235
|
+
time_elapsed = time.time() - ctx.obj.start_time
|
|
236
|
+
|
|
237
|
+
if ctx.obj.use_rich:
|
|
238
|
+
ctx.obj.spinner.stop()
|
|
239
|
+
if ctx.obj.console.is_terminal and not ctx.obj.no_pager:
|
|
240
|
+
command_output = ctx.obj.console.end_capture()
|
|
241
|
+
if command_output == "" and ctx.obj.verbose:
|
|
242
|
+
print("Completed in %-0.4f sec." % (time_elapsed))
|
|
243
|
+
else:
|
|
244
|
+
if ctx.obj.verbose:
|
|
245
|
+
command_output += "Completed in %-0.4f sec." % (time_elapsed)
|
|
246
|
+
# Ignore SIGINT during pager execution.
|
|
247
|
+
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
248
|
+
ctx.obj.pager(command_output)
|
|
249
|
+
else:
|
|
250
|
+
if ctx.obj.verbose:
|
|
251
|
+
print("Completed in %-0.4f sec." % (time_elapsed))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@main.command(name="whoami", help="Get information about account whose token is used")
|
|
255
|
+
@click.pass_context
|
|
256
|
+
def exe_whoami(ctx):
|
|
257
|
+
args = Arguments()
|
|
258
|
+
whoami_account(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@main.command(name="ping", help="Ping Rucio server")
|
|
262
|
+
@click.pass_context
|
|
263
|
+
def exe_ping(ctx):
|
|
264
|
+
args = Arguments()
|
|
265
|
+
ping(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@main.command(name="test-server", help="Test client against the server")
|
|
269
|
+
@click.pass_context
|
|
270
|
+
def exe_test_server(ctx):
|
|
271
|
+
args = Arguments()
|
|
272
|
+
test_server(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
rucio/cli/config.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from rucio.cli.bin_legacy.rucio_admin import delete_config_option, get_config, set_config_option
|
|
17
|
+
from rucio.cli.utils import Arguments
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group()
|
|
21
|
+
def config():
|
|
22
|
+
"Modify the configuration table"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# TODO Limit to just the section names
|
|
26
|
+
@config.command("list")
|
|
27
|
+
@click.option("-s", "--section", help="Filter by sections")
|
|
28
|
+
@click.option("-k", "--key", help="Show key's value, section required.")
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def list_(ctx, section, key):
|
|
31
|
+
"""List the sections or content of sections in the rucio.cfg"""
|
|
32
|
+
get_config(Arguments({"section": section, "key": key}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# TODO Change to only add new fields and cannot modify an existing field
|
|
36
|
+
@config.command("add")
|
|
37
|
+
@click.option("-s", "--section", help="Section name", required=True)
|
|
38
|
+
@click.option('--key', help='Attribute key', required=True)
|
|
39
|
+
@click.option('--value', help='Attribute value', required=True)
|
|
40
|
+
@click.pass_context
|
|
41
|
+
def add_(ctx, section, key, value):
|
|
42
|
+
"""
|
|
43
|
+
Add a new key/value to a section.
|
|
44
|
+
|
|
45
|
+
\b
|
|
46
|
+
Example, Add a key to an existing section:
|
|
47
|
+
$ rucio config add --section my-section --key key --value value
|
|
48
|
+
"""
|
|
49
|
+
args = Arguments({"section": section, "option": key, "value": value})
|
|
50
|
+
set_config_option(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@config.command("remove")
|
|
54
|
+
@click.option("-s", "--section", help="Section", required=True)
|
|
55
|
+
@click.option("-k", "--key", help="Key in section", required=True)
|
|
56
|
+
@click.pass_context
|
|
57
|
+
def remove(ctx, section, key):
|
|
58
|
+
"""Remove the section.key from the config."""
|
|
59
|
+
args = Arguments({"section": section, "option": key})
|
|
60
|
+
delete_config_option(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# @config.command("show")
|
|
64
|
+
# @click.pass_context
|
|
65
|
+
def show(ctx):
|
|
66
|
+
"""Show a single sections options"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# TODO Change this so that it only modifies existing fields
|
|
70
|
+
def update():
|
|
71
|
+
"""Modify an existing command"""
|
|
72
|
+
pass
|
rucio/cli/did.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from rucio.cli.bin_legacy.rucio import add_container, add_dataset, attach, close, delete_metadata, detach, erase, get_metadata, list_content, list_content_history, list_dids, list_parent_dids, reopen, set_metadata, stat, touch
|
|
17
|
+
from rucio.cli.utils import Arguments
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group()
|
|
21
|
+
def did():
|
|
22
|
+
"""Manage Data Identifiers - the source data objects"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@did.command("list")
|
|
26
|
+
@click.option("-r", "--recursive", default=False, is_flag=True, help="List data identifiers recursively.")
|
|
27
|
+
@click.option(
|
|
28
|
+
"--filter", "filter_",
|
|
29
|
+
help="""
|
|
30
|
+
Single or logically combined filtering expression(s) either in the form <key><operator><value>
|
|
31
|
+
or <value1><operator1><key><operator2><value2> (compound inequality).
|
|
32
|
+
Keys are equivalent to columns in the DID table.
|
|
33
|
+
Operators must belong to the set of (<=, >=, ==, !=, >, <). The following conventions for combining expressions
|
|
34
|
+
are used: ";" represents the logical OR operator; "," represents the logical AND operator',
|
|
35
|
+
""",
|
|
36
|
+
) # TODO Shorten this help and make supplying this easier
|
|
37
|
+
@click.option("--short", is_flag=True, default=False, help="Dump the list of DIDs")
|
|
38
|
+
@click.argument("did-pattern", nargs=-1)
|
|
39
|
+
@click.option("--parent", default=False, is_flag=True, help="List the parents of the DID - must use a full DID scope and name")
|
|
40
|
+
# TODO Implement or remove option - view https://github.com/rucio/rucio/issues/7230
|
|
41
|
+
@click.option("--pfn", hidden=True)
|
|
42
|
+
@click.option("--guid", hidden=True)
|
|
43
|
+
@click.pass_context
|
|
44
|
+
def list_(ctx, did_pattern, recursive, filter_, short, parent, pfn, guid):
|
|
45
|
+
"""
|
|
46
|
+
List the Data IDentifiers matching certain pattern.
|
|
47
|
+
Only the collections (i.e. dataset or container) are returned by default.
|
|
48
|
+
With the filter option, you can specify a list of metadata that the Data IDentifier should match
|
|
49
|
+
"""
|
|
50
|
+
if parent:
|
|
51
|
+
for did in did_pattern:
|
|
52
|
+
list_parent_dids(Arguments({"did": did}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
53
|
+
else:
|
|
54
|
+
args = Arguments({"did": did_pattern, "recursive": recursive, "filter": filter_, "short": short})
|
|
55
|
+
list_dids(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@did.command("show")
|
|
59
|
+
@click.argument("dids", nargs=-1)
|
|
60
|
+
@click.pass_context
|
|
61
|
+
def show(ctx, dids):
|
|
62
|
+
"""List attributes, statuses, or parents for data identifiers"""
|
|
63
|
+
stat(Arguments({"dids": dids}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@did.command("add")
|
|
67
|
+
@click.argument("did-name")
|
|
68
|
+
@click.option("--type", "dtype", type=click.Choice(["container", "dataset"]))
|
|
69
|
+
@click.option("--monotonic", is_flag=True, default=False, help="Monotonic status to True.")
|
|
70
|
+
@click.option("--lifetime", type=int, help="Lifetime in seconds.")
|
|
71
|
+
@click.pass_context
|
|
72
|
+
def add_(ctx, did_name, dtype, monotonic, lifetime):
|
|
73
|
+
"""Create a new collection-type DID"""
|
|
74
|
+
args = Arguments({"did": did_name, "monotonic": monotonic, "lifetime": lifetime})
|
|
75
|
+
if dtype == "container":
|
|
76
|
+
add_container(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
77
|
+
else:
|
|
78
|
+
add_dataset(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@did.command("update")
|
|
82
|
+
@click.argument("dids", nargs=-1)
|
|
83
|
+
@click.option("--rse", "--rse-name", help="The RSE of the DIDs")
|
|
84
|
+
@click.option("--touch", "operation", flag_value="touch", default=True, help="Touch one or more DIDs and set the last accessed date to the current date")
|
|
85
|
+
@click.option("--open", "operation", flag_value="open", help="Reopen a dataset or container (only for privileged users)")
|
|
86
|
+
@click.option("--close", "operation", flag_value="close", help="Close a dataset or container.")
|
|
87
|
+
@click.pass_context
|
|
88
|
+
def update(ctx, dids, rse, operation):
|
|
89
|
+
"""Touch one or more DIDs and set the last accessed date to the current date, or mark them as open or closed."""
|
|
90
|
+
args = Arguments({"dids": dids, "rse": rse})
|
|
91
|
+
if operation == "touch":
|
|
92
|
+
touch(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
93
|
+
elif operation == "open":
|
|
94
|
+
reopen(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
95
|
+
elif operation == "close":
|
|
96
|
+
close(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError("No operation specified, please use `--help` to see possibilities") # Should not be possible, but better safe than sorry
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@did.command("remove")
|
|
102
|
+
@click.option("--undo", is_flag=True, default=False, help="Undo erase DIDs. Only works if has been less than 24 hours since erase operation.")
|
|
103
|
+
@click.argument("dids", nargs=-1)
|
|
104
|
+
@click.pass_context
|
|
105
|
+
def remove(ctx, dids, undo):
|
|
106
|
+
"""
|
|
107
|
+
This command sets the lifetime of the DID in order to expire in the next 24 hours.
|
|
108
|
+
Expired DIDs are force-deleted (and their replicas purged).
|
|
109
|
+
The deletion is not reversible after 24 hours grace time period expired
|
|
110
|
+
"""
|
|
111
|
+
erase(Arguments({"dids": dids, "undo": undo}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@did.group()
|
|
115
|
+
def content():
|
|
116
|
+
"""Manage contents of collection type DIDs"""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@content.command("history")
|
|
120
|
+
@click.argument("dids", nargs=-1)
|
|
121
|
+
@click.pass_context
|
|
122
|
+
def content_history(ctx, dids):
|
|
123
|
+
"""List the content history of a collection-type DID"""
|
|
124
|
+
list_content_history(Arguments({"dids": dids}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@content.command("add")
|
|
128
|
+
@click.option("-to", "--to-did", required=True, help="Collection-type DID to which attach [DIDs]")
|
|
129
|
+
@click.option("-f", "--from-file", is_flag=True, default=False, help="[DIDs] is a file instead of a list of did names. The file should contain one did per line.")
|
|
130
|
+
@click.argument("dids", nargs=-1)
|
|
131
|
+
@click.pass_context
|
|
132
|
+
def content_add_(ctx, to_did, from_file, dids):
|
|
133
|
+
"""Attach a list [dids] of Data IDentifiers (file or collection-type) to an other Data IDentifier (collection-type)"""
|
|
134
|
+
args = Arguments({"dids": dids, "todid": to_did, "fromfile": from_file})
|
|
135
|
+
attach(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@content.command("remove")
|
|
139
|
+
@click.option("-f", "--from-did", help="Collection-type DID to remove DIDs from")
|
|
140
|
+
@click.argument("dids", nargs=-1)
|
|
141
|
+
@click.pass_context
|
|
142
|
+
def content_remove(ctx, dids, from_did):
|
|
143
|
+
"""Detach [dids], a list of DIDs (file or collection-type) from an other Data Identifier (collection type)"""
|
|
144
|
+
args = Arguments({"dids": dids, "fromdid": from_did})
|
|
145
|
+
detach(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@content.command("list")
|
|
149
|
+
@click.argument("dids", nargs=-1)
|
|
150
|
+
@click.option("--short", is_flag=True, default=False, help="Just dump the list of DIDs.")
|
|
151
|
+
@click.pass_context
|
|
152
|
+
def content_list_(ctx, dids, short):
|
|
153
|
+
"""List the content of a collection-type DID"""
|
|
154
|
+
args = Arguments({"dids": dids, "short": short})
|
|
155
|
+
list_content(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@did.group()
|
|
159
|
+
def metadata():
|
|
160
|
+
"""Manage metadata for DIDs"""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@metadata.command("add")
|
|
164
|
+
@click.argument("did")
|
|
165
|
+
@click.option('--key', help='Attribute key', required=True)
|
|
166
|
+
@click.option('--value', help='Attribute value', required=True)
|
|
167
|
+
@click.pass_context
|
|
168
|
+
def metadata_add_(ctx, did, key, value):
|
|
169
|
+
"""Add metadata to a DID"""
|
|
170
|
+
args = Arguments({"did": did, "key": key, "value": value})
|
|
171
|
+
set_metadata(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@metadata.command("remove")
|
|
175
|
+
@click.argument("did")
|
|
176
|
+
@click.option("--key", help="Key to remove from a DID's metadata.")
|
|
177
|
+
@click.pass_context
|
|
178
|
+
def metadata_remove(ctx, did, key):
|
|
179
|
+
"""Remove metadata from a DID"""
|
|
180
|
+
args = Arguments({"did": did, "key": key})
|
|
181
|
+
delete_metadata(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@metadata.command("list")
|
|
185
|
+
@click.argument("dids", nargs=-1)
|
|
186
|
+
@click.option("--plugin", help="Filter down to metadata from specific metadata plugin")
|
|
187
|
+
@click.pass_context
|
|
188
|
+
def metadata_list_(ctx, dids, plugin):
|
|
189
|
+
"""List metadata for a list of DIDs"""
|
|
190
|
+
args = Arguments({"dids": dids, "plugin": plugin})
|
|
191
|
+
get_metadata(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|
rucio/cli/download.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from rucio.cli.bin_legacy.rucio import download as download_exe
|
|
19
|
+
from rucio.cli.utils import Arguments
|
|
20
|
+
from rucio.common.config import config_get_float
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command()
|
|
24
|
+
@click.argument("dids", nargs=-1)
|
|
25
|
+
@click.option("--dir", default=".", help="The directory to store the downloaded file.")
|
|
26
|
+
@click.option("--allow-tape", is_flag=True, default=False, help="Also consider tape endpoints as source of the download.")
|
|
27
|
+
@click.option("--rses", "--rse-exp", help="RSE Expression to specify allowed sources")
|
|
28
|
+
@click.option("--impl", help="Transfer protocol implementation to use (e.g: xrootd, gfal.NoRename, webdav, ssh.Rsync, rclone).")
|
|
29
|
+
@click.option("--protocol", help="Force the protocol to use.")
|
|
30
|
+
@click.option("--nrandom", type=int, help="Download N random files from the DID.")
|
|
31
|
+
@click.option("--ndownloader", type=int, default=3, help="Choose the number of parallel processes for download.")
|
|
32
|
+
@click.option("--no-subdir", is_flag=True, default=False, help="Don't create a subdirectory for the scope of the files.")
|
|
33
|
+
@click.option("--pfn", help="Specify the exact PFN for the download.")
|
|
34
|
+
@click.option("--no-resolve-archives", is_flag=True, default=False, help="If set archives will not be considered for download.")
|
|
35
|
+
@click.option("--ignore-checksum", is_flag=True, default=False, help="Don't validate checksum for downloaded files.")
|
|
36
|
+
@click.option("--check-local-with-filesize-only", is_flag=True, default=False, help="Don't use checksum verification for already downloaded files, use filesize instead.")
|
|
37
|
+
@click.option(
|
|
38
|
+
"--transfer-timeout",
|
|
39
|
+
type=float,
|
|
40
|
+
default=config_get_float("download", "transfer_timeout", False, None),
|
|
41
|
+
help="Transfer timeout (in seconds). Default: computed dynamically from --transfer-speed-timeout. If set to any value >= 0, --transfer-speed-timeout is ignored.",
|
|
42
|
+
) # NOQA: E501
|
|
43
|
+
@click.option("--transfer-speed-timeout", type=float, default=None, help="Minimum allowed average transfer speed (in KBps). Default: 500. Used to dynamically compute the timeout if --transfer-timeout not set. Is not supported for --pfn.") # NOQA: E501
|
|
44
|
+
@click.option("--aria", default=False, help="Use aria2c utility if possible. (EXPERIMENTAL)")
|
|
45
|
+
@click.option("--archive-did", hidden=True) # Download from archive is transparent. This option is obsolete.
|
|
46
|
+
# TODO replace with click functionality to automatically pull env vars
|
|
47
|
+
@click.option("--trace-appid", default=os.environ.get("RUCIO_TRACE_APPID", None), hidden=True)
|
|
48
|
+
@click.option("--trace-dataset", default=os.environ.get("RUCIO_TRACE_DATASET", None), hidden=True)
|
|
49
|
+
@click.option("--trace-datasetscope", default=os.environ.get("RUCIO_TRACE_DATASETSCOPE", None), hidden=True)
|
|
50
|
+
@click.option("--trace-eventtype", default=os.environ.get("RUCIO_TRACE_EVENTTYPE", None), hidden=True)
|
|
51
|
+
@click.option("--trace-pq", default=os.environ.get("RUCIO_TRACE_PQ", None), hidden=True)
|
|
52
|
+
@click.option("--trace-taskid", default=os.environ.get("RUCIO_TRACE_TASKID", None), hidden=True)
|
|
53
|
+
@click.option("--trace-usrdn", default=os.environ.get("RUCIO_TRACE_USRDN", None), hidden=True)
|
|
54
|
+
@click.option("--filter", help="Filter files by key-value pairs like guid=2e2232aafac8324db452070304f8d745.")
|
|
55
|
+
@click.option("--scope", help="Scope if you are using the filter option and no full DID.")
|
|
56
|
+
@click.option("--metalink", help="Path to a metalink file.")
|
|
57
|
+
@click.option("--no-show-download-exceptions", default=False, is_flag=True, help="Does not raise NoFilesDownloaded, NotAllFilesDownloaded or incorrect number of output queue files Exception.") # NOQA: E501
|
|
58
|
+
@click.option("--replica-selection", help="Select the best replica using a replica sorting algorithm provided by replica sorter (e.g., random, geoip).")
|
|
59
|
+
@click.pass_context
|
|
60
|
+
def download(
|
|
61
|
+
ctx,
|
|
62
|
+
dids,
|
|
63
|
+
dir,
|
|
64
|
+
allow_tape,
|
|
65
|
+
rses,
|
|
66
|
+
impl,
|
|
67
|
+
protocol,
|
|
68
|
+
nrandom,
|
|
69
|
+
ndownloader,
|
|
70
|
+
no_subdir,
|
|
71
|
+
pfn,
|
|
72
|
+
no_resolve_archives,
|
|
73
|
+
ignore_checksum,
|
|
74
|
+
check_local_with_filesize_only,
|
|
75
|
+
transfer_timeout,
|
|
76
|
+
transfer_speed_timeout,
|
|
77
|
+
aria,
|
|
78
|
+
archive_did,
|
|
79
|
+
trace_appid,
|
|
80
|
+
trace_dataset,
|
|
81
|
+
trace_datasetscope,
|
|
82
|
+
trace_eventtype,
|
|
83
|
+
trace_pq,
|
|
84
|
+
trace_taskid,
|
|
85
|
+
trace_usrdn,
|
|
86
|
+
filter,
|
|
87
|
+
scope,
|
|
88
|
+
metalink,
|
|
89
|
+
no_show_download_exceptions,
|
|
90
|
+
replica_selection,
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
Download DID(s) (in the form of scope:name) to a local dir
|
|
94
|
+
"""
|
|
95
|
+
args = Arguments(
|
|
96
|
+
{
|
|
97
|
+
"dids": dids,
|
|
98
|
+
"dir": dir,
|
|
99
|
+
"allow_tape": allow_tape,
|
|
100
|
+
"rses": rses,
|
|
101
|
+
"impl": impl,
|
|
102
|
+
"protocol": protocol,
|
|
103
|
+
"nrandom": nrandom,
|
|
104
|
+
"ndownloader": ndownloader,
|
|
105
|
+
"no_subdir": no_subdir,
|
|
106
|
+
"pfn": pfn,
|
|
107
|
+
"no_resolve_archives": no_resolve_archives,
|
|
108
|
+
"ignore_checksum": ignore_checksum,
|
|
109
|
+
"check_local_with_filesize_only": check_local_with_filesize_only,
|
|
110
|
+
"transfer_timeout": transfer_timeout,
|
|
111
|
+
"transfer_speed_timeout": transfer_speed_timeout,
|
|
112
|
+
"aria": aria,
|
|
113
|
+
"archive_did": archive_did,
|
|
114
|
+
"trace_appid": trace_appid,
|
|
115
|
+
"trace_dataset": trace_dataset,
|
|
116
|
+
"trace_datasetscope": trace_datasetscope,
|
|
117
|
+
"trace_eventtype": trace_eventtype,
|
|
118
|
+
"trace_pq": trace_pq,
|
|
119
|
+
"trace_taskid": trace_taskid,
|
|
120
|
+
"trace_usrdn": trace_usrdn,
|
|
121
|
+
"filter": filter,
|
|
122
|
+
"scope": scope,
|
|
123
|
+
"metalink_file": metalink,
|
|
124
|
+
"deactivate_file_download_exceptions": no_show_download_exceptions,
|
|
125
|
+
"sort": replica_selection,
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
download_exe(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
|