Coverage for ocp_resources/utils/utils.py: 66%

64 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-07-29 12:31 +0300

1import yaml 

2from simple_logger.logger import get_logger 

3 

4LOGGER = get_logger(name=__name__) 

5 

6 

7def skip_existing_resource_creation_teardown(resource, export_str, user_exported_args, check_exists=True): 

8 """ 

9 Args: 

10 resource (Resource): Resource to match against. 

11 export_str (str): The user export str. (REUSE_IF_RESOURCE_EXISTS or SKIP_RESOURCE_TEARDOWN) 

12 user_exported_args (str): Value of export_str. (os.environ.get) 

13 check_exists (bool): Check if resource exists before return. (applied only for REUSE_IF_RESOURCE_EXISTS) 

14 

15 Returns: 

16 Resource or None: If resource match. 

17 """ 

18 

19 def _return_resource(_resource, _check_exists, _msg): 

20 """ 

21 Return the resource if exists when got _check_exists else return None. 

22 If _check_exists=False returns the resource. 

23 """ 

24 if not _check_exists: # In case of SKIP_RESOURCE_TEARDOWN 

25 return _resource 

26 

27 elif _resource.exists: # In case of REUSE_IF_RESOURCE_EXISTS 

28 LOGGER.warning(_msg) 

29 return _resource 

30 

31 resource.to_dict() 

32 resource_name = resource.res["metadata"]["name"] 

33 resource_namespace = resource.res["metadata"].get("namespace") 

34 skip_create_warn_msg = ( 

35 f"Skip resource {resource.kind} {resource_name} creation, using existing one." 

36 f" Got {export_str}={user_exported_args}" 

37 ) 

38 user_args = yaml.safe_load(stream=user_exported_args) 

39 if resource.kind in user_args: 

40 _resource_args = user_args[resource.kind] 

41 if not _resource_args: 

42 # Match only by kind, user didn't send name and/or namespace. 

43 return _return_resource( 

44 _resource=resource, 

45 _check_exists=check_exists, 

46 _msg=skip_create_warn_msg, 

47 ) 

48 

49 for _name, _namespace in _resource_args.items(): 

50 if resource_name == _name and (resource_namespace == _namespace or not (resource_namespace and _namespace)): 

51 return _return_resource( 

52 _resource=resource, 

53 _check_exists=check_exists, 

54 _msg=skip_create_warn_msg, 

55 ) 

56 

57 

58def convert_camel_case_to_snake_case(name: str) -> str: 

59 """ 

60 Converts a camel case string to snake case. 

61 

62 Args: 

63 name (str): The camel case string to convert. 

64 

65 Returns: 

66 str: The snake case representation of the input string. 

67 

68 Examples: 

69 >>> convert_camel_case_to_snake_case(string_="allocateLoadBalancerNodePorts") 

70 'allocate_load_balancer_node_ports' 

71 >>> convert_camel_case_to_snake_case(string_="clusterIPs") 

72 'cluster_ips' 

73 >>> convert_camel_case_to_snake_case(string_="additionalCORSAllowedOS") 

74 'additional_cors_allowed_os' 

75 

76 Notes: 

77 - This function assumes that the input string adheres to camel case conventions. 

78 - If the input string contains acronyms (e.g., "XMLHttpRequest"), they will be treated as separate words 

79 (e.g., "xml_http_request"). 

80 - The function handles both single-word camel case strings (e.g., "Service") and multi-word camel case strings 

81 (e.g., "myCamelCaseString"). 

82 """ 

83 import re 

84 

85 do_not_process_list = ["oauth", "kubevirt"] 

86 

87 # If the input string is in the do_not_proccess_list, return it as it is. 

88 if name.lower() in do_not_process_list: 

89 return name.lower() 

90 

91 formatted_str: str = "" 

92 

93 if name.islower(): 

94 return name 

95 

96 # For single words, e.g "Service" or "SERVICE" 

97 if name.istitle() or name.isupper(): 

98 return name.lower() 

99 

100 # To decide if underscore is needed before a char, keep the last char format. 

101 # If previous char is uppercase, underscode should not be added. Also applied for the first char in the string. 

102 last_capital_char: bool | None = None 

103 

104 # To decide if there are additional words ahead; if found, there is at least one more word ahead, else this is the 

105 # last word. Underscore should be added before it and all chars from here should be lowercase. 

106 following_capital_chars: re.Match | None = None 

107 

108 str_len_for_idx_check = len(name) - 1 

109 

110 for idx, char in enumerate(name): 

111 # If lower case, append to formatted string 

112 if char.islower(): 

113 formatted_str += char 

114 last_capital_char = False 

115 

116 # If first char is uppercase 

117 elif idx == 0: 

118 formatted_str += char.lower() 

119 last_capital_char = True 

120 

121 else: 

122 if idx < str_len_for_idx_check: 

123 following_capital_chars = re.search(r"[A-Z]", "".join(name[idx + 1 :])) 

124 if last_capital_char: 

125 if idx < str_len_for_idx_check and name[idx + 1].islower(): 

126 if following_capital_chars: 

127 formatted_str += f"_{char.lower()}" 

128 last_capital_char = True 

129 continue 

130 

131 remaining_str = "".join(name[idx:]) 

132 # The 2 letters in the string; uppercase char followed by lowercase char. 

133 # Example: `clusterIPs`, handle `Ps` at this point 

134 if idx + 1 == str_len_for_idx_check: 

135 formatted_str += remaining_str.lower() 

136 break 

137 

138 # The last word in the string; uppercase followed by multiple lowercase chars 

139 # Example: `dataVolumeTTLSeconds`, handle `Seconds` at this point 

140 elif remaining_str.istitle(): 

141 formatted_str += f"_{remaining_str.lower()}" 

142 break 

143 

144 else: 

145 formatted_str += char.lower() 

146 last_capital_char = True 

147 

148 else: 

149 formatted_str += char.lower() 

150 last_capital_char = True 

151 

152 else: 

153 formatted_str += f"_{char.lower()}" 

154 last_capital_char = True 

155 

156 return formatted_str