Skip to content
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
1 change: 1 addition & 0 deletions gradient/api_sdk/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
from .model_client import ModelsClient
from .notebook_client import NotebooksClient
from .project_client import ProjectsClient
from .secret_client import SecretsClient
from .sdk_client import SdkClient
from .tensorboards_client import TensorboardClient
3 changes: 2 additions & 1 deletion gradient/api_sdk/clients/sdk_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from . import DeploymentsClient, ExperimentsClient, HyperparameterJobsClient, ModelsClient, ProjectsClient, \
MachinesClient, NotebooksClient
MachinesClient, NotebooksClient, SecretsClient
from .job_client import JobsClient
from .. import logger as sdk_logger

Expand All @@ -18,3 +18,4 @@ def __init__(self, api_key, logger=sdk_logger.MuteLogger()):
self.projects = ProjectsClient(api_key=api_key, logger=logger)
self.machines = MachinesClient(api_key=api_key, logger=logger)
self.notebooks = NotebooksClient(api_key=api_key, logger=logger)
self.secrets = SecretsClient(api_key=api_key, logger=logger)
59 changes: 59 additions & 0 deletions gradient/api_sdk/clients/secret_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from .base_client import BaseClient
from .. import models, repositories
from ...exceptions import ReceivingDataFailedError

SECRET_ENTITIES = ("cluster", "project", "team")


class SecretsClient(BaseClient):
def _validate_entity(self, entity, entity_id):
if entity not in SECRET_ENTITIES:
raise ReceivingDataFailedError("Unknown entity type provided")
if not entity_id and entity != 'team':
raise ReceivingDataFailedError("Entity ID is required")

def list(self, entity, entity_id):
"""List secrets by entity type and ID.

:param str entity: entity type (ex: team, cluster, project)
:param str entity_id: entity ID

:returns: list of secrets
:rtype: list[models.Secret]
"""
self._validate_entity(entity, entity_id)

repository = self.build_repository(repositories.ListSecrets)
secrets = repository.list(entity=entity, entity_id=entity_id)
return secrets

def set(self, entity, entity_id, name, value):
"""Set entity secret.

:param str entity: entity type (ex: team, cluster, project)
:param str entity_id: entity ID
:param str name: secret name
:param str value: secret value

:returns:
:rtype: None
"""
self._validate_entity(entity, entity_id)

repository = self.build_repository(repositories.SetSecret)
repository.set(entity=entity, entity_id=entity_id, name=name, value=value)

def delete(self, entity, entity_id, name):
"""Delete entity secret.

:param str entity: entity type (ex: team, cluster, project)
:param str entity_id: entity ID
:param str name: secret name

:returns:
:rtype: None
"""
self._validate_entity(entity, entity_id)

repository = self.build_repository(repositories.DeleteSecret)
repository.delete(entity=entity, entity_id=entity_id, name=name)
1 change: 1 addition & 0 deletions gradient/api_sdk/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .model import Model, ModelFile
from .notebook import Notebook, NotebookStart
from .project import Project
from .secret import Secret
from .tag import Tag
from .tensorboard import Instance, Tensorboard
from .vm_type import VmType, VmTypeGpuModel
6 changes: 6 additions & 0 deletions gradient/api_sdk/models/secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import attr


@attr.s
class Secret(object):
name = attr.ib(type=str, default=None)
1 change: 1 addition & 0 deletions gradient/api_sdk/repositories/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
from .notebooks import CreateNotebook, DeleteNotebook, GetNotebook, ListNotebooks, GetNotebookMetrics, \
StreamNotebookMetrics, StopNotebook, StartNotebook, ForkNotebook, ListNotebookArtifacts
from .projects import CreateProject, ListProjects, DeleteProject, GetProject
from .secrets import ListSecrets, SetSecret, DeleteSecret
from .tensorboards import CreateTensorboard, GetTensorboard, ListTensorboards, UpdateTensorboard, DeleteTensorboard
55 changes: 55 additions & 0 deletions gradient/api_sdk/repositories/secrets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from .common import BaseRepository, ListResources
from .. import config, serializers


class SecretsMixin(object):
SERIALIZER_CLS = serializers.SecretSchema

def _get_api_url(self, **kwargs):
return config.config.CONFIG_HOST

def _resource_url(self, **kwargs):
return "/{}s/secrets/{}".format(kwargs.get("entity"), kwargs.get("name"))

def _get_request_params(self, kwargs):
params = {}

entity_id = kwargs.get("entity_id")
if entity_id:
params["{}Id".format(kwargs.get("entity"))] = entity_id

return params


class ListSecrets(SecretsMixin, ListResources):
def get_request_url(self, **kwargs):
return "/{}s/secrets".format(kwargs.get("entity"))


class SetSecret(SecretsMixin, BaseRepository):
def get_request_url(self, **kwargs):
return self._resource_url(**kwargs)

def _get_request_json(self, kwargs):
return {"value": kwargs.get("value")}

def _send_request(self, client, url, json=None, params=None):
response = client.put(url, json=json, params=params)
return response

def set(self, **kwargs):
response = self._get(**kwargs)
self._validate_response(response)


class DeleteSecret(SecretsMixin, BaseRepository):
def get_request_url(self, **kwargs):
return self._resource_url(**kwargs)

def _send_request(self, client, url, json=None, params=None):
response = client.delete(url, params=params)
return response

