Skip to content

Commit

Permalink
Work on k8s backend and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelldls committed Aug 2, 2024
1 parent 2cf86f3 commit cd7127f
Show file tree
Hide file tree
Showing 14 changed files with 109 additions and 114 deletions.
14 changes: 7 additions & 7 deletions src/edge_containers_cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ def main(
help="Service instances repository",
envvar=ENV.repo.value,
),
namespace: str = typer.Option(
ECContext().namespace,
"-n",
"--namespace",
help="Kubernetes namespace to use",
envvar=ENV.namespace.value,
target: str = typer.Option(
ECContext().target,
"-t",
"--target",
help="A K8S namespace or ARGOCD project",
envvar=ENV.target.value,
),
backend: ECBackends = typer.Option(
ECBackends.K8S,
Expand Down Expand Up @@ -110,7 +110,7 @@ def main(

context = ECContext(
repo=repo,
namespace=namespace,
target=target,
log_url=log_url,
)
ec_backend.set_context(context)
Expand Down
2 changes: 1 addition & 1 deletion src/edge_containers_cli/autocomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def autocomplete_backend_init(ctx: typer.Context):
params = ctx.parent.params # type: ignore
context = ECContext(
repo=params["repo"],
namespace=params["namespace"],
target=params["target"],
log_url=params["log_url"],
)
ec_backend.set_context(context)
Expand Down
8 changes: 4 additions & 4 deletions src/edge_containers_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def delete(
Remove a helm deployment from the cluster
"""
confirmation(
f"Remove all versions of {service_name} from the namespace `{backend.commands.namespace}`",
f"Remove all versions of {service_name} from the target `{backend.commands.target}`",
yes,
)
backend.commands.delete(service_name)
Expand All @@ -92,7 +92,7 @@ def deploy(
"""
confirmation(
f"Deploy {service_name.lower()} "
f"of version `{version}` to namespace `{backend.commands.namespace}`",
f"of version `{version}` to target `{backend.commands.target}`",
yes,
)
args = args if not wait else args + " --wait"
Expand All @@ -117,7 +117,7 @@ def deploy_local(
"""
confirmation(
f"Deploy local {svc_instance.name.lower()} "
f"from {svc_instance} to namespace `{backend.commands.namespace}`",
f"from {svc_instance} to target `{backend.commands.target}`",
yes,
)
backend.commands.deploy_local(svc_instance, args)
Expand Down Expand Up @@ -207,7 +207,7 @@ def ps(
False, "--wide", "-w", help="use a wide format with additional fields"
),
):
"""List the services running in the current namespace"""
"""List the services running in the current target"""
backend.commands.ps(running_only, wide)


Expand Down
20 changes: 10 additions & 10 deletions src/edge_containers_cli/cmds/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ class Commands(ABC):
"""

def __init__(self, ctx: ECContext):
self._namespace = ctx.namespace
self._namespace_valid = False
self._target = ctx.target
self._target_valid = False
self._repo = ctx.repo
self._log_url = ctx.log_url

@property
def namespace(self):
if not self._namespace_valid: # Only validate once
if self._namespace == ECContext().namespace:
def target(self):
if not self._target_valid: # Only validate once
if self._target == ECContext().target:
raise CommandError(
f"Please set {ENV.namespace.value} or pass --namespace"
f"Please set {ENV.target.value} or pass --target"
)
else:
self._validate_namespace()
self._namespace_valid = True
return self._namespace
self._validate_target()
self._target_valid = True
return self._target

@property
def repo(self):
Expand Down Expand Up @@ -126,7 +126,7 @@ def _get_logs(self, service_name: str, prev: bool) -> str:
def _logs(self, service_name: str, prev: bool):
print(self._get_logs(service_name, prev))

def _validate_namespace(self):
def _validate_target(self):
pass

def _all_services(self) -> list[str]:
Expand Down
17 changes: 6 additions & 11 deletions src/edge_containers_cli/cmds/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,21 @@ def deploy_local(self, service_path: Path):

def deploy(self):
"""
Generate an IOC helm chart and deploy it to the cluster
Clone a helm chart and deploy it to the cluster
"""
if not self.version:
raise CommandError("Version not found")

shell.run_command(
f"git clone {self.repo} {self.tmp} --depth=1 "
f"--single-branch --branch={self.version}",
)

self._do_deploy(self.tmp / "services" / self.service_name)

def _do_deploy(self, service_folder: Path):
"""
Generate an on the fly chart using beamline chart with config folder.
Deploy the resulting helm chart to the cluster.
Package a Helm chart and deploy it to the cluster
"""
print(f"Deploying {self.service_name}:{self.version}")
# package up the charts to get the appVersion set
shell.run_command(f"helm dependency update {service_folder}")

# package up the charts to get the appVersion set
with chdir(service_folder):
shell.run_command(
f"helm package {service_folder} -u --app-version {self.version}",
Expand All @@ -108,19 +102,20 @@ def _install(self, helm_chart: Path):
shared_vals = f"--values {helm_chart.parent.parent}/beamline_values.yaml "

helm_cmd = "template" if self.template else "upgrade --install"
namespace = f"--namespace {self.namespace} " if self.namespace else ""
cmd = (
f"helm {helm_cmd} {self.service_name} {helm_chart} "
f"{shared_vals} "
f"--values {helm_chart.parent}/values.yaml "
f"--namespace {self.namespace} "
f"{namespace} "
f"{self.args} "
)
shell.run_command(cmd, show=True, skip_on_dryrun=True)


def validate_instance_path(service_path: Path):
"""
verify that the service instance path is valid
verify that the chart path is valid
"""
log.info(f"checking {service_path}")
if not (service_path / "Chart.yaml").exists():
Expand Down
57 changes: 28 additions & 29 deletions src/edge_containers_cli/cmds/k8s_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,18 @@ def __init__(
super().__init__(ctx)

def attach(self, service_name):
fullname = check_service(service_name, self.namespace)
fullname = check_service(service_name, self.target)
shell.run_interactive(
f"kubectl -it -n {self.namespace} attach {fullname}", skip_on_dryrun=True
f"kubectl -it -n {self.target} attach {fullname}", skip_on_dryrun=True
)

def delete(self, service_name):
check_service(service_name, self.namespace)
shell.run_command(f"helm delete -n {self.namespace} {service_name}", skip_on_dryrun=True)
check_service(service_name, self.target)
shell.run_command(f"helm delete -n {self.target} {service_name}", skip_on_dryrun=True)

def deploy(self, service_name, version, args):
chart = Helm(
self.namespace,
self.target,
service_name,
args,
version,
Expand All @@ -68,50 +68,49 @@ def deploy(self, service_name, version, args):

def deploy_local(self, svc_instance, args):
service_name = svc_instance.name.lower()
chart = Helm(self.namespace, service_name, args=args)
chart = Helm(self.target, service_name, args=args)
chart.deploy_local(svc_instance)

def exec(self, service_name):
fullname = check_service(service_name, self.namespace)
shell.run_interactive(f"kubectl -it -n {self.namespace} exec {fullname} -- bash", skip_on_dryrun=True)
fullname = check_service(service_name, self.target)
shell.run_interactive(f"kubectl -it -n {self.target} exec {fullname} -- bash", skip_on_dryrun=True)

def logs(self, service_name, prev):
self._logs(service_name, prev)

def log_history(self, service_name):
check_service(service_name, self.namespace)
check_service(service_name, self.target)
url = self.log_url.format(service_name=service_name)
webbrowser.open(url)

def ps(self, running_only, wide):
self._ps(running_only, wide)

def restart(self, service_name):
check_service(service_name, self.namespace)
check_service(service_name, self.target)
pod_name = shell.run_command(
f"kubectl get -n {self.namespace} pod -l app={service_name} -o name",
f"kubectl get -n {self.target} pod -l app={service_name} -o name",
)
shell.run_command(f"kubectl delete -n {self.namespace} {pod_name}", skip_on_dryrun=True)
shell.run_command(f"kubectl delete -n {self.target} {pod_name}", skip_on_dryrun=True)

def start(self, service_name):
fullname = check_service(service_name, self.namespace)
shell.run_command(f"kubectl scale -n {self.namespace} {fullname} --replicas=1", skip_on_dryrun=True)
fullname = check_service(service_name, self.target)
shell.run_command(f"kubectl scale -n {self.target} {fullname} --replicas=1", skip_on_dryrun=True)

def stop(self, service_name):
fullname = check_service(service_name, self.namespace)
shell.run_command(f"kubectl scale -n {self.namespace} {fullname} --replicas=0 ", skip_on_dryrun=True)
fullname = check_service(service_name, self.target)
shell.run_command(f"kubectl scale -n {self.target} {fullname} --replicas=0 ", skip_on_dryrun=True)

def template(self, svc_instance, args):
datetime.strftime(datetime.now(), "%Y.%-m.%-d-b%-H.%-M")

service_name = svc_instance.name.lower()

chart = Helm(
self.namespace,
"",
service_name,
args=args,
template=True,
repo=self.repo,
)
chart.deploy_local(svc_instance)

Expand All @@ -121,7 +120,7 @@ def _get_services(self, running_only):
# Gives all services (running & not running) and their image
for resource in ["deployment", "statefulset"]:
kubectl_res = shell.run_command(
f"kubectl get {resource} -n {self.namespace} {jsonpath_deploy_info}",
f"kubectl get {resource} -n {self.target} {jsonpath_deploy_info}",
)
if kubectl_res:
res_df = polars.read_csv(
Expand All @@ -138,7 +137,7 @@ def _get_services(self, running_only):

# Gives the status, restarts for running services
kubectl_gtpo = shell.run_command(
f"kubectl get pods -n {self.namespace} {jsonpath_pod_info}",
f"kubectl get pods -n {self.target} {jsonpath_pod_info}",
)
if kubectl_gtpo:
gtpo_df = polars.read_csv(
Expand All @@ -163,7 +162,7 @@ def _get_services(self, running_only):

# Adds the version, deployment time for all services
helm_out = shell.run_command(
f"helm list -n {self.namespace} -o json"
f"helm list -n {self.target} -o json"
)
helm_df = polars.read_json(StringIO(str(helm_out)))
helm_df = helm_df.rename({"app_version": "version", "updated": "deployed"})
Expand All @@ -181,28 +180,28 @@ def _get_services(self, running_only):
return ServicesDataFrame(services_df)

def _get_logs(self, service_name, prev):
fullname = check_service(service_name, self.namespace)
fullname = check_service(service_name, self.target)
previous = "-p" if prev else ""

logs = shell.run_command(
f"kubectl -n {self.namespace} logs {fullname} {previous}",
f"kubectl -n {self.target} logs {fullname} {previous}",
error_OK=True,
)
return logs

def _validate_namespace(self):
def _validate_target(self):
"""
Verify we have a good namespace that exists in the cluster
"""
cmd = f"kubectl get namespace {self._namespace} -o name"
cmd = f"kubectl get namespace {self._target} -o name"
result = shell.run_command(cmd, error_OK=True)
if "NotFound" in str(result):
raise CommandError(f"Namespace '{self._namespace}' not found")
log.info("domain = %s", self._namespace)
raise CommandError(f"Namespace '{self._target}' not found")
log.info("domain = %s", self._target)

def _all_services(self):
columns = "-o custom-columns=NAME:metadata.name"
namespace = f"-n {self.namespace}"
namespace = f"-n {self.target}"
labels = "-l is_ioc==true"
command = f"kubectl {namespace} {labels} get statefulset {columns}"
all_list = shell.run_command(command).split()[1:]
Expand All @@ -213,7 +212,7 @@ def _running_services(self):
all = self._all_services()

columns = "-o custom-columns=NAME:metadata.name"
namespace = f"-n {self.namespace}"
namespace = f"-n {self.target}"
labels = "-l is_ioc==true"
selector = "--field-selector=status.phase==Running"
command = f"kubectl {namespace} {labels} get pod {selector} {columns}"
Expand Down
2 changes: 1 addition & 1 deletion src/edge_containers_cli/cmds/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ def __init__(

self.commands = commands
self.running_only = running_only
self.beamline = commands.namespace
self.beamline = commands.target

def compose(self) -> ComposeResult:
"""Create child widgets for the app."""
Expand Down
4 changes: 2 additions & 2 deletions src/edge_containers_cli/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ECLogLevels(str, Enum):

class ENV(str, Enum):
repo = "EC_SERVICES_REPO"
namespace = "EC_NAMESPACE"
target = "EC_TARGET"
backend = "EC_CLI_BACKEND"
verbose = "EC_VERBOSE"
dryrun = "EC_DRYRUN"
Expand All @@ -29,5 +29,5 @@ class ENV(str, Enum):
@dataclass
class ECContext:
repo: str = ""
namespace: str = ""
target: str = ""
log_url: str = ""
2 changes: 1 addition & 1 deletion src/edge_containers_cli/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,4 @@ def list_instances(service_name: str, repo: str, root_dir: Path, shared: str=Non

sorted_list = natsorted(svc_list)[::-1]
services_df = polars.from_dict({"version": sorted_list})
return services_df
return services_df
2 changes: 1 addition & 1 deletion src/edge_containers_cli/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def run_command(
return result

def run_interactive(
self, command: str, error_OK=False, skip_on_dryrun=False
self, command: str, error_OK=False, skip_on_dryrun=False,
) -> bool:
"""
Run a command and allow stdin and stdout, returns True on success
Expand Down
Loading

0 comments on commit cd7127f

Please sign in to comment.