|
| 1 | +import logging |
| 2 | +from dataclasses import dataclass, field |
| 3 | +import os |
| 4 | +from typing import Any |
| 5 | + |
| 6 | +from gitopscli.git_api import GitRepo |
| 7 | +from gitopscli.io_api.yaml_util import yaml_load, yaml_file_load |
| 8 | + |
| 9 | +from gitopscli.gitops_exception import GitOpsException |
| 10 | + |
| 11 | + |
| 12 | +@dataclass |
| 13 | +class AppTenantConfig: |
| 14 | + yaml: dict[str, dict[str, Any]] |
| 15 | + tenant_config: dict[str, dict[str, Any]] = field(default_factory=dict) |
| 16 | + repo_url: str = "" |
| 17 | + file_path: str = "" |
| 18 | + dirty: bool = False |
| 19 | + |
| 20 | + def __post_init__(self) -> None: |
| 21 | + if "config" in self.yaml: |
| 22 | + self.tenant_config = self.yaml["config"] |
| 23 | + else: |
| 24 | + self.tenant_config = self.yaml |
| 25 | + if "repository" not in self.tenant_config: |
| 26 | + raise GitOpsException("Cannot find key 'repository' in " + self.file_path) |
| 27 | + self.repo_url = str(self.tenant_config["repository"]) |
| 28 | + |
| 29 | + def list_apps(self) -> dict[str, dict[str, Any]]: |
| 30 | + return dict(self.tenant_config["applications"]) |
| 31 | + |
| 32 | + def merge_applications(self, desired_tenant_config: "AppTenantConfig") -> None: |
| 33 | + desired_apps = desired_tenant_config.list_apps() |
| 34 | + self.__delete_removed_applications(desired_apps) |
| 35 | + self.__add_new_applications(desired_apps) |
| 36 | + self.__update_custom_app_config(desired_apps) |
| 37 | + |
| 38 | + def __update_custom_app_config(self, desired_apps: dict[str, dict[str, Any]]) -> None: |
| 39 | + for desired_app_name, desired_app_value in desired_apps.items(): |
| 40 | + if desired_app_name in self.list_apps(): |
| 41 | + existing_application_value = self.list_apps()[desired_app_name] |
| 42 | + if "customAppConfig" not in desired_app_value: |
| 43 | + if existing_application_value and "customAppConfig" in existing_application_value: |
| 44 | + logging.info( |
| 45 | + "Removing customAppConfig in for %s in %s applications", |
| 46 | + existing_application_value, |
| 47 | + self.file_path, |
| 48 | + ) |
| 49 | + del existing_application_value["customAppConfig"] |
| 50 | + self.__set_dirty() |
| 51 | + else: |
| 52 | + if ( |
| 53 | + "customAppConfig" not in existing_application_value |
| 54 | + or existing_application_value["customAppConfig"] != desired_app_value["customAppConfig"] |
| 55 | + ): |
| 56 | + logging.info( |
| 57 | + "Updating customAppConfig in for %s in %s applications", |
| 58 | + existing_application_value, |
| 59 | + self.file_path, |
| 60 | + ) |
| 61 | + existing_application_value["customAppConfig"] = desired_app_value["customAppConfig"] |
| 62 | + self.__set_dirty() |
| 63 | + |
| 64 | + def __add_new_applications(self, desired_apps: dict[str, Any]) -> None: |
| 65 | + for desired_app_name, desired_app_value in desired_apps.items(): |
| 66 | + if desired_app_name not in self.list_apps().keys(): |
| 67 | + logging.info("Adding % in %s applications", desired_app_name, self.file_path) |
| 68 | + self.tenant_config["applications"][desired_app_name] = desired_app_value |
| 69 | + self.__set_dirty() |
| 70 | + |
| 71 | + def __delete_removed_applications(self, desired_apps: dict[str, Any]) -> None: |
| 72 | + for current_app in self.list_apps().keys(): |
| 73 | + if current_app not in desired_apps.keys(): |
| 74 | + logging.info("Removing %s from %s applications", current_app, self.file_path) |
| 75 | + del self.tenant_config["applications"][current_app] |
| 76 | + self.__set_dirty() |
| 77 | + |
| 78 | + def __set_dirty(self) -> None: |
| 79 | + self.dirty = True |
| 80 | + |
| 81 | + |
| 82 | +def __generate_config_from_tenant_repo( |
| 83 | + tenant_repo: GitRepo, |
| 84 | +) -> Any: # TODO: supposed to be ruamel object than Any pylint: disable=fixme |
| 85 | + tenant_app_dirs = __get_all_tenant_applications_dirs(tenant_repo) |
| 86 | + tenant_config_template = """ |
| 87 | + config: |
| 88 | + repository: {} |
| 89 | + applications: {{}} |
| 90 | + """.format( |
| 91 | + tenant_repo.get_clone_url() |
| 92 | + ) |
| 93 | + yaml = yaml_load(tenant_config_template) |
| 94 | + for app_dir in tenant_app_dirs: |
| 95 | + tenant_application_template = """ |
| 96 | + {}: {{}} |
| 97 | + """.format( |
| 98 | + app_dir |
| 99 | + ) |
| 100 | + tenant_applications_yaml = yaml_load(tenant_application_template) |
| 101 | + # dict path hardcoded as object generated will always be in v2 or later |
| 102 | + yaml["config"]["applications"].update(tenant_applications_yaml) |
| 103 | + custom_app_config = __get_custom_config(app_dir, tenant_repo) |
| 104 | + if custom_app_config: |
| 105 | + yaml["config"]["applications"][app_dir]["customAppConfig"] = custom_app_config |
| 106 | + return yaml |
| 107 | + |
| 108 | + |
| 109 | +def __get_all_tenant_applications_dirs(tenant_repo: GitRepo) -> set[str]: |
| 110 | + repo_dir = tenant_repo.get_full_file_path(".") |
| 111 | + applist = { |
| 112 | + name |
| 113 | + for name in os.listdir(repo_dir) |
| 114 | + if os.path.isdir(os.path.join(repo_dir, name)) and not name.startswith(".") |
| 115 | + } |
| 116 | + return applist |
| 117 | + |
| 118 | + |
| 119 | +def __get_custom_config(appname: str, tenant_config_git_repo: GitRepo) -> Any: |
| 120 | + custom_config_path = tenant_config_git_repo.get_full_file_path(f"{appname}/.config.yaml") |
| 121 | + if os.path.exists(custom_config_path): |
| 122 | + custom_config_content = yaml_file_load(custom_config_path) |
| 123 | + return custom_config_content |
| 124 | + return dict() |
| 125 | + |
| 126 | + |
| 127 | +def create_app_tenant_config_from_repo( |
| 128 | + tenant_repo: GitRepo, |
| 129 | +) -> "AppTenantConfig": |
| 130 | + tenant_repo.clone() |
| 131 | + tenant_config_yaml = __generate_config_from_tenant_repo(tenant_repo) |
| 132 | + return AppTenantConfig(yaml=tenant_config_yaml) |
0 commit comments