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

fix: cluster credentials to fit new schema #181

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
44 changes: 32 additions & 12 deletions silverback/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import TYPE_CHECKING, Optional
from silverback.cluster.client import RegistryCredentials

import click
import yaml # type: ignore[import-untyped]
Expand Down Expand Up @@ -789,8 +790,8 @@ def registry():
@cluster_client
def credentials_list(cluster: "ClusterClient"):
"""List container registry credentials"""

if creds := list(cluster.registry_credentials):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this tab may cause issues

if creds := cluster.registry_credentials:
click.echo(yaml.safe_dump(creds))

else:
Expand All @@ -814,17 +815,22 @@ def credentials_info(cluster: "ClusterClient", name: str):
@click.argument("registry")
@cluster_client
def credentials_new(cluster: "ClusterClient", name: str, registry: str):
"""Add registry private registry credentials. This command will prompt you for a username and
password.
"""Add registry private registry credentials. This command will prompt you for a username,
password, and email.
"""

username = click.prompt("Username")
password = click.prompt("Password", hide_input=True)
email = click.prompt("Email")

creds = cluster.new_credentials(
name=name, hostname=registry, username=username, password=password
name=name,
docker_server=registry,
docker_username=username,
docker_password=password,
docker_email=email
)
click.echo(yaml.safe_dump(creds.model_dump(exclude={"id"})))
click.echo(yaml.safe_dump(creds.model_dump()))


@registry.command(name="update")
Expand All @@ -838,22 +844,36 @@ def credentials_update(cluster: "ClusterClient", name: str, registry: str | None

username = click.prompt("Username")
password = click.prompt("Password", hide_input=True)
email = click.prompt("Email")

creds = creds.update(hostname=registry, username=username, password=password)
click.echo(yaml.safe_dump(creds.model_dump(exclude={"id"})))
creds = creds.update(
docker_server=registry,
docker_username=username,
docker_password=password,
docker_email=email
)
click.echo(yaml.safe_dump(creds.model_dump()))


@registry.command(name="remove")
@click.argument("name")
@cluster_client
def credentials_remove(cluster: "ClusterClient", name: str):
"""Remove a set of registry credentials"""
if not (creds := cluster.registry_credentials.get(name)):
# Verify the credential exists in the list
if name not in cluster.registry_credentials:
raise click.UsageError(f"Unknown credentials '{name}'")

creds.remove() # NOTE: No confirmation because can only delete if no references exist
click.secho(f"registry credentials '{creds.name}' removed.", fg="green", bold=True)

# Create instance and remove
creds = RegistryCredentials(
name=name,
docker_server="",
docker_username="",
docker_password="",
docker_email=""
)
creds.remove() # This will call DELETE /credentials/{name}
click.secho(f"registry credentials '{name}' removed.", fg="green", bold=True)

@cluster.group(cls=SectionedHelpGroup)
def vars():
Expand Down
50 changes: 34 additions & 16 deletions silverback/cluster/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ape.contracts import ContractInstance
from ape.logging import LogLevel
from apepay import Stream, StreamManager
from pydantic import computed_field
from pydantic import computed_field, ValidationError

from silverback.exceptions import ClientError
from silverback.version import version
Expand Down Expand Up @@ -74,19 +74,20 @@ def __hash__(self) -> int:
def update(
self,
name: str | None = None,
hostname: str | None = None,
username: str | None = None,
password: str | None = None,
docker_server: str | None = None,
docker_username: str | None = None,
docker_password: str | None = None,
docker_email: str | None = None,
) -> "RegistryCredentials":
response = self.cluster.put(
f"/credentials/{self.id}",
json=dict(name=name, hostname=hostname, username=username, password=password),
f"/credentials/{self.name}",
json=dict(name=name, docker_server=docker_server, docker_username=docker_username, docker_password=docker_password, docker_email=docker_email),
)
handle_error_with_response(response)
return self

def remove(self):
response = self.cluster.delete(f"/credentials/{self.id}")
response = self.cluster.delete(f"/credentials/{self.name}")
handle_error_with_response(response)


Expand Down Expand Up @@ -215,6 +216,7 @@ def logs(self) -> list[BotLogEntry]:

def remove(self, network: str):
response = self.cluster.delete(f"/bots/{self.name}", params={"network": network})
breakpoint()
handle_error_with_response(response)


Expand All @@ -230,6 +232,7 @@ def __init__(self, *args, **kwargs):
RegistryCredentials.cluster = self # Connect to cluster client
VariableGroup.cluster = self # Connect to cluster client
Bot.cluster = self # Connect to cluster client


def send(self, request, *args, **kwargs):
try:
Expand Down Expand Up @@ -262,24 +265,39 @@ def health(self) -> ClusterHealth:
handle_error_with_response(response)
return ClusterHealth.model_validate(response.json())

def get_credential_details(self, name: str) -> RegistryCredentialsInfo:
# Try to get full details of a single credential
response = self.get(f"/credentials/{name}")
handle_error_with_response(response)
return RegistryCredentialsInfo.model_validate(response.json())

@property
def registry_credentials(self) -> dict[str, RegistryCredentials]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might need to change this interface because it doesn't match this anymore

response = self.get("/credentials")
handle_error_with_response(response)

return {
creds.name: creds for creds in map(RegistryCredentials.model_validate, response.json())
}
name: self.get_credential_details(name) # We'd implement this to use the right HTTP method
for name in response.json()
}

def new_credentials(
self, name: str, hostname: str, username: str, password: str
self, name: str, docker_server: str, docker_username: str, docker_password: str, docker_email: str
) -> RegistryCredentials:
response = self.post(
"/credentials",
json=dict(name=name, hostname=hostname, username=username, password=password),
)
form = dict(
name=name,
docker_server=docker_server,
docker_username=docker_username,
docker_password=docker_password,
docker_email=docker_email,
)
response = self.post("/credentials", json=form)
handle_error_with_response(response)
return RegistryCredentials.model_validate(response.json())

try:
return RegistryCredentials.model_validate(response.json())
except ValidationError as e:
raise ClientError(f"Invalid response format: {e}")

@property
def variable_groups(self) -> dict[str, VariableGroup]:
response = self.get("/vars")
Expand Down
8 changes: 4 additions & 4 deletions silverback/cluster/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,11 @@ def cluster(self) -> ServiceHealth:


class RegistryCredentialsInfo(BaseModel):
id: str
name: str
hostname: str
created: datetime
updated: datetime
docker_server: str
docker_username: str
docker_password: str
docker_email: str


class VariableGroupInfo(BaseModel):
Expand Down
Loading