535 lines
17 KiB
Python
535 lines
17 KiB
Python
"""Project commands for the Reflex Cloud CLI."""
|
|
|
|
import json
|
|
|
|
import click
|
|
|
|
from reflex_cli import constants
|
|
from reflex_cli.utils import console
|
|
from reflex_cli.utils.exceptions import NotAuthenticatedError
|
|
|
|
|
|
@click.group()
|
|
def project_cli():
|
|
"""Commands for managing projects."""
|
|
|
|
|
|
@project_cli.command(name="create")
|
|
@click.argument("name", required=True)
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option(
|
|
"--json/--no-json",
|
|
"-j",
|
|
"as_json",
|
|
is_flag=True,
|
|
help="Whether to output the result in json format.",
|
|
)
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
"-i",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to use interactive mode.",
|
|
)
|
|
def create_project(
|
|
name: str,
|
|
token: str | None,
|
|
loglevel: str,
|
|
as_json: bool,
|
|
interactive: bool,
|
|
):
|
|
"""Create a new project."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
project = hosting.create_project(name=name, client=authenticated_client)
|
|
except ValueError as err:
|
|
console.error(str(err))
|
|
raise click.exceptions.Exit(1) from err
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|
|
|
|
if as_json:
|
|
console.print(json.dumps(project))
|
|
return
|
|
if project:
|
|
project = [project]
|
|
headers = list(project[0].keys())
|
|
table = [
|
|
[str(value) if value is not None else "" for value in p.values()]
|
|
for p in project
|
|
]
|
|
console.print_table(table, headers=headers)
|
|
else:
|
|
console.print(str(project))
|
|
|
|
|
|
@project_cli.command(name="invite")
|
|
@click.argument("role", required=True)
|
|
@click.argument("user", required=True)
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
"-i",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to use interactive mode.",
|
|
)
|
|
def invite_user_to_project(
|
|
role: str,
|
|
user: str,
|
|
token: str | None,
|
|
loglevel: str,
|
|
interactive: bool,
|
|
):
|
|
"""Invite a user to a project."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
result = hosting.invite_user_to_project(
|
|
role_id=role, user_id=user, client=authenticated_client
|
|
)
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|
|
|
|
if "failed" in result:
|
|
console.error(f"Unable to invite user to project: {result}")
|
|
raise click.exceptions.Exit(1)
|
|
console.success("Successfully invited user to project.")
|
|
|
|
|
|
@project_cli.command(name="select")
|
|
@click.argument("project_id", required=False)
|
|
@click.option("--project-name", help="The name of the project. ")
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to list configuration options and ask for confirmation.",
|
|
)
|
|
def select_project(
|
|
project_id: str | None,
|
|
project_name: str | None,
|
|
token: str | None,
|
|
loglevel: str,
|
|
interactive: bool,
|
|
):
|
|
"""Select a project."""
|
|
import httpx
|
|
|
|
from reflex_cli.utils import hosting
|
|
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
# check if provided project exists.
|
|
if project_id:
|
|
hosting.get_project(project_id, client=authenticated_client)
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|
|
except httpx.HTTPStatusError as ex:
|
|
try:
|
|
console.error(ex.response.json().get("detail"))
|
|
except json.JSONDecodeError:
|
|
console.error(ex.response.text)
|
|
raise click.exceptions.Exit(1) from ex
|
|
|
|
if project_name and not project_id:
|
|
result = hosting.search_project(
|
|
project_name, interactive=interactive, client=authenticated_client
|
|
)
|
|
project_id = result.get("id") if result else None
|
|
|
|
if not project_id:
|
|
console.error("No project selected. Please provide a valid project ID or name.")
|
|
raise click.exceptions.Exit(1)
|
|
|
|
console.set_log_level(loglevel)
|
|
result = hosting.select_project(project=project_id, token=token)
|
|
if "failed" in result:
|
|
console.error(result)
|
|
raise click.exceptions.Exit(1)
|
|
console.success(result)
|
|
|
|
|
|
@project_cli.command(name="selected")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
"-i",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to use interactive mode.",
|
|
)
|
|
def get_select_project(
|
|
loglevel: str,
|
|
token: str | None,
|
|
interactive: bool,
|
|
):
|
|
"""Get the currently selected project."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
project = hosting.get_selected_project()
|
|
if project:
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
project_details = hosting.get_project(
|
|
project_id=project, client=authenticated_client
|
|
)
|
|
console.print_table(
|
|
[[project, project_details["name"]]],
|
|
headers=["Selected Project ID", "Project Name"],
|
|
)
|
|
except NotAuthenticatedError:
|
|
console.error(
|
|
"You are not authenticated. Run `reflex login` to authenticate."
|
|
)
|
|
raise click.exceptions.Exit(1) from None
|
|
except Exception as e:
|
|
console.error(f"Unable to get the currently selected project: {e}")
|
|
else:
|
|
console.warn(
|
|
"no selected project. run `reflex cloud project select` to set one."
|
|
)
|
|
|
|
|
|
@project_cli.command(name="list")
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option(
|
|
"--json/--no-json",
|
|
"-j",
|
|
"as_json",
|
|
is_flag=True,
|
|
help="Whether to output the result in json format.",
|
|
)
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
"-i",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to use interactive mode.",
|
|
)
|
|
def get_projects(
|
|
token: str | None,
|
|
loglevel: str,
|
|
as_json: bool,
|
|
interactive: bool,
|
|
):
|
|
"""Retrieve a list of projects."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
projects = hosting.get_projects(client=authenticated_client)
|
|
if as_json:
|
|
console.print(json.dumps(projects))
|
|
return
|
|
if projects:
|
|
headers = list(projects[0].keys())
|
|
table = []
|
|
for project in projects:
|
|
row = []
|
|
for value in project.values():
|
|
if isinstance(value, (dict, list)):
|
|
row.append(json.dumps(value))
|
|
else:
|
|
row.append(str(value))
|
|
table.append(row)
|
|
console.print_table(table, headers=headers)
|
|
else:
|
|
# If returned empty list, print the empty
|
|
console.print(str(projects))
|
|
except NotAuthenticatedError:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from None
|
|
except Exception as e:
|
|
console.error(f"Unable to get projects: {e}")
|
|
raise click.exceptions.Exit(1) from e
|
|
|
|
|
|
@project_cli.command(name="roles")
|
|
@click.option(
|
|
"--project-id",
|
|
help="The ID of the project. If not provided, the selected project will be used. If no project_id is provided or selected throws an error.",
|
|
)
|
|
@click.option("--project-name", help="The name of the project. ")
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option(
|
|
"--json/--no-json",
|
|
"-j",
|
|
"as_json",
|
|
is_flag=True,
|
|
help="Whether to output the result in json format.",
|
|
)
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to list configuration options and ask for confirmation.",
|
|
)
|
|
def get_project_roles(
|
|
project_id: str | None,
|
|
project_name: str | None,
|
|
token: str | None,
|
|
loglevel: str,
|
|
as_json: bool,
|
|
interactive: bool,
|
|
):
|
|
"""Retrieve the roles for a project."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
if project_name and not project_id:
|
|
result = hosting.search_project(
|
|
project_name, client=authenticated_client, interactive=interactive
|
|
)
|
|
project_id = result.get("id") if result else None
|
|
if project_id is None:
|
|
project_id = hosting.get_selected_project()
|
|
if project_id is None:
|
|
console.error(
|
|
"no project_id provided or selected. Set it with `reflex cloud project roles --project-id \\[project_id]`"
|
|
)
|
|
raise click.exceptions.Exit(1)
|
|
|
|
roles = hosting.get_project_roles(
|
|
project_id=project_id, client=authenticated_client
|
|
)
|
|
|
|
if as_json:
|
|
console.print(json.dumps(roles))
|
|
return
|
|
if roles:
|
|
headers = list(roles[0].keys())
|
|
table = [
|
|
[str(value) if value is not None else "" for value in role.values()]
|
|
for role in roles
|
|
]
|
|
console.print_table(table, headers=headers)
|
|
else:
|
|
# If returned empty list, print the empty
|
|
console.print(str(roles))
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|
|
|
|
|
|
@project_cli.command(name="role-permissions")
|
|
@click.argument("role_id", required=True)
|
|
@click.option(
|
|
"--project-id",
|
|
help="The ID of the project. If not provided, the selected project will be used. If no project is selected, it throws an error.",
|
|
)
|
|
@click.option("--project-name", help="The name of the project. ")
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option(
|
|
"--json/--no-json",
|
|
"-j",
|
|
"as_json",
|
|
is_flag=True,
|
|
help="Whether to output the result in json format.",
|
|
)
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to list configuration options and ask for confirmation.",
|
|
)
|
|
def get_project_role_permissions(
|
|
role_id: str,
|
|
project_id: str | None,
|
|
project_name: str | None,
|
|
token: str | None,
|
|
loglevel: str,
|
|
as_json: bool,
|
|
interactive: bool,
|
|
):
|
|
"""Retrieve the permissions for a specific role in a project."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
if project_name and not project_id:
|
|
result = hosting.search_project(
|
|
project_name, client=authenticated_client, interactive=interactive
|
|
)
|
|
project_id = result.get("id") if result else None
|
|
if project_id is None:
|
|
project_id = hosting.get_selected_project()
|
|
if project_id is None:
|
|
console.error(
|
|
"no project_id provided or selected. Set it with `reflex cloud project role-permissions --project-id \\[project_id]`."
|
|
)
|
|
raise click.exceptions.Exit(1)
|
|
|
|
permissions = hosting.get_project_role_permissions(
|
|
project_id=project_id, role_id=role_id, client=authenticated_client
|
|
)
|
|
|
|
if as_json:
|
|
console.print(json.dumps(permissions))
|
|
return
|
|
if permissions:
|
|
headers = list(permissions[0].keys())
|
|
table = [
|
|
[
|
|
str(value) if value is not None else ""
|
|
for value in permission.values()
|
|
]
|
|
for permission in permissions
|
|
]
|
|
console.print_table(table, headers=headers)
|
|
else:
|
|
# If returned empty list, print the empty
|
|
console.print(str(permissions))
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|
|
|
|
|
|
@project_cli.command(name="users")
|
|
@click.option(
|
|
"--project-id",
|
|
help="The ID of the project. If not provided, the selected project will be used. If no project is selected, it throws an error.",
|
|
)
|
|
@click.option("--project-name", help="The name of the project. ")
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--loglevel",
|
|
type=click.Choice([level.value for level in constants.LogLevel]),
|
|
default=constants.LogLevel.INFO.value,
|
|
help="The log level to use.",
|
|
)
|
|
@click.option(
|
|
"--json/--no-json",
|
|
"-j",
|
|
"as_json",
|
|
is_flag=True,
|
|
help="Whether to output the result in json format.",
|
|
)
|
|
@click.option(
|
|
"--interactive/--no-interactive",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Whether to list configuration options and ask for confirmation.",
|
|
)
|
|
def get_project_role_users(
|
|
project_id: str | None,
|
|
project_name: str | None,
|
|
token: str | None,
|
|
loglevel: str,
|
|
as_json: bool,
|
|
interactive: bool,
|
|
):
|
|
"""Retrieve the users for a project."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
if project_name and not project_id:
|
|
result = hosting.search_project(
|
|
project_name, client=authenticated_client, interactive=interactive
|
|
)
|
|
project_id = result.get("id") if result else None
|
|
if project_id is None:
|
|
project_id = hosting.get_selected_project()
|
|
if project_id is None:
|
|
console.error(
|
|
"no project_id provided or selected. Set it with `reflex cloud project users --project-id \\[project_id]`"
|
|
)
|
|
raise click.exceptions.Exit(1)
|
|
|
|
users = hosting.get_project_role_users(
|
|
project_id=project_id, client=authenticated_client
|
|
)
|
|
|
|
if as_json:
|
|
console.print(json.dumps(users))
|
|
return
|
|
if users:
|
|
headers = list(users[0].keys())
|
|
table = [
|
|
[str(value) if value is not None else "" for value in user.values()]
|
|
for user in users
|
|
]
|
|
console.print_table(table, headers=headers)
|
|
else:
|
|
# If returned empty list, print the empty
|
|
console.print(str(users))
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|