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

feat(cli): add a global --context #4059

Merged
merged 9 commits into from
Jul 20, 2023
Merged
28 changes: 17 additions & 11 deletions src/bentoml_cli/bentos.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from bentoml._internal.cloud import BentoCloudClient
from bentoml._internal.container import DefaultBuilder

from .utils import Cli

BENTOML_FIGLET = """
██████╗ ███████╗███╗ ██╗████████╗ ██████╗ ███╗ ███╗██╗
██╔══██╗██╔════╝████╗ ██║╚══██╔══╝██╔═══██╗████╗ ████║██║
Expand Down Expand Up @@ -254,12 +256,10 @@ def import_bento_(bento_path: str) -> None: # type: ignore (not accessed)
default=False,
help="Force pull from yatai to local and overwrite even if it already exists in local",
)
@click.option(
"--context", type=click.STRING, default=None, help="Yatai context name."
)
def pull(bento_tag: str, force: bool, context: str) -> None: # type: ignore (not accessed)
@click.pass_obj
def pull(obj: Cli, bento_tag: str, force: bool) -> None: # type: ignore (not accessed)
"""Pull Bento from a yatai server."""
cloud_client.pull_bento(bento_tag, force=force, context=context)
cloud_client.pull_bento(bento_tag, force=force, context=obj.context)

@cli.command()
@click.argument("bento_tag", type=click.STRING)
Expand All @@ -276,16 +276,14 @@ def pull(bento_tag: str, force: bool, context: str) -> None: # type: ignore (no
default=10,
help="Number of threads to use for upload",
)
@click.option(
"--context", type=click.STRING, default=None, help="Yatai context name."
)
def push(bento_tag: str, force: bool, threads: int, context: str) -> None: # type: ignore (not accessed)
@click.pass_obj
def push(obj: Cli, bento_tag: str, force: bool, threads: int) -> None: # type: ignore (not accessed)
"""Push Bento to a yatai server."""
bento_obj = bento_store.get(bento_tag)
if not bento_obj:
raise click.ClickException(f"Bento {bento_tag} not found in local store")
cloud_client.push_bento(
bento_obj, force=force, threads=threads, context=context
bento_obj, force=force, threads=threads, context=obj.context
)

@cli.command()
Expand Down Expand Up @@ -326,6 +324,10 @@ def push(bento_tag: str, force: bool, threads: int, context: str) -> None: # ty
type=click.BOOL,
help="Whether to push the result bento to BentoCloud. Make sure to login with 'bentoml cloud login' first.",
)
@click.option(
"--force", is_flag=True, default=False, help="Forced push to BentoCloud"
)
@click.option("--threads", default=10, help="Number of threads to use for upload")
@click.pass_context
@inject
def build( # type: ignore (not accessed)
Expand All @@ -335,6 +337,8 @@ def build( # type: ignore (not accessed)
version: str,
output: t.Literal["tag", "default"],
push: bool,
force: bool,
threads: int,
containerize: bool,
_bento_store: BentoStore = Provide[BentoMLContainer.bento_store],
_cloud_client: BentoCloudClient = Provide[BentoMLContainer.bentocloud_client],
Expand Down Expand Up @@ -390,7 +394,9 @@ def build( # type: ignore (not accessed)
if push:
if not get_quiet_mode():
click.secho(f"\nPushing {bento} to BentoCloud...", fg="magenta")
_cloud_client.push_bento(bento)
_cloud_client.push_bento(
bento, force=force, threads=threads, context=ctx.obj.context
)
elif containerize:
backend: DefaultBuilder = t.cast(
"DefaultBuilder", os.getenv("BENTOML_CONTAINERIZE_BACKEND", "docker")
Expand Down
47 changes: 30 additions & 17 deletions src/bentoml_cli/cloud.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
from __future__ import annotations

import json
import typing as t

import click
import click_option_group as cog

if t.TYPE_CHECKING:
from .utils import Cli


def add_cloud_command(cli: click.Group) -> click.Group:
from bentoml._internal.cloud.client import RestApiClient
from bentoml._internal.cloud.config import CloudClientConfig
from bentoml._internal.cloud.config import CloudClientContext
from bentoml._internal.cloud.config import add_context
from bentoml._internal.cloud.config import default_context_name
from bentoml._internal.configuration import get_quiet_mode
from bentoml._internal.utils import bentoml_cattr
from bentoml.exceptions import CLIException
from bentoml_cli.utils import BentoMLCommandGroup
Expand Down Expand Up @@ -40,13 +43,8 @@ def cloud():
type=click.STRING,
help="BentoCloud or Yatai user API token",
)
@click.option(
"--context",
type=click.STRING,
help="BentoCloud or Yatai context name for the endpoint and API token",
default=default_context_name,
)
def login(endpoint: str, api_token: str, context: str) -> None: # type: ignore (not accessed)
@click.pass_obj
def login(obj: Cli, endpoint: str, api_token: str) -> None: # type: ignore (not accessed)
"""Login to BentoCloud or Yatai server."""
cloud_rest_client = RestApiClient(endpoint, api_token)
user = cloud_rest_client.get_current_user()
Expand All @@ -60,7 +58,7 @@ def login(endpoint: str, api_token: str, context: str) -> None: # type: ignore
raise CLIException("current organization is not found")

ctx = CloudClientContext(
name=context,
name=obj.context if obj.context is not None else default_context_name,
endpoint=endpoint,
api_token=api_token,
email=user.email,
Expand All @@ -71,18 +69,33 @@ def login(endpoint: str, api_token: str, context: str) -> None: # type: ignore
f"Successfully logged in to BentoCloud for {user.name} in {org.name}"
)

@cloud.command(aliases=["current-context"])
def get_current_context() -> None: # type: ignore (not accessed)
@cloud.command()
def current_context() -> None: # type: ignore (not accessed)
"""Get current cloud context."""
cur = CloudClientConfig.get_config().get_current_context()
if not get_quiet_mode():
click.echo(json.dumps(bentoml_cattr.unstructure(cur), indent=2))
click.echo(
json.dumps(
bentoml_cattr.unstructure(
CloudClientConfig.get_config().get_current_context()
),
indent=2,
)
)

@cloud.command()
def list_context() -> None: # type: ignore (not accessed)
"""List all available context."""
config = CloudClientConfig.get_config()
click.echo(
json.dumps(
bentoml_cattr.unstructure([i.name for i in config.contexts]), indent=2
)
)

@cloud.command()
@click.argument("context", type=click.STRING)
def update_current_context(context: str) -> None: # type: ignore (not accessed)
@click.argument("context_name", type=click.STRING)
def update_current_context(context_name: str) -> None: # type: ignore (not accessed)
"""Update current context"""
ctx = CloudClientConfig.get_config().set_current_context(context)
aarnphm marked this conversation as resolved.
Show resolved Hide resolved
ctx = CloudClientConfig.get_config().set_current_context(context_name)
click.echo(f"Successfully switched to context: {ctx.name}")

return cli
49 changes: 23 additions & 26 deletions src/bentoml_cli/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
TupleStrAny = tuple[str, ...]
from bentoml._internal.cloud.schemas import DeploymentListSchema
from bentoml._internal.cloud.schemas import DeploymentSchema

from .utils import Cli
else:
TupleStrAny = tuple

Expand Down Expand Up @@ -38,9 +40,6 @@ def shared_decorator(f: t.Callable[..., t.Any]):
type=click.STRING,
required=True,
),
click.option(
"--context", type=click.STRING, default=None, help="Yatai context name."
),
click.option(
"--cluster-name",
type=click.STRING,
Expand Down Expand Up @@ -70,13 +69,11 @@ def deployment_cli():
type=click.File(),
help="JSON file path for the deployment configuration",
)
@click.option(
"--context", type=click.STRING, default=None, help="Yatai context name."
)
@output_option
@click.pass_obj
def create( # type: ignore
obj: Cli,
file: str,
context: str,
output: t.Literal["json", "default"],
) -> DeploymentSchema:
"""Create a deployment on BentoCloud.
Expand All @@ -85,7 +82,9 @@ def create( # type: ignore
A deployment can be created using a json file with configurations.
The json file has the exact format as the one on BentoCloud Deployment UI.
"""
res = client.deployment.create_from_file(path_or_stream=file, context=context)
res = client.deployment.create_from_file(
path_or_stream=file, context=obj.context
)
if output == "default":
console.print(res)
elif output == "json":
Expand All @@ -101,15 +100,13 @@ def create( # type: ignore
)
@click.option("-n", "--name", type=click.STRING, help="Deployment name")
@click.option("--bento", type=click.STRING, help="Bento tag")
@click.option(
"--context", type=click.STRING, default=None, help="Yatai context name."
)
@output_option
@click.pass_obj
def update( # type: ignore
obj: Cli,
file: str | None,
name: str | None,
bento: str | None,
context: str,
output: t.Literal["json", "default"],
) -> DeploymentSchema:
"""Update a deployment on BentoCloud.
Expand All @@ -122,12 +119,11 @@ def update( # type: ignore
if name is not None:
click.echo("Reading from file, ignoring --name", err=True)
res = client.deployment.update_from_file(
path_or_stream=file,
context=context,
path_or_stream=file, context=obj.context
)
elif name is not None:
res = client.deployment.update(
name, bento=bento, context=context, latest_bento=True
name, bento=bento, context=obj.context, latest_bento=True
)
else:
raise click.BadArgumentUsage(
Expand All @@ -142,17 +138,18 @@ def update( # type: ignore

@deployment_cli.command()
@shared_decorator
@click.pass_obj
def get( # type: ignore
obj: Cli,
deployment_name: str,
context: str,
cluster_name: str,
kube_namespace: str,
output: t.Literal["json", "default"],
) -> DeploymentSchema:
"""Get a deployment on BentoCloud."""
res = client.deployment.get(
deployment_name=deployment_name,
context=context,
context=obj.context,
cluster_name=cluster_name,
kube_namespace=kube_namespace,
)
Expand All @@ -165,17 +162,18 @@ def get( # type: ignore

@deployment_cli.command()
@shared_decorator
@click.pass_obj
def terminate( # type: ignore
obj: Cli,
deployment_name: str,
context: str,
cluster_name: str,
kube_namespace: str,
output: t.Literal["json", "default"],
) -> DeploymentSchema:
"""Terminate a deployment on BentoCloud."""
res = client.deployment.terminate(
deployment_name=deployment_name,
context=context,
context=obj.context,
cluster_name=cluster_name,
kube_namespace=kube_namespace,
)
Expand All @@ -188,17 +186,18 @@ def terminate( # type: ignore

@deployment_cli.command()
@shared_decorator
@click.pass_obj
def delete( # type: ignore
obj: Cli,
deployment_name: str,
context: str,
cluster_name: str,
kube_namespace: str,
output: t.Literal["json", "default"],
) -> DeploymentSchema:
"""Delete a deployment on BentoCloud."""
res = client.deployment.delete(
deployment_name=deployment_name,
context=context,
context=obj.context,
cluster_name=cluster_name,
kube_namespace=kube_namespace,
)
Expand All @@ -210,9 +209,6 @@ def delete( # type: ignore
return res

@deployment_cli.command()
@click.option(
"--context", type=click.STRING, default=None, help="Yatai context name."
)
@click.option(
"--cluster-name", type=click.STRING, default=None, help="Name of the cluster."
)
Expand All @@ -235,8 +231,9 @@ def delete( # type: ignore
type=click.Choice(["json", "default", "table"]),
default="table",
)
@click.pass_obj
def list( # type: ignore
context: str,
obj: Cli,
cluster_name: str,
query: str,
search: str,
Expand All @@ -246,7 +243,7 @@ def list( # type: ignore
) -> DeploymentListSchema:
"""List existing deployments on BentoCloud."""
res = client.deployment.list(
context=context,
context=obj.context,
cluster_name=cluster_name,
query=query,
search=search,
Expand Down
Loading
Loading