remoteRF-server 1.1.0.dev33__py3-none-any.whl → 1.1.0.dev35__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.
- remoteRF_server/server/commands.py +2 -2
- remoteRF_server/server/federation/cli.py +43 -6
- remoteRF_server/server/federation/cluster.py +13 -7
- {remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/METADATA +1 -1
- {remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/RECORD +9 -9
- {remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/WHEEL +0 -0
- {remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/entry_points.txt +0 -0
- {remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/licenses/LICENSE +0 -0
- {remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/top_level.txt +0 -0
|
@@ -153,8 +153,8 @@ def _table() -> list[Command]:
|
|
|
153
153
|
Command(("cluster invite",), "Invite code for a lab: cluster invite <name>", _federation("invite"), "Clusters"),
|
|
154
154
|
Command(("cluster join",), "Join with a code: cluster join <code>", _federation("join"), "Clusters"),
|
|
155
155
|
Command(("cluster remove",), "Leave or disband: cluster remove <name>", _federation("remove"), "Clusters"),
|
|
156
|
-
Command(("cluster groups",), "
|
|
157
|
-
Command(("cluster codes",), "
|
|
156
|
+
Command(("cluster groups",), "Choose which of your groups a cluster shares (interactive)", _federation("groups"), "Clusters"),
|
|
157
|
+
Command(("cluster codes",), "Codes for your users to join a cluster: cluster codes [count]", _federation("codes"), "Clusters"),
|
|
158
158
|
Command(("cluster accept",), "", _federation("accept"), "Clusters", hidden=True),
|
|
159
159
|
Command(("global cluster",), "", _federation("cluster"), "Global", hidden=True),
|
|
160
160
|
# ---- gist ----
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import re
|
|
20
21
|
import sys
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
@@ -106,7 +107,7 @@ def _ask_for(verb: str) -> str:
|
|
|
106
107
|
what = {"create": "Name for the new cluster (short slug, e.g. ucla-lab)",
|
|
107
108
|
"invite": "Which cluster to invite a lab into",
|
|
108
109
|
"accept": "Which cluster's pending join to accept",
|
|
109
|
-
"groups": "Which cluster
|
|
110
|
+
"groups": "Which cluster to share groups with",
|
|
110
111
|
"codes": "Which cluster to make student codes for",
|
|
111
112
|
"remove": "Which cluster to leave (revokes the contract)"}[verb]
|
|
112
113
|
if verb != "create" and names:
|
|
@@ -115,6 +116,40 @@ def _ask_for(verb: str) -> str:
|
|
|
115
116
|
return _input(f"{what}{f' [{default}]' if default else ''}: ") or default
|
|
116
117
|
|
|
117
118
|
|
|
119
|
+
def _share_groups(name: str) -> int:
|
|
120
|
+
"""Pick, by number, which of this server's groups the cluster shares.
|
|
121
|
+
Toggling: a shared group chosen again is unshared."""
|
|
122
|
+
from ...common.utils.ansi_codes import Sty
|
|
123
|
+
from ..user_group_handler import user_group_handler
|
|
124
|
+
from . import cluster
|
|
125
|
+
|
|
126
|
+
all_groups = user_group_handler.get_all_user_groups(include_users=False) or []
|
|
127
|
+
if not all_groups:
|
|
128
|
+
_print(" No user groups yet: make one with `groups create`, then share it here.", Sty.WARNING)
|
|
129
|
+
return 1
|
|
130
|
+
shared = {g["group_id"] for g in cluster.groups(name)}
|
|
131
|
+
print(f" Groups on this server (shared with {name} are marked):")
|
|
132
|
+
for index, group in enumerate(all_groups, 1):
|
|
133
|
+
mark = "*" if int(group["group_id"]) in shared else " "
|
|
134
|
+
devices = group.get("devices_whitelist") or []
|
|
135
|
+
print(f" {mark} {index:>2} {group['group_name']} ({len(devices)} device{'s' if len(devices) != 1 else ''})")
|
|
136
|
+
answer = _input("Toggle by number, e.g. 2 or 1,3 (Enter to leave as is): ")
|
|
137
|
+
picks = [part for part in re.split(r"[\s,]+", answer) if part]
|
|
138
|
+
if not picks:
|
|
139
|
+
return 0
|
|
140
|
+
for pick in picks:
|
|
141
|
+
if not pick.isdigit() or not 1 <= int(pick) <= len(all_groups):
|
|
142
|
+
_print(f" {pick} is not a number in the list.", Sty.WARNING)
|
|
143
|
+
continue
|
|
144
|
+
group = all_groups[int(pick) - 1]
|
|
145
|
+
if int(group["group_id"]) in shared:
|
|
146
|
+
_print(f" {cluster.detach_group(name, str(group['group_id']))} is no longer shared with {name}.", Sty.GRAY)
|
|
147
|
+
else:
|
|
148
|
+
_print(f" {cluster.attach_group(name, str(group['group_id']))} is now shared with {name}; "
|
|
149
|
+
"everyone in the cluster gets it.", Sty.GRAY)
|
|
150
|
+
return 0
|
|
151
|
+
|
|
152
|
+
|
|
118
153
|
def _input(prompt: str) -> str:
|
|
119
154
|
# The console's own prompt session: a bare input() inside the running
|
|
120
155
|
# server fights prompt_toolkit for the terminal and kills the process.
|
|
@@ -128,7 +163,7 @@ def _input(prompt: str) -> str:
|
|
|
128
163
|
|
|
129
164
|
|
|
130
165
|
_USAGE = ("Usage: cluster list | cluster create|invite|remove <name> | cluster join <code> | "
|
|
131
|
-
"cluster groups <name>
|
|
166
|
+
"cluster groups [<name>] | cluster codes [<name>] [count]")
|
|
132
167
|
|
|
133
168
|
|
|
134
169
|
def cmd_cluster(argv: list[str]) -> int:
|
|
@@ -140,6 +175,10 @@ def cmd_cluster(argv: list[str]) -> int:
|
|
|
140
175
|
words = [a for a in argv[1:] if not a.startswith("--")]
|
|
141
176
|
if verb == "list" and not words:
|
|
142
177
|
return cmd_clusters()
|
|
178
|
+
if verb == "codes" and words and words[0].isdigit() and words[0] not in cluster.load():
|
|
179
|
+
words = [_ask_for(verb), words[0]] # `cluster codes 3`: the count came first
|
|
180
|
+
if not words[0]:
|
|
181
|
+
return 2
|
|
143
182
|
if not words and verb in ("create", "invite", "accept", "join", "remove", "groups", "codes"):
|
|
144
183
|
# Leaving the argument out asks for it (docs/deployment-groups.md §8).
|
|
145
184
|
answer = _ask_for(verb)
|
|
@@ -167,10 +206,8 @@ def cmd_cluster(argv: list[str]) -> int:
|
|
|
167
206
|
elif verb == "remove" and arg:
|
|
168
207
|
_print(f" {arg}: {cluster.remove(arg)}.", Sty.GRAY)
|
|
169
208
|
elif verb == "groups" and len(words) == 1:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
_print(f" Attach one with `cluster groups {arg} add <group>`; students of every lab in the "
|
|
173
|
-
"cluster get it. Group names stay on this server.", Sty.GRAY)
|
|
209
|
+
cluster._find(cluster.load(), arg)
|
|
210
|
+
return _share_groups(arg)
|
|
174
211
|
elif verb == "groups" and len(words) == 3 and words[1] in ("add", "remove"):
|
|
175
212
|
if words[1] == "add":
|
|
176
213
|
_print(f" {cluster.attach_group(arg, words[2])} is now shared with {arg}.", Sty.GRAY)
|
|
@@ -623,22 +623,28 @@ def _sync_contract(party: dict, peer: str, directory: list[dict]) -> str:
|
|
|
623
623
|
parties = {terms.get("deployment_a"), terms.get("deployment_b")}
|
|
624
624
|
if parties != {peer, store.local_deployment_id} or item["version"] != 1 or not item.get("events"):
|
|
625
625
|
continue
|
|
626
|
-
store
|
|
627
|
-
"protocol_version": "2", "terms": terms, "events": item["events"],
|
|
628
|
-
}))
|
|
629
|
-
if store.show(item["contract_id"], 1)["state"] != "revoked":
|
|
626
|
+
if _import(store, terms, item["events"]) != "revoked":
|
|
630
627
|
candidates.append((terms["valid_from"], item["contract_id"]))
|
|
631
628
|
if not candidates:
|
|
632
629
|
return party["state"]
|
|
633
630
|
party["contract_id"] = max(candidates)[1]
|
|
634
631
|
for item in directory:
|
|
635
632
|
if item["contract_id"] == party["contract_id"] and item.get("terms"):
|
|
636
|
-
store
|
|
637
|
-
"protocol_version": "2", "terms": item["terms"], "events": item["events"],
|
|
638
|
-
}))
|
|
633
|
+
_import(store, item["terms"], item["events"])
|
|
639
634
|
return store.show(party["contract_id"], 1)["state"]
|
|
640
635
|
|
|
641
636
|
|
|
637
|
+
def _import(store: ContractStore, terms: dict, events: list) -> str:
|
|
638
|
+
"""Import a directory entry; a contract already revoked here (an earlier
|
|
639
|
+
cluster between the same labs) is simply reported as such."""
|
|
640
|
+
try:
|
|
641
|
+
store.import_bundle(canonical_json({"protocol_version": "2", "terms": terms, "events": events}))
|
|
642
|
+
except ContractError as exc:
|
|
643
|
+
if exc.code not in ("contract_revoked", "contract_terminal", "invalid_transition"):
|
|
644
|
+
raise
|
|
645
|
+
return store.show(terms["contract_id"], 1)["state"]
|
|
646
|
+
|
|
647
|
+
|
|
642
648
|
def _advance(party: dict, peer: str, directory: list[dict], *, label: str, notify, is_member: bool) -> None:
|
|
643
649
|
"""Move one bilateral contract one step; notify on active / revoked."""
|
|
644
650
|
before = party.get("state")
|
|
@@ -52,7 +52,7 @@ remoteRF_server/host/host_tunnel_server.py,sha256=sz06xOtFbaDZSy1UQfw6faKhJmuOy5
|
|
|
52
52
|
remoteRF_server/server/__init__.py,sha256=-1h_xQjiE66oiWRO-vnLGJ4smpgUNhFuV-ZreW9_tD4,667
|
|
53
53
|
remoteRF_server/server/acc_perms.py,sha256=2loCvstVu8uvsb2uCv2Sk7iNQ5GyO_sBIkPQFzqOaRI,13334
|
|
54
54
|
remoteRF_server/server/cert_provider.py,sha256=CRDrZL4DSf-eL6wzPJzwucrzPLOr2PvNRq5V--oZtr0,6272
|
|
55
|
-
remoteRF_server/server/commands.py,sha256=
|
|
55
|
+
remoteRF_server/server/commands.py,sha256=xG_RAYm0nHDqN74b-QPHizw2OodfrYAvdYlVNNi7tGE,17279
|
|
56
56
|
remoteRF_server/server/console.py,sha256=J91LfwPVmBgielOxfE740NOYduekggyZkqi-47y_A4w,4472
|
|
57
57
|
remoteRF_server/server/control.py,sha256=Uq1Fk0zIoHHX_EXfz3OuNle8urvO_UhAiB5Lk5cs2e4,5177
|
|
58
58
|
remoteRF_server/server/device_manager.py,sha256=nRTvk_jeZFU3QnlM6qf3DIckU2V2e_gyDeD5gW14KQI,38802
|
|
@@ -70,8 +70,8 @@ remoteRF_server/server/deployment/__init__.py,sha256=YV86uafd8XiGD7sLaqNfO5xxmt5
|
|
|
70
70
|
remoteRF_server/server/deployment/capabilities.py,sha256=20RxmGG03VoQQZnV3jGhc-3i9B5hOl5fCopnsErFjPk,872
|
|
71
71
|
remoteRF_server/server/federation/__init__.py,sha256=auqdGiGnEGIKQiXX2jFVt8iWmfyZ-V3w4dUmtWDKAZE,735
|
|
72
72
|
remoteRF_server/server/federation/admin.py,sha256=t3F17FIytekzclPICmaFJEA4-F5OuWcDx3rgmFga17A,9187
|
|
73
|
-
remoteRF_server/server/federation/cli.py,sha256=
|
|
74
|
-
remoteRF_server/server/federation/cluster.py,sha256=
|
|
73
|
+
remoteRF_server/server/federation/cli.py,sha256=m3fzURqmnZH1pO7wHyqg5EUoo4ZPiw7P3K_9q8SXQu8,10703
|
|
74
|
+
remoteRF_server/server/federation/cluster.py,sha256=yKGFRAI-RHB_xZivJkxaK_Fniz2XGRUfArJXM_DH2WQ,33207
|
|
75
75
|
remoteRF_server/server/federation/contracts.py,sha256=NYh7lb_9etJHFfzXwevjC4HfSBUypLxg5xGeBgydLF4,29305
|
|
76
76
|
remoteRF_server/server/federation/destination.py,sha256=XawmGCiVbL_MfNNn8xwV9_bYxUDW1L7efCpL11K2JAc,35909
|
|
77
77
|
remoteRF_server/server/federation/devices.py,sha256=n5MvnD0UZyl4sFpiekG9ProanWXTYizYq6YchCflLgY,18061
|
|
@@ -90,9 +90,9 @@ remoteRF_server/tools/gen_certs.py,sha256=Vbyu8JjRu__UYPVmyCRXt5SOw0Za1HV7g1md3G
|
|
|
90
90
|
remoteRF_server/tools/gist_status.py,sha256=SFv2iQgQrIKT9gxKrU-_Dr7S0p0rE4wQM6J3OC6FcoM,4649
|
|
91
91
|
remoteRF_server/tools/gist_status_testing.py,sha256=IYw7UkPjAUpKDTtIlnYGb5g6SJkwz4TRGUfJJ6f4LhQ,2690
|
|
92
92
|
remoteRF_server/tools/introspect_uhd.py,sha256=vUpPb0rM9tsHPxWadS3EfMDE-gMd-TvNV0TjrZOQqBY,4531
|
|
93
|
-
remoterf_server-1.1.0.
|
|
94
|
-
remoterf_server-1.1.0.
|
|
95
|
-
remoterf_server-1.1.0.
|
|
96
|
-
remoterf_server-1.1.0.
|
|
97
|
-
remoterf_server-1.1.0.
|
|
98
|
-
remoterf_server-1.1.0.
|
|
93
|
+
remoterf_server-1.1.0.dev35.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
94
|
+
remoterf_server-1.1.0.dev35.dist-info/METADATA,sha256=meRywJNeTPy2Yol5Gc-G6zGPXnvPdyIkH-JlIe9H93Y,42069
|
|
95
|
+
remoterf_server-1.1.0.dev35.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
96
|
+
remoterf_server-1.1.0.dev35.dist-info/entry_points.txt,sha256=_bbKk65PfYi-n6zUjqOiOwcalf_Ws1TCGATIe1TukxE,136
|
|
97
|
+
remoterf_server-1.1.0.dev35.dist-info/top_level.txt,sha256=NuYWe8dSx3qdbxshWBwOgx68EHmJ9I8Zvxam4ARsQ_I,30
|
|
98
|
+
remoterf_server-1.1.0.dev35.dist-info/RECORD,,
|
|
File without changes
|
{remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{remoterf_server-1.1.0.dev33.dist-info → remoterf_server-1.1.0.dev35.dist-info}/top_level.txt
RENAMED
|
File without changes
|