diff --git a/.gitignore b/.gitignore index 90ac750..94cad3b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ MANIFEST .pypirc venv/ +env/ +.idea/ \ No newline at end of file diff --git a/ais_service_discovery/call_service.py b/ais_service_discovery/call_service.py index 39bef3a..3d0f665 100644 --- a/ais_service_discovery/call_service.py +++ b/ais_service_discovery/call_service.py @@ -23,7 +23,8 @@ sqs_adaptor = SqsAdaptor(sqs) send = Send(sqs_adaptor) -def run_service(service, body, opts={}): + +def run_service(service, body, opts = {}): type = service['Attributes']['type'] if type in ['cloud-function', 'function', 'lambda']: return func.call(**{ diff --git a/ais_service_discovery/discovery.py b/ais_service_discovery/discovery.py new file mode 100644 index 0000000..ff89896 --- /dev/null +++ b/ais_service_discovery/discovery.py @@ -0,0 +1,40 @@ +from boto3 import client +from .services import CloudmapAdapter, Services + +service_discovery = client('servicediscovery') + + +class DiscoveryFactory: + @staticmethod + def instance(namespace, service): + cloudmap_adaptor = CloudmapAdapter(service_discovery) + adapter = Services(cloudmap_adaptor) + return Discovery(adapter, namespace, service) + + +class Discovery: + def __init__(self, adapter: Services, namespace, service): + self.adapter = adapter + self.namespace = namespace + self.service = service + + def register(self, instance, kind, attributes): + if self.namespace and self.service: + attrs = {**attributes, 'type': kind} + return self.adapter.register(self.namespace, self.service, instance, attrs) + else: + raise Exception('you must defined a namespace and service') + + def discover(self, instance): + result = self.adapter.discover(self.namespace, self.service, instance) + return list(result['Instances']) + + def create_namespace(self, namespace): + return self.adapter.create_namespace(namespace) + + def create_service(self, service): + return self.adapter.create_service(self.namespace, service) + + def update_instance(self, instance, kind, attributes): + attrs = {**attributes, 'type': kind} + return self.adapter.update_instance(self.namespace, self.service, instance, attrs) diff --git a/ais_service_discovery/register.py b/ais_service_discovery/register.py new file mode 100644 index 0000000..f870622 --- /dev/null +++ b/ais_service_discovery/register.py @@ -0,0 +1,13 @@ +from boto3 import client +from .services import default_namespace, extract_service_parts, Services, \ + CloudmapAdapter + +service_discovery = client('servicediscovery') + +cloudmap_adaptor = CloudmapAdapter(service_discovery) +services = Services(cloudmap_adaptor) + + +def register(namespace: str, service: str, instance: str, kind: str, attributes: dict): + attrs = {**attributes, 'type': kind} + return services.register(namespace, service, instance, attrs) diff --git a/ais_service_discovery/services/__init__.py b/ais_service_discovery/services/__init__.py index fd4d40c..b329ad6 100644 --- a/ais_service_discovery/services/__init__.py +++ b/ais_service_discovery/services/__init__.py @@ -8,3 +8,15 @@ def __init__(self, adapter): def discover(self, *args, **kwargs): return self.adapter.discover(*args, **kwargs) + + def register(self, *args, **kwargs): + return self.adapter.register(*args, **kwargs) + + def create_namespace(self, *args, **kwargs): + return self.adapter.create_namespace(*args, **kwargs) + + def create_service(self, *args, **kwargs): + return self.adapter.create_service(*args, **kwargs) + + def update_instance(self, *args, **kwargs): + return self.adapter.update_instance(*args, **kwargs) diff --git a/ais_service_discovery/services/cloudmap.py b/ais_service_discovery/services/cloudmap.py index 00c722a..dbfff39 100644 --- a/ais_service_discovery/services/cloudmap.py +++ b/ais_service_discovery/services/cloudmap.py @@ -2,16 +2,85 @@ class CloudmapAdapter: def __init__(self, client): self.client = client - def filter_instances(self, response, id): - filter_function = lambda instance : instance['InstanceId'] == id - + def filter_instances(self, response, instance_id): + filter_function = lambda instance : instance['InstanceId'] == instance_id return { **response, 'Instances': filter(filter_function, response['Instances']) } - def discover(self, namespace, name, id): + def discover(self, namespace, service, instance): return self.filter_instances(self.client.discover_instances( NamespaceName=namespace, - ServiceName=name - ), id) + ServiceName=service, + ), instance) + + def register(self, namespace, service, instance, attributes): + namespace_id = self.get_namespace_id(namespace) + srv = self.get_service(namespace_id, service) + if srv is None: + srv = self.create_service(namespace, service) + existing = list(self.discover(namespace, service, instance)['Instances']) + if existing: + return existing + self.client.register_instance( + ServiceId=srv, + InstanceId=instance, + Attributes=attributes, + ) + return list(self.discover(namespace, service, instance)['Instances']) + + def get_service(self, namespace, service): + response = self.client.list_services( + Filters=[ + { + 'Name': 'NAMESPACE_ID', + 'Values': [ + namespace, + ], + 'Condition': 'EQ', + }, + ] + ) + for item in response['Services']: + if item['Name'] == service: + return item['Id'] + return None + + def get_namespace_id(self, namespace): + response = self.client.list_namespaces() + result = response['Namespaces'] + for item in result: + if item['Name'] == namespace: + return item['Id'] + return None + + def create_namespace(self, namespace): + response = self.client.create_http_namespace( + Name=namespace, + ) + return response['Namespace'] + + def create_service(self, namespace, service): + namespace_id = self.get_namespace_id(namespace) + response = self.client.create_service( + Name=service, + NamespaceId=namespace_id, + Type='HTTP' + ) + return response['Service']['Id'] + + def update_instance(self, namespace, service, instance, attributes): + self.deregister_instance(namespace, service, instance) + return self.register(namespace, service, instance, attributes) + + def deregister_instance(self, namespace, service, instance): + namespace_id = self.get_namespace_id(namespace) + service_id = self.get_service(namespace_id, service) + instances = list(self.discover(namespace, service, instance)['Instances']) + if len(instances) == 0: + return None + self.client.deregister_instance( + ServiceId=service_id, + InstanceId=instances[0]['InstanceId'], + ) diff --git a/ais_service_discovery/services/helpers.py b/ais_service_discovery/services/helpers.py index 086ee6a..1104b1c 100644 --- a/ais_service_discovery/services/helpers.py +++ b/ais_service_discovery/services/helpers.py @@ -19,7 +19,7 @@ def get_handler(service_id): def extract_service_parts(service_id): - ''' + """ This takes a service_id in the user-facing form and reconsitutes it into parts an in our internal form. For example, `namespace.service->handler`, becomes @@ -28,7 +28,7 @@ def extract_service_parts(service_id): This is because we have to differentiate between the three segments as there's no way of assuring which is a namespace and which is a service, as a namespace is optional. - ''' + """ # If service ID contains a NAMESPACE_NOTATION - which denotes that # the ID contains both a namespace and a service. # Split the two, check for a handler @@ -40,13 +40,13 @@ def extract_service_parts(service_id): if has_handler: [service_name, handler] = has_handler return { - 'namespace': namespace, - 'service': service_name, - 'handler': handler + 'namespace': namespace, + 'service': service_name, + 'handler': handler } return { - 'namespace': namespace, - 'service': service + 'namespace': namespace, + 'service': service } # If service has a function handler # return service with handler in base format. @@ -54,12 +54,12 @@ def extract_service_parts(service_id): if has_handler: [service_name, handler] = has_handler return { - 'namespace': default_namespace(), - 'service': service_name, - 'handler': handler + 'namespace': default_namespace(), + 'service': service_name, + 'handler': handler, } # Default behavior, returns serviceId given, with the default namespace. return { - 'namespace': default_namespace(), - 'service': service_id, + 'namespace': default_namespace(), + 'service': service_id, } diff --git a/example/__init__.py b/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/discovery_test.py b/example/discovery_test.py new file mode 100644 index 0000000..a9456b3 --- /dev/null +++ b/example/discovery_test.py @@ -0,0 +1,16 @@ +from ais_service_discovery.discovery import DiscoveryFactory + +discovery = DiscoveryFactory.instance("example-namespace", "test-service") + +instance = discovery.register("test", "dataset", { + "schema": "stage", + "table": "tester", +}) + +print(instance) + +result = discovery.update_instance("test", "dataset", {"testing": "123"}) +print("result: ", result) + +instance = discovery.discover("dataset") +print("instance: ", instance)