diracx-cli 0.0.1a7__tar.gz → 0.0.1a8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/PKG-INFO +1 -1
- diracx-cli-0.0.1a8/src/diracx/cli/internal/__init__.py +188 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx_cli.egg-info/PKG-INFO +1 -1
- diracx-cli-0.0.1a8/tests/test_internal.py +226 -0
- diracx-cli-0.0.1a7/src/diracx/cli/internal/__init__.py +0 -104
- diracx-cli-0.0.1a7/tests/test_internal.py +0 -72
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/README.md +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/pyproject.toml +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/setup.cfg +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx/cli/__init__.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx/cli/__main__.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx/cli/internal/legacy.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx/cli/jobs.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx/cli/py.typed +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx/cli/utils.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx_cli.egg-info/SOURCES.txt +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx_cli.egg-info/dependency_links.txt +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx_cli.egg-info/entry_points.txt +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx_cli.egg-info/requires.txt +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/src/diracx_cli.egg-info/top_level.txt +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/legacy/cs_sync/convert_integration_test.yaml +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/legacy/cs_sync/integration_test.cfg +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/legacy/cs_sync/integration_test.yaml +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/legacy/cs_sync/integration_test_secret.cfg +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/legacy/cs_sync/test_cssync.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/legacy/test_legacy.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/test_jobs.py +0 -0
- {diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/test_login.py +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated, Optional
|
|
3
|
+
|
|
4
|
+
import git
|
|
5
|
+
import typer
|
|
6
|
+
import yaml
|
|
7
|
+
from pydantic import parse_obj_as
|
|
8
|
+
|
|
9
|
+
from diracx.core.config import ConfigSource, ConfigSourceUrl
|
|
10
|
+
from diracx.core.config.schema import (
|
|
11
|
+
Config,
|
|
12
|
+
DIRACConfig,
|
|
13
|
+
GroupConfig,
|
|
14
|
+
IdpConfig,
|
|
15
|
+
OperationsConfig,
|
|
16
|
+
RegistryConfig,
|
|
17
|
+
UserConfig,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from ..utils import AsyncTyper
|
|
21
|
+
from . import legacy
|
|
22
|
+
|
|
23
|
+
app = AsyncTyper()
|
|
24
|
+
app.add_typer(legacy.app, name="legacy")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def generate_cs(
|
|
29
|
+
config_repo: str,
|
|
30
|
+
):
|
|
31
|
+
"""Generate a minimal DiracX configuration repository"""
|
|
32
|
+
# TODO: The use of parse_obj_as should be moved in to typer itself
|
|
33
|
+
config_repo = parse_obj_as(ConfigSourceUrl, config_repo)
|
|
34
|
+
if config_repo.scheme != "git+file":
|
|
35
|
+
raise NotImplementedError("Only git+file:// URLs are supported")
|
|
36
|
+
repo_path = Path(config_repo.path)
|
|
37
|
+
if repo_path.exists() and list(repo_path.iterdir()):
|
|
38
|
+
typer.echo(f"ERROR: Directory {repo_path} already exists", err=True)
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
|
|
41
|
+
config = Config(
|
|
42
|
+
Registry={},
|
|
43
|
+
DIRAC=DIRACConfig(),
|
|
44
|
+
Operations={"Defaults": OperationsConfig()},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
update_config_and_commit(
|
|
48
|
+
repo_path=repo_path, config=config, message="Initial commit"
|
|
49
|
+
)
|
|
50
|
+
typer.echo(f"Successfully created repo in {config_repo}", err=True)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command()
|
|
54
|
+
def add_vo(
|
|
55
|
+
config_repo: str,
|
|
56
|
+
*,
|
|
57
|
+
vo: Annotated[str, typer.Option()],
|
|
58
|
+
default_group: Optional[str] = "user",
|
|
59
|
+
idp_url: Annotated[str, typer.Option()],
|
|
60
|
+
idp_client_id: Annotated[str, typer.Option()],
|
|
61
|
+
):
|
|
62
|
+
"""Add a registry entry (vo) to an existing configuration repository"""
|
|
63
|
+
|
|
64
|
+
# TODO: The use of parse_obj_as should be moved in to typer itself
|
|
65
|
+
config_repo = parse_obj_as(ConfigSourceUrl, config_repo)
|
|
66
|
+
repo_path = Path(config_repo.path)
|
|
67
|
+
|
|
68
|
+
# A VO should at least contain a default group
|
|
69
|
+
new_registry = RegistryConfig(
|
|
70
|
+
IdP=IdpConfig(URL=idp_url, ClientID=idp_client_id),
|
|
71
|
+
DefaultGroup=default_group,
|
|
72
|
+
Users={},
|
|
73
|
+
Groups={
|
|
74
|
+
default_group: GroupConfig(
|
|
75
|
+
Properties={"NormalUser"}, Quota=None, Users=set()
|
|
76
|
+
)
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
config = ConfigSource.create_from_url(backend_url=repo_path).read_config()
|
|
81
|
+
|
|
82
|
+
if vo in config.Registry:
|
|
83
|
+
typer.echo(f"ERROR: VO {vo} already exists", err=True)
|
|
84
|
+
raise typer.Exit(1)
|
|
85
|
+
|
|
86
|
+
config.Registry[vo] = new_registry
|
|
87
|
+
|
|
88
|
+
update_config_and_commit(
|
|
89
|
+
repo_path=repo_path,
|
|
90
|
+
config=config,
|
|
91
|
+
message=f"Added vo {vo} registry (default group {default_group} and idp {idp_url})",
|
|
92
|
+
)
|
|
93
|
+
typer.echo(f"Successfully added vo to {config_repo}", err=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command()
|
|
97
|
+
def add_group(
|
|
98
|
+
config_repo: str,
|
|
99
|
+
*,
|
|
100
|
+
vo: Annotated[str, typer.Option()],
|
|
101
|
+
group: Annotated[str, typer.Option()],
|
|
102
|
+
properties: list[str] = ["NormalUser"],
|
|
103
|
+
):
|
|
104
|
+
"""Add a group to an existing vo in the configuration repository"""
|
|
105
|
+
|
|
106
|
+
# TODO: The use of parse_obj_as should be moved in to typer itself
|
|
107
|
+
config_repo = parse_obj_as(ConfigSourceUrl, config_repo)
|
|
108
|
+
repo_path = Path(config_repo.path)
|
|
109
|
+
|
|
110
|
+
new_group = GroupConfig(Properties=set(properties), Quota=None, Users=set())
|
|
111
|
+
|
|
112
|
+
config = ConfigSource.create_from_url(backend_url=repo_path).read_config()
|
|
113
|
+
|
|
114
|
+
if vo not in config.Registry:
|
|
115
|
+
typer.echo(f"ERROR: Virtual Organization {vo} does not exist", err=True)
|
|
116
|
+
raise typer.Exit(1)
|
|
117
|
+
|
|
118
|
+
if group in config.Registry[vo].Groups.keys():
|
|
119
|
+
typer.echo(f"ERROR: Group {group} already exists in {vo}", err=True)
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
|
|
122
|
+
config.Registry[vo].Groups[group] = new_group
|
|
123
|
+
|
|
124
|
+
update_config_and_commit(
|
|
125
|
+
repo_path=repo_path, config=config, message=f"Added group {group} in {vo}"
|
|
126
|
+
)
|
|
127
|
+
typer.echo(f"Successfully added group to {config_repo}", err=True)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.command()
|
|
131
|
+
def add_user(
|
|
132
|
+
config_repo: str,
|
|
133
|
+
*,
|
|
134
|
+
vo: Annotated[str, typer.Option()],
|
|
135
|
+
groups: Annotated[Optional[list[str]], typer.Option("--group")] = None,
|
|
136
|
+
sub: Annotated[str, typer.Option()],
|
|
137
|
+
preferred_username: Annotated[str, typer.Option()],
|
|
138
|
+
):
|
|
139
|
+
"""Add a user to an existing vo and group"""
|
|
140
|
+
|
|
141
|
+
# TODO: The use of parse_obj_as should be moved in to typer itself
|
|
142
|
+
config_repo = parse_obj_as(ConfigSourceUrl, config_repo)
|
|
143
|
+
|
|
144
|
+
repo_path = Path(config_repo.path)
|
|
145
|
+
|
|
146
|
+
new_user = UserConfig(PreferedUsername=preferred_username)
|
|
147
|
+
|
|
148
|
+
config = ConfigSource.create_from_url(backend_url=repo_path).read_config()
|
|
149
|
+
|
|
150
|
+
if vo not in config.Registry:
|
|
151
|
+
typer.echo(f"ERROR: Virtual Organization {vo} does not exist", err=True)
|
|
152
|
+
raise typer.Exit(1)
|
|
153
|
+
|
|
154
|
+
if sub in config.Registry[vo].Users:
|
|
155
|
+
typer.echo(f"ERROR: User {sub} already exists", err=True)
|
|
156
|
+
raise typer.Exit(1)
|
|
157
|
+
|
|
158
|
+
config.Registry[vo].Users[sub] = new_user
|
|
159
|
+
|
|
160
|
+
if not groups:
|
|
161
|
+
groups = [config.Registry[vo].DefaultGroup]
|
|
162
|
+
|
|
163
|
+
for group in set(groups):
|
|
164
|
+
if group not in config.Registry[vo].Groups:
|
|
165
|
+
typer.echo(f"ERROR: Group {group} does not exist in {vo}", err=True)
|
|
166
|
+
raise typer.Exit(1)
|
|
167
|
+
if sub in config.Registry[vo].Groups[group].Users:
|
|
168
|
+
typer.echo(f"ERROR: User {sub} already exists in group {group}", err=True)
|
|
169
|
+
raise typer.Exit(1)
|
|
170
|
+
|
|
171
|
+
config.Registry[vo].Groups[group].Users.add(sub)
|
|
172
|
+
|
|
173
|
+
update_config_and_commit(
|
|
174
|
+
repo_path=repo_path,
|
|
175
|
+
config=config,
|
|
176
|
+
message=f"Added user {sub} ({preferred_username}) to vo {vo} and groups {groups}",
|
|
177
|
+
)
|
|
178
|
+
typer.echo(f"Successfully added user to {config_repo}", err=True)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def update_config_and_commit(repo_path: Path, config: Config, message: str):
|
|
182
|
+
"""Update the yaml file in the repo and commit it"""
|
|
183
|
+
repo = git.Repo.init(repo_path)
|
|
184
|
+
yaml_path = repo_path / "default.yml"
|
|
185
|
+
typer.echo(f"Writing back configuration to {yaml_path}", err=True)
|
|
186
|
+
yaml_path.write_text(yaml.safe_dump(config.dict(exclude_unset=True)))
|
|
187
|
+
repo.index.add([yaml_path.relative_to(repo_path)])
|
|
188
|
+
repo.index.commit(message)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from typer.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from diracx.cli import app
|
|
7
|
+
from diracx.core.config import ConfigSource
|
|
8
|
+
|
|
9
|
+
runner = CliRunner()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize("protocol", [None, "git+file://"])
|
|
13
|
+
def test_generate_cs(tmp_path, protocol):
|
|
14
|
+
cs_repo = f"{tmp_path}"
|
|
15
|
+
if protocol is None:
|
|
16
|
+
cs_repo = f"git+file://{cs_repo}"
|
|
17
|
+
|
|
18
|
+
result = runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
19
|
+
assert result.exit_code == 0
|
|
20
|
+
assert (tmp_path / ".git").is_dir()
|
|
21
|
+
assert (tmp_path / "default.yml").is_file()
|
|
22
|
+
|
|
23
|
+
# Running a second time should fail
|
|
24
|
+
result = runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
25
|
+
assert result.exit_code != 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_add_vo(tmp_path):
|
|
29
|
+
cs_repo = f"git+file://{tmp_path}"
|
|
30
|
+
|
|
31
|
+
# Create the CS
|
|
32
|
+
runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
33
|
+
|
|
34
|
+
# Add a VO to it
|
|
35
|
+
vo1 = "testvo"
|
|
36
|
+
result = runner.invoke(
|
|
37
|
+
app,
|
|
38
|
+
[
|
|
39
|
+
"internal",
|
|
40
|
+
"add-vo",
|
|
41
|
+
cs_repo,
|
|
42
|
+
f"--vo={vo1}",
|
|
43
|
+
"--idp-url=https://idp.invalid",
|
|
44
|
+
"--idp-client-id=idp-client-id",
|
|
45
|
+
],
|
|
46
|
+
)
|
|
47
|
+
assert result.exit_code == 0, result.output
|
|
48
|
+
|
|
49
|
+
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
50
|
+
|
|
51
|
+
assert vo1 in config.Registry
|
|
52
|
+
assert config.Registry[vo1].DefaultGroup == "user"
|
|
53
|
+
assert config.Registry[vo1].IdP.URL == "https://idp.invalid"
|
|
54
|
+
assert config.Registry[vo1].IdP.ClientID == "idp-client-id"
|
|
55
|
+
|
|
56
|
+
# Add a second VO to it
|
|
57
|
+
vo2 = "lhcb"
|
|
58
|
+
result = runner.invoke(
|
|
59
|
+
app,
|
|
60
|
+
[
|
|
61
|
+
"internal",
|
|
62
|
+
"add-vo",
|
|
63
|
+
cs_repo,
|
|
64
|
+
f"--vo={vo2}",
|
|
65
|
+
"--idp-url=https://idp.example.invalid",
|
|
66
|
+
"--idp-client-id=idp-client-id2",
|
|
67
|
+
"--default-group",
|
|
68
|
+
"admin",
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
73
|
+
assert result.exit_code == 0, result.output
|
|
74
|
+
|
|
75
|
+
assert vo2 in config.Registry
|
|
76
|
+
assert config.Registry[vo2].DefaultGroup == "admin"
|
|
77
|
+
assert config.Registry[vo2].IdP.URL == "https://idp.example.invalid"
|
|
78
|
+
assert config.Registry[vo2].IdP.ClientID == "idp-client-id2"
|
|
79
|
+
|
|
80
|
+
# Try to insert a VO that already exists
|
|
81
|
+
result = runner.invoke(
|
|
82
|
+
app,
|
|
83
|
+
[
|
|
84
|
+
"internal",
|
|
85
|
+
"add-vo",
|
|
86
|
+
cs_repo,
|
|
87
|
+
f"--vo={vo1}",
|
|
88
|
+
"--idp-url=https://idp.invalid",
|
|
89
|
+
"--idp-client-id=idp-client-id",
|
|
90
|
+
],
|
|
91
|
+
)
|
|
92
|
+
assert result.exit_code != 0, result.output
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_add_group(tmp_path):
|
|
96
|
+
cs_repo = f"git+file://{tmp_path}"
|
|
97
|
+
vo = "testvo"
|
|
98
|
+
group1 = "testgroup1"
|
|
99
|
+
group2 = "testgroup2"
|
|
100
|
+
|
|
101
|
+
# Create the CS
|
|
102
|
+
runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
103
|
+
runner.invoke(
|
|
104
|
+
app,
|
|
105
|
+
[
|
|
106
|
+
"internal",
|
|
107
|
+
"add-vo",
|
|
108
|
+
cs_repo,
|
|
109
|
+
f"--vo={vo}",
|
|
110
|
+
"--idp-url=https://idp.invalid",
|
|
111
|
+
"--idp-client-id=idp-client-id",
|
|
112
|
+
],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Add a group to it
|
|
116
|
+
result = runner.invoke(
|
|
117
|
+
app, ["internal", "add-group", cs_repo, f"--vo={vo}", f"--group={group1}"]
|
|
118
|
+
)
|
|
119
|
+
assert result.exit_code == 0, result.output
|
|
120
|
+
|
|
121
|
+
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
122
|
+
|
|
123
|
+
assert group1 in config.Registry[vo].Groups
|
|
124
|
+
assert config.Registry[vo].Groups[group1].JobShare == 1000
|
|
125
|
+
assert config.Registry[vo].Groups[group1].Properties == {"NormalUser"}
|
|
126
|
+
assert config.Registry[vo].Groups[group1].Users == set()
|
|
127
|
+
|
|
128
|
+
# Add a second group to it
|
|
129
|
+
result = runner.invoke(
|
|
130
|
+
app,
|
|
131
|
+
[
|
|
132
|
+
"internal",
|
|
133
|
+
"add-group",
|
|
134
|
+
cs_repo,
|
|
135
|
+
f"--vo={vo}",
|
|
136
|
+
f"--group={group2}",
|
|
137
|
+
"--properties",
|
|
138
|
+
"NormalUser",
|
|
139
|
+
"--properties",
|
|
140
|
+
"AdminUser",
|
|
141
|
+
],
|
|
142
|
+
)
|
|
143
|
+
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
144
|
+
assert result.exit_code == 0, result.output
|
|
145
|
+
|
|
146
|
+
assert group2 in config.Registry[vo].Groups
|
|
147
|
+
assert config.Registry[vo].Groups[group2].JobShare == 1000
|
|
148
|
+
assert config.Registry[vo].Groups[group2].Properties == {"AdminUser", "NormalUser"}
|
|
149
|
+
assert config.Registry[vo].Groups[group2].Users == set()
|
|
150
|
+
|
|
151
|
+
# Try to insert a group that already exists
|
|
152
|
+
result = runner.invoke(
|
|
153
|
+
app, ["internal", "add-group", cs_repo, f"--vo={vo}", f"--group={group1}"]
|
|
154
|
+
)
|
|
155
|
+
assert result.exit_code != 0, result.output
|
|
156
|
+
|
|
157
|
+
# Try to insert a group with a non-existing VO
|
|
158
|
+
result = runner.invoke(
|
|
159
|
+
app,
|
|
160
|
+
["internal", "add-group", cs_repo, "--vo=nonexistingvo", f"--group={group1}"],
|
|
161
|
+
)
|
|
162
|
+
assert result.exit_code != 0, result.output
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.mark.parametrize("vo", ["nonexistingvo", "testvo"])
|
|
166
|
+
@pytest.mark.parametrize(
|
|
167
|
+
"user_group", [["nonexisting_group"], ["user"], ["user", "admin"], []]
|
|
168
|
+
)
|
|
169
|
+
def test_add_user(tmp_path, vo, user_group):
|
|
170
|
+
cs_repo = f"git+file://{tmp_path}"
|
|
171
|
+
|
|
172
|
+
sub = "lhcb:chaen"
|
|
173
|
+
preferred_username = "dontCallMeShirley"
|
|
174
|
+
|
|
175
|
+
# Create the CS
|
|
176
|
+
runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
177
|
+
runner.invoke(
|
|
178
|
+
app,
|
|
179
|
+
[
|
|
180
|
+
"internal",
|
|
181
|
+
"add-vo",
|
|
182
|
+
cs_repo,
|
|
183
|
+
"--vo=testvo",
|
|
184
|
+
"--idp-url=https://idp.invalid",
|
|
185
|
+
"--idp-client-id=idp-client-id",
|
|
186
|
+
],
|
|
187
|
+
)
|
|
188
|
+
runner.invoke(
|
|
189
|
+
app, ["internal", "add-group", cs_repo, "--vo=testvo", "--group=user"]
|
|
190
|
+
)
|
|
191
|
+
runner.invoke(
|
|
192
|
+
app, ["internal", "add-group", cs_repo, "--vo=testvo", "--group=admin"]
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
196
|
+
|
|
197
|
+
# Check the user isn't in it
|
|
198
|
+
if vo in config.Registry:
|
|
199
|
+
assert sub not in config.Registry[vo].Users
|
|
200
|
+
|
|
201
|
+
# Add a user to it
|
|
202
|
+
result = runner.invoke(
|
|
203
|
+
app,
|
|
204
|
+
[
|
|
205
|
+
"internal",
|
|
206
|
+
"add-user",
|
|
207
|
+
cs_repo,
|
|
208
|
+
f"--vo={vo}",
|
|
209
|
+
f"--sub={sub}",
|
|
210
|
+
f"--preferred-username={preferred_username}",
|
|
211
|
+
]
|
|
212
|
+
+ [f"--group={x}" for x in user_group],
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if "nonexistingvo" in vo or "nonexisting_group" in user_group:
|
|
216
|
+
assert result.exit_code != 0
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
assert result.exit_code == 0, result.output
|
|
220
|
+
|
|
221
|
+
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
222
|
+
# check the user is defined
|
|
223
|
+
assert vo in config.Registry
|
|
224
|
+
assert sub in config.Registry[vo].Users
|
|
225
|
+
for group in user_group or ["user"]:
|
|
226
|
+
assert config.Registry[vo].Groups[group].Users == {sub}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
import git
|
|
4
|
-
import typer
|
|
5
|
-
import yaml
|
|
6
|
-
from pydantic import parse_obj_as
|
|
7
|
-
|
|
8
|
-
from diracx.core.config import ConfigSource, ConfigSourceUrl
|
|
9
|
-
from diracx.core.config.schema import (
|
|
10
|
-
Config,
|
|
11
|
-
DIRACConfig,
|
|
12
|
-
GroupConfig,
|
|
13
|
-
IdpConfig,
|
|
14
|
-
OperationsConfig,
|
|
15
|
-
RegistryConfig,
|
|
16
|
-
UserConfig,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
from ..utils import AsyncTyper
|
|
20
|
-
from . import legacy
|
|
21
|
-
|
|
22
|
-
app = AsyncTyper()
|
|
23
|
-
app.add_typer(legacy.app, name="legacy")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@app.command()
|
|
27
|
-
def generate_cs(
|
|
28
|
-
config_repo: str,
|
|
29
|
-
*,
|
|
30
|
-
vo: str = "testvo",
|
|
31
|
-
user_group: str = "user",
|
|
32
|
-
idp_url: str = "https://idp.invalid",
|
|
33
|
-
idp_client_id: str = "idp-client-id",
|
|
34
|
-
):
|
|
35
|
-
"""Generate a minimal DiracX configuration repository"""
|
|
36
|
-
# TODO: The use of parse_obj_as should be moved in to typer itself
|
|
37
|
-
config_repo = parse_obj_as(ConfigSourceUrl, config_repo)
|
|
38
|
-
if config_repo.scheme != "git+file":
|
|
39
|
-
raise NotImplementedError("Only git+file:// URLs are supported")
|
|
40
|
-
repo_path = Path(config_repo.path)
|
|
41
|
-
if repo_path.exists() and list(repo_path.iterdir()):
|
|
42
|
-
typer.echo(f"ERROR: Directory {repo_path} already exists", err=True)
|
|
43
|
-
raise typer.Exit(1)
|
|
44
|
-
|
|
45
|
-
registry = RegistryConfig(
|
|
46
|
-
IdP=IdpConfig(URL=idp_url, ClientID=idp_client_id),
|
|
47
|
-
DefaultGroup=user_group,
|
|
48
|
-
Users={},
|
|
49
|
-
Groups={
|
|
50
|
-
user_group: GroupConfig(Properties={"NormalUser"}, Quota=None, Users=set())
|
|
51
|
-
},
|
|
52
|
-
)
|
|
53
|
-
config = Config(
|
|
54
|
-
Registry={vo: registry},
|
|
55
|
-
DIRAC=DIRACConfig(),
|
|
56
|
-
Operations={"Defaults": OperationsConfig()},
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
repo = git.Repo.init(repo_path, initial_branch="master")
|
|
60
|
-
yaml_path = repo_path / "default.yml"
|
|
61
|
-
typer.echo(f"Writing configuration to {yaml_path}", err=True)
|
|
62
|
-
yaml_path.write_text(yaml.safe_dump(config.dict(exclude_unset=True)))
|
|
63
|
-
repo.index.add([yaml_path.relative_to(repo_path)])
|
|
64
|
-
repo.index.commit("Initial commit")
|
|
65
|
-
typer.echo(f"Successfully created repo in {config_repo}", err=True)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@app.command()
|
|
69
|
-
def add_user(
|
|
70
|
-
config_repo: str,
|
|
71
|
-
*,
|
|
72
|
-
vo: str = "testvo",
|
|
73
|
-
user_group: str = "user",
|
|
74
|
-
sub: str = "usersub",
|
|
75
|
-
preferred_username: str = "preferred_username",
|
|
76
|
-
):
|
|
77
|
-
"""Add a user to an existing vo and group"""
|
|
78
|
-
|
|
79
|
-
# TODO: The use of parse_obj_as should be moved in to typer itself
|
|
80
|
-
config_repo = parse_obj_as(ConfigSourceUrl, config_repo)
|
|
81
|
-
|
|
82
|
-
repo_path = Path(config_repo.path)
|
|
83
|
-
|
|
84
|
-
new_user = UserConfig(PreferedUsername=preferred_username)
|
|
85
|
-
|
|
86
|
-
config = ConfigSource.create_from_url(backend_url=repo_path).read_config()
|
|
87
|
-
|
|
88
|
-
if sub in config.Registry[vo].Users:
|
|
89
|
-
typer.echo(f"ERROR: User {sub} already exists", err=True)
|
|
90
|
-
raise typer.Exit(1)
|
|
91
|
-
|
|
92
|
-
config.Registry[vo].Users[sub] = new_user
|
|
93
|
-
|
|
94
|
-
config.Registry[vo].Groups[user_group].Users.add(sub)
|
|
95
|
-
|
|
96
|
-
repo = git.Repo.init(repo_path)
|
|
97
|
-
yaml_path = repo_path / "default.yml"
|
|
98
|
-
typer.echo(f"Writing back configuration to {yaml_path}", err=True)
|
|
99
|
-
yaml_path.write_text(yaml.safe_dump(config.dict(exclude_unset=True)))
|
|
100
|
-
repo.index.add([yaml_path.relative_to(repo_path)])
|
|
101
|
-
repo.index.commit(
|
|
102
|
-
f"Added user {sub} ({preferred_username}) to vo {vo} and user_group {user_group}"
|
|
103
|
-
)
|
|
104
|
-
typer.echo(f"Successfully added user to {config_repo}", err=True)
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from typer.testing import CliRunner
|
|
5
|
-
|
|
6
|
-
from diracx.cli import app
|
|
7
|
-
from diracx.core.config import ConfigSource
|
|
8
|
-
|
|
9
|
-
runner = CliRunner()
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@pytest.mark.parametrize("protocol", [None, "git+file://"])
|
|
13
|
-
def test_generate_cs(tmp_path, protocol):
|
|
14
|
-
cs_repo = f"{tmp_path}"
|
|
15
|
-
if protocol is None:
|
|
16
|
-
cs_repo = f"git+file://{cs_repo}"
|
|
17
|
-
|
|
18
|
-
result = runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
19
|
-
assert result.exit_code == 0
|
|
20
|
-
assert (tmp_path / ".git").is_dir()
|
|
21
|
-
assert (tmp_path / "default.yml").is_file()
|
|
22
|
-
|
|
23
|
-
# Running a second time should fail
|
|
24
|
-
result = runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
25
|
-
assert result.exit_code != 0
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@pytest.mark.parametrize("vo", ["nonexistingvo", "testvo"])
|
|
29
|
-
@pytest.mark.parametrize("user_group", ["nonexisting_group", "user"])
|
|
30
|
-
def test_add_user(tmp_path, vo, user_group):
|
|
31
|
-
cs_repo = f"git+file://{tmp_path}"
|
|
32
|
-
|
|
33
|
-
sub = "lhcb:chaen"
|
|
34
|
-
preferred_username = "dontCallMeShirley"
|
|
35
|
-
|
|
36
|
-
# Create the CS
|
|
37
|
-
runner.invoke(app, ["internal", "generate-cs", cs_repo])
|
|
38
|
-
|
|
39
|
-
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
40
|
-
|
|
41
|
-
# Check the user isn't in it
|
|
42
|
-
if vo in config.Registry:
|
|
43
|
-
assert sub not in config.Registry[vo].Users
|
|
44
|
-
|
|
45
|
-
# Add a user to it
|
|
46
|
-
result = runner.invoke(
|
|
47
|
-
app,
|
|
48
|
-
[
|
|
49
|
-
"internal",
|
|
50
|
-
"add-user",
|
|
51
|
-
cs_repo,
|
|
52
|
-
"--vo",
|
|
53
|
-
vo,
|
|
54
|
-
"--user-group",
|
|
55
|
-
user_group,
|
|
56
|
-
"--sub",
|
|
57
|
-
sub,
|
|
58
|
-
"--preferred-username",
|
|
59
|
-
preferred_username,
|
|
60
|
-
],
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
if "nonexisting" in vo or "nonexisting" in user_group:
|
|
64
|
-
assert result.exit_code != 0
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
assert result.exit_code == 0, result.output
|
|
68
|
-
|
|
69
|
-
config = ConfigSource.create_from_url(backend_url=cs_repo).read_config()
|
|
70
|
-
# check the user is defined
|
|
71
|
-
assert vo in config.Registry
|
|
72
|
-
assert sub in config.Registry[vo].Users
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{diracx-cli-0.0.1a7 → diracx-cli-0.0.1a8}/tests/legacy/cs_sync/convert_integration_test.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|