def validate_autoinstall_file(self, obj, is_profile): """ Validate automatic installation file used by a system/profile @param Item obj system/profile @param bool is_profile if obj is a profile @return [bool, int, list] list with validation result, errors type and list of errors """ last_errors = [] blended = utils.blender(self.collection_mgr.api, False, obj) # get automatic installation template autoinstall = blended["autoinstall"] if autoinstall is None or autoinstall == "": self.logger.info("%s has no automatic installation template set, skipping" % obj.name) return [True, None, None] # generate automatic installation file os_version = blended["os_version"] self.logger.info("----------------------------") self.logger.debug("osversion: %s" % os_version) if is_profile: self.generate_autoinstall(profile=obj) else: self.generate_autoinstall(system=obj) last_errors = self.autoinstallgen.get_last_errors() if len(last_errors) > 0: return [False, TEMPLATING_ERROR, last_errors]
def _generate_netboot_system(self, system, cfglines: List[str], exclude_dns: bool): """ Generates the ISOLINUX cfg configuration for any systems included in the image. :param system: The system which the configuration should be generated for. :param cfglines: The already existing lines of the configuration. :param exclude_dns: If DNS configuration should be excluded or not. """ self.logger.info("processing system: %s", system.name) profile = system.get_conceptual_parent() dist = profile.get_conceptual_parent() distname = self.make_shorter(dist.name) self.copy_boot_files(dist, self.isolinuxdir, distname) cfglines.append("") cfglines.append("LABEL %s" % system.name) cfglines.append(" MENU LABEL %s" % system.name) cfglines.append(" kernel %s.krn" % distname) data = utils.blender(self.api, False, system) if not re.match(r"[a-z]+://.*", data["autoinstall"]): data[ "autoinstall"] = "http://%s:%s/cblr/svc/op/autoinstall/system/%s" % ( data["server"], data["http_port"], system.name, ) append_builder = AppendLineBuilder(distro_name=distname, data=data) append_line = append_builder.generate_system(dist, system, exclude_dns) cfglines.append(append_line)
def validate_autoinstall_file(self, obj, is_profile: bool) -> list: """ Validate automatic installation file used by a system/profile. :param obj: system/profile :param is_profile: if obj is a profile :returns: [bool, int, list] list with validation result, errors type and list of errors """ last_errors = [] blended = utils.blender(self.collection_mgr.api, False, obj) # get automatic installation template autoinstall = blended["autoinstall"] if autoinstall is None or autoinstall == "": self.logger.info( "%s has no automatic installation template set, skipping" % obj.name) return [True, 0, ()] # generate automatic installation file os_version = blended["os_version"] self.logger.info("----------------------------") self.logger.debug("osversion: %s" % os_version) if is_profile: self.generate_autoinstall(profile=obj) else: self.generate_autoinstall(system=obj) last_errors = self.autoinstallgen.get_last_errors() if len(last_errors) > 0: return [False, TEMPLATING_ERROR, last_errors] else: return [True, 0, ()]
def test_generate_profile(self, api, create_kernel_initrd, fk_kernel, fk_initrd, cleanup_items): # Arrange folder = create_kernel_initrd(fk_kernel, fk_initrd) kernel_path = os.path.join(folder, fk_kernel) initrd_path = os.path.join(folder, fk_initrd) test_distro = Distro(api) test_distro.name = "testdistro" test_distro.kernel = kernel_path test_distro.initrd = initrd_path api.add_distro(test_distro) test_profile = Profile(api) test_profile.name = "testprofile" test_profile.distro = test_distro.name api.add_profile(test_profile) blendered_data = utils.blender(api, False, test_profile) test_builder = AppendLineBuilder(test_distro.name, blendered_data) # Act result = test_builder.generate_profile("suse") # Assert # Very basic test yes but this is the expected result atm # TODO: Make tests more sophisticated assert ( result == " append initrd=testdistro.img install=http://127.0.0.1:80/cblr/links/testdistro autoyast=default.ks" )
def run(api, args, logger): settings = api.settings() for distro in api.distros(): # collapse the object down to a rendered datastructure # the second argument set to false means we don't collapse hashes/arrays into a flat string target = utils.blender(api, False, distro) # Create metadata for the templar function # Right now, just using img_path, but adding more # cobbler variables here would probably be good metadata = {} metadata["img_path"] = os.path.join("/tftpboot/images", distro.name) # Create the templar instance templater = templar.Templar() # Loop through the hash of fetchable files, # executing a cp for each one for file in target["fetchable_files"].keys(): file_dst = templater.render(file, metadata, None) try: shutil.copyfile(target["fetchable_files"][file], file_dst) api.log( "copied file %s to %s for %s" % (target["fetchable_files"][file], file_dst, distro.name)) except: logger.error( "failed to copy file %s to %s for %s" % (target["fetchable_files"][file], file_dst, distro.name)) return 1 return 0
def run(api,args,logger): settings = api.settings() for distro in api.distros(): # collapse the object down to a rendered datastructure # the second argument set to false means we don't collapse hashes/arrays into a flat string target = utils.blender(api, False, distro) # Create metadata for the templar function # Right now, just using img_path, but adding more # cobbler variables here would probably be good metadata = {} metadata["img_path"] = os.path.join("/tftpboot/images",distro.name) # Create the templar instance templater = templar.Templar() # Loop through the hash of fetchable files, # executing a cp for each one for file in target["fetchable_files"].keys(): file_dst = templater.render(file,metadata,None) try: shutil.copyfile(target["fetchable_files"][file], file_dst) api.log("copied file %s to %s for %s" % (target["fetchable_files"][file],file_dst,distro.name)) except: logger.error("failed to copy file %s to %s for %s" % (target["fetchable_files"][file],file_dst,distro.name)) return 1 return 0
def _generate_netboot_profile(self, profile, cfglines: List[str]): """ Generates the ISOLINUX cfg configuration for any profiles included in the image. :param profile: The profile which the configuration should be generated for. :param cfglines: The already existing lines of the configuration. """ self.logger.info('Processing profile: "%s"', profile.name) dist = profile.get_conceptual_parent() distname = self.make_shorter(dist.name) self.copy_boot_files(dist, self.isolinuxdir, distname) cfglines.append("") cfglines.append("LABEL %s" % profile.name) cfglines.append(" MENU LABEL %s" % profile.name) cfglines.append(" kernel %s.krn" % distname) data = utils.blender(self.api, False, profile) # SUSE is not using 'text'. Instead 'textmode' is used as kernel option. if dist is not None: utils.kopts_overwrite(data["kernel_options"], self.api.settings().server, dist.breed) if not re.match(r"[a-z]+://.*", data["autoinstall"]): data[ "autoinstall"] = "http://%s:%s/cblr/svc/op/autoinstall/profile/%s" % ( data["server"], data["http_port"], profile.name, ) append_builder = AppendLineBuilder(distro_name=distname, data=data) append_line = append_builder.generate_profile(dist.breed) cfglines.append(append_line)
def generate_autoinstall(self, profile=None, system=None) -> str: """ This is an internal method for generating an autoinstall config/script. Please use the ``generate_autoinstall_for_*`` methods. If you insist on using this mehtod please only supply a profile or a system, not both. :param profile: The profile to use for generating the autoinstall config/script. :param system: The system to use for generating the autoinstall config/script. If both arguments are given, this wins. :return: The autoinstall script or configuration file as a string. """ obj = system obj_type = "system" if system is None: obj = profile obj_type = "profile" meta = utils.blender(self.api, False, obj) autoinstall_rel_path = meta["autoinstall"] if not autoinstall_rel_path: return "# automatic installation file value missing or invalid at %s %s" % (obj_type, obj.name) # get parent distro distro = profile.get_conceptual_parent() if system is not None: distro = system.get_conceptual_parent().get_conceptual_parent() # make autoinstall_meta metavariable available at top level autoinstall_meta = meta["autoinstall_meta"] del meta["autoinstall_meta"] meta.update(autoinstall_meta) # add package repositories metadata to autoinstall metavariables if distro.breed == "redhat": meta["yum_repo_stanza"] = self.generate_repo_stanza(obj, (system is None)) meta["yum_config_stanza"] = self.generate_config_stanza(obj, (system is None)) # FIXME: implement something similar to zypper (SUSE based distros) and apt (Debian based distros) meta["kernel_options"] = utils.dict_to_string(meta["kernel_options"]) if "kernel_options_post" in meta: meta["kernel_options_post"] = utils.dict_to_string(meta["kernel_options_post"]) # add install_source_directory metavariable to autoinstall metavariables if distro is based on Debian if distro.breed in ["debian", "ubuntu"] and "tree" in meta: urlparts = urllib.parse.urlsplit(meta["tree"]) meta["install_source_directory"] = urlparts[2] try: autoinstall_path = "%s/%s" % (self.settings.autoinstall_templates_dir, autoinstall_rel_path) raw_data = utils.read_file_contents(autoinstall_path) data = self.templar.render(raw_data, meta, None) return data except FileNotFoundError: error_msg = "automatic installation file %s not found at %s" \ % (meta["autoinstall"], self.settings.autoinstall_templates_dir) self.api.logger.warning(error_msg) return "# %s" % error_msg
def generate_repo_stanza(self, obj, is_profile=True): """ Automatically attaches yum repos to profiles/systems in automatic installation files (kickstart files) that contain the magic $yum_repo_stanza variable. This includes repo objects as well as the yum repos that are part of split tree installs, whose data is stored with the distro (example: RHEL5 imports) """ buf = "" blended = utils.blender(self.api, False, obj) repos = blended["repos"] # keep track of URLs and be sure to not include any duplicates included = {} for repo in repos: # see if this is a source_repo or not repo_obj = self.api.find_repo(repo) if repo_obj is not None: yumopts = '' for opt in repo_obj.yumopts: # filter invalid values to the repo statement in automatic # installation files if not opt.lower() in validate.AUTOINSTALL_REPO_BLACKLIST: yumopts = yumopts + " %s=%s" % (opt, repo_obj.yumopts[opt]) if 'enabled' not in repo_obj.yumopts or repo_obj.yumopts['enabled'] == '1': if repo_obj.mirror_locally: baseurl = "http://%s/cobbler/repo_mirror/%s" % (blended["http_server"], repo_obj.name) if baseurl not in included: buf = buf + "repo --name=%s --baseurl=%s\n" % (repo_obj.name, baseurl) included[baseurl] = 1 else: if repo_obj.mirror not in included: buf = buf + "repo --name=%s --baseurl=%s %s\n" % (repo_obj.name, repo_obj.mirror, yumopts) included[repo_obj.mirror] = 1 else: # FIXME: what to do if we can't find the repo object that is listed? # this should be a warning at another point, probably not here # so we'll just not list it so the automatic installation file # will still work as nothing will be here to read the output noise. # Logging might be useful. pass if is_profile: distro = obj.get_conceptual_parent() else: distro = obj.get_conceptual_parent().get_conceptual_parent() source_repos = distro.source_repos count = 0 for x in source_repos: count = count + 1 if not x[1] in included: buf = buf + "repo --name=source-%s --baseurl=%s\n" % (count, x[1]) included[x[1]] = 1 return buf
def generate_autoinstall(self, profile=None, system=None): obj = system obj_type = "system" if system is None: obj = profile obj_type = "profile" meta = utils.blender(self.api, False, obj) autoinstall_rel_path = meta["autoinstall"] if not autoinstall_rel_path: return "# automatic installation file value missing or invalid at %s %s" % ( obj_type, obj.name) # get parent distro distro = profile.get_conceptual_parent() if system is not None: distro = system.get_conceptual_parent().get_conceptual_parent() # make autoinstall_meta metavariable available at top level autoinstall_meta = meta["autoinstall_meta"] del meta["autoinstall_meta"] meta.update(autoinstall_meta) # add package repositories metadata to autoinstall metavariables if distro.breed == "redhat": meta["yum_repo_stanza"] = self.generate_repo_stanza( obj, (system is None)) meta["yum_config_stanza"] = self.generate_config_stanza( obj, (system is None)) # FIXME: implement something similar to zypper (SUSE based distros) and apt # (Debian based distros) meta["kernel_options"] = utils.dict_to_string(meta["kernel_options"]) if "kernel_options_post" in meta: meta["kernel_options_post"] = utils.dict_to_string( meta["kernel_options_post"]) # add install_source_directory metavariable to autoinstall metavariables # if distro is based on Debian if distro.breed in ["debian", "ubuntu"] and "tree" in meta: urlparts = urllib.parse.urlsplit(meta["tree"]) meta["install_source_directory"] = urlparts[2] try: autoinstall_path = "%s/%s" % ( self.settings.autoinstall_templates_dir, autoinstall_rel_path) raw_data = utils.read_file_contents(autoinstall_path, self.api.logger) data = self.templar.render(raw_data, meta, None, obj) return data except FileNotFoundException: error_msg = "automatic installation file %s not found at %s" % ( meta["autoinstall"], self.settings.autoinstall_templates_dir) self.api.logger.warning(error_msg) return "# %s" % error_msg
def run(api, args, logger): """ This method runs the trigger, meaning in this case that old puppet certs are automatically removed via puppetca. The list of args should have two elements: - 0: system or profile - 1: the name of the system or profile :param api: The api to resolve external information with. :param args: Already described above. :param logger: The logger to audit the action with. :return: "0" on success. If unsuccessful this raises an exception. """ objtype = args[0] name = args[1] if objtype != "system": return 0 settings = api.settings() if not str( settings.puppet_auto_setup).lower() in ["1", "yes", "y", "true"]: return 0 if not str(settings.remove_old_puppet_certs_automatically).lower() in [ "1", "yes", "y", "true" ]: return 0 system = api.find_system(name) system = utils.blender(api, False, system) hostname = system["hostname"] if not re.match(r'[\w-]+\..+', hostname): search_domains = system['name_servers_search'] if search_domains: hostname += '.' + search_domains[0] if not re.match(r'[\w-]+\..+', hostname): default_search_domains = system['default_name_servers_search'] if default_search_domains: hostname += '.' + default_search_domains[0] puppetca_path = settings.puppetca_path cmd = [puppetca_path, 'cert', 'clean', hostname] rc = 0 try: rc = utils.subprocess_call(logger, cmd, shell=False) except: if logger is not None: logger.warning("failed to execute %s" % puppetca_path) if rc != 0: if logger is not None: logger.warning("puppet cert removal for %s failed" % name) return 0
def createrepo_walker(self, repo, dirname, fnames): """ Used to run createrepo on a copied Yum mirror. :param repo: The repository object to run for. :param dirname: The directory to run in. :param fnames: Not known what this is for. """ if os.path.exists(dirname) or repo['breed'] == 'rsync': utils.remove_yum_olddata(dirname) # add any repo metadata we can use mdoptions = [] if os.path.isfile("%s/.origin/repodata/repomd.xml" % (dirname)): if HAS_LIBREPO: rd = self.librepo_getinfo("%s/.origin" % (dirname)) elif HAS_YUM: rmd = yum.repoMDObject.RepoMD( '', "%s/.origin/repodata/repomd.xml" % (dirname)) rd = rmd.repoData else: utils.die(self.logger, "yum/librepo is required to use this feature") if "group" in rd: if HAS_LIBREPO: groupmdfile = rd['group']['location_href'] else: groupmdfile = rmd.getData("group").location[1] mdoptions.append("-g %s" % groupmdfile) if "prestodelta" in rd: # need createrepo >= 0.9.7 to add deltas if utils.get_family() in ("redhat", "suse"): cmd = "/usr/bin/rpmquery --queryformat=%{VERSION} createrepo" createrepo_ver = utils.subprocess_get(self.logger, cmd) if not createrepo_ver[0:1].isdigit(): cmd = "/usr/bin/rpmquery --queryformat=%{VERSION} createrepo_c" createrepo_ver = utils.subprocess_get( self.logger, cmd) if utils.compare_versions_gt(createrepo_ver, "0.9.7"): mdoptions.append("--deltas") else: self.logger.error( "this repo has presto metadata; you must upgrade createrepo to >= 0.9.7 first and then need to resync the repo through Cobbler." ) blended = utils.blender(self.api, False, repo) flags = blended.get("createrepo_flags", "(ERROR: FLAGS)") try: cmd = "createrepo %s %s %s" % (" ".join(mdoptions), flags, pipes.quote(dirname)) utils.subprocess_call(self.logger, cmd) except: utils.log_exc(self.logger) self.logger.error("createrepo failed.") del fnames[:] # we're in the right place
def generate_autoinstall(self, profile=None, system=None): obj = system obj_type = "system" if system is None: obj = profile obj_type = "profile" meta = utils.blender(self.api, False, obj) autoinstall_rel_path = meta["autoinstall"] if not autoinstall_rel_path: return "# automatic installation file value missing or invalid at %s %s" % (obj_type, obj.name) # get parent distro distro = profile.get_conceptual_parent() if system is not None: distro = system.get_conceptual_parent().get_conceptual_parent() # make autoinstall_meta metavariable available at top level autoinstall_meta = meta["autoinstall_meta"] del meta["autoinstall_meta"] meta.update(autoinstall_meta) # add package repositories metadata to autoinstall metavariables if distro.breed == "redhat": meta["yum_repo_stanza"] = self.generate_repo_stanza(obj, (system is None)) meta["yum_config_stanza"] = self.generate_config_stanza(obj, (system is None)) # FIXME: implement something similar to zypper (SUSE based distros) and apt # (Debian based distros) meta["kernel_options"] = utils.dict_to_string(meta["kernel_options"]) if "kernel_options_post" in meta: meta["kernel_options_post"] = utils.dict_to_string(meta["kernel_options_post"]) # add install_source_directory metavariable to autoinstall metavariables # if distro is based on Debian if distro.breed in ["debian", "ubuntu"] and "tree" in meta: urlparts = urlparse.urlsplit(meta["tree"]) meta["install_source_directory"] = urlparts[2] try: autoinstall_path = "%s/%s" % (self.settings.autoinstall_templates_dir, autoinstall_rel_path) raw_data = utils.read_file_contents(autoinstall_path, self.api.logger) data = self.templar.render(raw_data, meta, None, obj) if distro.breed == "suse": # AutoYaST profile data = self.generate_autoyast(profile, system, data) return data except FileNotFoundException: error_msg = "automatic installation file %s not found at %s" % (meta["autoinstall"], self.settings.autoinstall_templates_dir) self.api.logger.warning(error_msg) return "# %s" % error_msg
def test_blender(): # Arrange test_api = CobblerAPI() root_item = Distro(test_api) # Act result = utils.blender(test_api, False, root_item) # Assert assert len(result) == 147 assert "server" in result assert "os_version" in result
def dump_vars(self, formatted_output: bool = True): """ Dump all variables. :param formatted_output: Whether to format the output or not. :return: The raw or formatted data. """ raw = utils.blender(self.api, False, self) if formatted_output: return pprint.pformat(raw) else: return raw
def test_blender(): # Arrange # TODO: Create some objects api = CobblerAPI() root_item = None expected = {} # Act result = utils.blender(api, False, root_item) # Assert assert expected == result
def generate_bootcfg(self, what, name): if what.lower() not in ("profile", "system"): return "# bootcfg is only valid for profiles and systems" distro = None if what == "profile": obj = self.api.find_profile(name=name) distro = obj.get_conceptual_parent() else: obj = self.api.find_system(name=name) distro = obj.get_conceptual_parent().get_conceptual_parent() # For multi-arch distros, the distro name in distro_mirror # may not contain the arch string, so we need to figure out # the path based on where the kernel is stored. We do this # because some distros base future downloads on the initial # URL passed in, so all of the files need to be at this location # (which is why we can't use the images link, which just contains # the kernel and initrd). distro_mirror_name = string.join(distro.kernel.split('/')[-2:-1], '') blended = utils.blender(self.api, False, obj) autoinstall_meta = blended.get("autoinstall_meta", {}) try: del blended["autoinstall_meta"] except: pass blended.update(autoinstall_meta) # make available at top level blended['distro'] = distro_mirror_name # FIXME: img_path should probably be moved up into the # blender function to ensure they're consistently # available to templates across the board if obj.enable_gpxe: blended['img_path'] = 'http://%s:%s/cobbler/links/%s' % ( self.settings.server, self.settings.http_port, distro.name) else: blended['img_path'] = os.path.join("/images", distro.name) template = os.path.join( self.settings.boot_loader_conf_template_dir, "bootcfg_%s_%s.template" % (what.lower(), distro.os_version)) if not os.path.exists(template): return "# boot.cfg template not found for the %s named %s (filename=%s)" % ( what, name, template) template_fh = open(template) template_data = template_fh.read() template_fh.close() return self.templar.render(template_data, blended, None)
def get_yum_config(self, obj, is_profile): """ Return one large yum repo config blob suitable for use by any target system that requests it. :param obj: The object to generate the yumconfig for. :param is_profile: If the requested object is a profile. (Parameter not used currently) :type is_profile: bool :return: The generated yumconfig or the errors. :rtype: str """ totalbuf = "" blended = utils.blender(self.api, False, obj) input_files = [] # Tack on all the install source repos IF there is more than one. This is basically to support things like # RHEL5 split trees if there is only one, then there is no need to do this. included = {} for r in blended["source_repos"]: filename = self.settings.webdir + "/" + "/".join( r[0].split("/")[4:]) if filename not in included: input_files.append(filename) included[filename] = 1 for repo in blended["repos"]: path = os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo") if path not in included: input_files.append(path) included[path] = 1 for infile in input_files: try: infile_h = open(infile) except: # File does not exist and the user needs to run reposync before we will use this, Cobbler check will # mention this problem totalbuf += "\n# error: could not read repo source: %s\n\n" % infile continue infile_data = infile_h.read() infile_h.close() outfile = None # disk output only totalbuf += self.templar.render(infile_data, blended, outfile, None) totalbuf += "\n\n" return totalbuf
def dump_vars(self, data, format=True): """ Dump all variables. :param data: Unused parameter in this method. :param format: Whether to format the output or not. :return: The raw or formatted data. """ raw = utils.blender(self.collection_mgr.api, False, self) if format: return pprint.pformat(raw) else: return raw
def run(api, args, logger): """ The obligatory Cobbler modules hook. :param api: The api to resolve all information with. :param args: This is an array with two items. The first may be ``system`` or ``profile`` and the second is the name of this system or profile. :param logger: The logger to audit all actions with. :return: ``0`` or nothing. """ objtype = args[0] name = args[1] # ip = args[2] # ip or "?" if objtype != "system": return 0 settings = api.settings() if not str( settings.puppet_auto_setup).lower() in ["1", "yes", "y", "true"]: return 0 if not str(settings.sign_puppet_certs_automatically).lower() in [ "1", "yes", "y", "true" ]: return 0 system = api.find_system(name) system = utils.blender(api, False, system) hostname = system["hostname"] if not re.match(r'[\w-]+\..+', hostname): search_domains = system['name_servers_search'] if search_domains: hostname += '.' + search_domains[0] puppetca_path = settings.puppetca_path cmd = [puppetca_path, 'cert', 'sign', hostname] rc = 0 try: rc = utils.subprocess_call(logger, cmd, shell=False) except: if logger is not None: logger.warning("failed to execute %s" % puppetca_path) if rc != 0: if logger is not None: logger.warning("signing of puppet cert for %s failed" % name) return 0
def createrepo_walker(self, repo, dirname: str, fnames): """ Used to run createrepo on a copied Yum mirror. :param repo: The repository object to run for. :param dirname: The directory to run in. :param fnames: Not known what this is for. """ if os.path.exists(dirname) or repo.breed == RepoBreeds.RSYNC: utils.remove_yum_olddata(dirname) # add any repo metadata we can use mdoptions = [] origin_path = os.path.join(dirname, ".origin") repodata_path = os.path.join(origin_path, "repodata") if os.path.isfile(os.path.join(repodata_path, "repomd.xml")): rd = self.librepo_getinfo(origin_path) if "group" in rd: groupmdfile = rd['group']['location_href'] mdoptions.append("-g %s" % os.path.join(origin_path, groupmdfile)) if "prestodelta" in rd: # need createrepo >= 0.9.7 to add deltas if utils.get_family() in ("redhat", "suse"): cmd = "/usr/bin/rpmquery --queryformat=%{VERSION} createrepo" createrepo_ver = utils.subprocess_get(cmd) if not createrepo_ver[0:1].isdigit(): cmd = "/usr/bin/rpmquery --queryformat=%{VERSION} createrepo_c" createrepo_ver = utils.subprocess_get(cmd) if utils.compare_versions_gt(createrepo_ver, "0.9.7"): mdoptions.append("--deltas") else: self.logger.error( "this repo has presto metadata; you must upgrade createrepo to >= 0.9.7 " "first and then need to resync the repo through Cobbler." ) blended = utils.blender(self.api, False, repo) flags = blended.get("createrepo_flags", "(ERROR: FLAGS)") try: cmd = "createrepo %s %s %s" % (" ".join(mdoptions), flags, pipes.quote(dirname)) utils.subprocess_call(cmd) except: utils.log_exc() self.logger.error("createrepo failed.") del fnames[:] # we're in the right place
def generate_kickstart(self, profile=None, system=None): obj = system if system is None: obj = profile meta = utils.blender(self.api, False, obj) kickstart_path = meta["kickstart"] if not kickstart_path: return "# kickstart is missing or invalid: %s" % meta["kickstart"] ksmeta = meta["ks_meta"] del meta["ks_meta"] meta.update(ksmeta) # make available at top level meta["yum_repo_stanza"] = self.generate_repo_stanza( obj, (system is None)) meta["yum_config_stanza"] = self.generate_config_stanza( obj, (system is None)) meta["kernel_options"] = utils.dict_to_string(meta["kernel_options"]) # add extra variables for other distro types if "tree" in meta: urlparts = urlparse.urlsplit(meta["tree"]) meta["install_source_directory"] = urlparts[2] try: raw_data = utils.read_file_contents(kickstart_path, self.api.logger) if raw_data is None: return "# kickstart is sourced externally: %s" % meta[ "kickstart"] distro = profile.get_conceptual_parent() if system is not None: distro = system.get_conceptual_parent().get_conceptual_parent() data = self.templar.render(raw_data, meta, None, obj) if distro.breed == "suse": # AutoYaST profile data = self.generate_autoyast(profile, system, data) return data except FileNotFoundException: self.api.logger.warning("kickstart not found: %s" % meta["kickstart"]) return "# kickstart not found: %s" % meta["kickstart"]
def generate_config_stanza(self, obj, is_profile=True): """ Add in automatic to configure /etc/yum.repos.d on the remote system if the kickstart file contains the magic $yum_config_stanza. """ if not self.settings.yum_post_install_mirror: return "" blended = utils.blender(self.api, False, obj) if is_profile: url = "http://%s/cblr/svc/op/yum/profile/%s" % (blended["http_server"], obj.name) else: url = "http://%s/cblr/svc/op/yum/system/%s" % (blended["http_server"], obj.name) return "wget \"%s\" --output-document=/etc/yum.repos.d/cobbler-config.repo\n" % (url)
def run(api, args, logger): objtype = args[0] # "system" or "profile" name = args[1] # name of system or profile # ip = args[2] # ip or "?" if objtype != "system": return 0 settings = api.settings() if not str( settings.puppet_auto_setup).lower() in ["1", "yes", "y", "true"]: return 0 if not str(settings.remove_old_puppet_certs_automatically).lower() in [ "1", "yes", "y", "true" ]: return 0 system = api.find_system(name) system = utils.blender(api, False, system) hostname = system["hostname"] if not re.match(r'[\w-]+\..+', hostname): search_domains = system['name_servers_search'] if search_domains: hostname += '.' + search_domains[0] if not re.match(r'[\w-]+\..+', hostname): default_search_domains = system['default_name_servers_search'] if default_search_domains: hostname += '.' + default_search_domains[0] puppetca_path = settings.puppetca_path cmd = [puppetca_path, 'cert', 'clean', hostname] rc = 0 try: rc = utils.subprocess_call(logger, cmd, shell=False) except: if logger is not None: logger.warning("failed to execute %s" % puppetca_path) if rc != 0: if logger is not None: logger.warning("puppet cert removal for %s failed" % name) return 0
def generate_config_stanza(self, obj, is_profile=True): """ Add in automatic to configure /etc/yum.repos.d on the remote system if the automatic installation file (kickstart file) contains the magic $yum_config_stanza. """ if not self.settings.yum_post_install_mirror: return "" blended = utils.blender(self.api, False, obj) if is_profile: url = "http://%s/cblr/svc/op/yum/profile/%s" % (blended["http_server"], obj.name) else: url = "http://%s/cblr/svc/op/yum/system/%s" % (blended["http_server"], obj.name) return "curl \"%s\" --output /etc/yum.repos.d/cobbler-config.repo\n" % (url)
def write_boot_files_distro(self, distro): # Collapse the object down to a rendered datastructure. # The second argument set to false means we don't collapse dicts/arrays into a flat string. target = utils.blender(self.api, False, distro) # Create metadata for the templar function. # Right now, just using local_img_path, but adding more Cobbler variables here would probably be good. metadata = {} metadata["local_img_path"] = os.path.join(self.bootloc, "images", distro.name) metadata["web_img_path"] = os.path.join(self.webdir, "distro_mirror", distro.name) # Create the templar instance. Used to template the target directory templater = templar.Templar(self.collection_mgr) # Loop through the dict of boot files, executing a cp for each one self.logger.info("processing boot_files for distro: %s" % distro.name) for boot_file in list(target["boot_files"].keys()): rendered_target_file = templater.render(boot_file, metadata, None) rendered_source_file = templater.render( target["boot_files"][boot_file], metadata, None) try: for file in glob.glob(rendered_source_file): if file == rendered_source_file: # this wasn't really a glob, so just copy it as is filedst = rendered_target_file else: # this was a glob, so figure out what the destination file path/name should be tgt_path, tgt_file = os.path.split(file) rnd_path, rnd_file = os.path.split( rendered_target_file) filedst = os.path.join(rnd_path, tgt_file) if not os.path.isdir(rnd_path): utils.mkdir(rnd_path) if not os.path.isfile(filedst): shutil.copyfile(file, filedst) self.collection_mgr.api.log("copied file %s to %s for %s" % (file, filedst, distro.name)) except: self.logger.error("failed to copy file %s to %s for %s", file, filedst, distro.name) return 0
def run(api, args) -> int: """ The obligatory Cobbler modules hook. :param api: The api to resolve all information with. :param args: This is an array with two items. The first must be ``system``, if the value is different we do an early and the second is the name of this system or profile. :return: ``0`` or nothing. """ objtype = args[0] name = args[1] if objtype != "system": return 0 settings = api.settings() if not settings.puppet_auto_setup: return 0 if not settings.sign_puppet_certs_automatically: return 0 system = api.find_system(name) system = utils.blender(api, False, system) hostname = system["hostname"] if not re.match(r"[\w-]+\..+", hostname): search_domains = system["name_servers_search"] if search_domains: hostname += "." + search_domains[0] puppetca_path = settings.puppetca_path cmd = [puppetca_path, "cert", "sign", hostname] rc = 0 try: rc = utils.subprocess_call(cmd, shell=False) except: logger.warning("failed to execute %s", puppetca_path) if rc != 0: logger.warning("signing of puppet cert for %s failed", name) return 0
def generate_autoinstall(self, profile=None, system=None): obj = system obj_type = "system" if system is None: obj = profile obj_type = "profile" meta = utils.blender(self.api, False, obj) autoinstall_rel_path = meta["autoinstall"] if not autoinstall_rel_path: return "# automatic installation file value missing or invalid at %s %s" % (obj_type, obj.name) autoinstall_meta = meta["autoinstall_meta"] del meta["autoinstall_meta"] meta.update(autoinstall_meta) # make available at top level meta["yum_repo_stanza"] = self.generate_repo_stanza(obj, (system is None)) meta["yum_config_stanza"] = self.generate_config_stanza(obj, (system is None)) meta["kernel_options"] = utils.dict_to_string(meta["kernel_options"]) # add extra variables for other distro types if "tree" in meta: urlparts = urlparse.urlsplit(meta["tree"]) meta["install_source_directory"] = urlparts[2] try: autoinstall_path = "%s/%s" % (self.settings.autoinstall_templates_dir, autoinstall_rel_path) raw_data = utils.read_file_contents(autoinstall_path, self.api.logger) distro = profile.get_conceptual_parent() if system is not None: distro = system.get_conceptual_parent().get_conceptual_parent() data = self.templar.render(raw_data, meta, None, obj) if distro.breed == "suse": # AutoYaST profile data = self.generate_autoyast(profile, system, data) return data except FileNotFoundException: error_msg = "automatic installation file %s not found at %s" % (meta["autoinstall"], self.settings.autoinstall_templates_dir) self.api.logger.warning(error_msg) return "# %s" % error_msg
def run(api, args, logger): objtype = args[0] # "system" or "profile" name = args[1] # name of system or profile # ip = args[2] # ip or "?" if objtype != "system": return 0 settings = api.settings() if not str(settings.puppet_auto_setup).lower() in ["1", "yes", "y", "true"]: return 0 if not str(settings.remove_old_puppet_certs_automatically).lower() in ["1", "yes", "y", "true"]: return 0 system = api.find_system(name) system = utils.blender(api, False, system) hostname = system["hostname"] if not re.match(r'[\w-]+\..+', hostname): search_domains = system['name_servers_search'] if search_domains: hostname += '.' + search_domains[0] if not re.match(r'[\w-]+\..+', hostname): default_search_domains = system['default_name_servers_search'] if default_search_domains: hostname += '.' + default_search_domains[0] puppetca_path = settings.puppetca_path cmd = [puppetca_path, 'cert', 'clean', hostname] rc = 0 try: rc = utils.subprocess_call(logger, cmd, shell=False) except: if logger is not None: logger.warning("failed to execute %s" % puppetca_path) if rc != 0: if logger is not None: logger.warning("puppet cert removal for %s failed" % name) return 0
def _generate_descendant( self, descendant, cfglines: List[str], distro, airgapped: bool, repo_names_to_copy: dict, ): """ Generate the ISOLINUX cfg configuration file for the descendant. :param descendant: The descendant to generate the config file for. Must be a profile or system object. :param cfglines: The content of the file which has already been generated. :param distro: The parent distro. :param airgapped: Whether the generated ISO should be bootable in an airgapped environment or not. :param repo_names_to_copy: The repository names to copy in the case of an airgapped environment. """ menu_indent = 0 if descendant.COLLECTION_TYPE == "system": menu_indent = 4 data = utils.blender(self.api, False, descendant) # SUSE is not using 'text'. Instead 'textmode' is used as kernel option. if distro is not None: utils.kopts_overwrite(data["kernel_options"], self.api.settings().server, distro.breed) cfglines.append("") cfglines.append("LABEL %s" % descendant.name) if menu_indent: cfglines.append(" MENU INDENT %d" % menu_indent) cfglines.append(" MENU LABEL %s" % descendant.name) cfglines.append(" kernel %s" % os.path.basename(distro.kernel)) cfglines.append( _generate_append_line_standalone(data, distro, descendant)) autoinstall_data = self._generate_autoinstall_data( descendant, distro, airgapped, data, repo_names_to_copy) autoinstall_name = os.path.join(self.isolinuxdir, "%s.cfg" % descendant.name) with open(autoinstall_name, "w+") as autoinstall_file: autoinstall_file.write(autoinstall_data)
def generate_kickstart(self, profile=None, system=None): obj = system if system is None: obj = profile meta = utils.blender(self.api, False, obj) kickstart_path = meta["kickstart"] if not kickstart_path: return "# kickstart is missing or invalid: %s" % meta["kickstart"] ksmeta = meta["ks_meta"] del meta["ks_meta"] meta.update(ksmeta) # make available at top level meta["yum_repo_stanza"] = self.generate_repo_stanza(obj, (system is None)) meta["yum_config_stanza"] = self.generate_config_stanza(obj, (system is None)) meta["kernel_options"] = utils.dict_to_string(meta["kernel_options"]) # add extra variables for other distro types if "tree" in meta: urlparts = urlparse.urlsplit(meta["tree"]) meta["install_source_directory"] = urlparts[2] try: raw_data = utils.read_file_contents(kickstart_path, self.api.logger) if raw_data is None: return "# kickstart is sourced externally: %s" % meta["kickstart"] distro = profile.get_conceptual_parent() if system is not None: distro = system.get_conceptual_parent().get_conceptual_parent() data = self.templar.render(raw_data, meta, None, obj) if distro.breed == "suse": # AutoYaST profile data = self.generate_autoyast(profile, system, data) return data except FileNotFoundException: self.api.logger.warning("kickstart not found: %s" % meta["kickstart"]) return "# kickstart not found: %s" % meta["kickstart"]
def generate_config_stanza(self, obj, is_profile: bool = True): """ Add in automatic to configure /etc/yum.repos.d on the remote system if the automatic installation file (template file) contains the magic $yum_config_stanza. :param obj: The profile or system to generate a generate a config stanza for. :param is_profile: If the object is a profile. If False it is assumed that the object is a system. :return: The curl command to execute to get the configuration for a system or profile. """ if not self.settings.yum_post_install_mirror: return "" blended = utils.blender(self.api, False, obj) if is_profile: url = "http://%s/cblr/svc/op/yum/profile/%s" % (blended["http_server"], obj.name) else: url = "http://%s/cblr/svc/op/yum/system/%s" % (blended["http_server"], obj.name) return "curl \"%s\" --output /etc/yum.repos.d/cobbler-config.repo\n" % (url)
def generate_script(self, what, objname, script_name): if what == "profile": obj = self.api.find_profile(name=objname) else: obj = self.api.find_system(name=objname) if not obj: return "# %s named %s not found" % (what, objname) distro = obj.get_conceptual_parent() while distro.get_conceptual_parent(): distro = distro.get_conceptual_parent() blended = utils.blender(self.api, False, obj) autoinstall_meta = blended.get("autoinstall_meta", {}) try: del blended["autoinstall_meta"] except: pass blended.update(autoinstall_meta) # make available at top level # FIXME: img_path should probably be moved up into the # blender function to ensure they're consistently # available to templates across the board if obj.enable_gpxe: blended['img_path'] = 'http://%s:%s/cobbler/links/%s' % ( self.settings.server, self.settings.http_port, distro.name) else: blended['img_path'] = os.path.join("/images", distro.name) template = os.path.normpath( os.path.join("/var/lib/cobbler/autoinstall_scripts", script_name)) if not os.path.exists(template): return "# script template %s not found" % script_name template_fh = open(template) template_data = template_fh.read() template_fh.close() return self.templar.render(template_data, blended, None, obj)
def write_boot_files_distro(self, distro): # collapse the object down to a rendered datastructure # the second argument set to false means we don't collapse # dicts/arrays into a flat string target = utils.blender(self.collection_mgr.api, False, distro) # Create metadata for the templar function # Right now, just using local_img_path, but adding more # cobbler variables here would probably be good metadata = {} metadata["local_img_path"] = os.path.join(utils.tftpboot_location(), "images", distro.name) # Create the templar instance. Used to template the target directory templater = templar.Templar(self.collection_mgr) # Loop through the dict of boot files, # executing a cp for each one self.logger.info("processing boot_files for distro: %s" % distro.name) for file in list(target["boot_files"].keys()): rendered_file = templater.render(file, metadata, None) try: for f in glob.glob(target["boot_files"][file]): if f == target["boot_files"][file]: # this wasn't really a glob, so just copy it as is filedst = rendered_file else: # this was a glob, so figure out what the destination # file path/name should be tgt_path, tgt_file = os.path.split(f) rnd_path, rnd_file = os.path.split(rendered_file) filedst = os.path.join(rnd_path, tgt_file) if not os.path.isfile(filedst): shutil.copyfile(f, filedst) self.collection_mgr.api.log("copied file %s to %s for %s" % (f, filedst, distro.name)) except: self.logger.error("failed to copy file %s to %s for %s" % (f, filedst, distro.name)) return 0
def write_configs(self): """ DHCP files are written when ``manage_dhcp`` is set in our settings. """ template_file = "/etc/cobbler/dhcp.template" blender_cache = {} try: f2 = open(template_file, "r") except Exception: raise CX("error reading template: %s" % template_file) template_data = "" template_data = f2.read() f2.close() # Use a simple counter for generating generic names where a hostname is not available. counter = 0 # We used to just loop through each system, but now we must loop through each network interface of each system. dhcp_tags = {"default": {}} yaboot = "/yaboot" # FIXME: ding should evolve into the new dhcp_tags dict ding = {} ignore_macs = [] for system in self.systems: if not system.is_management_supported(cidr_ok=False): continue profile = system.get_conceptual_parent() distro = profile.get_conceptual_parent() # if distro is None then the profile is really an image record for (name, system_interface) in list(system.interfaces.items()): # We make a copy because we may modify it before adding it to the dhcp_tags and we don't want to affect # the master copy. interface = copy.deepcopy(system_interface) if interface["if_gateway"]: interface["gateway"] = interface["if_gateway"] else: interface["gateway"] = system.gateway mac = interface["mac_address"] if interface["interface_type"] in ("bond_slave", "bridge_slave", "bonded_bridge_slave"): if interface["interface_master"] not in system.interfaces: # Can't write DHCP entry; master interface does not exist continue # We may have multiple bonded interfaces, so we need a composite index into ding. name_master = "%s-%s" % (system.name, interface["interface_master"]) if name_master not in ding: ding[name_master] = {interface["interface_master"]: []} if len(ding[name_master][interface["interface_master"]]) == 0: ding[name_master][interface["interface_master"]].append(mac) else: ignore_macs.append(mac) ip = system.interfaces[interface["interface_master"]]["ip_address"] netmask = system.interfaces[interface["interface_master"]]["netmask"] dhcp_tag = system.interfaces[interface["interface_master"]]["dhcp_tag"] host = system.interfaces[interface["interface_master"]]["dns_name"] if ip is None or ip == "": for (nam2, int2) in list(system.interfaces.items()): if nam2.startswith(interface["interface_master"] + ".") \ and int2["ip_address"] is not None \ and int2["ip_address"] != "": ip = int2["ip_address"] break interface["ip_address"] = ip interface["netmask"] = netmask else: ip = interface["ip_address"] netmask = interface["netmask"] dhcp_tag = interface["dhcp_tag"] host = interface["dns_name"] if distro is not None: interface["distro"] = distro.to_dict() if mac is None or mac == "": # can't write a DHCP entry for this system continue counter = counter + 1 # the label the entry after the hostname if possible if host is not None and host != "": if name != "eth0": interface["name"] = "%s-%s" % (host, name) else: interface["name"] = "%s" % (host) else: interface["name"] = "generic%d" % counter # add references to the system, profile, and distro # for use in the template if system.name in blender_cache: blended_system = blender_cache[system.name] else: blended_system = utils.blender(self.api, False, system) blender_cache[system.name] = blended_system interface["next_server"] = blended_system["next_server"] interface["filename"] = blended_system.get("filename") interface["netboot_enabled"] = blended_system["netboot_enabled"] interface["hostname"] = blended_system["hostname"] interface["owner"] = blended_system["name"] interface["enable_gpxe"] = blended_system["enable_gpxe"] interface["name_servers"] = blended_system["name_servers"] interface["mgmt_parameters"] = blended_system["mgmt_parameters"] # Explicitly declare filename for other (non x86) archs as in DHCP discover package mostly the # architecture cannot be differed due to missing bits... if distro is not None and not interface.get("filename"): if distro.arch == "ppc" or distro.arch == "ppc64": interface["filename"] = yaboot elif distro.arch == "ppc64le": interface["filename"] = "grub/grub.ppc64le" elif distro.arch == "aarch64": interface["filename"] = "grub/grubaa64.efi" if not self.settings.always_write_dhcp_entries: if not interface["netboot_enabled"] and interface['static']: continue if dhcp_tag == "": dhcp_tag = blended_system.get("dhcp_tag", "") if dhcp_tag == "": dhcp_tag = "default" if dhcp_tag not in dhcp_tags: dhcp_tags[dhcp_tag] = { mac: interface } else: dhcp_tags[dhcp_tag][mac] = interface # Remove macs from redundant slave interfaces from dhcp_tags otherwise you get duplicate ip's in the installer. for dt in list(dhcp_tags.keys()): for m in list(dhcp_tags[dt].keys()): if m in ignore_macs: del dhcp_tags[dt][m] # we are now done with the looping through each interface of each system metadata = { "date": time.asctime(time.gmtime()), "cobbler_server": "%s:%s" % (self.settings.server, self.settings.http_port), "next_server": self.settings.next_server, "yaboot": yaboot, "dhcp_tags": dhcp_tags } self.logger.info("generating %s", self.settings_file) self.templar.render(template_data, metadata, self.settings_file)
def run(api, args): settings = api.settings() if not settings.windows_enabled: return 0 if not HAS_HIVEX: logger.info( "python3-hivex or python3-pefile not found. If you need Automatic Windows Installation support, " "please install.") return 0 profiles = api.profiles() systems = api.systems() templ = templar.Templar(api._collection_mgr) tgen = tftpgen.TFTPGen(api._collection_mgr) with open( os.path.join(settings.windows_template_dir, post_inst_cmd_template_name)) as template_win: post_tmpl_data = template_win.read() with open( os.path.join(settings.windows_template_dir, answerfile_template_name)) as template_win: tmpl_data = template_win.read() with open( os.path.join(settings.windows_template_dir, startnet_template_name)) as template_start: tmplstart_data = template_start.read() def gen_win_files(distro, meta): (kernel_path, kernel_name) = os.path.split(distro.kernel) distro_path = utils.find_distro_path(settings, distro) distro_dir = wim_file_name = os.path.join(settings.tftpboot_location, "images", distro.name) web_dir = os.path.join(settings.webdir, "images", distro.name) is_winpe = "winpe" in meta and meta['winpe'] != "" is_bcd = "bcd" in meta and meta['bcd'] != "" if "kernel" in meta: kernel_name = meta["kernel"] kernel_name = os.path.basename(kernel_name) is_wimboot = "wimboot" in kernel_name if is_wimboot: distro_path = os.path.join(settings.webdir, "distro_mirror", distro.name) kernel_path = os.path.join(distro_path, "Boot") if "kernel" in meta and "wimboot" not in distro.kernel: tgen.copy_single_distro_file( os.path.join(settings.tftpboot_location, kernel_name), distro_dir, False) tgen.copy_single_distro_file( os.path.join(distro_dir, kernel_name), web_dir, True) if "post_install_script" in meta: post_install_dir = distro_path if distro.os_version not in ("XP", "2003"): post_install_dir = os.path.join(post_install_dir, "sources") post_install_dir = os.path.join(post_install_dir, "$OEM$", "$1") if not os.path.exists(post_install_dir): utils.mkdir(post_install_dir) data = templ.render(post_tmpl_data, meta, None) post_install_script = os.path.join(post_install_dir, meta["post_install_script"]) logger.info('Build post install script: ' + post_install_script) with open(post_install_script, "w+") as pi_file: pi_file.write(data) if "answerfile" in meta: data = templ.render(tmpl_data, meta, None) answerfile_name = os.path.join(distro_dir, meta["answerfile"]) logger.info('Build answer file: ' + answerfile_name) with open(answerfile_name, "w+") as answerfile: answerfile.write(data) tgen.copy_single_distro_file(answerfile_name, distro_path, False) tgen.copy_single_distro_file(answerfile_name, web_dir, True) if "kernel" in meta and "bootmgr" in meta: wk_file_name = os.path.join(distro_dir, kernel_name) wl_file_name = os.path.join(distro_dir, meta["bootmgr"]) tl_file_name = os.path.join(kernel_path, "bootmgr.exe") if distro.os_version in ("XP", "2003") and not is_winpe: tl_file_name = os.path.join(kernel_path, "setupldr.exe") if len(meta["bootmgr"]) != 5: logger.error( "The loader name should be EXACTLY 5 character") return 1 pat1 = re.compile(br'NTLDR', re.IGNORECASE) pat2 = re.compile(br'winnt\.sif', re.IGNORECASE) with open(tl_file_name, 'rb') as file: out = data = file.read() if "answerfile" in meta: if len(meta["answerfile"]) != 9: logger.error( "The response file name should be EXACTLY 9 character" ) return 1 out = pat2.sub(bytes(meta["answerfile"], 'utf-8'), data) else: if len(meta["bootmgr"]) != 11: logger.error( "The Boot manager file name should be EXACTLY 11 character" ) return 1 bcd_name = "bcd" if is_bcd: bcd_name = meta["bcd"] if len(bcd_name) != 3: logger.error( "The BCD file name should be EXACTLY 3 character") return 1 if not os.path.isfile(tl_file_name): logger.error("File not found: %s" % tl_file_name) return 1 pat1 = re.compile(br'bootmgr\.exe', re.IGNORECASE) pat2 = re.compile(br'(\\.B.o.o.t.\\.)(B)(.)(C)(.)(D)', re.IGNORECASE) bcd_name = bytes( "\\g<1>" + bcd_name[0] + "\\g<3>" + bcd_name[1] + "\\g<5>" + bcd_name[2], 'utf-8') with open(tl_file_name, 'rb') as file: out = file.read() if not is_wimboot: logger.info('Patching build Loader: %s' % wl_file_name) out = pat2.sub(bcd_name, out) if tl_file_name != wl_file_name: logger.info('Build Loader: %s from %s' % (wl_file_name, tl_file_name)) with open(wl_file_name, 'wb+') as file: file.write(out) tgen.copy_single_distro_file(wl_file_name, web_dir, True) if not is_wimboot: if distro.os_version not in ("XP", "2003") or is_winpe: pe = pefile.PE(wl_file_name, fast_load=True) pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum() pe.write(filename=wl_file_name) with open(distro.kernel, 'rb') as file: data = file.read() out = pat1.sub(bytes(meta["bootmgr"], 'utf-8'), data) if wk_file_name != distro.kernel: logger.info("Build PXEBoot: %s from %s" % (wk_file_name, distro.kernel)) with open(wk_file_name, 'wb+') as file: file.write(out) tgen.copy_single_distro_file(wk_file_name, web_dir, True) if is_bcd: obcd_file_name = os.path.join(kernel_path, "bcd") bcd_file_name = os.path.join(distro_dir, meta["bcd"]) wim_file_name = 'winpe.wim' if not os.path.isfile(obcd_file_name): logger.error("File not found: %s" % obcd_file_name) return 1 if is_winpe: wim_file_name = meta["winpe"] if is_wimboot: wim_file_name = '\\Boot\\' + wim_file_name sdi_file_name = '\\Boot\\' + 'boot.sdi' else: wim_file_name = os.path.join("/images", distro.name, wim_file_name) sdi_file_name = os.path.join("/images", distro.name, os.path.basename(distro.initrd)) logger.info('Build BCD: %s from %s for %s' % (bcd_file_name, obcd_file_name, wim_file_name)) bcdedit(obcd_file_name, bcd_file_name, wim_file_name, sdi_file_name) tgen.copy_single_distro_file(bcd_file_name, web_dir, True) if is_winpe: ps_file_name = os.path.join(distro_dir, meta["winpe"]) wim_pl_name = os.path.join(kernel_path, "winpe.wim") cmd = ["/usr/bin/cp", "--reflink=auto", wim_pl_name, ps_file_name] utils.subprocess_call(logger, cmd, shell=False) tgen.copy_single_distro_file(ps_file_name, web_dir, True) if os.path.exists(wimupdate): data = templ.render(tmplstart_data, meta, None) pi_file = tempfile.NamedTemporaryFile() pi_file.write(bytes(data, 'utf-8')) pi_file.flush() cmd = [ wimupdate, ps_file_name, "--command=add " + pi_file.name + " /Windows/System32/startnet.cmd" ] utils.subprocess_call(cmd, shell=False) pi_file.close() for profile in profiles: distro = profile.get_conceptual_parent() if distro and distro.breed == "windows": logger.info('Profile: ' + profile.name) meta = utils.blender(api, False, profile) autoinstall_meta = meta.get("autoinstall_meta", {}) meta.update(autoinstall_meta) gen_win_files(distro, meta) for system in systems: profile = system.get_conceptual_parent() autoinstall_meta = system.autoinstall_meta if not profile or not autoinstall_meta or autoinstall_meta == {}: continue distro = profile.get_conceptual_parent() if distro and distro.breed == "windows": logger.info('System: ' + system.name) meta = utils.blender(api, False, system) gen_win_files(distro, autoinstall_meta) return 0
def run(api, args): if not HAS_HIVEX: logger.info("python3-hivex or python3-pefile not found. If you need Automatic Windows Installation support, " "please install.") return 0 distros = api.distros() profiles = api.profiles() templ = templar.Templar(api._collection_mgr) template_win = open(post_inst_cmd_template_name) tmpl_data = template_win.read() template_win.close() for distro in distros: if distro.breed == "windows": meta = utils.blender(api, False, distro) if "post_install" in distro.kernel_options: data = templ.render(tmpl_data, meta, None) pi_file = open(distro.kernel_options["post_install"], "w+") pi_file.write(data) pi_file.close() template_win = open(sif_template_name) tmpl_data = template_win.read() template_win.close() template_start = open(startnet_template_name) tmplstart_data = template_start.read() template_start.close() logger.info("\nWindows profiles:") for profile in profiles: distro = profile.get_conceptual_parent() if distro.breed == "windows": logger.info('Profile: ' + profile.name) meta = utils.blender(api, False, profile) (distro_path, pxeboot_name) = os.path.split(distro.kernel) if "sif" in profile.kernel_options: data = templ.render(tmpl_data, meta, None) if distro.os_version in ("7", "2008", "8", "2012", "2016", "2019", "10"): sif_file_name = os.path.join(distro_path, 'sources', profile.kernel_options["sif"]) else: sif_file_name = os.path.join(distro_path, profile.kernel_options["sif"]) sif_file = open(sif_file_name, "w+") sif_file.write(data) sif_file.close() logger.info('Build answer file: ' + sif_file_name) if "pxeboot" in profile.kernel_options and "bootmgr" in profile.kernel_options: wk_file_name = os.path.join(distro_path, profile.kernel_options["pxeboot"]) wl_file_name = os.path.join(distro_path, profile.kernel_options["bootmgr"]) logger.info("Build PXEBoot: " + wk_file_name) if distro.os_version in ("7", "2008", "8", "2012", "2016", "2019", "10"): if len(profile.kernel_options["bootmgr"]) != 11: logger.error("The loader name should be EXACTLY 11 character") return 1 if "bcd" in profile.kernel_options: if len(profile.kernel_options["bcd"]) != 3: logger.error("The BCD name should be EXACTLY 5 character") return 1 tl_file_name = os.path.join(distro_path, 'bootmgr.exe') pat1 = re.compile(br'bootmgr\.exe', re.IGNORECASE) pat2 = re.compile(br'(\\.B.o.o.t.\\.)(B)(.)(C)(.)(D)', re.IGNORECASE) bcd_name = 'BCD' if "bcd" in profile.kernel_options: bcd_name = profile.kernel_options["bcd"] bcd_name = bytes("\\g<1>" + bcd_name[0] + "\\g<3>" + bcd_name[1] + "\\g<5>" + bcd_name[2], 'utf-8') data = open(tl_file_name, 'rb').read() out = pat2.sub(bcd_name, data) else: if len(profile.kernel_options["bootmgr"]) != 5: logger.error("The loader name should be EXACTLY 5 character") return 1 if len(profile.kernel_options["sif"]) != 9: logger.error("The response should be EXACTLY 9 character") return 1 tl_file_name = os.path.join(distro_path, 'setupldr.exe') pat1 = re.compile(br'NTLDR', re.IGNORECASE) pat2 = re.compile(br'winnt\.sif', re.IGNORECASE) data = open(tl_file_name, 'rb').read() out = pat2.sub(bytes(profile.kernel_options["sif"], 'utf-8'), data) logger.info('Build Loader: ' + wl_file_name) if out != data: open(wl_file_name, 'wb+').write(out) if distro.os_version in ("7", "2008", "8", "2012", "2016", "2019", "10"): pe = pefile.PE(wl_file_name, fast_load=True) pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum() pe.write(filename=wl_file_name) data = open(distro.kernel, 'rb').read() out = pat1.sub(bytes(profile.kernel_options["bootmgr"], 'utf-8'), data) if out != data: open(wk_file_name, 'wb+').write(out) if "bcd" in profile.kernel_options: obcd_file_name = os.path.join(distro_path, 'boot', 'BCD') bcd_file_name = os.path.join(distro_path, 'boot', profile.kernel_options["bcd"]) wim_file_name = 'winpe.wim' if "winpe" in profile.kernel_options: wim_file_name = profile.kernel_options["winpe"] if distro.boot_loader == "ipxe": wim_file_name = '\\Boot\\' + wim_file_name sdi_file_name = '\\Boot\\' + 'boot.sdi' else: wim_file_name = os.path.join('/winos', distro.name, 'boot', wim_file_name) sdi_file_name = os.path.join('/winos', distro.name, 'boot', 'boot.sdi') logger.info('Build BCD: ' + bcd_file_name + ' for ' + wim_file_name) bcdedit(obcd_file_name, bcd_file_name, wim_file_name, sdi_file_name) if "winpe" in profile.kernel_options: ps_file_name = os.path.join(distro_path, "boot", profile.kernel_options["winpe"]) if distro.os_version in ("7", "2008"): wim_pl_name = wim7_template_name elif distro.os_version in ("8", "2012", "2016", "2019", "10"): wim_pl_name = wim8_template_name else: raise ValueError("You are trying to use an unsupported distro!") cmd = "/usr/bin/cp --reflink=auto " + wim_pl_name + " " + ps_file_name utils.subprocess_call(cmd, shell=True) if os.path.exists(wimupdate): data = templ.render(tmplstart_data, meta, None) pi_file = tempfile.NamedTemporaryFile() pi_file.write(bytes(data, 'utf-8')) pi_file.flush() cmd = wimupdate + ' ' + ps_file_name + ' --command="add ' + pi_file.name cmd += ' /Windows/System32/startnet.cmd"' utils.subprocess_call(cmd, shell=True) pi_file.close() return 0
def generate_autoyast(self, profile=None, system=None, raw_data=None): self.api.logger.info("autoyast XML file found. Checkpoint: profile=%s system=%s" % (profile, system)) nopxe = "\nwget \"http://%s/cblr/svc/op/nopxe/system/%s\" -O /dev/null" runpost = "\ncurl \"http://%s/cblr/svc/op/trig/mode/post/%s/%s\" > /dev/null" runpre = "\nwget \"http://%s/cblr/svc/op/trig/mode/pre/%s/%s\" -O /dev/null" what = "profile" blend_this = profile if system: what = "system" blend_this = system blended = utils.blender(self.api, False, blend_this) srv = blended["http_server"] document = xml.dom.minidom.parseString(raw_data) # do we already have the #raw comment in the XML? (addComment = 0 means, don't add #raw comment) addComment = 1 for node in document.childNodes[1].childNodes: if node.nodeType == node.ELEMENT_NODE and node.tagName == "cobbler": addComment = 0 break # add some cobbler information to the XML file # maybe that should be configureable if addComment == 1: # startComment = document.createComment("\ncobbler_system_name=$system_name\ncobbler_server=$server\n#raw\n") # endComment = document.createComment("\n#end raw\n") cobblerElement = document.createElement("cobbler") cobblerElementSystem = xml.dom.minidom.Element("system_name") cobblerElementProfile = xml.dom.minidom.Element("profile_name") if (system is not None): cobblerTextSystem = document.createTextNode(system.name) cobblerElementSystem.appendChild(cobblerTextSystem) if (profile is not None): cobblerTextProfile = document.createTextNode(profile.name) cobblerElementProfile.appendChild(cobblerTextProfile) cobblerElementServer = document.createElement("server") cobblerTextServer = document.createTextNode(blended["http_server"]) cobblerElementServer.appendChild(cobblerTextServer) cobblerElement.appendChild(cobblerElementServer) cobblerElement.appendChild(cobblerElementSystem) cobblerElement.appendChild(cobblerElementProfile) # FIXME: this is all broken and no longer works. # this entire if block should probably not be # hard-coded anyway # self.api.log(document.childNodes[2].childNodes) # document.childNodes[1].insertBefore( cobblerElement, document.childNodes[2].childNodes[1]) # document.childNodes[1].insertBefore( cobblerElement, document.childNodes[1].childNodes[0]) name = profile.name if system is not None: name = system.name if str(self.settings.pxe_just_once).upper() in ["1", "Y", "YES", "TRUE"]: self.addAutoYaSTScript(document, "chroot-scripts", nopxe % (srv, name)) if self.settings.run_install_triggers: # notify cobblerd when we start/finished the installation self.addAutoYaSTScript(document, "pre-scripts", runpre % (srv, what, name)) self.addAutoYaSTScript(document, "init-scripts", runpost % (srv, what, name)) return document.toxml()
def dump_vars(self, data, format=True): raw = utils.blender(self.config.api, False, self) if format: return pprint.pformat(raw) else: return raw
def dump_vars(self, data, format=True): raw = utils.blender(self.collection_mgr.api, False, self) if format: return pprint.pformat(raw) else: return raw
def generate_repo_stanza(self, obj, is_profile=True): """ Automatically attaches yum repos to profiles/systems in automatic installation files (template files) that contain the magic $yum_repo_stanza variable. This includes repo objects as well as the yum repos that are part of split tree installs, whose data is stored with the distro (example: RHEL5 imports) :param obj: The profile or system to generate the repo stanza for. :param is_profile: If True then obj is a profile, otherwise obj has to be a system. Otherwise this method will silently fail. :return: The string with the attached yum repos. :rtype: str """ buf = "" blended = utils.blender(self.api, False, obj) repos = blended["repos"] # keep track of URLs and be sure to not include any duplicates included = {} for repo in repos: # see if this is a source_repo or not repo_obj = self.api.find_repo(repo) if repo_obj is not None: yumopts = '' for opt in repo_obj.yumopts: # filter invalid values to the repo statement in automatic installation files if opt in ['exclude', 'include']: value = repo_obj.yumopts[opt].replace(' ', ',') yumopts = yumopts + " --%spkgs=%s" % (opt, value) elif not opt.lower( ) in validate.AUTOINSTALL_REPO_BLACKLIST: yumopts += " %s=%s" % (opt, repo_obj.yumopts[opt]) if 'enabled' not in repo_obj.yumopts or repo_obj.yumopts[ 'enabled'] == '1': if repo_obj.mirror_locally: baseurl = "http://%s/cobbler/repo_mirror/%s" % ( blended["http_server"], repo_obj.name) if baseurl not in included: buf += "repo --name=%s --baseurl=%s\n" % ( repo_obj.name, baseurl) included[baseurl] = 1 else: if repo_obj.mirror not in included: buf += "repo --name=%s --baseurl=%s %s\n" % ( repo_obj.name, repo_obj.mirror, yumopts) included[repo_obj.mirror] = 1 else: # FIXME: what to do if we can't find the repo object that is listed? # This should be a warning at another point, probably not here so we'll just not list it so the # automatic installation file will still work as nothing will be here to read the output noise. # Logging might be useful. pass if is_profile: distro = obj.get_conceptual_parent() else: distro = obj.get_conceptual_parent().get_conceptual_parent() source_repos = distro.source_repos count = 0 for x in source_repos: count += 1 if not x[1] in included: buf += "repo --name=source-%s --baseurl=%s\n" % (count, x[1]) included[x[1]] = 1 return buf
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(api._config).render(input_data, metadata, None) sendmail = True for prefix in settings.build_reporting_ignorelist: if prefix != '' and name.lower().startswith(prefix): sendmail = False if sendmail: # Send the mail # FIXME: on error, return non-zero server_handle = smtplib.SMTP(smtp_server) server_handle.sendmail(from_addr, to_addr.split(','), message) server_handle.quit() return 0
def write_dhcp_file(self): """ DHCP files are written when manage_dhcp is set in /etc/cobbler/settings. """ template_file = "/etc/cobbler/dhcp.template" blender_cache = {} try: f2 = open(template_file, "r") except: raise CX(_("error reading template: %s") % template_file) template_data = "" template_data = f2.read() f2.close() # use a simple counter for generating generic names where a hostname # is not available counter = 0 # we used to just loop through each system, but now we must loop # through each network interface of each system. dhcp_tags = {"default": {}} yaboot = "/yaboot" # FIXME: ding should evolve into the new dhcp_tags dict ding = {} ignore_macs = [] for system in self.systems: if not system.is_management_supported(cidr_ok=False): continue profile = system.get_conceptual_parent() distro = profile.get_conceptual_parent() # if distro is None then the profile is really an image record for (name, system_interface) in list(system.interfaces.items()): # We make a copy because we may modify it before adding it to the dhcp_tags # and we don't want to affect the master copy. interface = copy.deepcopy(system_interface) if interface["if_gateway"]: interface["gateway"] = interface["if_gateway"] else: interface["gateway"] = system.gateway mac = interface["mac_address"] if interface["interface_type"] in ("bond_slave", "bridge_slave", "bonded_bridge_slave"): if interface["interface_master"] not in system.interfaces: # Can't write DHCP entry; master interface does not exist continue # We may have multiple bonded interfaces, so we need a composite index into ding. name_master = "%s-%s" % (system.name, interface["interface_master"]) if name_master not in ding: ding[name_master] = {interface["interface_master"]: []} if len(ding[name_master][interface["interface_master"]]) == 0: ding[name_master][interface["interface_master"]].append(mac) else: ignore_macs.append(mac) ip = system.interfaces[interface["interface_master"]]["ip_address"] netmask = system.interfaces[interface["interface_master"]]["netmask"] dhcp_tag = system.interfaces[interface["interface_master"]]["dhcp_tag"] host = system.interfaces[interface["interface_master"]]["dns_name"] if ip is None or ip == "": for (nam2, int2) in list(system.interfaces.items()): if (nam2.startswith(interface["interface_master"] + ".") and int2["ip_address"] is not None and int2["ip_address"] != ""): ip = int2["ip_address"] break interface["ip_address"] = ip interface["netmask"] = netmask else: ip = interface["ip_address"] netmask = interface["netmask"] dhcp_tag = interface["dhcp_tag"] host = interface["dns_name"] if distro is not None: interface["distro"] = distro.to_dict() if mac is None or mac == "": # can't write a DHCP entry for this system continue counter = counter + 1 # the label the entry after the hostname if possible if host is not None and host != "": if name != "eth0": interface["name"] = "%s-%s" % (host, name) else: interface["name"] = "%s" % (host) else: interface["name"] = "generic%d" % counter # add references to the system, profile, and distro # for use in the template if system.name in blender_cache: blended_system = blender_cache[system.name] else: blended_system = utils.blender(self.api, False, system) blender_cache[system.name] = blended_system interface["next_server"] = blended_system["next_server"] interface["netboot_enabled"] = blended_system["netboot_enabled"] interface["hostname"] = blended_system["hostname"] interface["owner"] = blended_system["name"] interface["enable_gpxe"] = blended_system["enable_gpxe"] interface["name_servers"] = blended_system["name_servers"] if not self.settings.always_write_dhcp_entries: if not interface["netboot_enabled"] and interface['static']: continue if distro is not None: if distro.arch.startswith("ppc"): if blended_system["boot_loader"] == "pxelinux": del interface["filename"] elif distro.boot_loader == "grub2" or blended_system["boot_loader"] == "grub2": interface["filename"] = "boot/grub/powerpc-ieee1275/core.elf" else: interface["filename"] = yaboot if dhcp_tag == "": dhcp_tag = blended_system["dhcp_tag"] if dhcp_tag == "": dhcp_tag = "default" if dhcp_tag not in dhcp_tags: dhcp_tags[dhcp_tag] = { mac: interface } else: dhcp_tags[dhcp_tag][mac] = interface # remove macs from redundant slave interfaces from dhcp_tags # otherwise you get duplicate ip's in the installer for dt in list(dhcp_tags.keys()): for m in list(dhcp_tags[dt].keys()): if m in ignore_macs: del dhcp_tags[dt][m] # we are now done with the looping through each interface of each system metadata = { "date": time.asctime(time.gmtime()), "cobbler_server": "%s:%s" % (self.settings.server, self.settings.http_port), "next_server": self.settings.next_server, "yaboot": yaboot, "dhcp_tags": dhcp_tags } if self.logger is not None: self.logger.info("generating %s" % self.settings_file) self.templar.render(template_data, metadata, self.settings_file, None)