Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:58:22

0001 # -*- coding: utf-8 -*-
0002 # Copyright CERN since 2021
0003 #
0004 # Licensed under the Apache License, Version 2.0 (the "License");
0005 # you may not use this file except in compliance with the License.
0006 # You may obtain a copy of the License at
0007 #
0008 #    http://www.apache.org/licenses/LICENSE-2.0
0009 #
0010 # Unless required by applicable law or agreed to in writing, software
0011 # distributed under the License is distributed on an "AS IS" BASIS,
0012 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013 # See the License for the specific language governing permissions and
0014 # limitations under the License.
0015 
0016 # cloned from the rucio project: https://github.com/rucio/rucio/blob/master/tools/merge_rucio_configs.py
0017 
0018 import argparse
0019 import logging
0020 
0021 import os
0022 import json
0023 import sys
0024 
0025 import configparser
0026 
0027 
0028 # Multi-word sections used in kubernetes are slightly different from what idds expects.
0029 # Usually, it's just a .replace('-', '_'), but not for hermes2, which doesn't follow any convention.
0030 multi_word_sections = {
0031     'idds_transformer': 'idds_transformer',
0032 }
0033 
0034 
0035 def load_flat_config(flat_config):
0036     """
0037     takes a dict of the form: {"section_option": "value"}
0038     and converts to {"section": {"option": "value"}
0039     """
0040     config_dict = {}
0041     for flat_key, config_value in flat_config.items():
0042         section = option = None
0043         # Try parsing a multi-word section
0044         for mw_key in multi_word_sections:
0045             if flat_key.startswith(mw_key + '_'):
0046                 section = mw_key
0047                 option = flat_key[len(mw_key) + 1:]
0048 
0049         # It didn't match any known multi-word section, assume it's a single word
0050         if not section:
0051             section, option = flat_key.split('_', maxsplit=1)
0052 
0053         config_dict.setdefault(section, {})[option] = config_value
0054     return config_dict
0055 
0056 
0057 def fix_multi_word_sections(config_dict):
0058     return {multi_word_sections.get(section, section): config_for_section for section, config_for_section in config_dict.items()}
0059 
0060 
0061 def config_len(config_dict):
0062     return sum(len(option) for _, option in config_dict.items())
0063 
0064 
0065 def merge_configs(source_file_paths, dest_file_path, prefix, use_env=True, replace_whole_file=False, env_string=None, logger=logging.log):
0066     """
0067     Merge multiple configuration sources into one destination file.
0068     On conflicting values, relies on the default python's ConfigParser behavior: the value from last source wins.
0069     Sources can be either .ini or .json files. Json is supported as a compromise solution for easier integration
0070     with kubernetes (because both python and helm natively support it).
0071     If use_env=True, env variables starting with RUCIO_CFG_ are also merged as the last (highest priority) source.
0072     """
0073 
0074     if replace_whole_file and env_string:
0075         with open(dest_file_path, 'w') as dest_file:
0076             dest_file.write(env_string)
0077     else:
0078         parser = configparser.ConfigParser()
0079         for file_path in source_file_paths:
0080             if not os.path.exists(file_path):
0081                 logger(logging.WARNING, "Skipping config file {}: file doesn't exist".format(file_path))
0082                 continue
0083 
0084             try:
0085                 if file_path.endswith('.json'):
0086                     with open(file_path, 'r') as f:
0087                         file_config = fix_multi_word_sections(json.load(f))
0088                         parser.read_dict(file_config)
0089                 else:
0090                     local_parser = configparser.ConfigParser()
0091                     local_parser.read(file_path)
0092                     file_config = {section: {option: value for option, value in section_proxy.items()} for section, section_proxy in local_parser.items()}
0093 
0094                 parser.read_dict(file_config)
0095                 logger(logging.INFO, "Merged {} configuration values from {}".format(config_len(file_config), file_path))
0096             except Exception as error:
0097                 logger(logging.WARNING, "Skipping config file {} due to error: {}".format(file_path, error))
0098 
0099         if use_env:
0100             # env variables use the following format: "RUCIO_CFG_{section.substitute('-','_').upper}_{option.substitute('-', '_').upper}"
0101             env_config = {}
0102             for env_key, env_value in os.environ.items():
0103                 if not env_key.startswith(prefix):
0104                     continue
0105                 env_key = env_key[len(prefix):].lower()  # convert "RUCIO_CFG_WHATEVER" to "whatever"
0106                 env_config[env_key] = env_value
0107 
0108             env_config = fix_multi_word_sections(load_flat_config(env_config))
0109             parser.read_dict(env_config)
0110             logger(logging.INFO, "Merged {} configuration values from ENV".format(config_len(env_config)))
0111 
0112         if dest_file_path:
0113             with open(dest_file_path, 'w') as dest_file:
0114                 parser.write(dest_file)
0115         else:
0116             parser.write(sys.stdout)
0117 
0118 
0119 logging.getLogger().setLevel(logging.INFO)
0120 parser = argparse.ArgumentParser(description="Merge multiple configuration sources into one configuration file")
0121 parser.add_argument("--use-env", action="store_true", default=False, help='Also source config from RUCIO_CFG_* env variables')
0122 parser.add_argument('-s', '--source', type=str, nargs='*', help='Source config file paths (in .json or .ini format)')
0123 parser.add_argument('-d', '--destination', default=None, help='Destination file path')
0124 parser.add_argument('-p', '--prefix', default=None, help='Prefix to match the env')
0125 parser.add_argument('-r', "--replace-whole-file", action="store_true", default=False, help='whether to replace the whole file with the env-string')
0126 parser.add_argument('-e', '--env-string', default=None, help='The environment string to replace the whole file')
0127 args = parser.parse_args()
0128 
0129 merge_configs(args.source or [], args.destination, use_env=args.use_env, prefix=args.prefix,
0130               replace_whole_file=args.replace_whole_file, env_string=args.env_string)