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: view deps / scopes. Add find command #296

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions contxt/cli/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class Clients:
env: str
org_slug: Optional[str]

@cachedproperty
def accessible_orgs(self) -> str:
return self.contxt.get("organizations")

@cachedproperty
def org_id(self) -> str:
if not self.org_slug:
Expand Down
62 changes: 62 additions & 0 deletions contxt/cli/commands/find.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import click

from contxt.cli.clients import Clients
from contxt.cli.utils import print_table
from contxt.utils.serializer import Serializer


@click.group()
def find() -> None:
"""Search functions"""


@find.command("client")
@click.argument("CLIENT_ID")
@click.pass_obj
def client(clients: Clients, client_id: str) -> None:
"""Find a service instance by its Client ID"""
print(
f"Searching all accessible orgs ({','.join([x.get('slug') for x in clients.accessible_orgs])})"
)
for org in clients.accessible_orgs:
try:
instances = clients.contxt_deployments.get_service_instances(org.get("id"))
edges = clients.contxt_deployments.get_edge_nodes(org.get("id"))
instances.extend(edges)
for inst in instances:
client = inst.client_id
if client:
if client.find(client_id) > -1:
print(f'Found match in org {org.get("slug")}')
print(Serializer.to_pretty_cli(inst))
except Exception:
pass


@find.command("by-name")
@click.argument("NAME_MATCH")
@click.pass_obj
def by_name(clients: Clients, name_match: str) -> None:
"""Find a service instance by its name"""
print(
f"Searching all accessible orgs ({','.join([x.get('slug') for x in clients.accessible_orgs])})"
)
hits = []
for org in clients.accessible_orgs:
try:
instances = clients.contxt_deployments.get_service_instances(org.get("id"))
edges = clients.contxt_deployments.get_edge_nodes(org.get("id"))
instances.extend(edges)
for inst in instances:
name = inst.name
if name:
name = name.lower()
if name.find(name_match.lower()) > -1:
match = inst.__dict__
match["type"] = str(type(inst))
match["org"] = org.get("slug")
hits.append(match)
except Exception:
pass

print_table(hits, keys=["org", "type", "id", "name", "description", "client_id"])
125 changes: 118 additions & 7 deletions contxt/cli/commands/service_instances.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import sys
from typing import List, Optional

import click

from contxt.cli.clients import Clients
from contxt.cli.utils import OPTIONAL_PROMPT_KWARGS, fields_option, print_item, print_table, sort_option
from contxt.models.contxt import ServiceInstance
from contxt.models.contxt import ServiceInstance, ServiceInstanceGrant, ServiceInstanceScope


@click.group()
Expand All @@ -13,17 +14,23 @@ def service_instances() -> None:


@service_instances.command()
@click.argument("SERVICE_INSTANCE_ID", type=int, required=False)
@click.option("--service-id")
@click.pass_obj
@fields_option(default="id, slug, service_id, description", obj=ServiceInstance)
@sort_option(default="slug")
def get(clients: Clients, service_id: Optional[str], fields: List[str], sort: str) -> None:
def get(
clients: Clients, service_instance_id: int, service_id: Optional[str], fields: List[str], sort: str
) -> None:
"""Get service instance(s)"""
items = (
clients.contxt_deployments.get(f"{clients.org_id}/services/{service_id}/service_instances")
if service_id
else clients.contxt_deployments.get(f"{clients.org_id}/service_instances")
)
if service_instance_id:
items = [clients.contxt_deployments.get_service_instance(clients.org_id, service_instance_id)]
else:
items = (
clients.contxt_deployments.get(f"{clients.org_id}/services/{service_id}/service_instances")
if service_id
else clients.contxt_deployments.get(f"{clients.org_id}/service_instances")
)
print_table(items, keys=fields, sort_by=sort)


