This is an automated email from the ASF dual-hosted git repository. eamonford pushed a commit to branch config_map in repository https://gitbox.apache.org/repos/asf/incubator-sdap-ingester.git
commit 5fa6fb4060dc6a9252d2d840ed5f3dbbe91ffe7b Author: thomas loubrieu <[email protected]> AuthorDate: Wed Jun 3 16:54:30 2020 -0700 add config_operator working on local dir or remote git repo --- .gitignore | 4 +- sdap_ingest_manager/config/ConfigMap.py | 69 +++++++++++++++++---------- sdap_ingest_manager/config/LocalDirConfig.py | 49 +++++++++++++++++++ sdap_ingest_manager/config/RemoteGitConfig.py | 62 ++++++++++++++++++++++++ sdap_ingest_manager/config/__init__.py | 3 ++ sdap_ingest_manager/config/exceptions.py | 4 ++ sdap_ingest_manager/config_operator.py | 35 ++++++++++++++ tests/config/test_ConfigMap.py | 16 ++----- tests/resources/data/collections.yml | 4 +- 9 files changed, 206 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index b5cf7f0..0a97257 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ venv __pycache__/ dist/ build/ -*.DS_Store \ No newline at end of file +*.DS_Store +.eggs +temp/ diff --git a/sdap_ingest_manager/config/ConfigMap.py b/sdap_ingest_manager/config/ConfigMap.py index c5eb244..4143980 100644 --- a/sdap_ingest_manager/config/ConfigMap.py +++ b/sdap_ingest_manager/config/ConfigMap.py @@ -2,22 +2,25 @@ import logging from kubernetes import client, config from kubernetes.client.rest import ApiException +from sdap_ingest_manager.config.exceptions import UnreadableFileException + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + class ConfigMap: - def __init__(self, configmap_name, namespace, ingestion_order_store, output_collection='collections.yml'): - self._ingestion_order_store = ingestion_order_store + def __init__(self, configmap_name, namespace, git_remote_config): + self._git_remote_config = git_remote_config self._namespace = namespace self._configmap_name = configmap_name - self._output_collection = output_collection + config.load_kube_config() configuration = client.Configuration() - self._api_instance = client.CoreV1Api(client.ApiClient(configuration)) - + self._api_instance = client.ApiClient(configuration) + self._api_core_v1_instance = client.CoreV1Api(self._api_instance) def __del__(self): - pass + self._api_instance.close() def _create_configmap_object(self): @@ -25,9 +28,14 @@ class ConfigMap: name=self._configmap_name , namespace=self._namespace, ) - - data = {self._output_collection:self._ingestion_order_store.get_content()} + data = {} + for f in self._git_remote_config.get_files(): + try: + data[f] = self._git_remote_config.get_file_content(f) + except UnreadableFileException as e: + logger.error(f'file {f} cannot be read, ignored', e) + configmap = client.V1ConfigMap( api_version="v1", kind="ConfigMap", @@ -37,8 +45,11 @@ class ConfigMap: return configmap def _get_deployed_config(self): + """ + This method does not work in my test, the list of config available is not up to date + """ try: - api_list_response = self._api_instance.list_namespaced_config_map(self._namespace) + api_list_response = self._api_core_v1_instance.list_namespaced_config_map(self._namespace) config_keys = set() for item in api_list_response.items: config_keys = config_keys.union(item.data.keys()) @@ -47,24 +58,34 @@ class ConfigMap: finally: return config_keys - def publish(self): + def _replace(self): try: + logger.info(f'replace configMap entry {self._configmap_name}') + api_response = self._api_core_v1_instance.replace_namespaced_config_map( + name=self._configmap_name, + namespace=self._namespace, + body=self._create_configmap_object() + ) + logger.info(api_response) + except ApiException as e: + raise e - if self._output_collection in self._get_deployed_config(): - logger.info(f'replace configMap entry {self._output_collection}') - api_response = self._api_instance.replace_namespaced_config_map( - name=self._output_collection, - namespace=self._namespace, - body=self._create_configmap_object() - ) - else: - logger.info(f'create configMap entry {self._output_collection}') - api_response = self._api_instance.create_namespaced_config_map( - namespace=self._namespace, - body=self._create_configmap_object() - ) + def _create(self): + try: + logger.info(f'create configMap entry {self._configmap_name}') + api_response = self._api_core_v1_instance.create_namespaced_config_map( + namespace=self._namespace, + body=self._create_configmap_object() + ) logger.info(api_response) except ApiException as e: - logger.error("Exception when calling Kubernetes CoreV1Api %s\n" % e) + raise e + + def publish(self): + try: + self._create() + except ApiException as e: + logger.error("Exception when calling Kubernetes CoreV1Api ,create failed, try to replace %s\n" % e) + self._replace() diff --git a/sdap_ingest_manager/config/LocalDirConfig.py b/sdap_ingest_manager/config/LocalDirConfig.py new file mode 100644 index 0000000..d58f387 --- /dev/null +++ b/sdap_ingest_manager/config/LocalDirConfig.py @@ -0,0 +1,49 @@ +import os +import time +import logging + +from sdap_ingest_manager.config.exceptions import UnreadableFileException + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +LISTEN_FOR_UPDATE_INTERVAL_SECONDS = 1 + + +class LocalDirConfig: + def __init__(self, local_dir): + self._local_dir = local_dir + self._latest_update = self._get_latest_update() + + def get_files(self): + files = [] + for f in os.listdir(self._local_dir): + if os.path.isfile(os.path.join(self._local_dir, f)) \ + and 'README' not in f \ + and not f.startswith('.'): + files.append(f) + + return files + + def get_file_content(self, file_name): + logger.info(f'read configuration file {file_name}') + try: + with open(os.path.join(self._local_dir, file_name)) as f: + return f.read() + except UnicodeDecodeError as e: + raise UnreadableFileException(e) + + def _get_latest_update(self): + return time.ctime(max(os.path.getmtime(root) for root,_,_ in os.walk(self._local_dir))) + + def when_updated(self, callback): + while True: + time.sleep(LISTEN_FOR_UPDATE_INTERVAL_SECONDS) + latest_update = self._get_latest_update() + if latest_update > self._latest_update: + logger.info("local config dir has been updated") + callback() + self._latest_update = latest_update + else: + logger.debug("local config dir has not been updated") + diff --git a/sdap_ingest_manager/config/RemoteGitConfig.py b/sdap_ingest_manager/config/RemoteGitConfig.py new file mode 100644 index 0000000..a344246 --- /dev/null +++ b/sdap_ingest_manager/config/RemoteGitConfig.py @@ -0,0 +1,62 @@ +import logging +import os +import sys +import time +from git import Repo, Remote +from sdap_ingest_manager.config import LocalDirConfig + + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +LISTEN_FOR_UPDATE_INTERVAL_SECONDS = 5 + +class RemoteGitConfig(LocalDirConfig): + def __init__(self, git_url, + git_branch='master', + git_token=None + ): + """ + + :param git_url: + :param git_branch: + :param git_token: + """ + self._git_url = git_url if git_url.endswith(".git") else git_url + '.git' + self._git_branch = git_branch + self._git_token = git_token + local_dir = os.path.join(sys.prefix, 'sdap', 'conf') + super().__init__(local_dir) + self._repo = None + self._init_local_config_repo() + self._latest_commit_key = self._repo.head.commit.hexsha + + def _pull_remote(self): + o = self._repo.remotes.origin + res = o.pull() + return res[0].commit.hexsha # return the latest commit key + + def _init_local_config_repo(self): + self._repo = Repo.init(self._local_dir) + if len(self._repo.remotes) == 0 or 'origin' not in [r.name for r in self._repo.remotes]: + self._repo.create_remote('origin', self._git_url) + self._repo.git.fetch() + self._repo.git.checkout(self._git_branch) + + def when_updated(self, callback): + + while True: + time.sleep(LISTEN_FOR_UPDATE_INTERVAL_SECONDS) + remote_commit_key = self._pull_remote() + if remote_commit_key != self._latest_commit_key: + logger.info("remote git repository has been updated") + callback() + self._latest_commit_key = remote_commit_key + else: + logger.debug("remote git repository has not been updated") + + + + + + diff --git a/sdap_ingest_manager/config/__init__.py b/sdap_ingest_manager/config/__init__.py index 27a89b8..852920f 100644 --- a/sdap_ingest_manager/config/__init__.py +++ b/sdap_ingest_manager/config/__init__.py @@ -1 +1,4 @@ from sdap_ingest_manager.config.LocalConfiguration import LocalConfiguration +from sdap_ingest_manager.config.ConfigMap import ConfigMap +from sdap_ingest_manager.confg.LocalDirConfig import LocalDirConfig +from sdpa_ingest_manager.config.RemoteGitConfig import RemoteGitConfig \ No newline at end of file diff --git a/sdap_ingest_manager/config/exceptions.py b/sdap_ingest_manager/config/exceptions.py new file mode 100644 index 0000000..c06b881 --- /dev/null +++ b/sdap_ingest_manager/config/exceptions.py @@ -0,0 +1,4 @@ + + +class UnreadableFileException(Exception): + pass \ No newline at end of file diff --git a/sdap_ingest_manager/config_operator.py b/sdap_ingest_manager/config_operator.py new file mode 100644 index 0000000..fa2771a --- /dev/null +++ b/sdap_ingest_manager/config_operator.py @@ -0,0 +1,35 @@ +import argparse +from sdap_ingest_manager.config import RemoteGitConfig, LocalDirConfig, ConfigMap + + + +def main(): + parser = argparse.ArgumentParser(description="Run git configuration synchronization operator, work on local-dir or git-url") + input_group = parser.add_mutually_exclusive_group(required=True) + input_group.add_argument("-l", "--local-dir", + help="local directory where the configuration files are") + input_group.add_argument("-gu", "--git-url", + help="git repository from which the configuration files are pulled/saved") + parser.add_argument("-gb", "--git-branch", help="git branch from which the configuration files are pulled/saved", + default="master") + parser.add_argument("-gt", "--git-token", help="git personal access token used to access the repository") + + parser.add_argument("-n", "--namespace", help="kubernetes namespace where the configuration will be deployed", required=True) + parser.add_argument("-cm", "--config-map", help="configmap name in kubernetes", required=True) + + options = parser.parse_args() + + if options.local_dir: + config = LocalDirConfig(options.local_dir) + else: + config = RemoteGitConfig(options.git_url, branch=options.git_branch, token=options.git_token) + + config_map = ConfigMap(options.config_map, options.namespace, config) + config_map.publish() + + config.when_updated(config_map.publish) + + +if __name__ == "__main__": + main() + diff --git a/tests/config/test_ConfigMap.py b/tests/config/test_ConfigMap.py index a536202..2518b9a 100644 --- a/tests/config/test_ConfigMap.py +++ b/tests/config/test_ConfigMap.py @@ -5,25 +5,15 @@ from flask import Flask from flask_restplus import Api from sdap_ingest_manager.config.ConfigMap import ConfigMap -from sdap_ingest_manager.ingestion_order_store.FileIngestionOrderStore import FileIngestionOrderStore -from sdap_ingest_manager.ingestion_order_store.templates import Templates +from sdap_ingest_manager.config.RemoteGitConfig import RemoteGitConfig -flask_app = Flask(__name__) -app = Api(app=flask_app) -templates = Templates(app) class ConfigMapTest(unittest.TestCase): def test_createconfigmap(self): - test_ingestion_order_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..', - 'resources', - 'data', - 'collections.yml') - file_ingestion_order_store = FileIngestionOrderStore(path=test_ingestion_order_file, - order_template=templates.order_template) + remote_git_config = RemoteGitConfig("https://github.com/tloubrieu-jpl/sdap-ingester-config") - config_map = ConfigMap('collections.yml', 'sdap', file_ingestion_order_store) + config_map = ConfigMap('collection-ingester', 'sdap', remote_git_config) config_map.publish() diff --git a/tests/resources/data/collections.yml b/tests/resources/data/collections.yml index 07e795b..18226ba 100644 --- a/tests/resources/data/collections.yml +++ b/tests/resources/data/collections.yml @@ -1,9 +1,9 @@ avhrr-oi-analysed-sst: path: resources/history_manager/data/avhrr_oi/*.nc variable: analysed_sst - priority: 2 + priority: 8 avhrr-oi-analysed-sst2: path: resources/history_manager/data/avhrr_oi/*.nc variable: analysed_sst - priority: 1 \ No newline at end of file + priority: 1
