Merge yaml configurations
When merging YAML configurations into values.yaml, you can merge manually or with a tool of your choosing.
You can use the following merge_yaml.py script to merge YAML excerpts into values.yaml automatically. This script requires both Python and the ruamel.yaml package, which you can install using pip install ruamel.yaml.
To run the program, ensure that merge_yaml.py, values.yaml, and the yaml file that contains the configuration you want to add are all in your project directory. Then, run:
1 python merge_yaml.py values-to-merge.yaml values.yaml 
1 #!/usr/bin/env python 2 """ 3 Backup destination file and merge YAML contents of src into dest. 4 5 By default creates backups, overwrites destination, and clobbers lists. 6 7 Usage: 8 merge_yaml.py src dest [--create-backup=True] [--dry-run] [--show-stacktrace=False] [--merge-lists=True] [--help] 9 """ 10 11 import argparse 12 import os 13 import shutil 14 from datetime import datetime 15 import sys 16 from pathlib import Path 17 18 # Check Python version 19 if sys.version_info < (3, 0): 20 print("Error: This script requires Python 3.0 or greater.") 21 sys.exit(2) 22 23 # Try importing ruamel.yaml 24 try: 25 from ruamel.yaml import YAML 26 except ImportError: 27 print( 28 "Error: ruamel.yaml is not installed. Please install it using 'pip install ruamel.yaml'" 29 ) 30 sys.exit(2) 31 32 yaml = YAML() 33 34 def deep_merge(d1, d2, **kwargs): 35 """Deep merges dictionary d2 into dictionary d1.""" 36 merge_lists = kwargs.get("merge_lists") 37 for key, value in d2.items(): 38 if key in d1: 39 if isinstance(d1[key], dict) and isinstance(value, dict): 40 deep_merge(d1[key], value, **kwargs) 41 elif merge_lists and isinstance(d1[key], list) and isinstance(value, list): 42 d1[key].extend(value) 43 else: 44 d1[key] = value 45 else: 46 d1[key] = value 47 return d1 48 49 def load_yaml_file(filename): 50 """Load YAML data from a file.""" 51 if not os.path.exists(filename): 52 return {} 53 with open(filename, "r") as file: 54 return yaml.load(file) 55 56 def save_yaml_file(filename, data): 57 """Save YAML data to a file.""" 58 with open(filename, "w") as file: 59 yaml.dump(data, file) 60 61 def create_backup(filename): 62 """Create a timestamped backup of the file.""" 63 # create a directory called backups relative to the filename 64 backup_dir = filename.parent / "yaml_backups" 65 try: 66 backup_dir.mkdir(exist_ok=True) 67 except Exception as e: 68 print( 69 f"Error: Could not create backup directory {backup_dir}. Check your file-permissions or use --no-create-backup to skip creating a backup." 70 ) 71 exit(2) 72 73 timestamp = datetime.now().strftime("%y%m%d%H%M%S") 74 backup_filename = backup_dir / f"{filename.name}.{timestamp}.bak" 75 shutil.copyfile(filename, backup_filename) 76 print(f"Backup created: {backup_filename}") 77 78 def main(): 79 parser = argparse.ArgumentParser( 80 description="Deep merge YAML contents of src into dest." 81 ) 82 parser.add_argument("src", type=Path, help="Source filename") 83 parser.add_argument("dest", type=Path, help="Destination filename") 84 parser.add_argument( 85 "--create-backup", 86 type=bool, 87 default=True, 88 help="Create a backup of the destination file before merging", 89 ) 90 parser.add_argument( 91 "--dry-run", 92 action="store_true", 93 help="Print to stdout only, do not write to the destination file", 94 ) 95 # add a argument for showing the stack trace on yaml parse errors 96 parser.add_argument( 97 "--show-stacktrace", 98 action="store_true", 99 help="Show stack trace on yaml parse errors", 100 ) 101 # add an argument to clobber lists 102 parser.add_argument( 103 "--merge-lists", 104 action="store_true", 105 help="Merge list items instead of clobbering", 106 default=False, 107 ) 108 109 args = parser.parse_args() 110 111 src_filename = args.src.resolve().expanduser() 112 dest_filename = args.dest.resolve().expanduser() 113 114 # make sure both files exist 115 if not src_filename.exists(): 116 print(f"Error: {args.src} does not exist") 117 exit(2) 118 119 if not dest_filename.exists(): 120 print(f"Error: {args.dest} does not exist") 121 exit(2) 122 123 try: 124 src_data = load_yaml_file(src_filename) 125 except Exception as e: 126 print( 127 f"Error: {args.src} is not a valid YAML file. Run with --show-stacktrace to see the error." 128 ) 129 if args.show_stacktrace: 130 raise e 131 exit(2) 132 try: 133 dest_data = load_yaml_file(dest_filename) 134 except Exception as e: 135 print( 136 f"Error: {args.dest} is not a valid YAML file. Run with --show-stacktrace to see the error." 137 ) 138 if args.show_stacktrace: 139 raise e 140 exit(2) 141 142 if args.create_backup and not args.dry_run: 143 create_backup(dest_filename) 144 145 src_data = load_yaml_file(args.src) 146 dest_data = load_yaml_file(args.dest) 147 148 # if dest_data is empty, just copy src_data to dest_data 149 if not dest_data: 150 if not args.dry_run: 151 save_yaml_file(args.dest, src_data) 152 else: 153 merged_data = deep_merge(dest_data, src_data, merge_lists=args.merge_lists) 154 if not args.dry_run: 155 save_yaml_file(args.dest, merged_data) 156 print(f"Merged data from {args.src} into {args.dest}") 157 else: 158 yaml.dump(merged_data, sys.stdout) 159 160 if __name__ == "__main__": 161 main()