@@ -656,6 +656,8 @@ def get(opt, default, **kwargs):
656656 environ_str = get ('environment' , '' )
657657 environ_str = expand (environ_str , expansions , 'environment' )
658658 section .environment = dict_of_key_value_pairs (environ_str )
659+ section .environment_file = get ('environment_file' , None )
660+ section .environment_loader = get ('environment_loader' , None )
659661
660662 # extend expansions for global from [supervisord] environment definition
661663 for k , v in section .environment .items ():
@@ -674,6 +676,13 @@ def get(opt, default, **kwargs):
674676 env = section .environment .copy ()
675677 env .update (proc .environment )
676678 proc .environment = env
679+
680+ # set the environment file/loader on the process configs but let them override it
681+ if not proc .environment_file and not proc .environment_loader :
682+ if section .environment_file :
683+ proc .environment_file = section .environment_file
684+ elif section .environment_loader :
685+ proc .environment_loader = section .environment_loader
677686 section .server_configs = self .server_configs_from_parser (parser )
678687 section .profile_options = None
679688 return section
@@ -925,6 +934,8 @@ def get(section, opt, *args, **kwargs):
925934 numprocs = integer (get (section , 'numprocs' , 1 ))
926935 numprocs_start = integer (get (section , 'numprocs_start' , 0 ))
927936 environment_str = get (section , 'environment' , '' , do_expand = False )
937+ environment_file = get (section , 'environment_file' , '' , do_expand = False )
938+ environment_loader = get (section , 'environment_loader' , '' , do_expand = False )
928939 stdout_cmaxbytes = byte_size (get (section ,'stdout_capture_maxbytes' ,'0' ))
929940 stdout_events = boolean (get (section , 'stdout_events_enabled' ,'false' ))
930941 stderr_cmaxbytes = byte_size (get (section ,'stderr_capture_maxbytes' ,'0' ))
@@ -1057,6 +1068,8 @@ def get(section, opt, *args, **kwargs):
10571068 exitcodes = exitcodes ,
10581069 redirect_stderr = redirect_stderr ,
10591070 environment = environment ,
1071+ environment_file = environment_file ,
1072+ environment_loader = environment_loader ,
10601073 serverurl = serverurl )
10611074
10621075 programs .append (pconfig )
@@ -1875,7 +1888,7 @@ class ProcessConfig(Config):
18751888 'stderr_events_enabled' , 'stderr_syslog' ,
18761889 'stopsignal' , 'stopwaitsecs' , 'stopasgroup' , 'killasgroup' ,
18771890 'exitcodes' , 'redirect_stderr' ]
1878- optional_param_names = [ 'environment' , 'serverurl' ]
1891+ optional_param_names = [ 'environment' , 'environment_file' , 'environment_loader' , ' serverurl' ]
18791892
18801893 def __init__ (self , options , ** params ):
18811894 self .options = options
@@ -1939,6 +1952,57 @@ def make_dispatchers(self, proc):
19391952 dispatchers [stdin_fd ] = PInputDispatcher (proc , 'stdin' , stdin_fd )
19401953 return dispatchers , p
19411954
1955+ def load_external_environment_definition (self ):
1956+ return self .load_external_environment_definition_for_config (self )
1957+
1958+ # this is separated out in order to make it easier to test
1959+ @classmethod
1960+ def load_external_environment_definition_for_config (cls , config ):
1961+ # lazily load extra env vars before we drop privileges so that this can be used to load a secrets file
1962+ # or execute a program to get more env configuration. It doesn't have to be secrets, just config that
1963+ # needs to be separate from the supervisor config for whatever reason. The supervisor config interpolation
1964+ # is not supported here. The data format is just plain text, with one k=v value per line. Lines starting
1965+ # with '#' are ignored.
1966+ env = {}
1967+ envdata = None
1968+ if config .environment_file :
1969+ if os .path .exists (config .environment_file ):
1970+ try :
1971+ with open (config .environment_file , 'r' ) as f :
1972+ envdata = f .read ()
1973+
1974+ except Exception as e :
1975+ raise ProcessException ("environment_file read failure on %s: %s" % (config .environment_file , e ))
1976+
1977+ elif config .environment_loader :
1978+ try :
1979+ from subprocess import check_output , CalledProcessError
1980+ kwargs = dict (shell = True )
1981+ if not PY2 :
1982+ kwargs ['text' ] = True
1983+
1984+ envdata = check_output (config .environment_loader , ** kwargs )
1985+
1986+ except CalledProcessError as e :
1987+ raise ProcessException ("environment_loader failure with %s: %d, %s" % (config .environment_loader , e .returncode , e .output ))
1988+
1989+ if envdata :
1990+ extra_env = {}
1991+
1992+ for line in envdata .splitlines ():
1993+ line = line .strip ()
1994+ if line .startswith ('#' ): # ignore comments
1995+ continue
1996+
1997+ key , val = [s .strip () for s in line .split ('=' , 1 )]
1998+ if key :
1999+ extra_env [key .upper ()] = val
2000+
2001+ if extra_env :
2002+ env .update (extra_env )
2003+
2004+ return env
2005+
19422006class EventListenerConfig (ProcessConfig ):
19432007 def make_dispatchers (self , proc ):
19442008 # always use_stderr=True for eventlisteners because mixing stderr
0 commit comments