# CDS Artifact Manager Artifact Manager is a very simple gRPC service that lets you upload, download and delete CBA archives. It can be ran as a standalone micro service (using `server.py`) or you can include it's methods in a service like `py-executor`. ## Configuration Configuration is stored in `.ini` file, you can specify a path and name of a file using `CONFIGURATION` env variable. For possible variables please see example below (with inline comments): ``` [artifactManagerServer] port=50052 # A port on which the server will be listening logFile=server.log # Path to a log file maxWorkers=20 # Max number of concurent workers debug=true # Debug flag logConfig=logging.yaml # A special MDC logger config fileRepositoryBasePath=/tmp/ # A FS path where we should store CBA files ``` ## Methods Below is a list of gRPC methods handled by the service. The `proto` files are available in `artifact-manager/manager/proto` directory. All methods expect `CommonHeader` with: * `timestamp` - datetime in UTC with this: `%Y-%m-%dT%H:%M:%S.%fZ` format * `originatorId` - name of the component (eg. `CDS`) * `requestId` - ID of the request * `subRequestId` - Sub ID of the request * `flag` - TBD and an `ActionIdentifiers` with following fields: * `blueprintName` - name of the blueprint * `blueprintVersion` - version number of blueprint (as string) * `actionName` - TBD * `mode` - TBD ### Upload Upload `CBA.zip` file for storage in artifact manager. File needs to be sent as a binary data in `fileChunk` field. #### Example ``` stub: BluePrintManagementServiceStub = BluePrintManagementServiceStub(channel) with open(file_path, "rb") as cba_file: msg: BluePrintUploadInput = BluePrintUploadInput() msg.actionIdentifiers.blueprintName = "Test" msg.actionIdentifiers.blueprintVersion = "0.0.1" msg.fileChunk.chunk = cba_file.read() return stub.uploadBlueprint(msg) ``` ### Download Download existing `CBA.zip` file. #### Example ``` stub: BluePrintManagementServiceStub = BluePrintManagementServiceStub(channel) msg: BluePrintDownloadInput = BluePrintDownloadInput() msg.actionIdentifiers.blueprintName = "Test" msg.actionIdentifiers.blueprintVersion = "0.0.1" return stub.downloadBlueprint(msg) ``` ### Remove Delete existing `CBA.zip` file. #### Example ``` stub: BluePrintManagementServiceStub = BluePrintManagementServiceStub(channel) msg: BluePrintRemoveInput = BluePrintRemoveInput() msg.actionIdentifiers.blueprintName = "Test" msg.actionIdentifiers.blueprintVersion = "0.0.1" return stub.removeBlueprint(msg) ``` ## Full gRPC Client Example ``` import logging import sys from argparse import ArgumentParser, FileType, Namespace from configparser import ConfigParser from datetime import datetime from pathlib import Path import zipfile from grpc import Channel, ChannelCredentials, insecure_channel, secure_channel, ssl_channel_credentials from proto.BluePrintManagement_pb2 import ( BluePrintDownloadInput, BluePrintRemoveInput, BluePrintUploadInput, BluePrintManagementOutput, ) from proto.BluePrintManagement_pb2_grpc import BluePrintManagementServiceStub logging.basicConfig(level=logging.DEBUG) class ClientArgumentParser(ArgumentParser): """Client argument parser. It has two arguments: - config_file - provide a path to configuration file. Default is ./configuration-local.ini - actions - list of actions to do by client. It have to be a list of given values: upload, download, remove. """ DEFAULT_CONFIG_PATH: str = str(Path(__file__).resolve().with_name("configuration-local.ini")) def __init__(self, *args, **kwargs): """Initialize argument parser.""" super().__init__(*args, **kwargs) self.description: str = "Artifact Manager client example" self.add_argument( "--config_file", type=FileType("r"), default=self.DEFAULT_CONFIG_PATH, help="Path to the client configuration file. By default it's `configuration-local.ini` file from Artifact Manager directory", ) self.add_argument( "--actions", nargs="+", default=["upload", "download", "remove"], choices=["upload", "download", "remove"] ) class Client: """Client class. Implements methods which can be called to server. """ def __init__(self, channel: Channel, config: ConfigParser) -> None: """Initialize client class. :param channel: gprc channel object :param config: ConfigParser object with "client" section """ self.channel: Channel = channel self.stub: BluePrintManagementServiceStub = BluePrintManagementServiceStub(self.channel) self.config = config def upload(self) -> BluePrintManagementOutput: """Prepare upload message and send it to server.""" logging.info("Call upload client method") with open(self.config.get("client", "cba_file"), "rb") as cba_file: msg: BluePrintUploadInput = BluePrintUploadInput() msg.actionIdentifiers.blueprintName = "Test" msg.actionIdentifiers.blueprintVersion = "0.0.1" msg.fileChunk.chunk = cba_file.read() return self.stub.uploadBlueprint(msg) def download(self) -> BluePrintManagementOutput: """Prepare download message and send it to server.""" logging.info("Call download client method") msg: BluePrintDownloadInput = BluePrintDownloadInput() msg.actionIdentifiers.blueprintName = "Test" msg.actionIdentifiers.blueprintVersion = "0.0.1" return self.stub.downloadBlueprint(msg) def remove(self) -> BluePrintManagementOutput: """Prepare remove message and send it to server.""" logging.info("Call remove client method") msg: BluePrintRemoveInput = BluePrintRemoveInput() msg.actionIdentifiers.blueprintName = "Test" msg.actionIdentifiers.blueprintVersion = "0.0.1" return self.stub.removeBlueprint(msg) if __name__ == "__main__": arg_parser: ClientArgumentParser = ClientArgumentParser() args: Namespace = arg_parser.parse_args() config_parser: ConfigParser = ConfigParser() config_parser.read_file(args.config_file) server_address: str = f"{config_parser.get('client', 'address')}:{config_parser.get('client', 'port')}" if config_parser.getboolean("client", "use_ssl", fallback=False): logging.info(f"Create secure connection on {server_address}") with open(config_parser.get("client", "private_key_file"), "rb") as private_key_file, open( config_parser.get("client", "certificate_chain_file"), "rb" ) as certificate_chain_file: ssl_credentials: ChannelCredentials = ssl_channel_credentials( private_key=private_key_file.read(), certificate_chain=certificate_chain_file.read() ) channel: Channel = secure_channel(server_address, ssl_credentials) else: logging.info(f"Create insecure connection on {server_address}") channel: Channel = insecure_channel(server_address) with channel: client: Client = Client(channel, config_parser) for action in args.actions: logging.info("Get response") logging.info(getattr(client, action)()) ```