flwr-nightly 1.26.0.dev20260121__py3-none-any.whl → 1.26.0.dev20260122__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.
- flwr/cli/app.py +7 -7
- flwr/cli/app_cmd/publish.py +6 -30
- flwr/cli/app_cmd/review.py +3 -37
- flwr/cli/auth_plugin/auth_plugin.py +5 -10
- flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
- flwr/cli/auth_plugin/oidc_cli_plugin.py +27 -31
- flwr/cli/constant.py +8 -2
- flwr/cli/federation/ls.py +19 -35
- flwr/cli/login/login.py +29 -42
- flwr/cli/supernode/ls.py +19 -33
- flwr/cli/supernode/register.py +17 -30
- flwr/cli/supernode/unregister.py +17 -31
- flwr/cli/utils.py +38 -208
- flwr/server/superlink/linkstate/linkstate_factory.py +4 -4
- flwr/server/superlink/linkstate/sql_linkstate.py +1053 -35
- flwr/server/superlink/linkstate/sqlite_linkstate.py +3 -48
- flwr/server/superlink/linkstate/utils.py +47 -0
- flwr/supercore/constant.py +3 -0
- flwr/supercore/object_store/sql_object_store.py +82 -0
- flwr/supercore/sql_mixin.py +22 -5
- {flwr_nightly-1.26.0.dev20260121.dist-info → flwr_nightly-1.26.0.dev20260122.dist-info}/METADATA +1 -1
- {flwr_nightly-1.26.0.dev20260121.dist-info → flwr_nightly-1.26.0.dev20260122.dist-info}/RECORD +24 -23
- {flwr_nightly-1.26.0.dev20260121.dist-info → flwr_nightly-1.26.0.dev20260122.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.26.0.dev20260121.dist-info → flwr_nightly-1.26.0.dev20260122.dist-info}/entry_points.txt +0 -0
flwr/cli/app.py
CHANGED
|
@@ -67,17 +67,17 @@ app.command(**ALLOW_EXTRAS)(log)
|
|
|
67
67
|
app.command("list", **ALLOW_EXTRAS)(ls)
|
|
68
68
|
app.command(hidden=True, **ALLOW_EXTRAS)(ls)
|
|
69
69
|
app.command(**ALLOW_EXTRAS)(stop)
|
|
70
|
-
app.command()(login)
|
|
70
|
+
app.command(**ALLOW_EXTRAS)(login)
|
|
71
71
|
app.command(**ALLOW_EXTRAS)(pull)
|
|
72
72
|
|
|
73
73
|
# Create supernode command group
|
|
74
74
|
supernode_app = typer.Typer(help="Manage SuperNodes")
|
|
75
|
-
supernode_app.command()(supernode_register)
|
|
76
|
-
supernode_app.command()(supernode_unregister)
|
|
75
|
+
supernode_app.command(**ALLOW_EXTRAS)(supernode_register)
|
|
76
|
+
supernode_app.command(**ALLOW_EXTRAS)(supernode_unregister)
|
|
77
77
|
# Make it appear as "list"
|
|
78
|
-
supernode_app.command("list")(supernode_list)
|
|
78
|
+
supernode_app.command("list", **ALLOW_EXTRAS)(supernode_list)
|
|
79
79
|
# Hide "ls" command (left as alias)
|
|
80
|
-
supernode_app.command(hidden=True)(supernode_list)
|
|
80
|
+
supernode_app.command(hidden=True, **ALLOW_EXTRAS)(supernode_list)
|
|
81
81
|
app.add_typer(supernode_app, name="supernode")
|
|
82
82
|
|
|
83
83
|
# Create app command group
|
|
@@ -89,9 +89,9 @@ app.add_typer(app_app, name="app")
|
|
|
89
89
|
# Create federation command group
|
|
90
90
|
federation_app = typer.Typer(help="Manage Federations")
|
|
91
91
|
# Make it appear as "list"
|
|
92
|
-
federation_app.command("list")(federation_list)
|
|
92
|
+
federation_app.command("list", **ALLOW_EXTRAS)(federation_list)
|
|
93
93
|
# Hide "ls" command (left as alias)
|
|
94
|
-
federation_app.command(hidden=True)(federation_list)
|
|
94
|
+
federation_app.command(hidden=True, **ALLOW_EXTRAS)(federation_list)
|
|
95
95
|
app.add_typer(federation_app, name="federation")
|
|
96
96
|
|
|
97
97
|
# Create config command group
|
flwr/cli/app_cmd/publish.py
CHANGED
|
@@ -23,7 +23,6 @@ import requests
|
|
|
23
23
|
import typer
|
|
24
24
|
from requests import Response
|
|
25
25
|
|
|
26
|
-
from flwr.common.constant import FAB_CONFIG_FILE
|
|
27
26
|
from flwr.supercore.constant import (
|
|
28
27
|
APP_PUBLISH_EXCLUDE_PATTERNS,
|
|
29
28
|
APP_PUBLISH_INCLUDE_PATTERNS,
|
|
@@ -33,18 +32,17 @@ from flwr.supercore.constant import (
|
|
|
33
32
|
MAX_TOTAL_BYTES,
|
|
34
33
|
MIME_MAP,
|
|
35
34
|
PLATFORM_API_URL,
|
|
35
|
+
SUPERGRID_ADDRESS,
|
|
36
36
|
UTF8,
|
|
37
37
|
)
|
|
38
38
|
from flwr.supercore.version import package_version as flwr_version
|
|
39
39
|
|
|
40
40
|
from ..auth_plugin.oidc_cli_plugin import OidcCliPlugin
|
|
41
|
-
from ..
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
from ..utils import (
|
|
42
|
+
build_pathspec,
|
|
43
|
+
load_cli_auth_plugin_from_connection,
|
|
44
|
+
load_gitignore_patterns,
|
|
45
45
|
)
|
|
46
|
-
from ..constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
47
|
-
from ..utils import build_pathspec, load_cli_auth_plugin, load_gitignore_patterns
|
|
48
46
|
|
|
49
47
|
|
|
50
48
|
# pylint: disable=too-many-locals
|
|
@@ -55,35 +53,13 @@ def publish(
|
|
|
55
53
|
help="Project directory to upload (defaults to current directory)."
|
|
56
54
|
),
|
|
57
55
|
] = Path("."),
|
|
58
|
-
federation: Annotated[
|
|
59
|
-
str | None,
|
|
60
|
-
typer.Argument(
|
|
61
|
-
help="Name of the federation used for login before publishing app."
|
|
62
|
-
),
|
|
63
|
-
] = None,
|
|
64
|
-
federation_config_overrides: Annotated[
|
|
65
|
-
list[str] | None,
|
|
66
|
-
typer.Option(
|
|
67
|
-
"--federation-config",
|
|
68
|
-
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
69
|
-
),
|
|
70
|
-
] = None,
|
|
71
56
|
) -> None:
|
|
72
57
|
"""Publish a Flower App to the Flower Platform.
|
|
73
58
|
|
|
74
59
|
This command uploads your app project to the Flower Platform. Files are filtered
|
|
75
60
|
based on .gitignore patterns and allowed file extensions.
|
|
76
61
|
"""
|
|
77
|
-
|
|
78
|
-
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
79
|
-
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
80
|
-
config = process_loaded_project_config(config, errors, warnings)
|
|
81
|
-
federation, federation_config = validate_federation_in_project_config(
|
|
82
|
-
federation, config, federation_config_overrides
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
# Load the authentication plugin
|
|
86
|
-
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
|
62
|
+
auth_plugin = load_cli_auth_plugin_from_connection(SUPERGRID_ADDRESS)
|
|
87
63
|
auth_plugin.load_tokens()
|
|
88
64
|
if not isinstance(auth_plugin, OidcCliPlugin) or not auth_plugin.access_token:
|
|
89
65
|
typer.secho(
|
flwr/cli/app_cmd/review.py
CHANGED
|
@@ -28,8 +28,7 @@ from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
|
28
28
|
|
|
29
29
|
from flwr.common import now
|
|
30
30
|
from flwr.common.config import get_flwr_dir
|
|
31
|
-
from flwr.
|
|
32
|
-
from flwr.supercore.constant import PLATFORM_API_URL
|
|
31
|
+
from flwr.supercore.constant import PLATFORM_API_URL, SUPERGRID_ADDRESS
|
|
33
32
|
from flwr.supercore.primitives.asymmetric_ed25519 import (
|
|
34
33
|
create_message_to_sign,
|
|
35
34
|
load_private_key,
|
|
@@ -39,14 +38,8 @@ from flwr.supercore.utils import parse_app_spec, request_download_link
|
|
|
39
38
|
from flwr.supercore.version import package_version as flwr_version
|
|
40
39
|
|
|
41
40
|
from ..auth_plugin.oidc_cli_plugin import OidcCliPlugin
|
|
42
|
-
from ..config_utils import (
|
|
43
|
-
load_and_validate,
|
|
44
|
-
process_loaded_project_config,
|
|
45
|
-
validate_federation_in_project_config,
|
|
46
|
-
)
|
|
47
|
-
from ..constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
48
41
|
from ..install import install_from_fab
|
|
49
|
-
from ..utils import
|
|
42
|
+
from ..utils import load_cli_auth_plugin_from_connection
|
|
50
43
|
|
|
51
44
|
TRY_AGAIN_MESSAGE = "Please try again or press CTRL+C to abort.\n"
|
|
52
45
|
|
|
@@ -60,38 +53,11 @@ def review(
|
|
|
60
53
|
"Version is optional; defaults to the latest."
|
|
61
54
|
),
|
|
62
55
|
],
|
|
63
|
-
app_dir_login: Annotated[
|
|
64
|
-
Path,
|
|
65
|
-
typer.Argument(
|
|
66
|
-
help="Project directory to used for login before reviewing app."
|
|
67
|
-
),
|
|
68
|
-
] = Path("."),
|
|
69
|
-
federation: Annotated[
|
|
70
|
-
str | None,
|
|
71
|
-
typer.Argument(
|
|
72
|
-
help="Name of the federation used for login before reviewing app."
|
|
73
|
-
),
|
|
74
|
-
] = None,
|
|
75
|
-
federation_config_overrides: Annotated[
|
|
76
|
-
list[str] | None,
|
|
77
|
-
typer.Option(
|
|
78
|
-
"--federation-config",
|
|
79
|
-
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
80
|
-
),
|
|
81
|
-
] = None,
|
|
82
56
|
) -> None:
|
|
83
57
|
"""Download a FAB for <APP-ID>, unpack it for manual review, and upon confirmation
|
|
84
58
|
sign & submit the review to the Platform."""
|
|
85
|
-
|
|
86
|
-
pyproject_path = app_dir_login / FAB_CONFIG_FILE if app_dir_login else None
|
|
87
|
-
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
88
|
-
config = process_loaded_project_config(config, errors, warnings)
|
|
89
|
-
federation, federation_config = validate_federation_in_project_config(
|
|
90
|
-
federation, config, federation_config_overrides
|
|
91
|
-
)
|
|
59
|
+
auth_plugin = load_cli_auth_plugin_from_connection(SUPERGRID_ADDRESS)
|
|
92
60
|
|
|
93
|
-
# Load the authentication plugin
|
|
94
|
-
auth_plugin = load_cli_auth_plugin(app_dir_login, federation, federation_config)
|
|
95
61
|
auth_plugin.load_tokens()
|
|
96
62
|
if not isinstance(auth_plugin, OidcCliPlugin) or not auth_plugin.access_token:
|
|
97
63
|
typer.secho(
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
19
|
from collections.abc import Sequence
|
|
20
|
-
from pathlib import Path
|
|
21
20
|
|
|
22
21
|
from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
|
|
23
22
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
@@ -35,8 +34,8 @@ class CliAuthPlugin(ABC):
|
|
|
35
34
|
|
|
36
35
|
Parameters
|
|
37
36
|
----------
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
host : str
|
|
38
|
+
The address of the SuperLink Control API server.
|
|
40
39
|
"""
|
|
41
40
|
|
|
42
41
|
@staticmethod
|
|
@@ -66,20 +65,16 @@ class CliAuthPlugin(ABC):
|
|
|
66
65
|
"""
|
|
67
66
|
|
|
68
67
|
@abstractmethod
|
|
69
|
-
def __init__(self,
|
|
68
|
+
def __init__(self, host: str):
|
|
70
69
|
"""Abstract constructor."""
|
|
71
70
|
|
|
72
71
|
@abstractmethod
|
|
73
72
|
def store_tokens(self, credentials: AccountAuthCredentials) -> None:
|
|
74
|
-
"""Store authentication tokens to the
|
|
75
|
-
|
|
76
|
-
The credentials, including tokens, will be saved as a JSON file
|
|
77
|
-
at `credentials_path`.
|
|
78
|
-
"""
|
|
73
|
+
"""Store authentication tokens to the credential store."""
|
|
79
74
|
|
|
80
75
|
@abstractmethod
|
|
81
76
|
def load_tokens(self) -> None:
|
|
82
|
-
"""Load authentication tokens from the
|
|
77
|
+
"""Load authentication tokens from the credential store."""
|
|
83
78
|
|
|
84
79
|
@abstractmethod
|
|
85
80
|
def write_tokens_to_metadata(
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from collections.abc import Sequence
|
|
19
|
-
from pathlib import Path
|
|
20
19
|
|
|
21
20
|
from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
|
|
22
21
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
@@ -57,7 +56,7 @@ class NoOpCliAuthPlugin(CliAuthPlugin):
|
|
|
57
56
|
"""
|
|
58
57
|
raise LoginError("Account authentication is not enabled on this SuperLink.")
|
|
59
58
|
|
|
60
|
-
def __init__(self,
|
|
59
|
+
def __init__(self, host: str = "") -> None:
|
|
61
60
|
pass
|
|
62
61
|
|
|
63
62
|
def store_tokens(self, credentials: AccountAuthCredentials) -> None:
|
|
@@ -15,27 +15,25 @@
|
|
|
15
15
|
"""Flower CLI account auth plugin for OIDC."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import json
|
|
19
18
|
import time
|
|
20
19
|
import webbrowser
|
|
21
20
|
from collections.abc import Sequence
|
|
22
|
-
from pathlib import Path
|
|
23
|
-
from typing import Any
|
|
24
21
|
|
|
25
22
|
import typer
|
|
26
23
|
|
|
27
|
-
from flwr.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
AuthnType,
|
|
24
|
+
from flwr.cli.constant import (
|
|
25
|
+
ACCESS_TOKEN_STORE_KEY,
|
|
26
|
+
AUTHN_TYPE_STORE_KEY,
|
|
27
|
+
REFRESH_TOKEN_STORE_KEY,
|
|
32
28
|
)
|
|
29
|
+
from flwr.common.constant import ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, AuthnType
|
|
33
30
|
from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
|
|
34
31
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
35
32
|
GetAuthTokensRequest,
|
|
36
33
|
GetAuthTokensResponse,
|
|
37
34
|
)
|
|
38
35
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
36
|
+
from flwr.supercore.credential_store import get_credential_store
|
|
39
37
|
|
|
40
38
|
from .auth_plugin import CliAuthPlugin, LoginError
|
|
41
39
|
|
|
@@ -47,10 +45,11 @@ class OidcCliPlugin(CliAuthPlugin):
|
|
|
47
45
|
access to Flower SuperLink.
|
|
48
46
|
"""
|
|
49
47
|
|
|
50
|
-
def __init__(self,
|
|
48
|
+
def __init__(self, host: str):
|
|
51
49
|
self.access_token: str | None = None
|
|
52
50
|
self.refresh_token: str | None = None
|
|
53
|
-
self.
|
|
51
|
+
self.host = host
|
|
52
|
+
self.store = get_credential_store()
|
|
54
53
|
|
|
55
54
|
@staticmethod
|
|
56
55
|
def login(
|
|
@@ -109,32 +108,29 @@ class OidcCliPlugin(CliAuthPlugin):
|
|
|
109
108
|
raise LoginError("Process timed out.")
|
|
110
109
|
|
|
111
110
|
def store_tokens(self, credentials: AccountAuthCredentials) -> None:
|
|
112
|
-
"""Store authentication tokens to the
|
|
111
|
+
"""Store authentication tokens to the credential store."""
|
|
112
|
+
host = self.host
|
|
113
|
+
# Retrieve tokens
|
|
114
|
+
access_token = credentials.access_token
|
|
115
|
+
refresh_token = credentials.refresh_token
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
""
|
|
117
|
-
self.
|
|
118
|
-
self.refresh_token = credentials.refresh_token
|
|
119
|
-
json_dict = {
|
|
120
|
-
AUTHN_TYPE_JSON_KEY: AuthnType.OIDC,
|
|
121
|
-
ACCESS_TOKEN_KEY: credentials.access_token,
|
|
122
|
-
REFRESH_TOKEN_KEY: credentials.refresh_token,
|
|
123
|
-
}
|
|
117
|
+
# Store tokens in the credential store
|
|
118
|
+
self.store.set(AUTHN_TYPE_STORE_KEY % host, AuthnType.OIDC.encode("utf-8"))
|
|
119
|
+
self.store.set(ACCESS_TOKEN_STORE_KEY % host, access_token.encode("utf-8"))
|
|
120
|
+
self.store.set(REFRESH_TOKEN_STORE_KEY % host, refresh_token.encode("utf-8"))
|
|
124
121
|
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
# Update internal state
|
|
123
|
+
self.access_token = access_token
|
|
124
|
+
self.refresh_token = refresh_token
|
|
127
125
|
|
|
128
126
|
def load_tokens(self) -> None:
|
|
129
|
-
"""Load authentication tokens from the
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
access_token = json_dict.get(ACCESS_TOKEN_KEY)
|
|
133
|
-
refresh_token = json_dict.get(REFRESH_TOKEN_KEY)
|
|
127
|
+
"""Load authentication tokens from the credential store."""
|
|
128
|
+
access_token_bytes = self.store.get(ACCESS_TOKEN_STORE_KEY % self.host)
|
|
129
|
+
refresh_token_bytes = self.store.get(REFRESH_TOKEN_STORE_KEY % self.host)
|
|
134
130
|
|
|
135
|
-
if
|
|
136
|
-
self.access_token =
|
|
137
|
-
self.refresh_token =
|
|
131
|
+
if access_token_bytes is not None and refresh_token_bytes is not None:
|
|
132
|
+
self.access_token = access_token_bytes.decode("utf-8")
|
|
133
|
+
self.refresh_token = refresh_token_bytes.decode("utf-8")
|
|
138
134
|
|
|
139
135
|
def write_tokens_to_metadata(
|
|
140
136
|
self, metadata: Sequence[tuple[str, str | bytes]]
|
flwr/cli/constant.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Constants for CLI commands."""
|
|
16
16
|
|
|
17
|
+
from flwr.supercore.constant import SUPERGRID_ADDRESS
|
|
17
18
|
|
|
18
19
|
# General help message for config overrides
|
|
19
20
|
CONFIG_HELP_MESSAGE = (
|
|
@@ -96,11 +97,11 @@ class SimulationBackendConfigTomlKey:
|
|
|
96
97
|
FLOWER_CONFIG_FILE = "config.toml"
|
|
97
98
|
|
|
98
99
|
# The default configuration for the Flower config file
|
|
99
|
-
DEFAULT_FLOWER_CONFIG_TOML = """[superlink]
|
|
100
|
+
DEFAULT_FLOWER_CONFIG_TOML = f"""[superlink]
|
|
100
101
|
default = "local"
|
|
101
102
|
|
|
102
103
|
[superlink.supergrid]
|
|
103
|
-
address = "
|
|
104
|
+
address = "{SUPERGRID_ADDRESS}"
|
|
104
105
|
enable-account-auth = true
|
|
105
106
|
federation = "YOUR-FEDERATION-HERE"
|
|
106
107
|
|
|
@@ -109,3 +110,8 @@ options.num-supernodes = 10
|
|
|
109
110
|
options.backend.client-resources.num-cpus = 1
|
|
110
111
|
options.backend.client-resources.num-gpus = 0
|
|
111
112
|
"""
|
|
113
|
+
|
|
114
|
+
# Keys for storing account auth credentials in the credential store
|
|
115
|
+
AUTHN_TYPE_STORE_KEY = "flower.account-auth.%s.authn-type"
|
|
116
|
+
ACCESS_TOKEN_STORE_KEY = "flower.account-auth.%s.oidc-access-token"
|
|
117
|
+
REFRESH_TOKEN_STORE_KEY = "flower.account-auth.%s.oidc-refresh-token"
|
flwr/cli/federation/ls.py
CHANGED
|
@@ -16,22 +16,17 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import io
|
|
19
|
-
from
|
|
20
|
-
from typing import Annotated, Any, cast
|
|
19
|
+
from typing import Annotated, Any
|
|
21
20
|
|
|
22
21
|
import typer
|
|
23
22
|
from rich.console import Console
|
|
24
23
|
from rich.table import Table
|
|
25
24
|
from rich.text import Text
|
|
26
25
|
|
|
27
|
-
from flwr.cli.
|
|
28
|
-
|
|
29
|
-
load_and_validate,
|
|
30
|
-
process_loaded_project_config,
|
|
31
|
-
validate_federation_in_project_config,
|
|
32
|
-
)
|
|
26
|
+
from flwr.cli.config_migration import migrate
|
|
27
|
+
from flwr.cli.flower_config import read_superlink_connection
|
|
33
28
|
from flwr.cli.ls import _get_status_style
|
|
34
|
-
from flwr.common.constant import
|
|
29
|
+
from flwr.common.constant import NOOP_ACCOUNT_NAME, CliOutputFormat
|
|
35
30
|
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
36
31
|
from flwr.common.serde import run_from_proto
|
|
37
32
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
@@ -46,18 +41,14 @@ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
|
46
41
|
from flwr.supercore.utils import humanize_duration
|
|
47
42
|
|
|
48
43
|
from ..run_utils import RunRow, format_runs
|
|
49
|
-
from ..utils import flwr_cli_grpc_exc_handler,
|
|
44
|
+
from ..utils import flwr_cli_grpc_exc_handler, init_channel_from_connection
|
|
50
45
|
|
|
51
46
|
|
|
52
47
|
def ls( # pylint: disable=R0914, R0913, R0917, R0912
|
|
53
48
|
ctx: typer.Context,
|
|
54
|
-
|
|
55
|
-
Path,
|
|
56
|
-
typer.Argument(help="Path of the Flower project"),
|
|
57
|
-
] = Path("."),
|
|
58
|
-
toml_federation: Annotated[
|
|
49
|
+
superlink: Annotated[
|
|
59
50
|
str | None,
|
|
60
|
-
typer.Argument(help="Name of the
|
|
51
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
61
52
|
] = None,
|
|
62
53
|
output_format: Annotated[
|
|
63
54
|
str,
|
|
@@ -77,29 +68,22 @@ def ls( # pylint: disable=R0914, R0913, R0917, R0912
|
|
|
77
68
|
] = None,
|
|
78
69
|
) -> None:
|
|
79
70
|
"""List available federations."""
|
|
80
|
-
# Resolve command used (list or ls)
|
|
81
|
-
command_name = cast(str, ctx.command.name) if ctx.command else "ls"
|
|
82
|
-
|
|
83
71
|
suppress_output = output_format == CliOutputFormat.JSON
|
|
84
72
|
captured_output = io.StringIO()
|
|
73
|
+
|
|
74
|
+
if suppress_output:
|
|
75
|
+
redirect_output(captured_output)
|
|
76
|
+
|
|
77
|
+
# Migrate legacy usage if any
|
|
78
|
+
migrate(superlink, args=ctx.args)
|
|
79
|
+
|
|
80
|
+
# Read superlink connection configuration
|
|
81
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
82
|
+
channel = None
|
|
83
|
+
|
|
85
84
|
try:
|
|
86
|
-
if suppress_output:
|
|
87
|
-
redirect_output(captured_output)
|
|
88
|
-
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
89
|
-
|
|
90
|
-
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
91
|
-
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
92
|
-
config = process_loaded_project_config(config, errors, warnings)
|
|
93
|
-
toml_federation_name, federation_config = validate_federation_in_project_config(
|
|
94
|
-
toml_federation, config
|
|
95
|
-
)
|
|
96
|
-
exit_if_no_address(federation_config, f"federation {command_name}")
|
|
97
|
-
channel = None
|
|
98
85
|
try:
|
|
99
|
-
|
|
100
|
-
app, toml_federation_name, federation_config
|
|
101
|
-
)
|
|
102
|
-
channel = init_channel(app, federation_config, auth_plugin)
|
|
86
|
+
channel = init_channel_from_connection(superlink_connection)
|
|
103
87
|
stub = ControlStub(channel)
|
|
104
88
|
|
|
105
89
|
if federation:
|
flwr/cli/login/login.py
CHANGED
|
@@ -15,20 +15,13 @@
|
|
|
15
15
|
"""Flower command line interface `login` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from
|
|
19
|
-
from typing import Annotated
|
|
18
|
+
from typing import Annotated, cast
|
|
20
19
|
|
|
21
20
|
import typer
|
|
22
21
|
|
|
23
22
|
from flwr.cli.auth_plugin import LoginError, NoOpCliAuthPlugin
|
|
24
|
-
from flwr.cli.config_utils import (
|
|
25
|
-
exit_if_no_address,
|
|
26
|
-
get_insecure_flag,
|
|
27
|
-
load_and_validate,
|
|
28
|
-
process_loaded_project_config,
|
|
29
|
-
validate_federation_in_project_config,
|
|
30
|
-
)
|
|
31
23
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
24
|
+
from flwr.cli.utils import init_channel_from_connection
|
|
32
25
|
from flwr.common.typing import AccountAuthLoginDetails
|
|
33
26
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
34
27
|
GetLoginDetailsRequest,
|
|
@@ -36,58 +29,51 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
36
29
|
)
|
|
37
30
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
38
31
|
|
|
39
|
-
from ..
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
init_channel,
|
|
43
|
-
load_cli_auth_plugin,
|
|
44
|
-
)
|
|
32
|
+
from ..config_migration import migrate, warn_if_federation_config_overrides
|
|
33
|
+
from ..flower_config import read_superlink_connection
|
|
34
|
+
from ..utils import flwr_cli_grpc_exc_handler, load_cli_auth_plugin_from_connection
|
|
45
35
|
|
|
46
36
|
|
|
47
|
-
def login(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
typer.Argument(help="Path of the Flower App to run."),
|
|
51
|
-
] = Path("."),
|
|
52
|
-
federation: Annotated[
|
|
37
|
+
def login(
|
|
38
|
+
ctx: typer.Context,
|
|
39
|
+
superlink: Annotated[
|
|
53
40
|
str | None,
|
|
54
|
-
typer.Argument(help="Name of the
|
|
41
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
55
42
|
] = None,
|
|
56
43
|
federation_config_overrides: Annotated[
|
|
57
44
|
list[str] | None,
|
|
58
45
|
typer.Option(
|
|
59
46
|
"--federation-config",
|
|
60
47
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
48
|
+
hidden=True,
|
|
61
49
|
),
|
|
62
50
|
] = None,
|
|
63
51
|
) -> None:
|
|
64
52
|
"""Login to Flower SuperLink."""
|
|
65
|
-
|
|
53
|
+
# Warn `--federation-config` is ignored
|
|
54
|
+
warn_if_federation_config_overrides(federation_config_overrides)
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
# Migrate legacy usage if any
|
|
57
|
+
migrate(superlink, args=ctx.args)
|
|
69
58
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
exit_if_no_address(federation_config, "login")
|
|
59
|
+
# Read superlink connection configuration
|
|
60
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
61
|
+
superlink = superlink_connection.name
|
|
75
62
|
|
|
76
63
|
# Check if `enable-account-auth` is set to `true`
|
|
77
|
-
|
|
78
|
-
if not account_auth_enabled(federation_config):
|
|
64
|
+
if not superlink_connection.enable_account_auth:
|
|
79
65
|
typer.secho(
|
|
80
|
-
"❌ Account authentication is not enabled for the
|
|
81
|
-
f"'{
|
|
82
|
-
"in the
|
|
66
|
+
"❌ Account authentication is not enabled for the SuperLink connection "
|
|
67
|
+
f"'{superlink}'. To enable it, set `enable-account-auth = true` "
|
|
68
|
+
"in the configuration.",
|
|
83
69
|
fg=typer.colors.RED,
|
|
84
70
|
bold=True,
|
|
85
71
|
err=True,
|
|
86
72
|
)
|
|
87
73
|
raise typer.Exit(code=1)
|
|
74
|
+
|
|
88
75
|
# Check if insecure flag is set to `True`
|
|
89
|
-
insecure
|
|
90
|
-
if insecure:
|
|
76
|
+
if superlink_connection.insecure:
|
|
91
77
|
typer.secho(
|
|
92
78
|
"❌ `flwr login` requires TLS to be enabled. `insecure` must NOT be set to "
|
|
93
79
|
"`true` in the federation configuration.",
|
|
@@ -97,7 +83,7 @@ def login( # pylint: disable=R0914
|
|
|
97
83
|
)
|
|
98
84
|
raise typer.Exit(code=1)
|
|
99
85
|
|
|
100
|
-
channel =
|
|
86
|
+
channel = init_channel_from_connection(superlink_connection, NoOpCliAuthPlugin())
|
|
101
87
|
stub = ControlStub(channel)
|
|
102
88
|
|
|
103
89
|
login_request = GetLoginDetailsRequest()
|
|
@@ -105,8 +91,9 @@ def login( # pylint: disable=R0914
|
|
|
105
91
|
login_response: GetLoginDetailsResponse = stub.GetLoginDetails(login_request)
|
|
106
92
|
|
|
107
93
|
# Get the auth plugin
|
|
108
|
-
|
|
109
|
-
|
|
94
|
+
authn_plugin = load_cli_auth_plugin_from_connection(
|
|
95
|
+
cast(str, superlink_connection.address), login_response.authn_type
|
|
96
|
+
)
|
|
110
97
|
|
|
111
98
|
# Login
|
|
112
99
|
details = AccountAuthLoginDetails(
|
|
@@ -118,7 +105,7 @@ def login( # pylint: disable=R0914
|
|
|
118
105
|
)
|
|
119
106
|
try:
|
|
120
107
|
with flwr_cli_grpc_exc_handler():
|
|
121
|
-
credentials =
|
|
108
|
+
credentials = authn_plugin.login(details, stub)
|
|
122
109
|
typer.secho(
|
|
123
110
|
"✅ Login successful.",
|
|
124
111
|
fg=typer.colors.GREEN,
|
|
@@ -134,4 +121,4 @@ def login( # pylint: disable=R0914
|
|
|
134
121
|
raise typer.Exit(code=1) from None
|
|
135
122
|
|
|
136
123
|
# Store the tokens
|
|
137
|
-
|
|
124
|
+
authn_plugin.store_tokens(credentials)
|