Skip to content

Commit 6fa74be

Browse files
committed
update backup.p[y to add backup_resources
1 parent 9830319 commit 6fa74be

1 file changed

Lines changed: 134 additions & 1 deletion

File tree

src/mas/devops/backup.py

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import logging
1111
import os
1212
import yaml
13+
from openshift.dynamic import DynamicClient
14+
from openshift.dynamic.exceptions import NotFoundError
1315

1416
logger = 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

Comments
 (0)