242 lines
7.3 KiB
Python
242 lines
7.3 KiB
Python
"""Secrets commands for the Reflex Cloud CLI."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import click
|
|
|
|
from reflex_cli import constants
|
|
from reflex_cli.utils import console
|
|
from reflex_cli.utils.exceptions import NotAuthenticatedError
|
|
|
|
|
|
@click.group()
|
|
def secrets_cli():
|
|
"""Commands for managing secrets."""
|
|
|
|
|
|
@secrets_cli.command(name="list")
|
|
@click.argument("app_id", required=False)
|
|
@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_secrets(
|
|
app_id: str | None,
|
|
token: str | None,
|
|
loglevel: str,
|
|
as_json: bool,
|
|
interactive: bool,
|
|
):
|
|
"""Retrieve secrets for a given application."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
|
|
if not app_id:
|
|
config = hosting.read_config()
|
|
if config:
|
|
app_id = config.appid
|
|
if not isinstance(app_id, (str, type(None))):
|
|
console.error(
|
|
"app_id must be a string or None. Please check your config file."
|
|
)
|
|
raise click.exceptions.Exit(1)
|
|
|
|
if not app_id:
|
|
console.error("No valid app_id provided.")
|
|
raise click.exceptions.Exit(1)
|
|
|
|
secrets = hosting.get_secrets(app_id=app_id, client=authenticated_client)
|
|
if "failed" in secrets:
|
|
console.error(secrets)
|
|
raise click.exceptions.Exit(1)
|
|
if as_json:
|
|
console.print(secrets)
|
|
return
|
|
if secrets:
|
|
headers = ["Keys"]
|
|
table = [[key] for key in secrets]
|
|
console.print_table(table, headers=headers)
|
|
else:
|
|
console.print(str(secrets))
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|
|
|
|
|
|
@secrets_cli.command(name="update")
|
|
@click.argument("app_id", required=False)
|
|
@click.option(
|
|
"--envfile",
|
|
help="The path to an env file to use. Will override any envs set manually.",
|
|
)
|
|
@click.option(
|
|
"--env",
|
|
"envs",
|
|
multiple=True,
|
|
help="The environment variables to set: <key>=<value>. Required if envfile is not specified. For multiple envs, repeat this option, e.g. --env k1=v2 --env k2=v2.",
|
|
)
|
|
@click.option(
|
|
"--reboot/--no-reboot",
|
|
is_flag=True,
|
|
help="Automatically reboot your site with the new secrets",
|
|
)
|
|
@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 update_secrets(
|
|
app_id: str | None,
|
|
envfile: str | None,
|
|
envs: tuple[str, ...],
|
|
reboot: bool,
|
|
token: str | None,
|
|
loglevel: str,
|
|
interactive: bool,
|
|
):
|
|
"""Update secrets for a given application."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
|
|
if not app_id:
|
|
config = hosting.read_config()
|
|
if config:
|
|
app_id = config.appid
|
|
if not isinstance(app_id, (str, type(None))):
|
|
console.error(
|
|
"app_id must be a string or None. Please check your config file."
|
|
)
|
|
raise click.exceptions.Exit(1)
|
|
|
|
if not app_id:
|
|
console.error("No valid app_id provided.")
|
|
raise click.exceptions.Exit(1)
|
|
|
|
if envfile is None and not envs:
|
|
console.error("--envfile or --env must be provided")
|
|
raise click.exceptions.Exit(1)
|
|
|
|
if envfile and envs:
|
|
console.warn("--envfile is set; ignoring --env")
|
|
|
|
if envfile:
|
|
try:
|
|
from dotenv import ( # pyright: ignore[reportMissingImports]
|
|
dotenv_values,
|
|
)
|
|
except ImportError:
|
|
console.error(
|
|
"""The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
|
|
)
|
|
raise click.exceptions.Exit(1) from None
|
|
secrets = dotenv_values(envfile)
|
|
else:
|
|
secrets = hosting.process_envs(list(envs))
|
|
hosting.update_secrets(
|
|
app_id=app_id, secrets=secrets, reboot=reboot, 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
|
|
|
|
|
|
@secrets_cli.command(name="delete")
|
|
@click.argument("app_id", required=False)
|
|
@click.argument("key", required=True)
|
|
@click.option("--token", help="The authentication token.")
|
|
@click.option(
|
|
"--reboot/--no-reboot",
|
|
is_flag=True,
|
|
help="Automatically reboot your site with the new secrets",
|
|
)
|
|
@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 delete_secret(
|
|
app_id: str | None,
|
|
key: str,
|
|
token: str | None,
|
|
reboot: bool,
|
|
loglevel: str,
|
|
interactive: bool,
|
|
):
|
|
"""Delete a secret for a given application."""
|
|
from reflex_cli.utils import hosting
|
|
|
|
console.set_log_level(loglevel)
|
|
try:
|
|
authenticated_client = hosting.get_authenticated_client(
|
|
token=token, interactive=interactive
|
|
)
|
|
|
|
if not app_id:
|
|
config = hosting.read_config()
|
|
if config:
|
|
app_id = config.appid
|
|
if not isinstance(app_id, (str, type(None))):
|
|
console.error(
|
|
"app_id must be a string or None. Please check your config file."
|
|
)
|
|
raise click.exceptions.Exit(1)
|
|
|
|
if not app_id:
|
|
console.error("No valid app_id provided.")
|
|
raise click.exceptions.Exit(1)
|
|
|
|
result = hosting.delete_secret(
|
|
app_id=app_id, key=key, reboot=reboot, client=authenticated_client
|
|
)
|
|
if "failed" in result:
|
|
console.error(result)
|
|
raise click.exceptions.Exit(1)
|
|
console.success("Successfully deleted secret.")
|
|
except NotAuthenticatedError as err:
|
|
console.error("You are not authenticated. Run `reflex login` to authenticate.")
|
|
raise click.exceptions.Exit(1) from err
|