Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
def testForceUrlWithInvalidURLsRaisesExceptions(url):
    with pytest.raises(ValueError):
        forceUrl(url)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
def testForceUrl(url, expected):
    result = forceUrl(url)
    assert expected == result
    assert isinstance(result, unicode)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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)