Coverage for src/rsnapshot_docker_compose_backup/config/abstract_config.py: 34%

86 statements  

« prev     ^ index     » next       coverage.py v7.5.4, created at 2024-07-07 01:03 +0200

1import configparser 

2import os 

3from pathlib import Path 

4import re 

5from abc import ABC, abstractmethod 

6from typing import Any, Callable, Dict, List, Union 

7 

8from rsnapshot_docker_compose_backup.utils.regex import CaseInsensitiveRe 

9from rsnapshot_docker_compose_backup.structure.volume import Volume 

10 

11 

12class AbstractConfig(ABC): 

13 actionSection = "actions" 

14 varSection = "vars" 

15 backupOrder = [ 

16 "runtime_backup", 

17 "pre_stop", 

18 "stop", 

19 "pre_backup", 

20 "backup", 

21 "post_backup", 

22 "restart", 

23 "post_restart", 

24 ] 

25 

26 def __init__(self, config_path: Path, name: str): 

27 self.enabled_actions: Dict[str, bool] = {} 

28 self.backup_steps: Dict[str, str] = {} 

29 self.vars: Dict[str, Union[str, List[Volume]]] = {} 

30 for step in self.backupOrder: 

31 self.backup_steps[step] = "" 

32 self._load_config_file(config_path, name) 

33 self._init_vars(str(config_path)) 

34 

35 def _init_vars(self, config_path: str) -> None: 

36 self.vars["$dockerComposeFile"] = config_path 

37 

38 def _load_config_file(self, config_path: Path, section_name: str) -> None: 

39 section_name = section_name.lower() 

40 config_file = configparser.ConfigParser(allow_no_value=True) 

41 config_file.SECTCRE = CaseInsensitiveRe( 

42 re.compile(r"\[ *(?P<header>[^]]+?) *]") 

43 ) # type: ignore 

44 if os.path.isfile(config_path): 

45 config_file.read(config_path) 

46 if not config_file.sections(): 

47 raise Exception("The Config for {} has no Sections".format(config_path)) 

48 for step in self.backup_steps: 

49 if config_file.has_option(section_name, step): 

50 self.backup_steps[step] = ( 

51 config_file.get(section_name, step).strip() + "\n" 

52 ) 

53 actions_section = self.actions_name(section_name) 

54 if config_file.has_section(actions_section): 

55 for action in config_file.options(actions_section): 

56 val = config_file.get(actions_section, action, fallback=None) 

57 use = val is None or val.lower() in {"true"} 

58 self.enabled_actions[action] = use 

59 vars_section = self.vars_name(section_name) 

60 if config_file.has_section(vars_section): 

61 for var in config_file.options(vars_section): 

62 val = config_file.get(vars_section, var) 

63 self.vars["${}".format(var)] = val 

64 

65 def _resolve_vars( 

66 self, cmd: str, variables: Dict[str, Union[str, List[Volume]]] 

67 ) -> str: 

68 for var in variables.keys(): 

69 if var.lower() in cmd.lower(): 

70 replace_function = _replace_var.get(type(variables[var])) 

71 if not replace_function: 

72 raise Exception("Illegal Type") 

73 cmd = replace_function(cmd, var, variables[var]) 

74 return cmd 

75 

76 @abstractmethod 

77 def get_step(self, step: str) -> str: 

78 pass 

79 

80 @staticmethod 

81 def _create_subsection(super_section: str, sub_section: str) -> str: 

82 return "{}.{}".format(super_section, sub_section) 

83 

84 @staticmethod 

85 def actions_name(section_name: str) -> str: 

86 return AbstractConfig._create_subsection( 

87 section_name, AbstractConfig.actionSection 

88 ) 

89 

90 @staticmethod 

91 def vars_name(section_name: str) -> str: 

92 return AbstractConfig._create_subsection( 

93 section_name, AbstractConfig.varSection 

94 ) 

95 

96 

97def ireplace(old: str, new: str, text: str) -> str: 

98 idx = 0 

99 while idx < len(text): 

100 index_l = text.lower().find(old.lower(), idx) 

101 if index_l == -1: 

102 return text 

103 text = text[:index_l] + new + text[index_l + len(old) :] 

104 idx = index_l + len(new) 

105 return text 

106 

107 

108def _replace_list(cmd: str, var: str, val: List[Union[List[Any], str, Volume]]) -> str: 

109 result: str = "" 

110 for i in val: 

111 result += str(_replace_var[type(i)](cmd, var, i)) + "\n" 

112 return result 

113 

114 

115def _replace_str(cmd: str, var: str, val: str) -> str: 

116 return ireplace(var, val, cmd) 

117 

118 

119def _replace_volume(cmd: str, var: str, volume: Volume) -> str: 

120 tmp = _replace_str(cmd, var + ".name", volume.name) 

121 tmp = _replace_str(tmp, var + ".path", volume.path) 

122 tmp = _replace_str(tmp, var, volume.path) 

123 return tmp 

124 

125 

126_replace_var: Dict[type, Callable[..., str]] = { 

127 list: _replace_list, 

128 str: _replace_str, 

129 Volume: _replace_volume, 

130}