def post_init(self): # Assure that exec_args is set to the actual arguments used for execution if self.command: self.exec_args = shlex.split(self.command) # Lookup signal number if self.kill_signal is not None: self.kill_signal = get_signal_number(self.kill_signal) # Expand before, after and service_groups into sets/tuples self.before = set(_RE_LISTSEP.split(self.before)) if self.before is not None else set() self.after = set(_RE_LISTSEP.split(self.after)) if self.after is not None else set() self.service_groups = tuple(_RE_LISTSEP.split(self.service_groups)) if self.service_groups is not None else tuple() for sname in chain(self.before, self.after): if sname.upper() == sname and sname not in chain(self.system_group_names, self.system_service_names): raise ChParameterError("{0} dependency reference not valid; '{1}' is not a recognized system name" .format(self.name, sname)) for sname in self.service_groups: if sname.upper() == sname and sname not in self.system_group_names: raise ChParameterError("{0} contains an unrecognized system group name '{1}'".format(self.name, sname)) if 'IDLE' in self.after: raise Exception("{0} cannot specify services which start *after* service_group IDLE".format(self.name)) if 'INIT' in self.before: raise Exception("{0} cannot specify services which start *before* service_group INIT".format(self.name))
def __init__(self, service, family=None): self.service = service self.family = family self._pending = set() if service.process_timeout is not None: self.process_timeout = service.process_timeout if not service.environment: self._procenv = Environment() else: self._procenv = service.environment if not service.exec_args: raise ChParameterError( "No command or arguments provided for service") # If the service is enabled, assure we check for the presence of the executable now. This is # to catch any start-up situations (such as cron jobs without their executables being present). # However, we don't check this if a service is disabled. self._orig_executable = service.exec_args[0] if service.enabled: self._try_to_enable()
def __init__(self, service, family=None): super().__init__(service, family) self._proclist = set() if not service.port: raise ChParameterError( "inetd-type service {0} requires 'port=' parameter".format( self.name))
def _typecheck_assure_int(self, attr): "Assures that the specified attribute is a legal integer." val = getattr(self, attr) if val is None or isinstance(val, int): return try: setattr(self, attr, int(val)) except ValueError: raise ChParameterError("invalid integer parameter for '{0}': '{1}'".format(attr, val))
def _typecheck_assure_bool(self, attr): "Assures that the specified attribute is a legal boolean." val = getattr(self, attr) if val is None or isinstance(val, bool): return # First, try both 'true' and 'false' according to YAML conventions match = _RE_YAML_BOOL.match(str(val)) if not match: raise ChParameterError("invalid boolean parameter for '{0}': '{1}'".format(attr, val)) setattr(self, attr, bool(match.group('true')))
def _lookup_services(self, names): result = set() for name in names: serv = self.get(name) if not serv: serv = self.get(name + ".service") if not serv: raise ChParameterError("no such service: " + name) result.add(serv) return result
def get_signal_number(signame): sup = signame.upper() if sup.startswith('SIG') and not sup.startswith('SIG_'): num = getattr(signal, sup, None) else: try: num = int(signame) except ValueError: num = None if num is None: raise ChParameterError("Invalid signal specifier: " + str(signame)) return num
def __init__(self, service, family=None): super().__init__(service, family) if not self.interval: raise ChParameterError( "interval= property missing, required for cron service '{0}'". format(self.name)) # Support specials with or without the @ real_interval = _CRON_SPECIALS.get( self.interval) or _CRON_SPECIALS.get( '@' + self.interval) or self.interval # make a status note self.note = "{0} ({1})".format( self.interval, real_interval) if self.interval != real_interval else real_interval self._cron = crontab(real_interval, func=self._cron_hit, start=False)
def __init__(self, initdict, name = "MAIN", env = None, settings = None): self.name = name if settings: for sd in self._settings_defaults: if sd not in initdict: val = settings.get(sd) if val is not None: setattr(self, sd, val) for k,v in initdict.items(): setattr(self, k, v) # User names always have .xxx qualifier because of schema restrictions. Otherwise, it's a user # defined name subject to restrictions. splitname = self.name.rsplit('.', 1) if len(splitname) == 2 and splitname[0] == splitname[0].upper(): raise ChParameterError("all-uppercase names such as '{0}' are reserved for the system.".format(self.name)) # UID and GID are expanded according to the incoming environment, # since the new environment depends upon these. if env: env.expand_attributes(self, 'uid', 'gid') uid = self.get('uid') gid = self.get('gid') if gid is not None and uid is None: raise Exception("cannot specify 'gid' without 'uid'") # We can now use 'self' as our config, with all defaults. env = self.environment = Environment(env, uid=uid, gid=gid, config=self, resolve_xid = not self.get('optional', False)) self.augment_environment(env) if self._expand_these: env.expand_attributes(self, *self._expand_these) for attr,func in self._typecheck.items(): getattr(self, '_typecheck_'+func)(attr) self.post_init()
def start(self): """ Takes over startup and sets up our cron loop to handle starts instead. """ if not self.enabled or self._cron.handle: return self.start_attempted = True # Start up cron try: self._cron.start() except Exception: raise ChParameterError( "not a valid cron interval specification, '{0}'".format( self.interval)) self.loginfo( "cron service {0} scheduled using interval spec '{1}'".format( self.name, self.interval))
def maybe_create_user(user, uid=None, gid=None, using_file=None, default_home=None): """ If the user does not exist, then create one with the given name, and optionally the specified uid. If a gid is specified, create a group with the same name as the user, and the given gid. If the user does exist, then confirm that the uid and gid match, if either or both are specified. If 'using_file' is specified, then uid/gid are ignored and replaced with the uid/gid of the specified file. The file must exist and be readable. """ if using_file: stat = os.stat(using_file) if uid is None: uid = stat.st_uid if gid is None: gid = stat.st_gid if uid is not None: try: uid = int(uid) except ValueError: raise ChParameterError( "Specified UID is not a number: {0}".format(uid)) try: pwrec = lookup_user(user) except ChNotFoundError: pwrec = None # If the user exists, we do nothing, but we do validate that their UID and GID # exist. if pwrec: if uid is not None and uid != pwrec.pw_uid: raise ChParameterError( "User {0} exists, but does not have expected UID={1}".format( user, uid)) if gid is not None and lookup_group(gid).gr_gid != pwrec.pw_gid: raise ChParameterError( "User {0} exists, but does not have expected GID={1}".format( user, gid)) return # Now, we need to create the user, and optionally the group. if gid is not None: create_group = False try: newgid = lookup_group(gid).gr_name # always use name except ChNotFoundError: create_group = True try: newgid = int(gid) # must be a number at this point except ValueError: # We don't report the numeric error, because we *know* there is no such group # and we won't create a symbolic group with a randomly-created number. raise ChParameterError("Group does not exist: {0}".format(gid)) if create_group: groupadd(user, newgid) newgid = lookup_group(user).gr_name gid = newgid # always will be the group name # Test to see if the user directory itself already exists, which should be the case. # If it doesn't, then use the default, if provided. home = None if default_home: udd = get_user_directories_directory() if not os.path.exists(os.path.join(udd, user)): home = default_home useradd(user, uid, gid, home)
def _expand_into(self, k, wholematch, result, parent=None): """ Internal workhorse that expands the variable 'k' INTO the given result dictionary. The result dictionary will conatin the expanded values. The result dictionary is also a cache for nested and recursive environment expansion. 'wholematch' is None unless called from in an re.sub() (or similar context). If set, it indicates the complete expansion expression, including adornments. It is used as the default expansion when a variable is not defined. 'parent' is the name of the variable which was being expanded in the last recursion, to catch the special case of self-referential variables. """ match = _RE_OPERS.match(k) if match: (k, oper, repl, backtick) = match.groups() # Phase 1: Base variable value. Start by determining the value of variable # 'k' within the current context. # 1A: We have a backtick shortcut, such as $(`date`) if match and backtick: return self._recurse(result, backtick, parent) # 1B: We have an embedded self reference such as "PATH": "/bin:$(PATH)". We use # the last defined value in a prior environment as the value. elif parent == k and wholematch is not None: val = (self._get_shadow_environment(k) or _DICT_CONST).get(k) or '' # 1C: We have already calculated a result and will use that instead, but only # in a nested expansion. We re-evaluate top-levels all the time. elif wholematch is not None and k in result: val = result[k] # 1D: We have a variable which is not part of our environment at all, and # either treat it as empty, or as the wholematch value for further # processing elif k not in self: val = "" if match else wholematch # 1E: Finally, we will store this value and expand further. else: result[k] = self[ k] # assure that recursion attempts stop with this value val = result[k] = self._recurse(result, self[k], k) # We now have, in 'val', the fully expanded contents of the variable 'k' if not match: return val # Phase 2: Process any operators to return a possibily modified # value as the result of the complete expression. if oper == '?': if not val: raise ChVariableError(self._recurse(result, repl, parent)) elif oper == '/': smatch = _RE_SLASHOP.match(repl) if not smatch: raise ChParameterError( "invalid regex replacement syntax in '{0}'".format( match.group(0))) val = self._recurse( result, re.sub((smatch.group(3) and "(?" + smatch.group(3) + ")") + smatch.group(1), smatch.group(2).replace('\/', '/'), val), parent) elif oper == '|': vts = _RE_BAREBAR.split(repl, 3) if len(vts) == 1: # same as + val = '' if not val else self._recurse(result, vts[0], parent) elif len(vts) == 2: val = self._recurse(result, vts[0] if val else vts[1], parent) elif len(vts) >= 3: editval = vts[1] if fnmatch( val.replace(r'\|', '|').lower(), vts[0].lower()) else vts[2] val = self._recurse(result, editval.replace(r'\|', '|'), parent) elif oper == "+": val = '' if not val else self._recurse(result, repl, parent) elif oper == "_": # strict opposite of + val = '' if val else self._recurse(result, repl, parent) elif oper == "-": # bash :- if not val: val = self._recurse(result, repl, parent) return val