Expand Down Expand Up @@ -65,3 +72,107 @@ def create(
def delete(clients: Clients, id: str) -> None:
"""Delete service instance"""
clients.contxt_deployments.delete(f"{clients.org_id}/service_instances/{id}")


"""
Scopes Commands
"""


@service_instances.command("scopes")
@click.argument("SERVICE_INSTANCE_ID")
@fields_option(default="label, description", obj=ServiceInstanceScope)
@sort_option(default="label")
@click.pass_obj
def scopes(clients: Clients, service_instance_id: str, fields: List[str], sort: str) -> None:
"""Get scopes for a specific service instance"""
scopes = clients.contxt_deployments.get_service_instance_scopes(clients.org_id, service_instance_id)
print_table(items=scopes, keys=fields, sort_by=sort)


"""
Dependency Commands
"""


@service_instances.command("deps")
@click.argument("SERVICE_INSTANCE_ID")
@click.pass_obj
def get_dependencies(clients: Clients, service_instance_id: str) -> None:
"""Get dependencies for a specific service instance"""
dependencies = clients.contxt_deployments.get_service_instance_dependencies(
clients.org_id, service_instance_id
)
objs = []
for dep in dependencies:
to_service = clients.contxt_deployments.get_service_instance(
clients.org_id, dep.to_service_instance_id
)
if len(dep.ServiceInstanceScopes):
for row in dep.ServiceInstanceScopes:
objs.append(
{
"to_service_instance_id": dep.to_service_instance_id,
"to_service_instance_name": to_service.name,
"scope": row.label,
"description": row.description,
}
)
else:
objs.append(
{
"to_service_instance_id": dep.to_service_instance_id,
"to_service_instance_name": to_service.name,
"scope": "<No Scopes>",
"description": "<No Scopes>",
}
)
print_table(
items=objs,
keys=["to_service_instance_id", "to_service_instance_name", "scope", "description"],
sort_by="sort",
)


@service_instances.command("add-dep")
@click.option("--from-id", help="From Service ID", required=True)
@click.option("--to-id", help="To Service ID", required=True)
@click.pass_obj
def add_dep(clients: Clients, from_id: int, to_id: int) -> None:
"""Add a new dependency to a service instance"""
from_service = clients.contxt_deployments.get_service_instance(clients.org_id, from_id)
to_service = clients.contxt_deployments.get_service_instance(clients.org_id, to_id)

print(f"Creating dependency between {from_service.name} -> {to_service.name}")
grant = ServiceInstanceGrant(
from_service_instance_id=from_service.id, to_service_instance_id=to_service.id
)
dep = clients.contxt_deployments.create_service_dependency(clients.org_id, grant)
print(dep)


@service_instances.command("rm-dep")
@click.option("--from-id", help="From Service ID", required=True)
@click.option("--to-id", help="To Service ID", required=True)
@click.pass_obj
def remove_dep(clients: Clients, from_id: int, to_id: int) -> None:
"""Remove an existing dependency from a service instance"""
from_service = clients.contxt_deployments.get_service_instance(clients.org_id, from_id)
existing_deps = clients.contxt_deployments.get_service_instance_dependencies(clients.org_id, from_id)

target_dep = None
for dep in existing_deps:
print(dep)
if dep.to_service_instance_id == to_id:
target_dep = dep
break

if not target_dep:
print(
f"Service Instance with ID {to_id} is not currently a dependency "
f"of the service with ID {from_id} ({from_service.name})"
)
sys.exit(0)

clients.contxt_deployments.remove_service_dependency(clients.org_id, from_id, to_id)
print("Successfully removed dependency")
117 changes: 89 additions & 28 deletions contxt/models/contxt.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataclasses import dataclass
from datetime import datetime
from json import loads
from typing import ClassVar, List, Optional
from typing import Any, ClassVar, List, Optional

from . import ApiField, ApiObject, Parsers

Expand Down Expand Up @@ -282,38 +282,40 @@ class Service(ApiObject):
@dataclass
class ServiceInstance(ApiObject):
_api_fields: ClassVar = (
ApiField("id", data_type=int),
ApiField("id", data_type=int, optional=True),
ApiField("name"),
ApiField("slug"),
ApiField("description"),
ApiField("descriptor"),
ApiField("service_id", data_type=int),
ApiField("project_environment_id"),
ApiField("organization_id"),
ApiField("slug", optional=True),
ApiField("description", optional=True),
ApiField("descriptor", optional=True),
ApiField("service_id", data_type=int, optional=True),
ApiField("project_environment_id", optional=True),
ApiField("client_id"),
ApiField("command"),
ApiField("arguments"),
ApiField("last_deployed_at"),
ApiField("last_configured_at"),
ApiField("command", optional=True),
ApiField("arguments", optional=True),
ApiField("last_deployed_at", optional=True),
ApiField("last_configured_at", optional=True),
ApiField("service_env_variables", data_type=ServiceEnvironmentVariable, optional=True),
ApiField("frontend", data_type=Frontend, optional=True),
ApiField("image", data_type=Image, optional=True),
ApiField("service_type"),
ApiField("created_at", data_type=Parsers.datetime),
ApiField("service_type", optional=True),
ApiField("created_at", data_type=Parsers.datetime, optional=True),
)

id: int
name: str
slug: str
description: str
descriptor: str
service_id: int
project_environment_id: str
client_id: str
command: str
arguments: str
last_deployed_at: Optional[datetime]
last_configured_at: Optional[datetime]
service_type: str
organization_id: str
id: Optional[int] = None
slug: Optional[str] = None
description: Optional[str] = None
descriptor: Optional[str] = None
service_id: Optional[int] = None
project_environment_id: Optional[str] = None
command: Optional[str] = None
arguments: Optional[str] = None
last_deployed_at: Optional[datetime] = None
last_configured_at: Optional[datetime] = None
service_type: Optional[str] = None
service_env_variables: Optional[List[ServiceEnvironmentVariable]] = None
frontend: Optional[Frontend] = None
image: Optional[Image] = None
Expand Down Expand Up @@ -372,18 +374,18 @@ class ProjectEnvironment(ApiObject):
@dataclass
class EdgeNode(ApiObject):
_api_fields: ClassVar = (
ApiField("id", data_type=int),
ApiField("id", data_type=str),
ApiField("name"),
ApiField("stack_id"),
ApiField("project_id"),
ApiField("organization_id"),
ApiField("description"),
ApiField("client_id"),
ApiField("created_at", data_type=Parsers.datetime),
)

id: int
id: str
name: str
stack_id: int
project_id: int
organization_id: str
description: str
client_id: str
Expand Down Expand Up @@ -417,3 +419,62 @@ class Cluster(ApiObject):
certificate_authority: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None


@dataclass
class ServiceInstanceScope(ApiObject):
_api_fields: ClassVar = (
ApiField("id"),
ApiField("service_instance_id"),
ApiField("label"),
ApiField("service_instance_grant_scopes", optional=True),
ApiField("description"),
ApiField("created_at"),
ApiField("updated_at"),
)

id: str
service_instance_id: int
label: str
description: str
service_instance_grant_scopes: Optional[Any] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None


@dataclass
class ServiceInstanceGrant(ApiObject):
_api_fields: ClassVar = (
ApiField("id"),
ApiField("from_service_instance_id", creatable=True),
ApiField("to_service_instance_id", creatable=True),
ApiField("auth0_id"),
ApiField("ServiceInstanceScopes", data_type=ServiceInstanceScope, optional=True),
ApiField("created_at", data_type=Parsers.datetime),
ApiField("updated_at", data_type=Parsers.datetime),
)

from_service_instance_id: int
to_service_instance_id: int
auth0_id: Optional[str] = None
id: Optional[str] = None
ServiceInstanceScopes: Optional[List[ServiceInstanceScope]] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None


@dataclass
class ServiceInstanceGrantScope(ApiObject):
_api_fields: ClassVar = (
ApiField("id"),
ApiField("service_instance_grant_id"),
ApiField("service_instance_scope_id"),
ApiField("created_at", data_type=Parsers.datetime),
ApiField("updated_at", data_type=Parsers.datetime),
)

id: str
service_instance_grant_id: str
service_instance_scope_id: str
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
Loading