def get_items( self, collection_type: str ) -> Union[Distros, Profiles, Systems, Repos, Images, Mgmtclasses, Packages, Files, Menus]: """ Get a full collection of a single type. Valid Values vor ``collection_type`` are: "distro", "profile", "repo", "image", "mgmtclass", "package", "file" and "settings". :param collection_type: The type of collection to return. :return: The collection if ``collection_type`` is valid. :raises CX: If the ``collection_type`` is invalid. """ result: Union[Distros, Profiles, Systems, Repos, Images, Mgmtclasses, Packages, Files, Menus] if collection_type == "distro": result = self._distros elif collection_type == "profile": result = self._profiles elif collection_type == "system": result = self._systems elif collection_type == "repo": result = self._repos elif collection_type == "image": result = self._images elif collection_type == "mgmtclass": result = self._mgmtclasses elif collection_type == "package": result = self._packages elif collection_type == "file": result = self._files elif collection_type == "menu": result = self._menus elif collection_type == "settings": result = self.api.settings() else: raise CX("internal error, collection name \"%s\" not supported" % collection_type) return result
def _get_power_input(self, system, power_operation, logger, user, password): """ Creates an option string for the fence agent from the system data. This is an internal method. :param system: Cobbler system :type system: System :param power_operation: power operation. Valid values: on, off, status. Rebooting is implemented as a set of 2 operations (off and on) in a higher level method. :type power_operation: str :param logger: logger :type logger: Logger :param user: user to override system.power_user :type user: str :param password: password to override system.power_pass :type password: str :return: The option string for the fencer agent. :rtype: str """ self._check_power_conf(system, logger, user, password) power_input = "" if power_operation is None or power_operation not in [ 'on', 'off', 'status' ]: raise CX("invalid power operation") power_input += "action=" + power_operation + "\n" if system.power_address: power_input += "ip=" + system.power_address + "\n" if system.power_user: power_input += "username="******"\n" if system.power_id: power_input += "plug=" + system.power_id + "\n" if system.power_pass: power_input += "password="******"\n" if system.power_identity_file: power_input += "identity-file=" + system.power_identity_file + "\n" if system.power_options: power_input += system.power_options + "\n" return power_input
def remove(self, name, with_delete=True, with_sync=True, with_triggers=True, recursive=False, logger=None): """ Remove element named 'name' from the collection """ name = name.lower() obj = self.find(name=name) if obj is not None: if with_delete: if with_triggers: utils.run_triggers( self.collection_mgr.api, obj, "/var/lib/cobbler/triggers/delete/package/*", [], logger) self.lock.acquire() try: del self.listing[name] finally: self.lock.release() self.collection_mgr.serialize_delete(self, obj) if with_delete: if with_triggers: utils.run_triggers( self.collection_mgr.api, obj, "/var/lib/cobbler/triggers/delete/package/post/*", [], logger) utils.run_triggers(self.collection_mgr.api, obj, "/var/lib/cobbler/triggers/change/*", [], logger) return raise CX(_("cannot delete an object that does not exist: %s") % name)
def find(self, name=None, return_list=False, no_errors=False, **kargs): """ Return first object in the collection that maches all item='value' pairs passed, else return None if no objects can be found. When return_list is set, can also return a list. Empty list would be returned instead of None in that case. """ matches = [] # support the old style innovation without kwargs if name is not None: kargs["name"] = name kargs = self.__rekey(kargs) # no arguments is an error, so we don't return a false match if len(kargs) == 0: raise CX(_("calling find with no arguments")) # performance: if the only key is name we can skip the whole loop if len(kargs) == 1 and "name" in kargs and not return_list: try: return self.listing.get(kargs["name"].lower(), None) except: return self.listing.get(kargs["name"], None) self.lock.acquire() try: for (name, obj) in list(self.listing.items()): if obj.find_match(kargs, no_errors=no_errors): matches.append(obj) finally: self.lock.release() if not return_list: if len(matches) == 0: return None return matches[0] else: return matches
def deserialize(self): """ Load all cobbler_collections from disk :raises CX: if there is an error in deserialization """ for collection in ( self._menus, self._distros, self._repos, self._profiles, self._images, self._systems, self._mgmtclasses, self._packages, self._files, ): try: serializer.deserialize(collection) except Exception as e: raise CX("serializer: error loading collection %s: %s. Check /etc/cobbler/modules.conf" % (collection.collection_type(), e)) from e
def set_boot_loaders(self, boot_loaders): """ Setter of the boot loaders. :param boot_loaders: The boot loaders for the image. """ # allow the magic inherit string to persist if boot_loaders == "<<inherit>>": self.boot_loaders = "<<inherit>>" return if boot_loaders: boot_loaders_split = utils.input_string_or_list(boot_loaders) supported_boot_loaders = self.get_supported_boot_loaders() if not set(boot_loaders_split).issubset(supported_boot_loaders): raise CX( "Error with image %s - not all boot_loaders %s are supported %s" % (self.name, boot_loaders_split, supported_boot_loaders)) self.boot_loaders = boot_loaders_split else: self.boot_loaders = []
def __parse_config(): """ Parse the "users.conf" of Cobbler and return all data in a dictionary. :return: The data seperated by sections. Each section has a subdictionary with the key-value pairs. :rtype: dict """ etcfile = '/etc/cobbler/users.conf' if not os.path.exists(etcfile): raise CX(_("/etc/cobbler/users.conf does not exist")) # Make users case sensitive to handle kerberos config = ConfigParser() config.optionxform = str config.read(etcfile) alldata = {} sections = config.sections() for g in sections: alldata[str(g)] = {} opts = config.options(g) for o in opts: alldata[g][o] = 1 return alldata
def set_boot_loaders(self, boot_loaders): """ Set the bootloader for the distro. :param name: The name of the bootloader. Must be one of the supported ones. """ # allow the magic inherit string to persist if boot_loaders == "<<inherit>>": self.boot_loaders = "<<inherit>>" return if boot_loaders: names_split = utils.input_string_or_list(boot_loaders) supported_distro_boot_loaders = self.get_supported_boot_loaders() if not set(names_split).issubset(supported_distro_boot_loaders): raise CX( "Invalid boot loader names: %s. Supported boot loaders are: %s" % (boot_loaders, ' '.join(supported_distro_boot_loaders))) self.boot_loaders = names_split else: self.boot_loaders = []
def remove(self, name, with_delete: bool = True, with_sync: bool = True, with_triggers: bool = True, recursive: bool = False): """ Remove element named 'name' from the collection :raises CX: In case a non existent object should be deleted. """ name = name.lower() obj = self.find(name=name) if obj is None: raise CX("cannot delete an object that does not exist: %s" % name) if with_delete: if with_triggers: utils.run_triggers(self.api, obj, "/var/lib/cobbler/triggers/delete/file/*", []) self.lock.acquire() try: del self.listing[name] finally: self.lock.release() self.collection_mgr.serialize_delete(self, obj) if with_delete: if with_triggers: utils.run_triggers( self.api, obj, "/var/lib/cobbler/triggers/delete/file/post/*", []) utils.run_triggers(self.api, obj, "/var/lib/cobbler/triggers/change/*", []) return
def set_mac_address(self, address, interface): """ Set mc address on interface. @param: str address (mac address) @param: str interface (interface name) @returns: True or CX """ address = validate.mac_address(address) if address == "random": address = utils.get_random_mac(self.config.api) if address != "" and utils.input_boolean( self.config._settings.allow_duplicate_macs) is False: matched = self.config.api.find_items("system", {"mac_address": address}) for x in matched: if x.name != self.name: raise CX("MAC address duplicated: %s" % address) intf = self.__get_interface(interface) intf["mac_address"] = address.strip() return True
def generate_autoinstall_for_profile(self, g): """ Generate an autoinstall config or script for a profile. :param g: The Profile to generate the script/config for. :return: The generated output or an error message with a human readable description. :rtype: str """ g = self.api.find_profile(name=g) if g is None: return "# profile not found" distro = g.get_conceptual_parent() if distro is None: raise CX( _("profile %(profile)s references missing distro %(distro)s") % { "profile": g.name, "distro": g.distro }) return self.generate_autoinstall(profile=g)
def serialize_item(collection, item): """ Save a collection item to file system @param Collection collection collection @param Item item collection item """ if item.name is None or item.name == "": raise CX("name unset for item!") # FIXME: Need a better way to support collections/items # appending an 's' does not work in all cases if collection.collection_type() in ['mgmtclass']: filename = "/var/lib/cobbler/collections/%ses/%s" % ( collection.collection_type(), item.name) else: filename = "/var/lib/cobbler/collections/%ss/%s" % ( collection.collection_type(), item.name) _dict = item.to_dict() if capi.CobblerAPI().settings().serializer_pretty_json: sort_keys = True indent = 4 else: sort_keys = False indent = None filename += ".json" _dict = item.to_dict() fd = open(filename, "w+") data = simplejson.dumps(_dict, encoding="utf-8", sort_keys=sort_keys, indent=indent) fd.write(data) fd.close()
def write_tftpd_files(self): """ xinetd files are written when manage_tftp is set in /var/lib/cobbler/settings. """ template_file = "/etc/cobbler/tftpd.template" try: f = open(template_file, "r") except: raise CX(_("error reading template %s") % template_file) template_data = "" template_data = f.read() f.close() metadata = { "user": "******", "binary": "/usr/sbin/in.tftpd", "args": "%s" % self.bootloc } self.logger.info("generating %s" % self.settings_file) self.templar.render(template_data, metadata, self.settings_file, None)
def generate_autoinstall_for_system(self, sys_name): """ Generate an autoinstall config or script for a system. :param sys_name: The system name to generate an autoinstall script for. :return: The generated output or an error message with a human readable description. :rtype: str """ s = self.api.find_system(name=sys_name) if s is None: return "# system not found" p = s.get_conceptual_parent() if p is None: raise CX("system %(system)s references missing profile %(profile)s" % {"system": s.name, "profile": s.profile}) distro = p.get_conceptual_parent() if distro is None: # this is an image parented system, no automatic installation file available return "# image based systems do not have automatic installation files" return self.generate_autoinstall(profile=p, system=s)
def check_if_valid(self): """ Insure name, path, owner, group, and mode are set. Templates are only required for files, is_dir = False """ if not self.name: raise CX("name is required") if not self.path: raise CX("path is required") if not self.owner: raise CX("owner is required") if not self.group: raise CX("group is required") if not self.mode: raise CX("mode is required") if not self.is_dir and self.template == "": raise CX("Template is required when not a directory")
def authenticate(api_handle, username, password): """ Validate a username/password combo. This will pass the username and password back to Spacewalk to see if this authentication request is valid. See also: https://github.com/uyuni-project/uyuni/blob/master/java/code/src/com/redhat/rhn/frontend/xmlrpc/auth/AuthHandler.java#L133 :param api_handle: The api instance to retrieve settings of. :param username: The username to authenticate agains spacewalk/uyuni/SUSE Manager :param password: The password to authenticate agains spacewalk/uyuni/SUSE Manager :return: True if it succeeded, False otherwise. :rtype: bool """ if api_handle is None: raise CX("api_handle required. Please don't call this without it.") server = api_handle.settings().redhat_management_server user_enabled = api_handle.settings().redhat_management_permissive spacewalk_url = "https://%s/rpc/api" % server with ServerProxy(spacewalk_url, verbose=True) as client: if username == 'taskomatic_user' or __looks_like_a_token(password): # The tokens are lowercase hex, but a password can also be lowercase hex, so we have to try it as both a # token and then a password if we are unsure. We do it this way to be faster but also to avoid any login # failed stuff in the logs that we don't need to send. # Problem at this point, 0xdeadbeef is valid as a token but if that fails, it's also a valid password, so we # must try auth system #2 if __check_auth_token(client, api_handle, username, password) != 1: return __check_user_login(client, api_handle, user_enabled, username, password) return True else: # It's an older version of spacewalk, so just try the username/pass. # OR: We know for sure it's not a token because it's not lowercase hex. return __check_user_login(client, api_handle, user_enabled, username, password)
def get_module_name(category: str, field: str, fallback_module_name: Optional[str] = None) -> str: """ Get module name from configuration file (currently hardcoded ``/etc/cobbler/modules.conf``). :param category: Field category in configuration file. :param field: Field in configuration file :param fallback_module_name: Default value used if category/field is not found in configuration file :raises FileNotFoundError: If unable to find configuration file. :raises ValueError: If the category does not exist or the field is empty. :raises CX: If the field could not be read and no fallback_module_name was given. :returns: The name of the module. """ modules_conf_path = "/etc/cobbler/modules.conf" if not os.path.exists(modules_conf_path): raise FileNotFoundError("Configuration file at \"%s\" not found" % modules_conf_path) cp = ConfigParser() cp.read(modules_conf_path) # FIXME: We can't enabled this check since it is to strict atm. # if category not in MODULES_BY_CATEGORY: # raise ValueError("category must be one of: %s" % MODULES_BY_CATEGORY.keys()) if field.isspace(): raise ValueError( "field cannot be empty. Did you mean \"module\" maybe?") try: value = cp.get(category, field) except: if fallback_module_name is not None: value = fallback_module_name else: raise CX("Cannot find config file setting for: %s" % field) return value
def rsync_gen(self): """ Generate rsync modules of all repositories and distributions """ template_file = "/etc/cobbler/rsync.template" try: template = open(template_file, "r") except: raise CX("error reading template %s" % template_file) template_data = "" template_data = template.read() template.close() distros = [] for link in glob.glob(os.path.join(self.settings.webdir, 'links', '*')): distro = {} distro["path"] = os.path.realpath(link) distro["name"] = os.path.basename(link) distros.append(distro) repos = [ repo.name for repo in self.api.repos() if os.path.isdir( os.path.join(self.settings.webdir, "repo_mirror", repo.name)) ] metadata = { "date": time.asctime(time.gmtime()), "cobbler_server": self.settings.server, "distros": distros, "repos": repos, "webdir": self.settings.webdir } self.templar.render(template_data, metadata, "/etc/rsyncd.conf")
def get_items(self, collection_type): if collection_type == "distro": result = self._distros elif collection_type == "profile": result = self._profiles elif collection_type == "system": result = self._systems elif collection_type == "repo": result = self._repos elif collection_type == "image": result = self._images elif collection_type == "mgmtclass": result = self._mgmtclasses elif collection_type == "package": result = self._packages elif collection_type == "file": result = self._files elif collection_type == "settings": result = self._settings else: raise CX("internal error, collection name %s not supported" % collection_type) return result
def run(api, args, logger): """ The list of args should have one element: - 1: the name of the system or profile :param api: The api to resolve metadata with. :param args: This should be a list as described above. :param logger: This parameter is unused currently. :return: "0" on success. """ # FIXME: use the logger if len(args) < 3: raise CX("invalid invocation") name = args[1] settings = api.settings() anamon_enabled = str(settings.anamon_enabled) # Remove any files matched with the given glob pattern def unlink_files(globex): for f in glob.glob(globex): if os.path.isfile(f): try: os.unlink(f) except OSError: pass if str(anamon_enabled) in ["true", "1", "y", "yes"]: dirname = "/var/log/cobbler/anamon/%s" % name if os.path.isdir(dirname): unlink_files(os.path.join(dirname, "*")) # TODO - log somewhere that we cleared a systems anamon logs return 0
def __find_compare(self, from_search, from_obj): if isinstance(from_obj, basestring): # FIXME: fnmatch is only used for string to string comparisions # which should cover most major usage, if not, this deserves fixing if fnmatch.fnmatch(from_obj.lower(), from_search.lower()): return True else: return False else: if isinstance(from_search, basestring): if isinstance(from_obj, list): from_search = utils.input_string_or_list(from_search) for x in from_search: if x not in from_obj: return False return True if isinstance(from_obj, dict): (junk, from_search) = utils.input_string_or_hash( from_search, allow_multiples=True) for x in from_search.keys(): y = from_search[x] if x not in from_obj: return False if not (y == from_obj[x]): return False return True if isinstance(from_obj, bool): if from_search.lower() in ["true", "1", "y", "yes"]: inp = True else: inp = False if inp == from_obj: return True return False raise CX(_("find cannot compare type: %s") % type(from_obj))
def check_if_valid(self): if self.name is None: raise CX("name is required") if self.kernel is None: raise CX("Error with distro %s - kernel is required" % (self.name)) if self.initrd is None: raise CX("Error with distro %s - initrd is required" % (self.name)) if utils.file_is_remote(self.kernel): if not utils.remote_file_exists(self.kernel): raise CX("Error with distro %s - kernel '%s' not found" % (self.name, self.kernel)) elif not os.path.exists(self.kernel): raise CX("Error with distro %s - kernel not found" % (self.name)) if utils.file_is_remote(self.initrd): if not utils.remote_file_exists(self.initrd): raise CX("Error with distro %s - initrd path not found" % (self.name)) elif not os.path.exists(self.initrd): raise CX("Error with distro %s - initrd path not found" % (self.name))
def write_genders_file(config, profiles_genders, distros_genders, mgmtcls_genders): """ genders file is over-written when manage_genders is set in /var/lib/cobbler/settings. """ templar_inst = cobbler.templar.Templar(config) try: f2 = open(template_file, "r") except: raise CX(_("error reading template: %s") % template_file) template_data = "" template_data = f2.read() f2.close() metadata = { "date": time.asctime(time.gmtime()), "profiles_genders": profiles_genders, "distros_genders": distros_genders, "mgmtcls_genders": mgmtcls_genders } templar_inst.render(template_data, metadata, settings_file, None)
def run(self, adduser=None, addgroup=None, removeuser=None, removegroup=None): """ Automate setfacl commands """ ok = False if adduser: ok = True self.modacl(True, True, adduser) if addgroup: ok = True self.modacl(True, False, addgroup) if removeuser: ok = True self.modacl(False, True, removeuser) if removegroup: ok = True self.modacl(False, False, removegroup) if not ok: raise CX("no arguments specified, nothing to do")
def remove(self, name: str, with_delete: bool = True, with_sync: bool = True, with_triggers: bool = True, recursive: bool = False): """ Remove element named 'name' from the collection :raises CX: In case the name of the object was not given. """ name = name.lower() obj = self.find(name=name) if obj is None: raise CX("cannot delete an object that does not exist: %s" % name) if with_delete: if with_triggers: utils.run_triggers(self.api, obj, "/var/lib/cobbler/triggers/delete/system/pre/*", []) if with_sync: lite_sync = self.api.get_sync() lite_sync.remove_single_system(name) if obj.parent is not None and obj.name in obj.parent.children: obj.parent.children.remove(obj.name) # ToDo: Only serialize parent object, use: # Use self.collection_mgr.serialize_one_item(obj.parent) self.api.serialize() self.lock.acquire() try: del self.listing[name] finally: self.lock.release() self.collection_mgr.serialize_delete(self, obj) if with_delete: if with_triggers: utils.run_triggers(self.api, obj, "/var/lib/cobbler/triggers/delete/system/post/*", []) utils.run_triggers(self.api, obj, "/var/lib/cobbler/triggers/change/*", [])
def check_if_valid(self): """ Checks if the object is valid. This is the case if name, path, owner, group, and mode are set. Templates are only required for files if ``is_dir`` is true then template is not required. :raises CX: Raised in case a required argument is missing """ if not self.name: raise CX("name is required") if not self.path: raise CX("path is required") if not self.owner: raise CX("owner is required") if not self.group: raise CX("group is required") if not self.mode: raise CX("mode is required") if not self.is_dir and self.template == "": raise CX("Template is required when not a directory")
def __write_zone_files(self): """ Write out the forward and reverse zone files for all configured zones """ default_template_file = "/etc/cobbler/zone.template" cobbler_server = self.settings.server # this could be a config option too serial_filename = "/var/lib/cobbler/bind_serial" # need a counter for new bind format serial = time.strftime("%Y%m%d00") try: serialfd = open(serial_filename, "r") old_serial = serialfd.readline() # same date if serial[0:8] == old_serial[0:8]: if int(old_serial[8:10]) < 99: serial = "%s%.2i" % (serial[0:8], int(old_serial[8:10]) + 1) else: pass serialfd.close() except: pass serialfd = open(serial_filename, "w") serialfd.write(serial) serialfd.close() forward = self.__forward_zones() reverse = self.__reverse_zones() try: f2 = open(default_template_file, "r") except: raise CX( _("error reading template from file: %s") % default_template_file) default_template_data = "" default_template_data = f2.read() f2.close() zonefileprefix = self.settings.bind_chroot_path + self.zonefile_base for (zone, hosts) in list(forward.items()): metadata = { 'cobbler_server': cobbler_server, 'serial': serial, 'zonename': zone, 'zonetype': 'forward', 'cname_record': '', 'host_record': '' } if ":" in zone: long_zone = (self.__expand_IPv6(zone + '::1'))[:19] tokens = list(re.sub(':', '', long_zone)) tokens.reverse() zone_origin = '.'.join(tokens) + '.ip6.arpa.' else: zone_origin = '' # grab zone-specific template if it exists try: fd = open('/etc/cobbler/zone_templates/%s' % zone) # If this is an IPv6 zone, set the origin to the zone for this # template if zone_origin: template_data = r"\$ORIGIN " + zone_origin + "\n" + fd.read( ) else: template_data = fd.read() fd.close() except: # If this is an IPv6 zone, set the origin to the zone for this # template if zone_origin: template_data = r"\$ORIGIN " + zone_origin + "\n" + default_template_data else: template_data = default_template_data metadata['cname_record'] = self.__pretty_print_cname_records(hosts) metadata['host_record'] = self.__pretty_print_host_records(hosts) zonefilename = zonefileprefix + zone if self.logger is not None: self.logger.info("generating (forward) %s" % zonefilename) self.templar.render(template_data, metadata, zonefilename, None) for (zone, hosts) in list(reverse.items()): metadata = { 'cobbler_server': cobbler_server, 'serial': serial, 'zonename': zone, 'zonetype': 'reverse', 'cname_record': '', 'host_record': '' } # grab zone-specific template if it exists try: fd = open('/etc/cobbler/zone_templates/%s' % zone) template_data = fd.read() fd.close() except: template_data = default_template_data metadata['cname_record'] = self.__pretty_print_cname_records(hosts) metadata['host_record'] = self.__pretty_print_host_records( hosts, rectype='PTR') zonefilename = zonefileprefix + zone if self.logger is not None: self.logger.info("generating (reverse) %s" % zonefilename) self.templar.render(template_data, metadata, zonefilename, None)
def __write_secondary_conf(self): """ Write out the secondary.conf secondary config file from the template. """ settings_file = self.settings.bind_chroot_path + '/etc/secondary.conf' template_file = "/etc/cobbler/secondary.template" # forward_zones = self.settings.manage_forward_zones # reverse_zones = self.settings.manage_reverse_zones metadata = { 'forward_zones': list(self.__forward_zones().keys()), 'reverse_zones': [], 'zone_include': '' } for zone in metadata['forward_zones']: txt = """ zone "%(zone)s." { type slave; masters { %(master)s; }; file "data/%(zone)s"; }; """ % { 'zone': zone, 'master': self.settings.bind_master } metadata['zone_include'] = metadata['zone_include'] + txt for zone in list(self.__reverse_zones().keys()): # IPv6 zones are : delimited if ":" in zone: # if IPv6, assume xxxx:xxxx:xxxx:xxxx for the zone # 0123456789012345678 long_zone = (self.__expand_IPv6(zone + '::1'))[:19] tokens = list(re.sub(':', '', long_zone)) tokens.reverse() arpa = '.'.join(tokens) + '.ip6.arpa' else: # IPv4 zones split by '.' tokens = zone.split('.') tokens.reverse() arpa = '.'.join(tokens) + '.in-addr.arpa' # metadata['reverse_zones'].append((zone, arpa)) txt = """ zone "%(arpa)s." { type slave; masters { %(master)s; }; file "data/%(zone)s"; }; """ % { 'arpa': arpa, 'zone': zone, 'master': self.settings.bind_master } metadata['zone_include'] = metadata['zone_include'] + txt metadata['bind_master'] = self.settings.bind_master try: f2 = open(template_file, "r") except: raise CX(_("error reading template from file: %s") % template_file) template_data = "" template_data = f2.read() f2.close() if self.logger is not None: self.logger.info("generating %s" % settings_file) self.templar.render(template_data, metadata, settings_file, None)
def run(api, args, logger): # FIXME: make everything use the logger settings = api.settings() # go no further if this feature is turned off if not str(settings.build_reporting_enabled).lower() in [ "1", "yes", "y", "true" ]: return 0 objtype = args[0] # "target" or "profile" name = args[1] # name of target or profile boot_ip = args[2] # ip or "?" if objtype == "system": target = api.find_system(name) else: target = api.find_profile(name) # collapse the object down to a rendered datastructure target = utils.blender(api, False, target) if target == {}: raise CX("failure looking up target") to_addr = settings.build_reporting_email if to_addr == "": return 0 # add the ability to specify an MTA for servers that don't run their own smtp_server = settings.build_reporting_smtp_server if smtp_server == "": smtp_server = "localhost" # use a custom from address or fall back to a reasonable default from_addr = settings.build_reporting_sender if from_addr == "": from_addr = "cobbler@%s" % settings.server subject = settings.build_reporting_subject if subject == "": subject = '[Cobbler] install complete ' to_addr = ", ".join(to_addr) metadata = { "from_addr": from_addr, "to_addr": to_addr, "subject": subject, "boot_ip": boot_ip } metadata.update(target) input_template = open("/etc/cobbler/reporting/build_report_email.template") input_data = input_template.read() input_template.close() message = templar.Templar().render(input_data, metadata, None) # for debug, call # print message # Send the mail # FIXME: on error, return non-zero server_handle = smtplib.SMTP(smtp_server) server_handle.sendmail(from_addr, to_addr, message) server_handle.quit() return 0
def run(api, args): """ This method executes the trigger, meaning in this case that it updates the dns configuration. :param api: The api to read metadata from. :param args: Metadata to log. :return: "0" on success or a skipped task. If the task failed or problems occurred then an exception is raised. """ global logf action = None if __name__ == "cobbler.modules.nsupdate_add_system_post": action = "replace" elif __name__ == "cobbler.modules.nsupdate_delete_system_pre": action = "delete" else: return 0 settings = api.settings() if not settings.nsupdate_enabled: return 0 # read our settings if str(settings.nsupdate_log) is not None: logf = open(str(settings.nsupdate_log), "a+") nslog(">> starting %s %s\n" % (__name__, args)) if str(settings.nsupdate_tsig_key) is not None: keyring = dns.tsigkeyring.from_text({ str(settings.nsupdate_tsig_key[0]): str(settings.nsupdate_tsig_key[1]) }) else: keyring = None if str(settings.nsupdate_tsig_algorithm) is not None: keyring_algo = str(settings.nsupdate_tsig_algorithm) else: keyring_algo = "HMAC-MD5.SIG-ALG.REG.INT" # nslog( " algo %s, key %s : %s \n" % (keyring_algo,str(settings.nsupdate_tsig_key[0]), # str(settings.nsupdate_tsig_key[1])) ) # get information about this system system = api.find_system(args[0]) # process all interfaces and perform dynamic update for those with --dns-name for (name, interface) in system.interfaces.items(): host = interface.dns_name host_ip = interface.ip_address if not system.is_management_supported(cidr_ok=False): continue if not host: continue if host.find(".") == -1: continue domain = ".".join(host.split(".")[1:]) # get domain from host name host = host.split(".")[0] # strip domain nslog("processing interface %s : %s\n" % (name, interface)) nslog("lookup for '%s' domain master nameserver... " % domain) # get master nameserver ip address answers = dns.resolver.query(domain + '.', dns.rdatatype.SOA) soa_mname = answers[0].mname soa_mname_ip = None for rrset in answers.response.additional: if rrset.name == soa_mname: soa_mname_ip = str(rrset.items[0].address) if soa_mname_ip is None: ip = dns.resolver.query(soa_mname, "A") for answer in ip: soa_mname_ip = answer.to_text() nslog("%s [%s]\n" % (soa_mname, soa_mname_ip)) nslog("%s dns record for %s.%s [%s] .. " % (action, host, domain, host_ip)) # try to update zone with new record update = dns.update.Update(domain + '.', keyring=keyring, keyalgorithm=keyring_algo) if action == "replace": update.replace(host, 3600, dns.rdatatype.A, host_ip) update.replace(host, 3600, dns.rdatatype.TXT, '"cobbler (date: %s)"' % (time.strftime("%c"))) else: update.delete(host, dns.rdatatype.A, host_ip) update.delete(host, dns.rdatatype.TXT) try: response = dns.query.tcp(update, soa_mname_ip) rcode_txt = dns.rcode.to_text(response.rcode()) except dns.tsig.PeerBadKey: nslog("failed (refused key)\n>> done\n") logf.close() raise CX("nsupdate failed, server '%s' refusing our key" % soa_mname) nslog('response code: %s\n' % rcode_txt) # notice user about update failure if response.rcode() != dns.rcode.NOERROR: nslog('>> done\n') logf.close() raise CX("nsupdate failed (response: %s, name: %s.%s, ip %s, name server %s)" % (rcode_txt, host, domain, host_ip, soa_mname)) nslog('>> done\n') logf.close() return 0