def _parseConfig(filename): config = {} with codecs.open(filename, mode='r', encoding='utf-8') as opsircfile: for line in opsircfile: line = line.strip() if line.startswith(('#', ';')) or not line: continue try: key, value = line.split('=', 1) except ValueError: logger.debug2(u"Unable to split line {!r}".format(line)) continue key = key.strip() value = value.strip() if not value: logger.warning( "There is no value for {} in opsirc file {!r}, skipping.", key, filename) continue if key == 'address': config[key] = forceUrl(value) elif key == 'username': config[key] = forceUnicode(value) elif key == 'password': value = forceUnicode(value) logger.addConfidentialString(value) config[key] = value elif key == 'password file': passwordFilePath = os.path.expanduser(value) value = _readPasswordFile(passwordFilePath) logger.addConfidentialString(value) config['password'] = value else: logger.debug(u"Ignoring unknown key {}", key) logger.debug("Found the following usable keys in {!r}: {}", filename, ", ".join(config.keys())) return config
def testForceUrlWithInvalidURLsRaisesExceptions(url): with pytest.raises(ValueError): forceUrl(url)
def testForceUrlDoesNotForceLowercase(url, expected): """ Complete URLs must not be forced to lowercase because they could \ include an username / password combination for an proxy. """ assert expected == forceUrl(url)
def testForceUrl(url, expected): result = forceUrl(url) assert expected == result assert isinstance(result, unicode)
def _getRepository(self, config, section, forceRepositoryActivation=False, repositoryName=None, installAllAvailable=False, proxy=None): active = False baseUrl = None opsiDepotId = None for (option, value) in config.items(section): option = option.lower() value = value.strip() if option == 'active': active = forceBool(value) elif option == 'baseurl': if value: baseUrl = forceUrl(value) elif option == 'opsidepotid': if value: opsiDepotId = forceHostId(value) elif option == 'proxy': if value: proxy = forceUrl(value) repoName = section.replace('repository_', '', 1) if forceRepositoryActivation: if repoName == repositoryName: logger.debug("Activation for repository {0} forced.", repoName) active = True else: active = False repository = None if opsiDepotId: if not self.backend: raise RequiringBackendError( "Repository section '{0}' supplied an depot ID but we have no backend to check." .format(section)) depots = self.backend.host_getObjects(type='OpsiDepotserver', id=opsiDepotId) if not depots: raise ConfigurationError(u"Depot '%s' not found in backend" % opsiDepotId) if not depots[0].repositoryRemoteUrl: raise ConfigurationError( u"Repository remote url for depot '%s' not found in backend" % opsiDepotId) repository = ProductRepositoryInfo( name=repoName, baseUrl=depots[0].repositoryRemoteUrl, dirs=['/'], username=self.depotId, password=self.depotKey, opsiDepotId=opsiDepotId, active=active) elif baseUrl: if proxy: logger.info(u"Repository {} is using proxy {}", repoName, proxy) repository = ProductRepositoryInfo(name=repoName, baseUrl=baseUrl, proxy=proxy, active=active) else: raise MissingConfigurationValueError( u"Repository section '{0}': neither baseUrl nor opsiDepotId set" .format(section)) for (option, value) in config.items(section): if option.lower() == 'username': repository.username = forceUnicode(value.strip()) elif option.lower() == 'password': repository.password = forceUnicode(value.strip()) if repository.password: logger.addConfidentialString(repository.password) elif option.lower() == 'authcertfile': repository.authcertfile = forceFilename(value.strip()) elif option.lower() == 'authkeyfile': repository.authkeyfile = forceFilename(value.strip()) elif option.lower() == 'autoinstall': repository.autoInstall = forceBool(value.strip()) elif option.lower() == 'autoupdate': repository.autoUpdate = forceBool(value.strip()) elif option.lower() == 'autosetup': repository.autoSetup = forceBool(value.strip()) elif option.lower() == 'onlydownload': repository.onlyDownload = forceBool(value.strip()) elif option.lower() == 'inheritproductproperties': if not opsiDepotId: logger.warning( u"InheritProductProperties not possible with normal http ressource." ) repository.inheritProductProperties = False else: repository.inheritProductProperties = forceBool( value.strip()) elif option.lower() == 'dirs': repository.dirs = [] for directory in splitAndStrip(value, ','): repository.dirs.append(forceFilename(directory)) elif option.lower() == 'excludes': repository.excludes = [] for exclude in splitAndStrip(value, ','): repository.excludes.append(re.compile(exclude)) elif option.lower() == 'includeproductids': repository.includes = [] for include in splitAndStrip(value, ','): repository.includes.append(re.compile(include)) elif option.lower() == 'description': repository.description = forceUnicode(value) if installAllAvailable: repository.autoInstall = True repository.autoUpdate = True repository.excludes = [] return repository
def parse(self, configuration=None): """ Parse the configuration file. :param confiuration: Predefined configuration. Contents may be \ overriden based on values in configuration file. :rtype: dict """ logger.info(u"Reading config file '%s'" % self.configFile) if not os.path.isfile(self.configFile): raise OSError(u"Configuration file {!r} not found".format( self.configFile)) config = DEFAULT_CONFIG.copy() if configuration: config.update(configuration) config['repositories'] = [] try: iniFile = IniFile(filename=self.configFile, raw=True) configIni = iniFile.parse() for section in configIni.sections(): if section.lower() == 'general': for (option, value) in configIni.items(section): if option.lower() == 'packagedir': config["packageDir"] = forceFilename(value.strip()) elif option.lower() == 'logfile': value = forceFilename(value.strip()) logger.setLogFile(value) elif option.lower() == 'loglevel': logger.setFileLevel(forceInt(value.strip())) elif option.lower() == 'timeout': # TODO: find a better way! socket.setdefaulttimeout(float(value.strip())) elif option.lower() == 'tempdir': config["tempdir"] = value.strip() elif option.lower() == 'repositoryconfigdir': config["repositoryConfigDir"] = value.strip() elif option.lower() == 'proxy' and value.strip(): config["proxy"] = forceUrl(value.strip()) elif section.lower() == 'notification': for (option, value) in configIni.items(section): if option.lower() == 'active': config["notification"] = forceBool(value) elif option.lower() == 'smtphost': config["smtphost"] = forceHostAddress( value.strip()) elif option.lower() == 'smtpport': config["smtpport"] = forceInt(value.strip()) elif option.lower() == 'smtpuser': config["smtpuser"] = forceUnicode(value.strip()) elif option.lower() == 'smtppassword': config["smtppassword"] = forceUnicode( value.strip()) elif option.lower() == 'subject': config["subject"] = forceUnicode(value.strip()) elif option.lower() == 'use_starttls': config["use_starttls"] = forceBool(value.strip()) elif option.lower() == 'sender': config["sender"] = forceEmailAddress(value.strip()) elif option.lower() == 'receivers': config["receivers"] = [] for receiver in splitAndStrip(value, u","): config["receivers"].append( forceEmailAddress(receiver)) elif section.lower() == 'wol': for (option, value) in configIni.items(section): if option.lower() == 'active': config["wolAction"] = forceBool(value.strip()) elif option.lower() == 'excludeproductids': config['wolActionExcludeProductIds'] = [] for productId in splitAndStrip(value, u','): config["wolActionExcludeProductIds"].append( forceProductId(productId)) elif option.lower() == 'shutdownwanted': config["wolShutdownWanted"] = forceBool( value.strip()) elif option.lower() == 'startgap': config["wolStartGap"] = forceInt(value.strip()) if config["wolStartGap"] < 0: config["wolStartGap"] = 0 elif section.lower() == 'installation': for (option, value) in configIni.items(section): if option.lower() == 'windowstart': if not value.strip(): continue if not self.TIME_REGEX.search(value.strip()): raise ValueError( u"Start time '%s' not in needed format 'HH:MM'" % value.strip()) config[ "installationWindowStartTime"] = value.strip() elif option.lower() == 'windowend': if not value.strip(): continue if not self.TIME_REGEX.search(value.strip()): raise ValueError( u"End time '%s' not in needed format 'HH:MM'" % value.strip()) config["installationWindowEndTime"] = value.strip() elif option.lower() == 'exceptproductids': config['installationWindowExceptions'] = [] for productId in splitAndStrip(value, ','): config["installationWindowExceptions"].append( forceProductId(productId)) elif section.lower().startswith('repository'): try: repository = self._getRepository( configIni, section, config['forceRepositoryActivation'], config['repositoryName'], config['installAllAvailable'], config['proxy']) config['repositories'].append(repository) except MissingConfigurationValueError as mcverr: logger.debug( u"Configuration for {section} incomplete: {error}", error=mcverr, section=section) except ConfigurationError as cerr: logger.error( u"Configuration problem in {section}: {error}", error=cerr, section=section) except Exception as err: logger.error( u"Can't load repository from {section}: {error}", error=err, section=section) else: logger.error(u"Unhandled section '%s'" % section) except Exception as exclude: raise RuntimeError(u"Failed to read config file '%s': %s" % (self.configFile, exclude)) for configFile in getRepoConfigs(config['repositoryConfigDir']): iniFile = IniFile(filename=configFile, raw=True) try: repoConfig = iniFile.parse() for section in repoConfig.sections(): if not section.lower().startswith('repository'): continue try: repository = self._getRepository( repoConfig, section, config['forceRepositoryActivation'], config['repositoryName'], config['installAllAvailable'], proxy=config['proxy']) config['repositories'].append(repository) except MissingConfigurationValueError as mcverr: logger.debug( u"Configuration for {section} in {filename} incomplete: {error}", error=mcverr, section=section, filename=configFile) except ConfigurationError as cerr: logger.error( u"Configuration problem in {section} in {filename}: {error}", error=cerr, section=section, filename=configFile) except Exception as err: logger.error( u"Can't load repository from {section} in {filename}: {error}", error=err, section=section, filename=configFile) except Exception as error: logger.error( "Unable to load repositories from {filename}: {error}", filename=configFile, error=error) return config
def selectDepotserver(self, configService, mode="mount", event=None, productIds=[], masterOnly=False): # pylint: disable=dangerous-default-value,too-many-arguments,too-many-locals,too-many-branches,too-many-statements,redefined-builtin assert mode in ("mount", "sync") productIds = forceProductIdList(productIds) logger.notice("Selecting depot for products %s", productIds) logger.notice("MasterOnly --> '%s'", masterOnly) if event and event.eventConfig.useCachedProducts: cacheDepotDir = os.path.join( self.get('cache_service', 'storage_dir'), 'depot').replace('\\', '/').replace('//', '/') logger.notice("Using depot cache: %s", cacheDepotDir) self.set_temporary_depot_path(cacheDepotDir) if RUNNING_ON_WINDOWS: self.setTemporaryDepotDrive(cacheDepotDir.split(':')[0] + ':') else: self.setTemporaryDepotDrive(cacheDepotDir) self.set( 'depot_server', 'url', 'smb://localhost/noshare/' + ('/'.join(cacheDepotDir.split('/')[1:]))) return if not configService: raise Exception("Not connected to config service") selectedDepot = None configService.backend_setOptions({"addConfigStateDefaults": True}) depotIds = [] configStates = [] dynamicDepot = False depotProtocol = 'cifs' configStates = configService.configState_getObjects(configId=[ 'clientconfig.depot.dynamic', 'clientconfig.depot.protocol', 'opsiclientd.depot_server.depot_id', 'opsiclientd.depot_server.url' ], objectId=self.get( 'global', 'host_id')) for configState in configStates: if not configState.values or not configState.values[0]: continue if configState.configId == 'opsiclientd.depot_server.url' and configState.values: try: depotUrl = forceUrl(configState.values[0]) self.set('depot_server', 'depot_id', '') self.set('depot_server', 'url', depotUrl) logger.notice( "Depot url was set to '%s' from configState %s", depotUrl, configState) return except Exception as err: # pylint: disable=broad-except logger.error( "Failed to set depot url from values %s in configState %s: %s", configState.values, configState, err) elif configState.configId == 'opsiclientd.depot_server.depot_id' and configState.values: try: depotId = forceHostId(configState.values[0]) depotIds.append(depotId) logger.notice("Depot was set to '%s' from configState %s", depotId, configState) except Exception as err: # pylint: disable=broad-except logger.error( "Failed to set depot id from values %s in configState %s: %s", configState.values, configState, err) elif not masterOnly and ( configState.configId == 'clientconfig.depot.dynamic') and configState.values: dynamicDepot = forceBool(configState.values[0]) elif configState.configId == 'clientconfig.depot.protocol' and configState.values: depotProtocol = configState.values[0] logger.info("Using depot protocol '%s' from config state '%s'", depotProtocol, configState.configId) if event and event.eventConfig.depotProtocol: logger.info("Using depot protocol '%s' from event '%s'", event.eventConfig.depotProtocol, event.eventConfig.getName()) depotProtocol = event.eventConfig.depotProtocol if depotProtocol not in ("webdav", "cifs"): logger.error("Invalid protocol %s specified, using cifs", depotProtocol) depotProtocol = "cifs" #if depotProtocol == "webdav" and mode == "mount" and not RUNNING_ON_LINUX and not self.get('global', 'install_opsi_ca_into_os_store'): # logger.error("Using cifs instead of webdav to mount depot share because global.install_opsi_ca_into_os_store is disabled") # depotProtocol = "cifs" if dynamicDepot: if not depotIds: logger.info("Dynamic depot selection enabled") else: logger.info( "Dynamic depot selection enabled, but depot is already selected" ) else: logger.info("Dynamic depot selection disabled") if not depotIds: clientToDepotservers = configService.configState_getClientToDepotserver( clientIds=[self.get('global', 'host_id')], masterOnly=bool(not dynamicDepot), productIds=productIds) if not clientToDepotservers: raise Exception("Failed to get depot config from service") depotIds = [clientToDepotservers[0]['depotId']] if dynamicDepot: depotIds.extend(clientToDepotservers[0].get( 'alternativeDepotIds', [])) logger.debug("Fetching depot servers %s from config service", depotIds) masterDepot = None alternativeDepots = [] for depot in configService.host_getObjects(type='OpsiDepotserver', id=depotIds): logger.trace("Depot: %s", depot) if depot.id == depotIds[0]: masterDepot = depot else: alternativeDepots.append(depot) if not masterDepot: raise Exception( f"Failed to get info for master depot '{depotIds[0]}'") logger.info("Master depot for products %s is %s", productIds, masterDepot.id) selectedDepot = masterDepot if dynamicDepot: if alternativeDepots: logger.info("Got alternative depots for products: %s", productIds) for index, depot in enumerate(alternativeDepots, start=1): logger.info("%d. alternative depot is %s", index, depot.id) try: clientConfig = { "clientId": self.get('global', 'host_id'), "opsiHostKey": self.get('global', 'opsi_host_key'), "ipAddress": None, "netmask": None, "defaultGateway": None } try: gateways = netifaces.gateways() # pylint: disable=c-extension-no-member clientConfig["defaultGateway"], iface_name = gateways[ 'default'][netifaces.AF_INET] # pylint: disable=c-extension-no-member addr = netifaces.ifaddresses(iface_name)[ netifaces.AF_INET][0] # pylint: disable=c-extension-no-member clientConfig["netmask"] = addr["netmask"] clientConfig["ipAddress"] = addr["addr"] except Exception as gwe: raise RuntimeError( f"Failed to get network interface with default gateway: {gwe}" ) from gwe logger.info( "Passing client configuration to depot selection algorithm: %s", clientConfig) depotSelectionAlgorithm = configService.getDepotSelectionAlgorithm( ) logger.trace("depotSelectionAlgorithm:\n%s", depotSelectionAlgorithm) currentLocals = locals() exec(depotSelectionAlgorithm, None, currentLocals) # pylint: disable=exec-used selectDepot = currentLocals['selectDepot'] selectedDepot = selectDepot( clientConfig=clientConfig, masterDepot=masterDepot, alternativeDepots=alternativeDepots) if not selectedDepot: selectedDepot = masterDepot except Exception as err: # pylint: disable=broad-except logger.error("Failed to select depot: %s", err, exc_info=True) else: logger.info("No alternative depot for products: %s", productIds) logger.notice("Selected depot for mode '%s' is '%s', protocol '%s'", mode, selectedDepot, depotProtocol) self.set('depot_server', 'depot_id', selectedDepot.id) if depotProtocol == 'webdav': self.set('depot_server', 'url', selectedDepot.depotWebdavUrl) else: self.set('depot_server', 'url', selectedDepot.depotRemoteUrl)