def delete(self, **kwargs):
response = self._get(**kwargs)
self._validate_response(response)
1 change: 1 addition & 0 deletions gradient/api_sdk/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .model import Model, ModelFileSchema
from .notebook import NotebookSchema, NotebookStartSchema
from .project import Project
from .secret import SecretSchema
from .tag import TagSchema
from .tensorboard import InstanceSchema, TensorboardSchema, TensorboardDetailSchema
from .vm_type import VmTypeSchema, VmTypeGpuModelSchema
10 changes: 10 additions & 0 deletions gradient/api_sdk/serializers/secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import marshmallow as ma

from .. import models
from ..serializers.base import BaseSchema


class SecretSchema(BaseSchema):
MODEL = models.Secret

name = ma.fields.Str()
1 change: 1 addition & 0 deletions gradient/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import gradient.cli.notebooks
import gradient.cli.projects
import gradient.cli.run
import gradient.cli.secrets
import gradient.cli.tensorboards


Expand Down
88 changes: 88 additions & 0 deletions gradient/cli/secrets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import click

from gradient.cli import common
from gradient.cli.cli import cli
from gradient.cli.common import ClickGroup, api_key_option
from gradient.commands.secrets import DeleteSecretCommand, ListSecretsCommand, SetSecretCommand
from gradient.api_sdk.clients.secret_client import SECRET_ENTITIES


class EntityId(common.GradientOption):
def handle_parse_result(self, ctx, opts, args):
self.required = opts.get('entity') != 'team'
return super(EntityId, self).handle_parse_result(ctx, opts, args)


@cli.group("secrets", help="Manage secrets", cls=ClickGroup)
def secrets():
pass


@secrets.command("list", help="List secrets")
@click.argument("entity", type=click.Choice(SECRET_ENTITIES))
@click.option(
"--id",
"entity_id",
help="Entity ID",
cls=EntityId,
)
@api_key_option
@common.options_file
def get_secrets_list(api_key, entity, entity_id, options_file):
command = ListSecretsCommand(api_key=api_key)
command.execute(entity=entity, entity_id=entity_id)


@secrets.command("set", help="Set secret")
@click.argument("entity", type=click.Choice(SECRET_ENTITIES))
@click.option(
"--id",
"entity_id",
help="Entity ID",
cls=EntityId,
)
@click.option(
"--name",
"name",
prompt=True,
required=True,
help="Secret name",
cls=common.GradientOption,
)
@click.option(
"--value",
"value",
hide_input=True,
prompt=True,
required=True,
help="Secret value",
cls=common.GradientOption,
)
@api_key_option
@common.options_file
def set_secret(api_key, entity, entity_id, name, value, options_file):
command = SetSecretCommand(api_key=api_key)
command.execute(entity=entity, entity_id=entity_id, name=name, value=value)


@secrets.command("delete", help="Delete secret")
@click.argument("entity", type=click.Choice(SECRET_ENTITIES))
@click.option(
"--id",
"entity_id",
help="Entity ID",
cls=EntityId,
)
@click.option(
"--name",
"name",
prompt=True,
required=True,
help="Secret name",
cls=common.GradientOption,
)
@api_key_option
@common.options_file
def delete_secret(api_key, entity, entity_id, name, options_file):
command = DeleteSecretCommand(api_key=api_key)
command.execute(entity=entity, entity_id=entity_id, name=name)
46 changes: 46 additions & 0 deletions gradient/commands/secrets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import abc

import six

from gradient import api_sdk
from gradient.cli_constants import CLI_PS_CLIENT_NAME
from gradient.commands.common import BaseCommand, ListCommandMixin


@six.add_metaclass(abc.ABCMeta)
class BaseSecretsCommand(BaseCommand):
def _get_client(self, api_key, logger):
client = api_sdk.clients.SecretsClient(
api_key=api_key,
logger=logger,
ps_client_name=CLI_PS_CLIENT_NAME,
)
return client


class ListSecretsCommand(ListCommandMixin, BaseSecretsCommand):
def _get_instances(self, kwargs):
instances = self.client.list(**kwargs)
return instances

def _get_table_data(self, objects):
"""
:param list[Secret] objects: object
"""
data = [("Name",)]
for secret in objects:
data.append((secret.name,))

return data


class SetSecretCommand(BaseSecretsCommand):
def execute(self, entity, entity_id, name, value):
self.client.set(entity=entity, entity_id=entity_id, name=name, value=value)
self.logger.log("Set {} secret '{}'".format(entity, name))


class DeleteSecretCommand(BaseSecretsCommand):
def execute(self, entity, entity_id, name):
self.client.delete(entity=entity, entity_id=entity_id, name=name)
self.logger.log("Deleted {} secret '{}'".format(entity, name))
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def run(self):
'PyYAML==5.*',
'python-dateutil==2.*',
'websocket-client==0.57.*',
'gradient-utils>=0.1.1',
'gradient-utils>=0.1.2',
],
entry_points={'console_scripts': [
'gradient = gradient:main.main',
Expand Down
9 changes: 9 additions & 0 deletions tests/example_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -8062,3 +8062,12 @@
"message": "PSEOF"
}
]

LIST_SECRETS_RESPONSE = [
{
"name": "aws_access_key_id"
},
{
"name": "aws_secret_access_key"
}
]
Loading