Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/SK-1229 | Project resource for CLI extension #784

Merged
merged 29 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions fedn/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from .client_cmd import client_cmd # noqa: F401
from .combiner_cmd import combiner_cmd # noqa: F401
from .config_cmd import config_cmd # noqa: F401
from .controller_cmd import controller_cmd # noqa: F401
from .hooks_cmd import hooks_cmd # noqa: F401
from .login_cmd import login_cmd # noqa: F401
from .main import main # noqa: F401
from .model_cmd import model_cmd # noqa: F401
from .package_cmd import package_cmd # noqa: F401
from .project_cmd import project_cmd # noqa: F401
from .round_cmd import round_cmd # noqa: F401
from .run_cmd import run_cmd # noqa: F401
from .session_cmd import session_cmd # noqa: F401
from .status_cmd import status_cmd # noqa: F401
from .validation_cmd import validation_cmd # noqa: F401
from .controller_cmd import controller_cmd # noqa: F401
from .login_cmd import login_cmd # noqa: F401
34 changes: 5 additions & 29 deletions fedn/cli/client_cmd.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import uuid

import click
import requests

from fedn.cli.main import main
from fedn.cli.shared import CONTROLLER_DEFAULTS, apply_config, get_api_url, get_token, print_response
from fedn.cli.shared import CONTROLLER_DEFAULTS, apply_config, get_response, print_response
from fedn.common.exceptions import InvalidClientConfig
from fedn.network.clients.client_v2 import Client as ClientV2
from fedn.network.clients.client_v2 import ClientOptions
Expand Down Expand Up @@ -48,22 +47,13 @@ def list_clients(ctx, protocol: str, host: str, port: str, token: str = None, n_
- result: list of clients

"""
url = get_api_url(protocol=protocol, host=host, port=port, endpoint="clients")
headers = {}

if n_max:
headers["X-Limit"] = n_max

_token = get_token(token)

if _token:
headers["Authorization"] = _token

try:
response = requests.get(url, headers=headers)
print_response(response, "clients", None)
except requests.exceptions.ConnectionError:
click.echo(f"Error: Could not connect to {url}")
response = get_response(protocol=protocol, host=host, port=port, endpoint="clients", token=token, headers=headers, usr_api=False, usr_token=False)
print_response(response, "clients", None)


@click.option("-p", "--protocol", required=False, default=CONTROLLER_DEFAULTS["protocol"], help="Communication protocol of controller (api)")
Expand All @@ -79,22 +69,8 @@ def get_client(ctx, protocol: str, host: str, port: str, token: str = None, id:
- result: client with given id

"""
url = get_api_url(protocol=protocol, host=host, port=port, endpoint="clients")
headers = {}

_token = get_token(token)

if _token:
headers["Authorization"] = _token

if id:
url = f"{url}{id}"

try:
response = requests.get(url, headers=headers)
print_response(response, "client", id)
except requests.exceptions.ConnectionError:
click.echo(f"Error: Could not connect to {url}")
response = get_response(protocol=protocol, host=host, port=port, endpoint=f"clients/{id}", token=token, headers={}, usr_api=False, usr_token=False)
print_response(response, "client", id)


def _validate_client_params(config: dict):
Expand Down
34 changes: 5 additions & 29 deletions fedn/cli/combiner_cmd.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import uuid

import click
import requests

from .main import main
from .shared import CONTROLLER_DEFAULTS, apply_config, get_api_url, get_token, print_response
from .shared import CONTROLLER_DEFAULTS, apply_config, get_response, print_response


@main.group("combiner")
Expand Down Expand Up @@ -77,22 +76,13 @@ def list_combiners(ctx, protocol: str, host: str, port: str, token: str = None,
- result: list of combiners

"""
url = get_api_url(protocol=protocol, host=host, port=port, endpoint="combiners")
headers = {}

if n_max:
headers["X-Limit"] = n_max

_token = get_token(token)

if _token:
headers["Authorization"] = _token

try:
response = requests.get(url, headers=headers)
print_response(response, "combiners", None)
except requests.exceptions.ConnectionError:
click.echo(f"Error: Could not connect to {url}")
response = get_response(protocol=protocol, host=host, port=port, endpoint="combiners", token=token, headers=headers, usr_api=False, usr_token=False)
print_response(response, "combiners", None)


@click.option("-p", "--protocol", required=False, default=CONTROLLER_DEFAULTS["protocol"], help="Communication protocol of controller (api)")
Expand All @@ -108,19 +98,5 @@ def get_combiner(ctx, protocol: str, host: str, port: str, token: str = None, id
- result: combiner with given id

"""
url = get_api_url(protocol=protocol, host=host, port=port, endpoint="combiners")
headers = {}

_token = get_token(token)

if _token:
headers["Authorization"] = _token

if id:
url = f"{url}{id}"

try:
response = requests.get(url, headers=headers)
print_response(response, "combiner", id)
except requests.exceptions.ConnectionError:
click.echo(f"Error: Could not connect to {url}")
response = get_response(protocol=protocol, host=host, port=port, endpoint=f"combiners/{id}", token=token, headers={}, usr_api=False, usr_token=False)
print_response(response, "combiner", id)
97 changes: 77 additions & 20 deletions fedn/cli/login_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import click
import requests
import yaml

from .main import main
from .shared import STUDIO_DEFAULTS, get_response, set_context

# Replace this with the platform's actual login endpoint
home_dir = os.path.expanduser("~")
Expand All @@ -19,19 +19,26 @@ def login_cmd(ctx):


@login_cmd.command("login")
@click.option("-p", "--protocol", required=False, default="https", help="Communication protocol")
@click.option("-H", "--host", required=False, default="fedn.scaleoutsystems.com", help="Hostname of controller (api)")
@click.option("-u", "--username", required=False, default=None, help="username in studio")
@click.option("-P", "--password", required=False, default=None, help="password in studio")
@click.option("-p", "--protocol", required=False, default=STUDIO_DEFAULTS["protocol"], help="Communication protocol of studio (api)")
@click.option("-H", "--host", required=False, default=STUDIO_DEFAULTS["host"], help="Hostname of studio (api)")
@click.pass_context
def login_cmd(ctx, protocol: str, host: str):
"""Logging into FEDn Studio"""
def login_cmd(ctx, protocol: str, host: str, username: str, password: str):
"""Login to FEDn Studio"""
# Step 1: Display welcome message
click.secho("Welcome to Scaleout FEDn!", fg="green")

url = f"{protocol}://{host}/api/token/"

# Step 3: Prompt for username and password
username = input("Please enter your username: ")
password = getpass("Please enter your password: ")
if username is None and password is None:
username = input("Please enter your username: ")
password = getpass("Please enter your password: ")
elif password is None:
password = getpass("Please enter your password: ")
else:
username = input("Please enter your username: ")

# Call the authentication API
try:
Expand All @@ -44,18 +51,68 @@ def login_cmd(ctx, protocol: str, host: str):

# Handle the response
if response.status_code == 200:
data = response.json()
if data.get("access"):
click.secho("Login successful!", fg="green")
context_path = os.path.join(home_dir, ".fedn")
if not os.path.exists(context_path):
os.makedirs(context_path)
try:
with open(f"{context_path}/context.yaml", "w") as yaml_file:
yaml.dump(data, yaml_file, default_flow_style=False) # Add access and refresh tokens to context yaml file
except Exception as e:
print(f"Error: Failed to write to YAML file. Details: {e}")
context_data = get_context(response, protocol, host)

context_path = os.path.join(home_dir, ".fedn")
if not os.path.exists(context_path):
os.makedirs(context_path)
set_context(context_path, context_data)
else:
click.secho(f"Unexpected error: {response.status_code}", fg="red")


# Sets the context for a given user
def get_context(response, protocol: str, host: str):
"""Generates content for context file with the following data:
User tokens: access and refresh token to authenticate user towards Studio
Active project tokens: access and refresh token to authenticate user towards controller
Active project id: slug of active project
Active project url: controller url of active project
"""
context_data = {"User tokens": {}, "Active project tokens": {}, "Active project id": {}, "Active project url": {}}
user_token_data = response.json()
if user_token_data.get("access"):
context_data["User tokens"] = user_token_data
studio_api = True
headers_projects = {}
user_access_token = user_token_data.get("access")
response_projects = get_response(
protocol=protocol,
host=host,
port=None,
endpoint="projects",
token=user_access_token,
headers=headers_projects,
usr_api=studio_api,
usr_token=True,
)
if response_projects.status_code == 200:
projects_response_json = response_projects.json()
if len(projects_response_json) > 0:
id = projects_response_json[0].get("slug")
context_data["Active project id"] = id
headers_projects["X-Project-Slug"] = id
response_project_tokens = get_response(
protocol=protocol,
host=host,
port=None,
endpoint="admin-token",
token=user_access_token,
headers=headers_projects,
usr_api=studio_api,
usr_token=False,
)
if response_project_tokens.status_code == 200:
project_tokens = response_project_tokens.json()
context_data["Active project tokens"] = project_tokens
controller_url = f"{protocol}://{host}/{id}-fedn-reducer"
context_data["Active project url"] = controller_url
click.secho("Login successful!", fg="green")
else:
click.secho(f"Unexpected error: {response_project_tokens.status_code}", fg="red")
else:
click.secho("Login failed. Please check your credentials.", fg="red")
click.secho(f"Unexpected error: {response_projects.status_code}", fg="red")
else:
click.secho(f"Unexpected error: {response.text}", fg="red")
click.secho("Login failed. Please check your credentials.", fg="red")

return context_data
52 changes: 11 additions & 41 deletions fedn/cli/model_cmd.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import click
import requests

from .main import main
from .shared import CONTROLLER_DEFAULTS, get_api_url, get_token, print_response
from .shared import CONTROLLER_DEFAULTS, get_response, print_response


@main.group("model")
@click.pass_context
def model_cmd(ctx):
""":param ctx:
"""
""":param ctx:"""
pass


@click.option("-p", "--protocol", required=False, default=CONTROLLER_DEFAULTS["protocol"], help="Communication protocol of controller (api)")
@click.option("-H", "--host", required=False, default=CONTROLLER_DEFAULTS["host"], help="Hostname of controller (api)")
@click.option("-P", "--port", required=False, default=CONTROLLER_DEFAULTS["port"], help="Port of controller (api)")
@click.option("-t", "--token", required=False, help="Authentication token")
@click.option("-session_id", "--session_id", required=False, help="models in session with given session id")
@click.option("-s", "--session_id", required=False, help="models in session with given session id")
@click.option("--n_max", required=False, help="Number of items to list")
@model_cmd.command("list")
@click.pass_context
Expand All @@ -28,28 +26,18 @@ def list_models(ctx, protocol: str, host: str, port: str, token: str = None, ses
- result: list of models

"""
url = get_api_url(protocol=protocol, host=host, port=port, endpoint="models")


headers = {}

if n_max:
headers["X-Limit"] = n_max

_token = get_token(token)

if _token:
headers["Authorization"] = _token

if session_id:
url = f"{url}?session_id={session_id}"


try:
response = requests.get(url, headers=headers)
print_response(response, "models", None)
except requests.exceptions.ConnectionError:
click.echo(f"Error: Could not connect to {url}")
response = get_response(
protocol=protocol, host=host, port=port, endpoint=f"models/?session_id={session_id}", token=token, headers=headers, usr_api=False, usr_token=False
)
else:
response = get_response(protocol=protocol, host=host, port=port, endpoint="models", token=token, headers=headers, usr_api=False, usr_token=False)
print_response(response, "models", None)


@click.option("-p", "--protocol", required=False, default=CONTROLLER_DEFAULTS["protocol"], help="Communication protocol of controller (api)")
Expand All @@ -65,23 +53,5 @@ def get_model(ctx, protocol: str, host: str, port: str, token: str = None, id: s
- result: model with given id

"""
url = get_api_url(protocol=protocol, host=host, port=port, endpoint="models")


headers = {}


_token = get_token(token)

if _token:
headers["Authorization"] = _token

if id:
url = f"{url}{id}"


try:
response = requests.get(url, headers=headers)
print_response(response, "model", id)
except requests.exceptions.ConnectionError:
click.echo(f"Error: Could not connect to {url}")
response = get_response(protocol=protocol, host=host, port=port, endpoint=f"models/{id}", token=token, headers={}, usr_api=False, usr_token=False)
print_response(response, "model", id)
Loading
Loading