diff options
Diffstat (limited to 'ms/py-executor/resource_resolution/resource_resolution.py')
-rw-r--r-- | ms/py-executor/resource_resolution/resource_resolution.py | 219 |
1 files changed, 191 insertions, 28 deletions
diff --git a/ms/py-executor/resource_resolution/resource_resolution.py b/ms/py-executor/resource_resolution/resource_resolution.py index e4f162f8f..3b8c19b74 100644 --- a/ms/py-executor/resource_resolution/resource_resolution.py +++ b/ms/py-executor/resource_resolution/resource_resolution.py @@ -13,8 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. """ +import json +from dataclasses import dataclass, field from enum import Enum, unique from logging import Logger, getLogger +from os import getenv from types import TracebackType from typing import Any, Dict, Generator, Optional, Type @@ -22,7 +25,8 @@ from google.protobuf import json_format from proto.BluePrintProcessing_pb2 import ExecutionServiceInput, ExecutionServiceOutput -from .client import Client +from .grpc import Client as GrpcClient +from .http import Client as HttpClient @unique @@ -179,6 +183,39 @@ class WorkflowExecutionResult: return json_format.MessageToDict(self.execution_output.payload) +@dataclass +class Template: + """Template dataclass. + + Store resolved template data. + It keeps also ResourceResolution object to call `store_template` method. + """ + + resource_resolution: "ResourceResolution" = field(repr=False) + blueprint_name: str + blueprint_version: str + artifact_name: str = None + result: str = None + resolution_key: str = None + resource_type: str = None + resource_id: str = None + + def store(self) -> None: + """Store template using blueprintprocessor HTTP API. + + It uses ResourceResolution `store_template` method. + """ + self.resource_resolution.store_template( + blueprint_name=self.blueprint_name, + blueprint_version=self.blueprint_version, + artifact_name=self.artifact_name, + result=self.result, + resolution_key=self.resolution_key, + resource_type=self.resource_type, + resource_id=self.resource_id, + ) + + class ResourceResolution: """Resource resolution class. @@ -191,20 +228,26 @@ class ResourceResolution: self, *, server_address: str = "127.0.0.1", - server_port: int = "9111", + # GRPC client configuration + grpc_server_port: int = 9111, use_ssl: bool = False, root_certificates: bytes = None, private_key: bytes = None, certificate_chain: bytes = None, - # Authentication header configuration + # Authentication header configuration for GRPC client use_header_auth: bool = False, header_auth_token: str = None, + # HTTP client configuration + http_server_port: int = 8080, + http_auth_user: str = None, + http_auth_pass: str = None, + http_use_ssl: bool = True, ) -> None: """Resource resolution object initialization. Args: server_address (str, optional): gRPC server address. Defaults to "127.0.0.1". - server_port (int, optional): gRPC server address port. Defaults to "9111". + grpc_server_port (int, optional): gRPC server address port. Defaults to 9111. use_ssl (bool, optional): Boolean flag to determine if secure channel should be created or not. Defaults to False. root_certificates (bytes, optional): The PEM-encoded root certificates. None if it shouldn't be used. @@ -216,33 +259,49 @@ class ResourceResolution: use_header_auth (bool, optional): Boolean flag to determine if authorization headed shoud be added for every call or not. Defaults to False. header_auth_token (str, optional): Authorization token value. Defaults to None. + If no value is provided "AUTH_TOKEN" environment variable will be used. + http_server_port (int, optional): HTTP server address port. Defaults to 8080. + http_auth_user (str, optional): Username used for HTTP requests authorization. Defaults to None. + If no value is provided "API_USERNAME" environment variable will be used. + http_auth_pass (str, optional): Password used for HTTP requests authorization. Defaults to None. + If no value is provided "API_PASSWORD" environment variable will be used. + http_use_ssl (bool, optional): Determines if secure connection should be used for HTTP requests. + Defaults to False. """ # Logger self.logger: Logger = getLogger(__name__) - # Client settings - self.client_server_address: str = server_address - self.client_server_port: str = server_port - self.client_use_ssl: bool = use_ssl - self.client_root_certificates: bytes = root_certificates - self.client_private_key: bytes = private_key - self.client_certificate_chain: bytes = certificate_chain - self.client_use_header_auth: bool = use_header_auth - self.client_header_auth_token: str = header_auth_token - self.client: Client = None + # GrpcClient settings + self.grpc_client_server_address: str = server_address + self.grpc_client_server_port: str = grpc_server_port + self.grpc_client_use_ssl: bool = use_ssl + self.grpc_client_root_certificates: bytes = root_certificates + self.grpc_client_private_key: bytes = private_key + self.grpc_client_certificate_chain: bytes = certificate_chain + self.grpc_client_use_header_auth: bool = use_header_auth + self.grpc_client_header_auth_token: str = header_auth_token or getenv("AUTH_TOKEN") + self.grpc_client: GrpcClient = None + # HttpClient settings + self.http_client: HttpClient = HttpClient( + server_address, + server_port=http_server_port, + auth_user=http_auth_user or getenv("API_USERNAME"), + auth_pass=http_auth_pass or getenv("API_PASSWORD"), + use_ssl=http_use_ssl, + ) def __enter__(self) -> "ResourceResolution": """Enter ResourceResolution instance context. - Client connection is created. + GrpcClient connection is created. """ - self.client = Client( - server_address=f"{self.client_server_address}:{self.client_server_port}", - use_ssl=self.client_use_ssl, - root_certificates=self.client_root_certificates, - private_key=self.client_private_key, - certificate_chain=self.client_certificate_chain, - use_header_auth=self.client_use_header_auth, - header_auth_token=self.client_header_auth_token, + self.grpc_client = GrpcClient( + server_address=f"{self.grpc_client_server_address}:{self.grpc_client_server_port}", + use_ssl=self.grpc_client_use_ssl, + root_certificates=self.grpc_client_root_certificates, + private_key=self.grpc_client_private_key, + certificate_chain=self.grpc_client_certificate_chain, + use_header_auth=self.grpc_client_use_header_auth, + header_auth_token=self.grpc_client_header_auth_token, ) return self @@ -254,9 +313,9 @@ class ResourceResolution: ) -> None: """Exit ResourceResolution instance context. - Client connection is closed. + GrpcClient connection is closed. """ - self.client.close() + self.grpc_client.close() def execute_workflows(self, *workflows: WorkflowExecution) -> Generator[WorkflowExecutionResult, None, None]: """Execute provided workflows. @@ -270,7 +329,7 @@ class ResourceResolution: AttributeError: Raises if client object is not created. It occurs only if you not uses context manager. Then user have to create client instance for ResourceResolution object by himself calling: ``` - resource_resoulution.client = Client( + resource_resoulution.client = GrpcClient( server_address=f"{resource_resoulution.client_server_address}:{resource_resoulution.client_server_port}", use_ssl=resource_resoulution.client_use_ssl, root_certificates=resource_resoulution.client_root_certificates, @@ -287,8 +346,112 @@ class ResourceResolution: with both WorkflowExection object and server response for it's request. """ self.logger.debug("Execute workflows") - if not self.client: + if not self.grpc_client: raise AttributeError("gRPC client not connected") - for response, workflow in zip(self.client.process((workflow.message for workflow in workflows)), workflows): + for response, workflow in zip( + self.grpc_client.process((workflow.message for workflow in workflows)), workflows + ): yield WorkflowExecutionResult(workflow, response) + + def _check_template_resolve_params( + self, resolution_key: str = None, resource_type: str = None, resource_id: str = None + ): + """Check template API request parameters. + + It's possible to store/retrieve templates using pair of artifact name and resolution key OR + resource type and resource id. This method checks if valid combination of parameters were used. + + Args: + resolution_key (str, optional): resolutionKey HTTP request parameter value. Defaults to None. + resource_type (str, optional): resourceType HTTP request parameter value. Defaults to None. + resource_id (str, optional): resourceId HTTP request parameter value. Defaults to None. + + Raises: + AttributeError: Invalid combination of parametes used + """ + if not any([resolution_key, all([resource_type, resource_id])]): + raise AttributeError( + "To store/retrieve template resolution_key and artifact_name or both resource_type and resource_id have to be provided" + ) + + def store_template( + self, + blueprint_name: str, + blueprint_version: str, + result: str, + artifact_name: str, + resolution_key: str = None, + resource_type: str = None, + resource_id: str = None, + ) -> None: + """Store template using blueprintprocessor HTTP API. + + Prepare and send a request to store resolved template using blueprint name, blueprint version + and pair of artifact name and resolution key OR resource type and resource id. + + Method returns Template dataclass, which stores all template data and can be used to update + it's result. + + Args: + blueprint_name (str): Blueprint name + blueprint_version (str): Blueprint version + result (str): Template result + artifact_name (str): Artifact name + resolution_key (str, optional): Resolution key. Defaults to None. + resource_type (str, optional): Resource type. Defaults to None. + resource_id (str, optional): Resource ID. Defaults to None. + """ + self.logger.debug("Store template") + self._check_template_resolve_params(resolution_key, resource_type, resource_id) + base_endpoint: str = f"template/{blueprint_name}/{blueprint_version}" + if resolution_key and artifact_name: + endpoint: str = f"{base_endpoint}/{artifact_name}/{resolution_key}" + else: + endpoint: str = f"{base_endpoint}/{resource_type}/{resource_id}" + response = self.http_client.send_request( + "POST", endpoint, headers={"Content-Type": "application/json"}, data=json.dumps({"result": result}) + ) + + def retrieve_template( + self, + blueprint_name: str, + blueprint_version: str, + artifact_name: str, + resolution_key: str = None, + resource_type: str = None, + resource_id: str = None, + ) -> Template: + """Get stored template using blueprintprocessor's HTTP API. + + Prepare and send a request to retrieve resolved template using blueprint name, blueprint version + and pair of artifact name and resolution key OR resource type and resource id. + + Args: + blueprint_name (str): Blueprint name + blueprint_version (str): Blueprint version + artifact_name (str): Artifact name + resolution_key (str, optional): Resolution key. Defaults to None. + resource_type (str, optional): Resource type. Defaults to None. + resource_id (str, optional): Resource ID. Defaults to None. + """ + self.logger.debug("Retrieve template") + self._check_template_resolve_params(resolution_key, resource_type, resource_id) + params: dict = {"bpName": blueprint_name, "bpVersion": blueprint_version, "artifactName": artifact_name} + if resolution_key: + params.update({"resolutionKey": resolution_key}) + else: + params.update({"resourceType": resource_type, "resourceId": resource_id}) + response = self.http_client.send_request( + "GET", "template", headers={"Accept": "application/json"}, params=params + ) + return Template( + resource_resolution=self, + blueprint_name=blueprint_name, + blueprint_version=blueprint_version, + artifact_name=artifact_name, + resolution_key=resolution_key, + resource_type=resource_type, + resource_id=resource_id, + result=response.json()["result"], + ) |