6565 default_css_filename , keyword_any , keyword_auto , keyword_all , \
6666 keyword_file , keyword_env , cert_valid_days , oid_valid_days , \
6767 generic_valid_days , DEFAULT_USER_ID_FORMAT , valid_user_id_formats , \
68- valid_filter_methods , default_twofactor_auth_apps
68+ valid_filter_methods , default_twofactor_auth_apps , \
69+ mig_conf_section_dirname
6970 from mig .shared .logger import Logger , SYSLOG_GDP
7071 from mig .shared .htmlgen import menu_items , vgrid_items
7172 from mig .shared .fileio import read_file , load_json , write_file
7273except ImportError as ioe :
7374 print ("could not import migrid modules" )
7475
7576
77+ def include_section_contents (logger , config , section , load_path , verbose = False ):
78+ """Include additional section contents from load_path in config."""
79+ if not os .path .exists (load_path ):
80+ msg = "no such %r section config in %s" % (section , load_path )
81+ if verbose :
82+ print (msg )
83+ logger .error (msg )
84+ return False
85+
86+ logger .debug ("include %r section from %s" % (section , load_path ))
87+ section_config = ConfigParser ()
88+ section_config .read ([load_path ])
89+ if section not in section_config .sections ():
90+ msg = "missing required %r section in config %s" % (section , load_path )
91+ if verbose :
92+ print (msg )
93+ logger .error (msg )
94+ return False
95+ other_sections = [i for i in section_config .sections () if not i == section ]
96+ if other_sections :
97+ msg = "only %r section will be read from %s" % (section , load_path )
98+ if verbose :
99+ print (msg )
100+ logger .warning (msg )
101+ if section not in config .sections ():
102+ logger .debug ("add %r section to main config" % section )
103+ config .add_section (section )
104+ for (key , val ) in section_config .items (section ):
105+ logger .debug ("add config key %r in %r section" % (key , section ))
106+ config .set (section , key , val )
107+ logger .debug ("done including %r section to main config" % section )
108+ return True
109+
110+
76111def expand_external_sources (logger , val ):
77112 """Expand a string containing ENV::NAME, FILE::PATH or FILE::PATH$$CACHE
78113 references to fill in the content of the corresponding environment, file or
@@ -923,6 +958,45 @@ def reload_config(self, verbose, skip_log=False, disable_auth_log=False,
923958 else :
924959 self .user_db_home = os .path .join (self .state_path , 'user_db_home' )
925960
961+ # Allow section confs included from file
962+ if config .has_option ('GLOBAL' , 'include_sections' ):
963+ self .include_sections = config .get ('GLOBAL' , 'include_sections' )
964+ else :
965+ self .include_sections = os .path .join (self .mig_server_home ,
966+ mig_conf_section_dirname )
967+
968+ # NOTE: for simplicity we do NOT allow overrides in GLOBAL section
969+ no_override_sections = ['GLOBAL' ]
970+ self .include_sections = os .path .normpath (self .include_sections )
971+ if os .path .isdir (self .include_sections ):
972+ msg = "read extra config sections from %s" % self .include_sections
973+ if verbose :
974+ print (msg )
975+ logger .info (msg )
976+ for section_filename in os .listdir (self .include_sections ):
977+ # skip dotfiles and non-confs
978+ if section_filename .startswith ('.' ):
979+ continue
980+ if not section_filename .endswith ('.conf' ):
981+ msg = "%r is not on required sectionname.conf form" % \
982+ section_filename
983+ if verbose :
984+ print (msg )
985+ logger .warning (msg )
986+ continue
987+ section_path = os .path .join (self .include_sections ,
988+ section_filename )
989+ section = section_filename .replace ('.conf' , '' ).upper ()
990+ if section in no_override_sections :
991+ msg = "skip unsupported %r section override in %r" % \
992+ (section , section_filename )
993+ if verbose :
994+ print (msg )
995+ logger .warning (msg )
996+ continue
997+ include_section_contents (logger , config , section , section_path ,
998+ verbose )
999+
9261000 if config .has_option ('GLOBAL' , 'admin_list' ):
9271001 # Parse semi-colon separated list of admins with optional spaces
9281002 admins = config .get ('GLOBAL' , 'admin_list' )
@@ -1669,15 +1743,12 @@ def reload_config(self, verbose, skip_log=False, disable_auth_log=False,
16691743 self .jupyter_services = []
16701744 # Load generated jupyter sections
16711745 for section in config .sections ():
1672- if 'JUPYTER_' in section :
1746+ if section . startswith ( 'JUPYTER_' ) :
16731747 # Allow service_desc to be a file that should be read
16741748 if config .has_option (section , 'service_desc' ):
1675- service_desc = config .get (section , 'service_desc' )
1676- if os .path .exists (service_desc ) \
1677- and os .path .isfile (service_desc ):
1678- content = read_file (service_desc , logger )
1679- if content :
1680- config .set (section , 'service_desc' , content )
1749+ content = expand_external_sources (
1750+ logger , config .get (section , 'service_desc' ))
1751+ config .set (section , 'service_desc' , content )
16811752
16821753 self .jupyter_services .append ({option : config .get (section ,
16831754 option )
@@ -1697,15 +1768,12 @@ def reload_config(self, verbose, skip_log=False, disable_auth_log=False,
16971768 'service_jumphost_key' ]
16981769 # Load generated cloud sections
16991770 for section in config .sections ():
1700- if 'CLOUD_' in section :
1771+ if section . startswith ( 'CLOUD_' ) :
17011772 # Allow service_desc to be a file that should be read
17021773 if config .has_option (section , 'service_desc' ):
1703- service_desc = config .get (section , 'service_desc' )
1704- if os .path .exists (service_desc ) \
1705- and os .path .isfile (service_desc ):
1706- content = read_file (service_desc , logger )
1707- if content :
1708- config .set (section , 'service_desc' , content )
1774+ content = expand_external_sources (
1775+ logger , config .get (section , 'service_desc' ))
1776+ config .set (section , 'service_desc' , content )
17091777
17101778 service = {option : config .get (section , option ) for option in
17111779 config .options (section )}
0 commit comments