def set_parent(self, parent_name): """ Instead of a --distro, set the parent of this object to another profile and use the values from the parent instead of this one where the values for this profile aren't filled in, and blend them together where they are hashes. Basically this enables profile inheritance. To use this, the object MUST have been constructed with is_subobject=True or the default values for everything will be screwed up and this will likely NOT work. So, API users -- make sure you pass is_subobject=True into the constructor when using this. """ old_parent = self.get_parent() if isinstance(old_parent, item.Item): old_parent.children.pop(self.name, 'pass') if parent_name is None or parent_name == '': self.parent = '' return True if parent_name == self.name: # check must be done in two places as set_parent could be called before/after # set_name... raise CX(_("self parentage is weird")) found = self.config.profiles().find(name=parent_name) if found is None: raise CX( _("profile %s not found, inheritance not possible") % parent_name) self.parent = parent_name self.depth = found.depth + 1 parent = self.get_parent() if isinstance(parent, item.Item): parent.children[self.name] = self return True
def set_name(self, name): """ Set the name. If the name is a MAC or IP, and the first MAC and/or IP is not defined, go ahead and fill that value in. """ if self.name not in ["", None] and self.parent not in [ "", None ] and self.name == self.parent: raise CX(_("self parentage is weird")) if not isinstance(name, basestring): raise CX(_("name must be a string")) for x in name: if not x.isalnum() and not x in ["_", "-", ".", ":", "+"]: raise CX(_("invalid characters in name: %s") % x) # Stuff here defaults to eth0. Yes, it's ugly and hardcoded, but so was # the default interface behaviour that's now removed. ;) # --Jasper Capel if utils.is_mac(name): intf = self.__get_interface("eth0") if intf["mac_address"] == "": intf["mac_address"] = name elif utils.is_ip(name): intf = self.__get_interface("eth0") if intf["ip_address"] == "": intf["ip_address"] = name self.name = name return True
def sync_dhcp(self): restart_dhcp = str(self.settings.restart_dhcp).lower() which_dhcp_module = module_loader.get_module_name("dhcp", "module").strip() if self.settings.manage_dhcp: self.write_dhcp() if which_dhcp_module == "manage_isc": service_name = utils.dhcp_service_name(self.api) if restart_dhcp != "0": rc = utils.subprocess_call(self.logger, "dhcpd -t -q", shell=True) if rc != 0: error_msg = "dhcpd -t failed" self.logger.error(error_msg) raise CX(error_msg) service_restart = "service %s restart" % service_name rc = utils.subprocess_call(self.logger, service_restart, shell=True) if rc != 0: error_msg = "%s failed" % service_name self.logger.error(error_msg) raise CX(error_msg) elif which_dhcp_module == "manage_dnsmasq": if restart_dhcp != "0": rc = utils.subprocess_call(self.logger, "service dnsmasq restart") if rc != 0: error_msg = "service dnsmasq restart failed" self.logger.error(error_msg) raise CX(error_msg)
def check_if_valid(self): if self.name is None or self.name == "": raise CX("name is required") distro = self.get_conceptual_parent() if distro is None: raise CX("Error with profile %s - distro is required" % (self.name))
def auto_add_repos(self): """ Import any repos this server knows about and mirror them. Credit: Seth Vidal. """ self.log("auto_add_repos") try: import yum except: raise CX(_("yum is not installed")) version = yum.__version__ (a, b, c) = version.split(".") version = a * 1000 + b * 100 + c if version < 324: raise CX(_("need yum > 3.2.4 to proceed")) base = yum.YumBase() base.doRepoSetup() repos = base.repos.listEnabled() if len(repos) == 0: raise CX(_("no repos enabled/available -- giving up.")) for repo in repos: url = repo.urls[0] cobbler_repo = self.new_repo() auto_name = repo.name.replace(" ", "") # FIXME: probably doesn't work for yum-rhn-plugin ATM cobbler_repo.set_mirror(url) cobbler_repo.set_name(auto_name) print "auto adding: %s (%s)" % (auto_name, url) self._config.repos().add(cobbler_repo, save=True) # run cobbler reposync to apply changes return True
def set_class_name(self, name): if not isinstance(name, basestring): raise CX(_("class name must be a string")) for x in name: if not x.isalnum() and x not in ["_", "-", ".", ":", "+"]: raise CX(_("invalid characters in class name: '%s'" % name)) self.class_name = name return True
def check_if_valid(self): if self.name is None or self.name == "": raise CX("name is required") if self.profile is None or self.profile == "": if self.image is None or self.image == "": raise CX( "Error with system %s - profile or image is required" % (self.name))
def set_initrd(self, initrd): """ Specifies an initrd image. Path search works as in set_kernel. File must be named appropriately. """ if initrd is None or initrd == "": raise CX("initrd not specified") if utils.find_initrd(initrd): self.initrd = initrd return True raise CX(_("initrd not found"))
def set_power_type(self, power_type): # FIXME: modularize this better if power_type is None: power_type = "" choices = utils.get_power_types() if not choices: raise CX("you need to have fence-agents installed") if power_type not in choices: raise CX("power management type must be one of: %s" % ",".join(choices)) self.power_type = power_type return True
def copy_single_distro_files(self, d, dirtree, symlink_ok): distros = os.path.join(dirtree, "images") distro_dir = os.path.join(distros, d.name) utils.mkdir(distro_dir) kernel = utils.find_kernel(d.kernel) # full path initrd = utils.find_initrd(d.initrd) # full path if kernel is None: raise CX("kernel not found: %(file)s, distro: %(distro)s" % {"file": d.kernel, "distro": d.name}) if initrd is None: raise CX("initrd not found: %(file)s, distro: %(distro)s" % {"file": d.initrd, "distro": d.name}) # Koan manages remote kernel itself, but for consistent PXE # configurations the synchronization is still necessary if not utils.file_is_remote(kernel): b_kernel = os.path.basename(kernel) dst1 = os.path.join(distro_dir, b_kernel) utils.linkfile(kernel, dst1, symlink_ok=symlink_ok, api=self.api, logger=self.logger) else: b_kernel = os.path.basename(kernel) dst1 = os.path.join(distro_dir, b_kernel) utils.copyremotefile(kernel, dst1, api=None, logger=self.logger) if not utils.file_is_remote(initrd): b_initrd = os.path.basename(initrd) dst2 = os.path.join(distro_dir, b_initrd) utils.linkfile(initrd, dst2, symlink_ok=symlink_ok, api=self.api, logger=self.logger) else: b_initrd = os.path.basename(initrd) dst1 = os.path.join(distro_dir, b_initrd) utils.copyremotefile(initrd, dst1, api=None, logger=self.logger) if "nexenta" == d.breed: try: os.makedirs(os.path.join(distro_dir, 'platform', 'i86pc', 'kernel', 'amd64')) os.makedirs(os.path.join(distro_dir, 'platform', 'i86pc', 'amd64')) except OSError: pass b_kernel = os.path.basename(kernel) utils.linkfile(kernel, os.path.join(distro_dir, 'platform', 'i86pc', 'kernel', 'amd64', b_kernel), symlink_ok=symlink_ok, api=self.api, logger=self.logger) b_initrd = os.path.basename(initrd) utils.linkfile(initrd, os.path.join(distro_dir, 'platform', 'i86pc', 'amd64', b_initrd), symlink_ok=symlink_ok, api=self.api, logger=self.logger) # the [:-7] removes the architecture if os.path.isdir(os.path.join('/var', 'www', 'cobbler', 'links', d.name, 'install_profiles')): shutil.rmtree(os.path.join('/var', 'www', 'cobbler', 'links', d.name, 'install_profiles')) shutil.copytree(os.path.join('/var', 'lib', 'cobbler', 'kickstarts', 'install_profiles'), os.path.join('/var', 'www', 'cobbler', 'links', d.name, 'install_profiles'))
def set_kernel(self, kernel): """ Specifies a kernel. The kernel parameter is a full path, a filename in the configured kernel directory (set in /etc/cobbler.conf) or a directory path that would contain a selectable kernel. Kernel naming conventions are checked, see docs in the utils module for find_kernel. """ if kernel is None or kernel == "": raise CX("kernel not specified") if utils.find_kernel(kernel): self.kernel = kernel return True raise CX("kernel not found: %s" % kernel)
def validate_power_type(power_type): """ Check if a power management type is valid @param str power_type power management type @raise CX if power management type is invalid """ power_types = get_power_types() if not power_types: raise CX("you need to have fence-agents installed") if power_type not in power_types: raise CX("power management type must be one of: %s" % ",".join(power_types))
def rename_interface(self, names): """ Used to rename an interface. """ (name, newname) = names if not name in self.interfaces: raise CX(_("Interface %s does not exist" % name)) if newname in self.interfaces: raise CX(_("Interface %s already exists" % newname)) else: self.interfaces[newname] = self.interfaces[name] del self.interfaces[name] return True
def set_file(self, filename): """ Stores the image location. This should be accessible on all nodes that need to access it. Format: can be one of the following: * username:password@hostname:/path/to/the/filename.ext * username@hostname:/path/to/the/filename.ext * hostname:/path/to/the/filename.ext * /path/to/the/filename.ext """ uri = "" scheme = auth = hostname = path = "" # we'll discard the protocol if it's supplied, for legacy support if filename.find("://") != -1: scheme, uri = filename.split("://") filename = uri else: uri = filename if filename.find("@") != -1: auth, filename = filename.split("@") # extract the hostname # 1. if we have a colon, then everything before it is a hostname # 2. if we don't have a colon, then check if we had a scheme; if # we did, then grab all before the first forward slash as the # hostname; otherwise, we've got a bad file if filename.find(":") != -1: hostname, filename = filename.split(":") elif filename[0] != '/': if len(scheme) > 0: index = filename.find("/") hostname = filename[:index] filename = filename[index:] else: raise CX(_("invalid file: %s" % filename)) # raise an exception if we don't have a valid path if len(filename) > 0 and filename[0] != '/': raise CX(_("file contains an invalid path: %s" % filename)) if filename.find("/") != -1: path, filename = filename.rsplit("/", 1) if len(filename) == 0: raise CX(_("missing filename")) if len(auth) > 0 and len(hostname) == 0: raise CX( _("a hostname must be specified with authentication details")) self.file = uri return True
def set_image(self, image_name): """ Set the system to use a certain named image. Works like set_profile but cannot be used at the same time. It's one or the other. """ old_parent = self.get_parent() if image_name in ["delete", "None", "~", ""] or image_name is None: self.image = "" if isinstance(old_parent, item.Item): old_parent.children.pop(self.name, 'pass') return True self.profile = "" # mutual exclusion rule img = self.config.images().find(name=image_name) if img is not None: self.image = image_name self.depth = img.depth + 1 if isinstance(old_parent, item.Item): old_parent.children.pop(self.name, 'pass') new_parent = self.get_parent() if isinstance(new_parent, item.Item): new_parent.children[self.name] = self return True raise CX(_("invalid image name (%s)") % image_name)
def set_profile(self, profile_name): """ Set the system to use a certain named profile. The profile must have already been loaded into the Profiles collection. """ old_parent = self.get_parent() if profile_name in ["delete", "None", "~", ""] or profile_name is None: self.profile = "" if isinstance(old_parent, item.Item): old_parent.children.pop(self.name, 'pass') return True self.image = "" # mutual exclusion rule p = self.config.profiles().find(name=profile_name) if p is not None: self.profile = profile_name self.depth = p.depth + 1 # subprofiles have varying depths. if isinstance(old_parent, item.Item): old_parent.children.pop(self.name, 'pass') new_parent = self.get_parent() if isinstance(new_parent, item.Item): new_parent.children[self.name] = self return True raise CX(_("invalid profile name: %s") % profile_name)
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") return True
def run(api, args, logger): # FIXME: use the logger if len(args) < 3: raise CX("invalid invocation") # objtype = args[0] # "system" or "profile" name = args[1] # name of system or profile # ip = args[2] # ip or "?" 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 deserialize(self): """ Load all collections from disk @raise CX if there is an error in deserialization """ for collection in ( self._settings, self._distros, self._repos, self._profiles, self._images, self._systems, self._mgmtclasses, self._packages, self._files, ): try: if not serializer.deserialize(collection): raise "" except: raise CX( "serializer: error loading collection %s. Check /etc/cobbler/modules.conf" % collection.collection_type())
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.config.api, obj, "/var/lib/cobbler/triggers/delete/system/pre/*", [], logger) if with_sync: lite_sync = action_litesync.BootLiteSync(self.config, logger=logger) lite_sync.remove_single_system(name) self.lock.acquire() try: del self.listing[name] finally: self.lock.release() self.config.serialize_delete(self, obj) if with_delete: if with_triggers: utils.run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/system/post/*", [], logger) utils.run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/change/*", [], logger) return True raise CX(_("cannot delete an object that does not exist: %s") % name)
def set_name(self, name): """ All objects have names, and with the exception of System they aren't picky about it. """ if self.name not in ["", None] and self.parent not in [ "", None ] and self.name == self.parent: raise CX(_("self parentage is weird")) if not isinstance(name, basestring): raise CX(_("name must be a string")) for x in name: if not x.isalnum() and not x in ["_", "-", ".", ":", "+"]: raise CX(_("invalid characters in name: '%s'" % name)) self.name = name return True
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.config.api, obj, "/var/lib/cobbler/triggers/delete/file/*", [], logger) del self.listing[name] self.config.serialize_delete(self, obj) if with_delete: if with_triggers: utils.run_triggers( self.config.api, obj, "/var/lib/cobbler/triggers/delete/file/post/*", [], logger) utils.run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/change/*", [], logger) return True raise CX(_("cannot delete an object that does not exist: %s") % name)
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", None)
def set_network_count(self, num): if num is None or num == "": num = 1 try: self.network_count = int(num) except: raise CX("invalid network count (%s)" % num) return True
def sync_dhcp(self): restart_dhcp = str(self.settings.restart_dhcp).lower() service_name = utils.dhcp_service_name(self.api) if restart_dhcp != "0": rc = utils.subprocess_call(self.logger, "dhcpd -t -q", shell=True) if rc != 0: error_msg = "dhcpd -t failed" self.logger.error(error_msg) raise CX(error_msg) service_restart = "service %s restart" % service_name rc = utils.subprocess_call(self.logger, service_restart, shell=True) if rc != 0: error_msg = "%s failed" % service_name self.logger.error(error_msg) raise CX(error_msg)
def set_gateway(self, gateway): if gateway is None: gateway = "" if utils.is_ip(gateway) or gateway == "": self.gateway = gateway else: raise CX(_("invalid format for gateway IP address (%s)") % gateway) return True
def __connect(): # TODO: detect connection error global mongodb try: mongodb = Connection('localhost', 27017)['cobbler'] except: # FIXME: log error raise CX("Unable to connect to Mongo database")
def sync_dhcp(self): restart_dhcp = str(self.settings.restart_dhcp).lower() if restart_dhcp != "0": rc = utils.subprocess_call(self.logger, "service dnsmasq restart") if rc != 0: error_msg = "service dnsmasq restart failed" self.logger.error(error_msg) raise CX(error_msg)
def __write_named_conf(self): """ Write out the named.conf main config file from the template. """ settings_file = self.settings.bind_chroot_path + self.settings_file template_file = "/etc/cobbler/named.template" # forward_zones = self.settings.manage_forward_zones # reverse_zones = self.settings.manage_reverse_zones metadata = {'forward_zones': self.__forward_zones().keys(), 'reverse_zones': [], 'zone_include': ''} for zone in metadata['forward_zones']: txt = """ zone "%(zone)s." { type master; file "%(zone)s"; }; """ % {'zone': zone} metadata['zone_include'] = metadata['zone_include'] + txt for zone in self.__reverse_zones().keys(): # IPv6 zones are : delimited if ":" in zone: # if IPv6, assume xxxx:xxxx:xxxx:xxxx # 0123456789012345678 long_zone = (self.__expand_IPv6(zone + '::1'))[:19] tokens = list(re.sub(':', '', long_zone)) tokens.reverse() arpa = '.'.join(tokens) + '.ip6.arpa' else: # IPv4 address split by '.' tokens = zone.split('.') tokens.reverse() arpa = '.'.join(tokens) + '.in-addr.arpa' # metadata['reverse_zones'].append((zone, arpa)) txt = """ zone "%(arpa)s." { type master; file "%(zone)s"; }; """ % {'arpa': arpa, 'zone': zone} metadata['zone_include'] = metadata['zone_include'] + txt 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 get_module_from_file(category, field, fallback_module_name=None, just_name=False): 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) if just_name: return value rc = MODULE_CACHE.get(value, None) if rc is None: raise CX(_("Failed to load module for %s/%s") % (category, field)) return rc