1010import logging
1111import os
1212import yaml
13+ from openshift .dynamic import DynamicClient
14+ from openshift .dynamic .exceptions import NotFoundError
1315
1416logger = logging .getLogger (name = __name__ )
1517
@@ -33,8 +35,19 @@ def copyContentsToYamlFile(file_path: str, content: dict) -> bool:
3335 Write dictionary content to a YAML file
3436 """
3537 try :
38+ # Create a custom dumper that uses literal style for multi-line strings
39+ class LiteralDumper (yaml .SafeDumper ):
40+ pass
41+
42+ def str_representer (dumper , data ):
43+ if '\n ' in data :
44+ return dumper .represent_scalar ('tag:yaml.org,2002:str' , data , style = '|' )
45+ return dumper .represent_scalar ('tag:yaml.org,2002:str' , data )
46+
47+ LiteralDumper .add_representer (str , str_representer )
48+
3649 with open (file_path , 'w' ) as yaml_file :
37- yaml .dump (content , yaml_file , default_flow_style = False )
50+ yaml .dump (content , yaml_file , default_flow_style = False , Dumper = LiteralDumper )
3851 return True
3952 except Exception as e :
4053 logger .error (f"Error writing to YAML file { file_path } : { e } " )
@@ -64,3 +77,123 @@ def filterResourceData(data: dict) -> dict:
6477 del filteredCopy ['status' ]
6578
6679 return filteredCopy
80+
81+
82+ def extract_secrets_from_dict (data , secret_names = None ):
83+ """
84+ Recursively extract secret names from a dictionary structure.
85+ Looks for keys named 'secretName' and collects their values.
86+
87+ Args:
88+ data: Dictionary to search
89+ secret_names: Set to collect secret names (created if None)
90+
91+ Returns:
92+ Set of secret names found
93+ """
94+ if secret_names is None :
95+ secret_names = set ()
96+
97+ if isinstance (data , dict ):
98+ for key , value in data .items ():
99+ # Check if this key is 'secretName' and has a string value
100+ if key == 'secretName' and isinstance (value , str ) and value :
101+ secret_names .add (value )
102+ # Recursively search nested structures
103+ elif isinstance (value , (dict , list )):
104+ extract_secrets_from_dict (value , secret_names )
105+
106+ elif isinstance (data , list ):
107+ for item in data :
108+ if isinstance (item , (dict , list )):
109+ extract_secrets_from_dict (item , secret_names )
110+
111+ return secret_names
112+
113+
114+ def backupResources (dynClient : DynamicClient , namespace : str , kind : str , api_version : str , backup_path : str , name = None ) -> tuple :
115+ """
116+ Backup resources of a given kind in a namespace.
117+ If name is provided, backs up that specific resource.
118+ If name is None, backs up all resources of that kind.
119+
120+ Args:
121+ dynClient: Kubernetes dynamic client
122+ namespace: Namespace to backup from
123+ kind: Resource kind (e.g., 'MongoCfg', 'Secret')
124+ api_version: API version (e.g., 'config.mas.ibm.com/v1')
125+ backup_path: Path to save backup files
126+ name: Optional specific resource name
127+
128+ Returns:
129+ tuple: (backed_up_count: int, not_found_count: int, failed_count: int, discovered_secrets: set)
130+ """
131+ discovered_secrets = set ()
132+ backed_up_count = 0
133+ not_found_count = 0
134+ failed_count = 0
135+
136+ try :
137+ resourceAPI = dynClient .resources .get (api_version = api_version , kind = kind )
138+
139+ if name :
140+ # Backup specific named resource
141+ logger .info (f"Backing up { kind } '{ name } ' from namespace '{ namespace } ' (API version: { api_version } )" )
142+ try :
143+ resource = resourceAPI .get (name = name , namespace = namespace )
144+ if resource :
145+ resources_to_process = [resource ]
146+ else :
147+ logger .info (f"{ kind } '{ name } ' not found in namespace '{ namespace } ', skipping backup" )
148+ not_found_count = 1
149+ return (backed_up_count , not_found_count , failed_count , discovered_secrets )
150+ except NotFoundError :
151+ logger .error (f"{ kind } '{ name } ' not found in namespace '{ namespace } ', skipping backup" )
152+ not_found_count = 1
153+ return (backed_up_count , not_found_count , failed_count , discovered_secrets )
154+ else :
155+ # Backup all resources of this kind
156+ logger .info (f"Backing up all { kind } resources from namespace '{ namespace } ' (API version: { api_version } )" )
157+ resources = resourceAPI .get (namespace = namespace )
158+ resources_to_process = resources .items
159+
160+ # Process each resource
161+ for resource in resources_to_process :
162+ resource_name = resource ["metadata" ]["name" ]
163+ resource_dict = resource .to_dict ()
164+
165+ # Extract secrets from this resource if it's not a Secret itself
166+ if kind != 'Secret' :
167+ secrets = extract_secrets_from_dict (resource_dict .get ('spec' , {}))
168+ if secrets :
169+ logger .info (f"Found { len (secrets )} secret reference(s) in { kind } '{ resource_name } ': { ', ' .join (sorted (secrets ))} " )
170+ discovered_secrets .update (secrets )
171+
172+ # Backup the resource
173+ resource_file_path = f"{ backup_path } /{ resource_name } .yaml"
174+ filtered_resource = filterResourceData (resource_dict )
175+ if copyContentsToYamlFile (resource_file_path , filtered_resource ):
176+ logger .info (f"Successfully backed up { kind } '{ resource_name } ' to '{ resource_file_path } '" )
177+ backed_up_count += 1
178+ else :
179+ logger .error (f"Failed to back up { kind } '{ resource_name } ' to '{ resource_file_path } '" )
180+ failed_count += 1
181+
182+ if backed_up_count > 0 :
183+ logger .info (f"Successfully backed up { backed_up_count } { kind } resource(s)" )
184+ elif not name :
185+ logger .info (f"No { kind } resources found in namespace '{ namespace } '" )
186+
187+ return (backed_up_count , not_found_count , failed_count , discovered_secrets )
188+
189+ except NotFoundError :
190+ if name :
191+ logger .info (f"{ kind } '{ name } ' not found in namespace '{ namespace } '" )
192+ not_found_count = 1
193+ else :
194+ logger .info (f"No { kind } resources found in namespace '{ namespace } '" )
195+ return (backed_up_count , not_found_count , failed_count , discovered_secrets )
196+ except Exception as e :
197+ logger .error (f"Error backing up { kind } resources: { e } " )
198+ failed_count = 1
199+ return (backed_up_count , not_found_count , failed_count , discovered_secrets )
0 commit comments