def configFromCommandSpec(cls, spec, user = None, default = None, extra_settings = None, disable_console_log = False): """ A command specification (typically specified with the --config=<file_or_dir> command line option) is used to create a configuration object. The target may be either a file or a directory. If it is a file, then the file itself will be the only configuration read. If it is a directory, then a search is made for any top-level files which end in .conf or .yaml, and those will be combined according to lexicographic order. If the configuration path is a relative path, then it is relative to either the root directory, or the home directory of the given user. This allows a user-specific configuration to automatically take effect if desired. """ frombase = '/' if user: frombase = lookup_user(user).pw_dir trypath = os.path.join(frombase, spec) debug("TRY CONFIG PATH: {0}".format(trypath)) if not os.path.exists(trypath): return cls(default = default) else: os.environ[ENV_CONFIG_DIR] = os.path.dirname(trypath) if os.path.isdir(trypath): return cls(*[os.path.join(trypath, f) for f in sorted(os.listdir(trypath)) if f.endswith('.yaml') or f.endswith('.conf')], default = default, uid = user, extra_settings = extra_settings, disable_console_log = disable_console_log) return cls(trypath, default = default, uid = user, extra_settings = extra_settings, disable_console_log = disable_console_log)
def _try_to_enable(self): service = self.service if self._orig_executable: try: service.exec_args[0] = executable_path( self._orig_executable, service.environment.expanded()) except FileNotFoundError: if service.optional: service.enabled = False self.loginfo( "optional service {0} disabled since '{1}' is not present" .format(self.name, self._orig_executable)) return elif service.ignore_failures: service.enabled = False self.logwarn( "(ignored) service {0} executable '{1}' is not present" .format(self.name, self._orig_executable)) return raise ChNotFoundError("executable '{0}' not found".format( service.exec_args[0])) # Now we know this service is truly enabled, we need to assure its credentials # are correct. senv = service.environment if senv and senv.uid is not None and not self._pwrec: self._pwrec = lookup_user(senv.uid, senv.gid) service.enabled = True
def _try_to_enable(self): service = self.service if self._orig_executable: try: service.exec_args[0] = executable_path(self._orig_executable, service.environment.expanded()) except FileNotFoundError: if service.optional: service.enabled = False self.loginfo("optional service {0} disabled since '{1}' is not present".format(self.name, self._orig_executable)) return elif service.ignore_failures: service.enabled = False self.logwarn("(ignored) service {0} executable '{1}' is not present".format(self.name, self._orig_executable)) return raise ChNotFoundError("executable '{0}' not found".format(service.exec_args[0])) # Now we know this service is truly enabled, we need to assure its credentials # are correct. senv = service.environment if senv and senv.uid is not None and not self._pwrec: self._pwrec = lookup_user(senv.uid, senv.gid) service.enabled = True
def _backtick_expand(self, cmd): """ Performs rudimentary backtick expansion after all other environment variables have been expanded. Because these are cached, the user should not expect results to differ for different environment contexts, nor should the environment itself be relied upon. """ # Accepts either a string or match object if not isinstance(cmd, str): cmd = cmd.group(1) if not self._cls_backtick: return "`" + cmd + "`" key = '{0}:{1}:{2}'.format(self.uid, self.gid, cmd) result = self._cls_btcache.get(key) if result is None: if self.uid: try: pwrec = lookup_user(self.uid, self.gid) except ChNotFoundError as ex: ex.annotate( '(required for backtick expansion `{0}`)'.format(cmd)) raise ex else: pwrec = None def _proc_setup(): if pwrec: os.setgid(pwrec.pw_gid) os.setuid(pwrec.pw_uid) try: result = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, preexec_fn=_proc_setup) result = result.decode() except Exception as ex: error(ex, "Backtick expansion returned error: " + str(ex)) result = "" result = result.strip().replace("\n", " ") if self._cls_use_btcache: self._cls_btcache[key] = result return result
def _backtick_expand(self, cmd): """ Performs rudimentary backtick expansion after all other environment variables have been expanded. Because these are cached, the user should not expect results to differ for different environment contexts, nor should the environment itself be relied upon. """ # Accepts either a string or match object if not isinstance(cmd, str): cmd = cmd.group(1) if not self._cls_backtick: return "`" + cmd + "`" key = '{0}:{1}:{2}'.format(self.uid, self.gid, cmd) result = self._cls_btcache.get(key) if result is None: if self.uid: try: pwrec = lookup_user(self.uid, self.gid) except ChNotFoundError as ex: ex.annotate('(required for backtick expansion `{0}`)'.format(cmd)) raise ex else: pwrec = None def _proc_setup(): if pwrec: os.setgid(pwrec.pw_gid) os.setuid(pwrec.pw_uid) try: result = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, preexec_fn=_proc_setup) result = result.decode() except Exception as ex: error(ex, "Backtick expansion returned error: " + str(ex)) result = "" result = result.strip().replace("\n", " ") if self._cls_use_btcache: self._cls_btcache[key] = result return result
def __init__(self, from_env = os.environ, config = None, uid = None, gid = None, resolve_xid = True): """ Create a new environment. An environment may have a user associated with it. If so, then it will be pre-populated with the user's HOME, USER and LOGNAME so that expansions can reference these. Note that if resolve_xid is False, then credentials if they do not exist, but leave the uid/gid the same. This means that certain features, like HOME variables, will not be properly set, leading to possible interactions between the optional components and their actual specification. However, this is better than having optional components trigger errors because uninstalled software did not create uid's needed for operation. The onus is on the service itself (in cproc) to assure that checking is performed. Note also that environments which use backtick expansions will *still* fail, because the backticks must occur within the context of the specified user, and it would be a security violation to allow a default. """ super().__init__() #print("\n--ENV INIT", config, uid, from_env, from_env and getattr(from_env, 'uid', None)) userenv = dict() # Inherit user from passed-in environment self._shadow = getattr(from_env, '_shadow', None) shadow = None # we don't bother to recreate this in any complex fashion unless we need to if uid is None: self.uid = getattr(from_env, 'uid', self.uid) self.gid = getattr(from_env, 'gid', self.gid) else: pwrec = None try: pwrec = lookup_user(uid, gid) except ChNotFoundError: if resolve_xid: raise self.uid = uid self.gid = gid if pwrec: self.uid = pwrec.pw_uid self.gid = pwrec.pw_gid userenv['HOME'] = pwrec.pw_dir userenv['USER'] = userenv['LOGNAME'] = pwrec.pw_name if not config: if from_env: self.update(from_env) self.update(userenv) else: inherit = config.get('env_inherit') or ['*'] if inherit and from_env: self.update({k:v for k,v in from_env.items() if any([fnmatch(k,pat) for pat in inherit])}) self.update(userenv) add = config.get('env_set') unset = config.get('env_unset') if add or unset: self._shadow = shadow = (getattr(self, '_shadow') or _DICT_CONST).copy() if add: for k,v in add.items(): if from_env and k in from_env: shadow[k] = from_env # we keep track of the environment where the predecessor originated self[k] = v if unset: patmatch = lambda p: any([fnmatch(p,pat) for pat in unset]) for delkey in [k for k in self.keys() if patmatch(k)]: del self[delkey] for delkey in [k for k in shadow.keys() if patmatch(k)]: del shadow[delkey]
def __init__(self, from_env=os.environ, config=None, uid=None, gid=None, resolve_xid=True): """ Create a new environment. An environment may have a user associated with it. If so, then it will be pre-populated with the user's HOME, USER and LOGNAME so that expansions can reference these. Note that if resolve_xid is False, then credentials if they do not exist, but leave the uid/gid the same. This means that certain features, like HOME variables, will not be properly set, leading to possible interactions between the optional components and their actual specification. However, this is better than having optional components trigger errors because uninstalled software did not create uid's needed for operation. The onus is on the service itself (in cproc) to assure that checking is performed. Note also that environments which use backtick expansions will *still* fail, because the backticks must occur within the context of the specified user, and it would be a security violation to allow a default. """ super().__init__() #print("\n--ENV INIT", config, uid, from_env, from_env and getattr(from_env, 'uid', None)) userenv = dict() # Inherit user from passed-in environment self._shadow = getattr(from_env, '_shadow', None) shadow = None # we don't bother to recreate this in any complex fashion unless we need to if uid is None: self.uid = getattr(from_env, 'uid', self.uid) self.gid = getattr(from_env, 'gid', self.gid) else: pwrec = None try: pwrec = lookup_user(uid, gid) except ChNotFoundError: if resolve_xid: raise self.uid = uid self.gid = gid if pwrec: self.uid = pwrec.pw_uid self.gid = pwrec.pw_gid userenv['HOME'] = pwrec.pw_dir userenv['USER'] = userenv['LOGNAME'] = pwrec.pw_name if not config: if from_env: self.update(from_env) self.update(userenv) else: inherit = config.get('env_inherit') or ['*'] if inherit and from_env: self.update({ k: v for k, v in from_env.items() if any([fnmatch(k, pat) for pat in inherit]) }) self.update(userenv) add = config.get('env_set') unset = config.get('env_unset') if add or unset: self._shadow = shadow = (getattr(self, '_shadow') or _DICT_CONST).copy() if add: for k, v in add.items(): if from_env and k in from_env: shadow[ k] = from_env # we keep track of the environment where the predecessor originated self[k] = v if unset: patmatch = lambda p: any([fnmatch(p, pat) for pat in unset]) for delkey in [k for k in self.keys() if patmatch(k)]: del self[delkey] for delkey in [k for k in shadow.keys() if patmatch(k)]: del shadow[delkey]