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: add secret management commands #4764

Merged
merged 9 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions src/bentoml/_internal/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .schemas.schemasv1 import CreateDeploymentSchema as CreateDeploymentSchemaV1
from .schemas.schemasv1 import CreateModelRepositorySchema
from .schemas.schemasv1 import CreateModelSchema
from .schemas.schemasv1 import CreateSecretSchema
from .schemas.schemasv1 import DeploymentFullSchema
from .schemas.schemasv1 import DeploymentListSchema
from .schemas.schemasv1 import FinishUploadBentoSchema
Expand All @@ -32,8 +33,11 @@
from .schemas.schemasv1 import OrganizationSchema
from .schemas.schemasv1 import PreSignMultipartUploadUrlSchema
from .schemas.schemasv1 import ResourceInstanceSchema
from .schemas.schemasv1 import SecretListSchema
from .schemas.schemasv1 import SecretSchema
from .schemas.schemasv1 import UpdateBentoSchema
from .schemas.schemasv1 import UpdateDeploymentSchema
from .schemas.schemasv1 import UpdateSecretSchema
from .schemas.schemasv1 import UserSchema
from .schemas.schemasv2 import CreateDeploymentSchema as CreateDeploymentSchemaV2
from .schemas.schemasv2 import DeploymentFullSchema as DeploymentFullSchemaV2
Expand Down Expand Up @@ -561,6 +565,47 @@ def get_latest_model(
models = resp.json()["items"]
return schema_from_object(models[0], ModelSchema) if models else None

def list_secrets(
self,
count: int | None = None,
q: str | None = None,
search: str | None = None,
start: int | None = None,
) -> SecretListSchema:
url = urljoin(self.endpoint, "/api/v1/org_secrets")
if not count:
count = 10
if not start:
start = 0
resp = self.session.get(
url,
params={
"count": count,
"q": q,
"search": search,
"start": start,
},
)
self._check_resp(resp)
return schema_from_json(resp.text, SecretListSchema)

def create_secret(self, secret: CreateSecretSchema) -> SecretSchema:
url = urljoin(self.endpoint, "/api/v1/org_secrets")
resp = self.session.post(url, content=schema_to_json(secret))
self._check_resp(resp)
return schema_from_json(resp.text, SecretSchema)

def delete_secret(self, name: str):
url = urljoin(self.endpoint, f"/api/v1/org_secrets/{name}")
resp = self.session.delete(url)
self._check_resp(resp)

def update_secret(self, name: str, secret: UpdateSecretSchema) -> SecretSchema:
url = urljoin(self.endpoint, f"/api/v1/org_secrets/{name}")
resp = self.session.patch(url, content=schema_to_json(secret))
self._check_resp(resp)
return schema_from_json(resp.text, SecretSchema)


class RestApiClientV2(BaseRestApiClient):
def create_deployment(
Expand Down
3 changes: 3 additions & 0 deletions src/bentoml/_internal/cloud/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class DeploymentConfigParameters:
instance_type: str | None = None
strategy: str | None = None
envs: t.List[dict[str, t.Any]] | None = None
secrets: t.List[str] | None = (None,)
extras: dict[str, t.Any] | None = None
config_dict: dict[str, t.Any] | None = None
config_file: str | t.TextIO | None = None
Expand All @@ -83,6 +84,7 @@ def verify(
or self.instance_type
or self.strategy
or self.envs
or self.secrets
or self.extras
)

Expand All @@ -107,6 +109,7 @@ def verify(
("cluster", self.cluster),
("access_authorization", self.access_authorization),
("envs", self.envs),
("secrets", self.secrets),
]
if v is not None
}
Expand Down
47 changes: 47 additions & 0 deletions src/bentoml/_internal/cloud/schemas/schemasv1.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,50 @@ class DeploymentFullSchema(DeploymentSchema):
__omit_if_default__ = True
__forbid_extra_keys__ = True
urls: list[str]


@attr.define
class SecretItem:
key: str
sub_path: t.Optional[str] = attr.field(default=None)
value: t.Optional[str] = attr.field(default=None)


@attr.define
class SecretContentSchema:
type: str
items: t.List[SecretItem]
path: t.Optional[str] = attr.field(default=None)


@attr.define
class SecretSchema(ResourceSchema):
__omit_if_default__ = True
__forbid_extra_keys__ = False
description: str
creator: UserSchema
content: SecretContentSchema


@attr.define
class SecretListSchema(BaseListSchema):
__omit_if_default__ = True
__forbid_extra_keys__ = False
items: t.List[SecretSchema]


@attr.define
class CreateSecretSchema:
__omit_if_default__ = True
__forbid_extra_keys__ = False
name: str
description: str
content: SecretContentSchema


@attr.define
class UpdateSecretSchema:
__omit_if_default__ = True
__forbid_extra_keys__ = False
description: str
content: SecretContentSchema
1 change: 1 addition & 0 deletions src/bentoml/_internal/cloud/schemas/schemasv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class DeploymentConfigSchema:
__forbid_extra_keys__ = False
access_authorization: bool = attr.field(default=False)
envs: t.Optional[t.List[EnvItemSchema]] = attr.field(default=None)
secrets: t.Optional[t.List[str]] = attr.field(default=None)
services: t.Dict[str, DeploymentServiceConfig] = attr.field(factory=dict)


Expand Down
115 changes: 115 additions & 0 deletions src/bentoml/_internal/cloud/secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from __future__ import annotations

import typing as t

import attr
import yaml

from ..utils import bentoml_cattr
from .config import get_rest_api_client
from .schemas.schemasv1 import CreateSecretSchema
from .schemas.schemasv1 import SecretContentSchema
from .schemas.schemasv1 import SecretItem
from .schemas.schemasv1 import SecretSchema
from .schemas.schemasv1 import UpdateSecretSchema


@attr.define
class SecretInfo(SecretSchema):
created_by: str

def to_dict(self) -> dict[str, t.Any]:
return bentoml_cattr.unstructure(self)

def to_yaml(self):
dt = self.to_dict()
return yaml.dump(dt, sort_keys=False)

@classmethod
def from_secret_schema(cls, secret_schema: SecretSchema) -> SecretInfo:
return cls(
name=secret_schema.name,
uid=secret_schema.uid,
resource_type=secret_schema.resource_type,
labels=secret_schema.labels,
created_at=secret_schema.created_at,
updated_at=secret_schema.updated_at,
deleted_at=secret_schema.deleted_at,
created_by=secret_schema.creator.name,
description=secret_schema.description,
creator=secret_schema.creator,
content=secret_schema.content,
)


@attr.define
class Secret:
@classmethod
def list(
cls,
context: str | None = None,
search: str | None = None,
) -> t.List[SecretInfo]:
cloud_rest_client = get_rest_api_client(context)
secrets = cloud_rest_client.v1.list_secrets(search=search)
return [SecretInfo.from_secret_schema(secret) for secret in secrets.items]

@classmethod
def create(
cls,
context: str | None = None,
name: str | None = None,
description: str | None = None,
type: str | None = None,
path: str | None = None,
key_vals: t.List[t.Tuple[str, str]] = [],
) -> SecretInfo:
secret_schema = CreateSecretSchema(
name=name,
description=description,
content=SecretContentSchema(
type=type,
path=path,
items=[
SecretItem(key=key_val[0], value=key_val[1]) for key_val in key_vals
],
),
)
cloud_rest_client = get_rest_api_client(context)
secret = cloud_rest_client.v1.create_secret(secret_schema)
return SecretInfo.from_secret_schema(secret)

@classmethod
def delete(
cls,
context: str | None = None,
name: str | None = None,
):
if name is None:
raise ValueError("name is required")
cloud_rest_client = get_rest_api_client(context)
cloud_rest_client.v1.delete_secret(name)

@classmethod
def update(
cls,
context: str | None = None,
name: str | None = None,
description: str | None = None,
type: str | None = None,
path: str | None = None,
key_vals: t.List[t.Tuple[str, str]] = [],
) -> SecretInfo:
secret_schema = UpdateSecretSchema(
description=description,
content=SecretContentSchema(
type=type,
path=path,
items=[
SecretItem(key=key_val[0], value=key_val[1]) for key_val in key_vals
],
),
)
cloud_rest_client = get_rest_api_client(context)
secret = cloud_rest_client.v1.update_secret(name, secret_schema)
return SecretInfo.from_secret_schema(secret)
2 changes: 2 additions & 0 deletions src/bentoml_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def create_bentoml_cli() -> click.Command:
from bentoml_cli.deployment import deployment_command
from bentoml_cli.env import env_command
from bentoml_cli.models import model_command
from bentoml_cli.secret import secret_command
from bentoml_cli.serve import serve_command
from bentoml_cli.start import start_command
from bentoml_cli.utils import BentoMLCommandGroup
Expand Down Expand Up @@ -45,6 +46,7 @@ def bentoml_cli():
bentoml_cli.add_command(containerize_command)
bentoml_cli.add_command(deploy_command)
bentoml_cli.add_command(deployment_command)
bentoml_cli.add_command(secret_command)

if psutil.WINDOWS:
import sys
Expand Down
18 changes: 18 additions & 0 deletions src/bentoml_cli/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ def raise_deployment_config_error(err: BentoMLException, action: str) -> t.NoRet
help="List of environment variables pass by --env key=value, --env ...",
multiple=True,
)
@click.option(
"--secret",
type=click.STRING,
help="List of secret names pass by --secret name1, --secret name2, ...",
multiple=True,
)
@click.option(
"-f",
"--config-file",
Expand Down Expand Up @@ -126,6 +132,7 @@ def deploy_command(
instance_type: str | None,
strategy: str | None,
env: tuple[str] | None,
secret: tuple[str] | None,
config_file: str | t.TextIO | None,
config_dict: str | None,
wait: bool,
Expand All @@ -147,6 +154,7 @@ def deploy_command(
instance_type=instance_type,
strategy=strategy,
env=env,
secret=secret,
config_file=config_file,
config_dict=config_dict,
wait=wait,
Expand Down Expand Up @@ -474,6 +482,12 @@ def apply( # type: ignore
help="List of environment variables pass by --env key=value, --env ...",
multiple=True,
)
@click.option(
"--secret",
type=click.STRING,
help="List of secret names pass by --secret name1, --secret name2, ...",
multiple=True,
)
@click.option(
"-f",
"--config-file",
Expand Down Expand Up @@ -512,6 +526,7 @@ def create(
instance_type: str | None,
strategy: str | None,
env: tuple[str] | None,
secret: tuple[str] | None,
config_file: str | t.TextIO | None,
config_dict: str | None,
wait: bool,
Expand All @@ -533,6 +548,7 @@ def create(
instance_type=instance_type,
strategy=strategy,
env=env,
secret=secret,
config_file=config_file,
config_dict=config_dict,
wait=wait,
Expand Down Expand Up @@ -706,6 +722,7 @@ def create_deployment(
instance_type: str | None,
strategy: str | None,
env: tuple[str] | None,
secret: tuple[str] | None,
config_file: str | t.TextIO | None,
config_dict: str | None,
wait: bool,
Expand All @@ -729,6 +746,7 @@ def create_deployment(
if env is not None
else None
),
secrets=secret,
config_file=config_file,
config_dict=cfg_dict,
)
Expand Down
Loading
Loading