Skip to content

Commit

Permalink
Feature/SK-1229 | Project resource for CLI extension (#784)
Browse files Browse the repository at this point in the history
* session_id flag added

* minor code fix

* added new line end of session cmd file

* fixed acc to feedback

* code fix

* fixed conflict

* conflict + minor fix

* spelling fix:)

* resolving conflict

* trying to add get_client function again

* header prints removed

* fixed feedback

* ruff linting fix

* in progress

* added default project to context file

* changed from project name to project slug in context file

* first draft of list, get, set and create project

* Added project resource

* bug fix in delete project

* handle wrong project slug from user input

* minor bug fix

* bug fixes and studio default values added

* added option to write username and password as arguments in login

* bug and code redundancy fixes

* fix

* resolved fix requests

* fixed change requests

---------

Co-authored-by: KatHellg <[email protected]>
  • Loading branch information
KatHellg and KatHellg authored Jan 23, 2025
1 parent e475618 commit 363c0a6
Show file tree
Hide file tree
Showing 12 changed files with 463 additions and 310 deletions.
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

0 comments on commit 363c0a6

Please sign in to comment.