def __init__(self): super(API, self).__init__() self.__plugins = set() self.__plugins_by_key = {} self.__default_map = {} self.__instances = {} self.__next = {} self.__done = set() self.env = Env()
def api_connect(context=None): """ Initialize IPA API with the provided context. `context` can be any of: * `server` (default) * `ansible-freeipa` * `cli_installer` """ env = Env() env._bootstrap() env._finalize_core(**dict(DEFAULT_CONFIG)) # available contexts are 'server', 'ansible-freeipa' and 'cli_installer' if context is None: context = 'server' api.bootstrap(context=context, debug=env.debug, log=None) api.finalize() if api.env.in_server: backend = api.Backend.ldap2 else: backend = api.Backend.rpcclient if not backend.isconnected(): backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
def gen_env_boostrap_finalize_core(etc_ipa, default_config): env = Env() #env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None) #env._finalize_core(**dict(constants.DEFAULT_CONFIG)) env._bootstrap(context='installer', confdir=etc_ipa, log=None) env._finalize_core(**dict(default_config)) return env
def api_connect(): """ Create environment, initialize api and connect to ldap2 """ env = Env() env._bootstrap() env._finalize_core(**dict(DEFAULT_CONFIG)) api.bootstrap(context='server', debug=env.debug, log=None) api.finalize() api.Backend.ldap2.connect()
def api_connect(context=None): """ Create environment, initialize api and connect to ldap2 """ env = Env() env._bootstrap() env._finalize_core(**dict(DEFAULT_CONFIG)) # available contexts are 'server', 'ansible-freeipa' and 'cli_installer' if context is None: context = 'server' api.bootstrap(context=context, debug=env.debug, log=None) api.finalize() if api.env.in_server: backend = api.Backend.ldap2 else: backend = api.Backend.rpcclient if not backend.isconnected(): backend.connect()
class API(ReadOnly): """ Dynamic API object through which `Plugin` instances are accessed. """ def __init__(self): super(API, self).__init__() self.__plugins = set() self.__plugins_by_key = {} self.__default_map = {} self.__instances = {} self.__next = {} self.__done = set() self.env = Env() @property def bases(self): raise NotImplementedError @property def packages(self): raise NotImplementedError def __len__(self): """ Return the number of plugin namespaces in this API object. """ return len(self.bases) def __iter__(self): """ Iterate (in ascending order) through plugin namespace names. """ return (base.__name__ for base in self.bases) def __contains__(self, name): """ Return True if this API object contains plugin namespace ``name``. :param name: The plugin namespace name to test for membership. """ return name in set(self) def __getitem__(self, name): """ Return the plugin namespace corresponding to ``name``. :param name: The name of the plugin namespace you wish to retrieve. """ if name in self: try: return getattr(self, name) except AttributeError: pass raise KeyError(name) def __call__(self): """ Iterate (in ascending order by name) through plugin namespaces. """ for name in self: try: yield getattr(self, name) except AttributeError: raise KeyError(name) def is_production_mode(self): """ If the object has self.env.mode defined and that mode is production return True, otherwise return False. """ return getattr(self.env, 'mode', None) == 'production' def __doing(self, name): if name in self.__done: raise Exception('%s.%s() already called' % (self.__class__.__name__, name)) self.__done.add(name) def __do_if_not_done(self, name): if name not in self.__done: getattr(self, name)() def isdone(self, name): return name in self.__done def bootstrap(self, parser=None, **overrides): """ Initialize environment variables and logging. """ self.__doing('bootstrap') self.log = log_mgr.get_logger(self) self.env._bootstrap(**overrides) self.env._finalize_core(**dict(DEFAULT_CONFIG)) # Add the argument parser if not parser: parser = self.build_global_parser() self.parser = parser root_logger = logging.getLogger() # If logging has already been configured somewhere else (like in the # installer), don't add handlers or change levels: if root_logger.handlers or self.env.validate_api: return if self.env.debug: level = logging.DEBUG else: level = logging.INFO root_logger.setLevel(level) for attr in self.env: match = re.match( r'^log_logger_level_' r'(debug|info|warn|warning|error|critical|\d+)$', attr) if not match: continue level = ipa_log_manager.convert_log_level(match.group(1)) value = getattr(self.env, attr) regexps = re.split('\s*,\s*', value) # Add the regexp, it maps to the configured level for regexp in regexps: root_logger.addFilter(ipa_log_manager.Filter(regexp, level)) # Add stderr handler: level = logging.INFO if self.env.debug: level = logging.DEBUG else: if self.env.context == 'cli': if self.env.verbose > 0: level = logging.INFO else: level = logging.WARNING handler = logging.StreamHandler() handler.setLevel(level) handler.setFormatter(ipa_log_manager.Formatter(LOGGING_FORMAT_STDERR)) root_logger.addHandler(handler) # Add file handler: if self.env.mode in ('dummy', 'unit_test'): return # But not if in unit-test mode if self.env.log is None: return log_dir = path.dirname(self.env.log) if not path.isdir(log_dir): try: os.makedirs(log_dir) except OSError: logger.error('Could not create log_dir %r', log_dir) return level = logging.INFO if self.env.debug: level = logging.DEBUG try: handler = logging.FileHandler(self.env.log) except IOError as e: logger.error('Cannot open log file %r: %s', self.env.log, e) return handler.setLevel(level) handler.setFormatter(ipa_log_manager.Formatter(LOGGING_FORMAT_FILE)) root_logger.addHandler(handler) def build_global_parser(self, parser=None, context=None): """ Add global options to an optparse.OptionParser instance. """ def config_file_callback(option, opt, value, parser): if not ipautil.file_exists(value): parser.error( _("%(filename)s: file not found") % dict(filename=value)) parser.values.conf = value if parser is None: parser = optparse.OptionParser( add_help_option=False, formatter=IPAHelpFormatter(), usage='%prog [global-options] COMMAND [command-options]', description='Manage an IPA domain', version=('VERSION: %s, API_VERSION: %s' % (VERSION, API_VERSION)), epilog='\n'.join([ 'See "ipa help topics" for available help topics.', 'See "ipa help <TOPIC>" for more information on a ' 'specific topic.', 'See "ipa help commands" for the full list of commands.', 'See "ipa <COMMAND> --help" for more information on a ' 'specific command.', ])) parser.disable_interspersed_args() parser.add_option("-h", "--help", action="help", help='Show this help message and exit') parser.add_option( '-e', dest='env', metavar='KEY=VAL', action='append', help='Set environment variable KEY to VAL', ) parser.add_option( '-c', dest='conf', metavar='FILE', action='callback', callback=config_file_callback, type='string', help='Load configuration from FILE.', ) parser.add_option( '-d', '--debug', action='store_true', help='Produce full debuging output', ) parser.add_option( '--delegate', action='store_true', help='Delegate the TGT to the IPA server', ) parser.add_option( '-v', '--verbose', action='count', help= 'Produce more verbose output. A second -v displays the XML-RPC request', ) if context == 'cli': parser.add_option('-a', '--prompt-all', action='store_true', help='Prompt for ALL values (even if optional)') parser.add_option('-n', '--no-prompt', action='store_false', dest='interactive', help='Prompt for NO values (even if required)') parser.add_option( '-f', '--no-fallback', action='store_false', dest='fallback', help='Only use the server configured in /etc/ipa/default.conf') return parser def bootstrap_with_global_options(self, parser=None, context=None): parser = self.build_global_parser(parser, context) (options, args) = parser.parse_args() overrides = {} if options.env is not None: assert type(options.env) is list for item in options.env: try: (key, value) = item.split('=', 1) except ValueError: # FIXME: this should raise an IPA exception with an # error code. # --Jason, 2008-10-31 pass overrides[str(key.strip())] = value.strip() for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive', 'fallback', 'delegate'): value = getattr(options, key, None) if value is not None: overrides[key] = value if hasattr(options, 'prod'): overrides['webui_prod'] = options.prod if context is not None: overrides['context'] = context self.bootstrap(parser, **overrides) return (options, args) def load_plugins(self): """ Load plugins from all standard locations. `API.bootstrap` will automatically be called if it hasn't been already. """ self.__doing('load_plugins') self.__do_if_not_done('bootstrap') if self.env.mode in ('dummy', 'unit_test'): return for package in self.packages: self.add_package(package) # FIXME: This method has no unit test def add_package(self, package): """ Add plugin modules from the ``package``. :param package: A package from which to add modules. """ package_name = package.__name__ package_file = package.__file__ package_dir = path.dirname(path.abspath(package_file)) parent = sys.modules[package_name.rpartition('.')[0]] parent_dir = path.dirname(path.abspath(parent.__file__)) if parent_dir == package_dir: raise errors.PluginsPackageError(name=package_name, file=package_file) logger.debug("importing all plugin modules in %s...", package_name) modules = getattr(package, 'modules', find_modules_in_dir(package_dir)) modules = ['.'.join((package_name, name)) for name in modules] for name in modules: logger.debug("importing plugin module %s", name) try: module = importlib.import_module(name) except errors.SkipPluginModule as e: logger.debug("skipping plugin module %s: %s", name, e.reason) continue except Exception as e: if self.env.startup_traceback: logger.exception("could not load plugin module %s", name) raise try: self.add_module(module) except errors.PluginModuleError as e: logger.debug("%s", e) def add_module(self, module): """ Add plugins from the ``module``. :param module: A module from which to add plugins. """ try: register = module.register except AttributeError: pass else: if isinstance(register, Registry): for kwargs in register: self.add_plugin(**kwargs) return raise errors.PluginModuleError(name=module.__name__) def add_plugin(self, plugin, override=False, no_fail=False): """ Add the plugin ``plugin``. :param plugin: A subclass of `Plugin` to attempt to add. :param override: If true, override an already added plugin. """ if not callable(plugin): raise TypeError('plugin must be callable; got %r' % plugin) # Find the base class or raise SubclassError: for base in plugin.bases: if issubclass(base, self.bases): break else: raise errors.PluginSubclassError( plugin=plugin, bases=self.bases, ) # Check override: prev = self.__plugins_by_key.get(plugin.full_name) if prev: if not override: if no_fail: return else: # Must use override=True to override: raise errors.PluginOverrideError( base=base.__name__, name=plugin.name, plugin=plugin, ) self.__plugins.remove(prev) self.__next[plugin] = prev else: if override: if no_fail: return else: # There was nothing already registered to override: raise errors.PluginMissingOverrideError( base=base.__name__, name=plugin.name, plugin=plugin, ) # The plugin is okay, add to sub_d: self.__plugins.add(plugin) self.__plugins_by_key[plugin.full_name] = plugin def finalize(self): """ Finalize the registration, instantiate the plugins. `API.bootstrap` will automatically be called if it hasn't been already. """ self.__doing('finalize') self.__do_if_not_done('load_plugins') if self.env.env_confdir is not None: if self.env.env_confdir == self.env.confdir: logger.info("IPA_CONFDIR env sets confdir to '%s'.", self.env.confdir) for plugin in self.__plugins: if not self.env.validate_api: if plugin.full_name not in DEFAULT_PLUGINS: continue else: try: default_version = self.__default_map[plugin.name] except KeyError: pass else: # Technicall plugin.version is not an API version. The # APIVersion class can handle plugin versions. It's more # lean than pkg_resource.parse_version(). version = ipautil.APIVersion(plugin.version) default_version = ipautil.APIVersion(default_version) if version < default_version: continue self.__default_map[plugin.name] = plugin.version production_mode = self.is_production_mode() for base in self.bases: for plugin in self.__plugins: if not any(issubclass(b, base) for b in plugin.bases): continue if not self.env.plugins_on_demand: self._get(plugin) name = base.__name__ if not production_mode: assert not hasattr(self, name) setattr(self, name, APINameSpace(self, base)) for instance in six.itervalues(self.__instances): if not production_mode: assert instance.api is self if not self.env.plugins_on_demand: instance.ensure_finalized() if not production_mode: assert islocked(instance) self.__finalized = True if not production_mode: lock(self) def _get(self, plugin): if not callable(plugin): raise TypeError('plugin must be callable; got %r' % plugin) if plugin not in self.__plugins: raise KeyError(plugin) try: instance = self.__instances[plugin] except KeyError: instance = self.__instances[plugin] = plugin(self) return instance def get_plugin_next(self, plugin): if not callable(plugin): raise TypeError('plugin must be callable; got %r' % plugin) return self.__next[plugin]
def promote_check(installer): options = installer installer._enrollment_performed = False installer._top_dir = tempfile.mkdtemp("ipa") # check selinux status, http and DS ports, NTP conflicting services common_check(options.no_ntp) client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) if not client_fstore.has_files(): ensure_enrolled(installer) else: if (options.domain_name or options.server or options.realm_name or options.host_name or options.password or options.keytab): print("IPA client is already configured on this system, ignoring " "the --domain, --server, --realm, --hostname, --password " "and --keytab options.") # The NTP configuration can not be touched on pre-installed client: if options.no_ntp or options.ntp_servers or options.ntp_pool: raise ScriptError( "NTP configuration cannot be updated during promotion") sstore = sysrestore.StateFile(paths.SYSRESTORE) fstore = sysrestore.FileStore(paths.SYSRESTORE) env = Env() env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None) env._finalize_core(**dict(constants.DEFAULT_CONFIG)) # pylint: disable=no-member xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host)) api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=installutils.realm_to_ldapi_uri(env.realm), xmlrpc_uri=xmlrpc_uri) # pylint: enable=no-member api.finalize() config = ReplicaConfig() config.realm_name = api.env.realm config.host_name = api.env.host config.domain_name = api.env.domain config.master_host_name = api.env.server config.ca_host_name = api.env.ca_host config.kra_host_name = config.ca_host_name config.ca_ds_port = 389 config.setup_ca = options.setup_ca config.setup_kra = options.setup_kra config.dir = installer._top_dir config.basedn = api.env.basedn http_pkcs12_file = None http_pkcs12_info = None http_ca_cert = None dirsrv_pkcs12_file = None dirsrv_pkcs12_info = None dirsrv_ca_cert = None pkinit_pkcs12_file = None pkinit_pkcs12_info = None pkinit_ca_cert = None if options.http_cert_files: if options.http_pin is None: options.http_pin = installutils.read_password( "Enter Apache Server private key unlock", confirm=False, validate=False, retry=False) if options.http_pin is None: raise ScriptError( "Apache Server private key unlock password required") http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12( cert_files=options.http_cert_files, key_password=options.http_pin, key_nickname=options.http_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) http_pkcs12_info = (http_pkcs12_file.name, http_pin) if options.dirsrv_cert_files: if options.dirsrv_pin is None: options.dirsrv_pin = installutils.read_password( "Enter Directory Server private key unlock", confirm=False, validate=False, retry=False) if options.dirsrv_pin is None: raise ScriptError( "Directory Server private key unlock password required") dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12( cert_files=options.dirsrv_cert_files, key_password=options.dirsrv_pin, key_nickname=options.dirsrv_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin) if options.pkinit_cert_files: if options.pkinit_pin is None: options.pkinit_pin = installutils.read_password( "Enter Kerberos KDC private key unlock", confirm=False, validate=False, retry=False) if options.pkinit_pin is None: raise ScriptError( "Kerberos KDC private key unlock password required") pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12( cert_files=options.pkinit_cert_files, key_password=options.pkinit_pin, key_nickname=options.pkinit_cert_name, ca_cert_files=options.ca_cert_files, realm_name=config.realm_name) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) if (options.http_cert_files and options.dirsrv_cert_files and http_ca_cert != dirsrv_ca_cert): raise RuntimeError("Apache Server SSL certificate and Directory " "Server SSL certificate are not signed by the same" " CA certificate") if (options.http_cert_files and options.pkinit_cert_files and http_ca_cert != pkinit_ca_cert): raise RuntimeError("Apache Server SSL certificate and PKINIT KDC " "certificate are not signed by the same CA " "certificate") installutils.verify_fqdn(config.host_name, options.no_host_dns) installutils.verify_fqdn(config.master_host_name, options.no_host_dns) ccache = os.environ['KRB5CCNAME'] kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env), paths.KRB5_KEYTAB, ccache) cafile = paths.IPA_CA_CRT if not os.path.isfile(cafile): raise RuntimeError("CA cert file is not available! Please reinstall" "the client and try again.") ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name) xmlrpc_uri = 'https://{}/ipa/xml'.format( ipautil.format_netloc(config.master_host_name)) remote_api = create_api(mode=None) remote_api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=ldapuri, xmlrpc_uri=xmlrpc_uri) remote_api.finalize() installer._remote_api = remote_api with rpc_client(remote_api) as client: check_remote_version(client, parse_version(api.env.version)) check_remote_fips_mode(client, api.env.fips_mode) conn = remote_api.Backend.ldap2 replman = None try: # Try out authentication conn.connect(ccache=ccache) replman = ReplicationManager(config.realm_name, config.master_host_name, None) promotion_check_ipa_domain(conn, remote_api.env.basedn) # Make sure that domain fulfills minimal domain level # requirement domain_level = current_domain_level(remote_api) check_domain_level_is_supported(domain_level) if domain_level < constants.MIN_DOMAIN_LEVEL: raise RuntimeError( "Cannot promote this client to a replica. The domain level " "must be raised to {mindomainlevel} before the replica can be " "installed".format(mindomainlevel=constants.MIN_DOMAIN_LEVEL)) # Check authorization result = remote_api.Command['hostgroup_find']( cn=u'ipaservers', host=[unicode(api.env.host)])['result'] add_to_ipaservers = not result if add_to_ipaservers: if options.password and not options.admin_password: raise errors.ACIError(info="Not authorized") if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: installutils.check_creds(options, config.realm_name) installer._ccache = os.environ.get('KRB5CCNAME') finally: os.environ['KRB5CCNAME'] = ccache conn.disconnect() conn.connect(ccache=installer._ccache) try: result = remote_api.Command['hostgroup_show']( u'ipaservers', all=True, rights=True)['result'] if 'w' not in result['attributelevelrights']['member']: raise errors.ACIError(info="Not authorized") finally: conn.disconnect() conn.connect(ccache=ccache) # Check that we don't already have a replication agreement if replman.get_replication_agreement(config.host_name): msg = ("A replication agreement for this host already exists. " "It needs to be removed.\n" "Run this command:\n" " %% ipa-replica-manage del {host} --force".format( host=config.host_name)) raise ScriptError(msg, rval=3) # Detect if the other master can handle replication managers # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'), ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name)) try: conn.get_entry(dn) except errors.NotFound: msg = ("The Replication Managers group is not available in " "the domain. Replica promotion requires the use of " "Replication Managers to be able to replicate data. " "Upgrade the peer master or use the ipa-replica-prepare " "command on the master and use a prep file to install " "this replica.") logger.error("%s", msg) raise ScriptError(rval=3) dns_masters = remote_api.Object['dnsrecord'].get_dns_masters() if dns_masters: if not options.no_host_dns: logger.debug('Check forward/reverse DNS resolution') resolution_ok = ( check_dns_resolution(config.master_host_name, dns_masters) and check_dns_resolution(config.host_name, dns_masters)) if not resolution_ok and installer.interactive: if not ipautil.user_input("Continue?", False): raise ScriptError(rval=0) else: logger.debug('No IPA DNS servers, ' 'skipping forward/reverse resolution check') entry_attrs = conn.get_ipa_config() subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0] if subject_base is not None: config.subject_base = DN(subject_base) # Find if any server has a CA ca_host = service.find_providing_server('CA', conn, config.ca_host_name) if ca_host is not None: config.ca_host_name = ca_host ca_enabled = True if options.dirsrv_cert_files: logger.error("Certificates could not be provided when " "CA is present on some master.") raise ScriptError(rval=3) else: if options.setup_ca: logger.error("The remote master does not have a CA " "installed, can't set up CA") raise ScriptError(rval=3) ca_enabled = False if not options.dirsrv_cert_files: logger.error("Cannot issue certificates: a CA is not " "installed. Use the --http-cert-file, " "--dirsrv-cert-file options to provide " "custom certificates.") raise ScriptError(rval=3) kra_host = service.find_providing_server('KRA', conn, config.kra_host_name) if kra_host is not None: config.kra_host_name = kra_host kra_enabled = True else: if options.setup_kra: logger.error("There is no KRA server in the domain, " "can't setup a KRA clone") raise ScriptError(rval=3) kra_enabled = False if ca_enabled: options.realm_name = config.realm_name options.host_name = config.host_name ca.install_check(False, config, options) if kra_enabled: try: kra.install_check(remote_api, config, options) except RuntimeError as e: raise ScriptError(e) if options.setup_dns: dns.install_check(False, remote_api, True, options, config.host_name) config.ips = dns.ip_addresses else: config.ips = installutils.get_server_ip_address( config.host_name, not installer.interactive, False, options.ip_addresses) # check addresses here, dns module is doing own check no_matching_interface_for_ip_address_warning(config.ips) if options.setup_adtrust: adtrust.install_check(False, options, remote_api) except errors.ACIError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nInsufficient privileges to promote the server." "\nPossible issues:" "\n- A user has insufficient privileges" "\n- This client has insufficient privileges " "to become an IPA replica") except errors.LDAPError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nUnable to connect to LDAP server %s" % config.master_host_name) finally: if replman and replman.conn: replman.conn.unbind() if conn.isconnected(): conn.disconnect() # check connection if not options.skip_conncheck: if add_to_ipaservers: # use user's credentials when the server host is not ipaservers if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: replica_conn_check(config.master_host_name, config.host_name, config.realm_name, options.setup_ca, 389, options.admin_password, principal=options.principal, ca_cert_file=cafile) finally: if add_to_ipaservers: os.environ['KRB5CCNAME'] = ccache installer._ca_enabled = ca_enabled installer._kra_enabled = kra_enabled installer._ca_file = cafile installer._fstore = fstore installer._sstore = sstore installer._config = config installer._add_to_ipaservers = add_to_ipaservers installer._dirsrv_pkcs12_file = dirsrv_pkcs12_file installer._dirsrv_pkcs12_info = dirsrv_pkcs12_info installer._http_pkcs12_file = http_pkcs12_file installer._http_pkcs12_info = http_pkcs12_info installer._pkinit_pkcs12_file = pkinit_pkcs12_file installer._pkinit_pkcs12_info = pkinit_pkcs12_info
# # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # """ WSGI appliction for IPA server. """ from ipalib import api from ipalib.config import Env from ipalib.constants import DEFAULT_CONFIG # Determine what debug level is configured. We can only do this # by reading in the configuration file(s). The server always reads # default.conf and will also read in `context'.conf. env = Env() env._bootstrap(context='server', log=None) env._finalize_core(**dict(DEFAULT_CONFIG)) # Initialize the API with the proper debug level api.bootstrap(context='server', debug=env.debug, log=None) try: api.finalize() except Exception as e: api.log.error('Failed to start IPA: %s' % e) else: api.log.info('*** PROCESS START ***') # This is the WSGI callable: def application(environ, start_response): if not environ['wsgi.multithread']:
def __init__(self): super(API, self).__init__() self.__plugins = {} self.__done = set() self.env = Env()
class API(ReadOnly): """ Dynamic API object through which `Plugin` instances are accessed. """ register = Registrar() def __init__(self): super(API, self).__init__() self.__plugins = {} self.__done = set() self.env = Env() @property def bases(self): raise NotImplementedError @property def modules(self): raise NotImplementedError def __len__(self): """ Return the number of plugin namespaces in this API object. """ return len(self.bases) def __iter__(self): """ Iterate (in ascending order) through plugin namespace names. """ return (base.__name__ for base in self.bases) def __contains__(self, name): """ Return True if this API object contains plugin namespace ``name``. :param name: The plugin namespace name to test for membership. """ return name in set(self) def __getitem__(self, name): """ Return the plugin namespace corresponding to ``name``. :param name: The name of the plugin namespace you wish to retrieve. """ if name in self: try: return getattr(self, name) except AttributeError: pass raise KeyError(name) def __call__(self): """ Iterate (in ascending order by name) through plugin namespaces. """ for name in self: try: yield getattr(self, name) except AttributeError: raise KeyError(name) def is_production_mode(self): """ If the object has self.env.mode defined and that mode is production return True, otherwise return False. """ return getattr(self.env, 'mode', None) == 'production' def __doing(self, name): if name in self.__done: raise Exception( '%s.%s() already called' % (self.__class__.__name__, name) ) self.__done.add(name) def __do_if_not_done(self, name): if name not in self.__done: getattr(self, name)() def isdone(self, name): return name in self.__done def bootstrap(self, parser=None, **overrides): """ Initialize environment variables and logging. """ self.__doing('bootstrap') self.log_mgr = log_mgr log = log_mgr.root_logger self.log = log self.env._bootstrap(**overrides) self.env._finalize_core(**dict(DEFAULT_CONFIG)) # Add the argument parser if not parser: parser = self.build_global_parser() self.parser = parser # If logging has already been configured somewhere else (like in the # installer), don't add handlers or change levels: if log_mgr.configure_state != 'default' or self.env.validate_api: return log_mgr.default_level = 'info' log_mgr.configure_from_env(self.env, configure_state='api') # Add stderr handler: level = 'info' if self.env.debug: level = 'debug' else: if self.env.context == 'cli': if self.env.verbose > 0: level = 'info' else: level = 'warning' if 'console' in log_mgr.handlers: log_mgr.remove_handler('console') log_mgr.create_log_handlers([dict(name='console', stream=sys.stderr, level=level, format=LOGGING_FORMAT_STDERR)]) # Add file handler: if self.env.mode in ('dummy', 'unit_test'): return # But not if in unit-test mode if self.env.log is None: return log_dir = path.dirname(self.env.log) if not path.isdir(log_dir): try: os.makedirs(log_dir) except OSError: log.error('Could not create log_dir %r', log_dir) return level = 'info' if self.env.debug: level = 'debug' try: log_mgr.create_log_handlers([dict(name='file', filename=self.env.log, level=level, format=LOGGING_FORMAT_FILE)]) except IOError as e: log.error('Cannot open log file %r: %s', self.env.log, e) return def build_global_parser(self, parser=None, context=None): """ Add global options to an optparse.OptionParser instance. """ if parser is None: parser = optparse.OptionParser( add_help_option=False, formatter=IPAHelpFormatter(), usage='%prog [global-options] COMMAND [command-options]', description='Manage an IPA domain', version=('VERSION: %s, API_VERSION: %s' % (VERSION, API_VERSION)), epilog='\n'.join([ 'See "ipa help topics" for available help topics.', 'See "ipa help <TOPIC>" for more information on a ' 'specific topic.', 'See "ipa help commands" for the full list of commands.', 'See "ipa <COMMAND> --help" for more information on a ' 'specific command.', ])) parser.disable_interspersed_args() parser.add_option("-h", "--help", action="help", help='Show this help message and exit') parser.add_option('-e', dest='env', metavar='KEY=VAL', action='append', help='Set environment variable KEY to VAL', ) parser.add_option('-c', dest='conf', metavar='FILE', help='Load configuration from FILE', ) parser.add_option('-d', '--debug', action='store_true', help='Produce full debuging output', ) parser.add_option('--delegate', action='store_true', help='Delegate the TGT to the IPA server', ) parser.add_option('-v', '--verbose', action='count', help='Produce more verbose output. A second -v displays the XML-RPC request', ) if context == 'cli': parser.add_option('-a', '--prompt-all', action='store_true', help='Prompt for ALL values (even if optional)' ) parser.add_option('-n', '--no-prompt', action='store_false', dest='interactive', help='Prompt for NO values (even if required)' ) parser.add_option('-f', '--no-fallback', action='store_false', dest='fallback', help='Only use the server configured in /etc/ipa/default.conf' ) return parser def bootstrap_with_global_options(self, parser=None, context=None): parser = self.build_global_parser(parser, context) (options, args) = parser.parse_args() overrides = {} if options.env is not None: assert type(options.env) is list for item in options.env: try: (key, value) = item.split('=', 1) except ValueError: # FIXME: this should raise an IPA exception with an # error code. # --Jason, 2008-10-31 pass overrides[str(key.strip())] = value.strip() for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive', 'fallback', 'delegate'): value = getattr(options, key, None) if value is not None: overrides[key] = value if hasattr(options, 'prod'): overrides['webui_prod'] = options.prod if context is not None: overrides['context'] = context self.bootstrap(parser, **overrides) return (options, args) def load_plugins(self): """ Load plugins from all standard locations. `API.bootstrap` will automatically be called if it hasn't been already. """ self.__doing('load_plugins') self.__do_if_not_done('bootstrap') if self.env.mode in ('dummy', 'unit_test'): return for module in self.modules: self.import_plugins(module) # FIXME: This method has no unit test def import_plugins(self, module): """ Import plugins from ``module``. :param module: Name of the module to import. This might be a wildcard in the form ```package.*``` in which case all modules from the given package are loaded. """ if module.endswith('.*'): subpackage = module[:-2] try: plugins = importlib.import_module(subpackage) except ImportError as e: self.log.error("cannot import plugins sub-package %s: %s", subpackage, e) raise package, dot, part = subpackage.rpartition('.') parent = sys.modules[package] parent_dir = path.dirname(path.abspath(parent.__file__)) plugins_dir = path.dirname(path.abspath(plugins.__file__)) if parent_dir == plugins_dir: raise errors.PluginsPackageError( name=subpackage, file=plugins.__file__ ) self.log.debug("importing all plugin modules in %s...", subpackage) modules = find_modules_in_dir(plugins_dir) modules = ['.'.join((subpackage, name)) for name in modules] else: modules = [module] for name in modules: self.log.debug("importing plugin module %s", name) try: module = importlib.import_module(name) except errors.SkipPluginModule as e: self.log.debug("skipping plugin module %s: %s", name, e.reason) except Exception as e: if self.env.startup_traceback: import traceback self.log.error("could not load plugin module %s\n%s", name, traceback.format_exc()) raise else: self.add_module(module) def add_module(self, module): """ Add plugins from the ``module``. :param module: A module from which to add plugins. """ for name in dir(module): klass = getattr(module, name) if not inspect.isclass(klass): continue if klass not in self.register: continue kwargs = self.register[klass] self.add_plugin(klass, **kwargs) def add_plugin(self, klass, override=False): """ Add the plugin ``klass``. :param klass: A subclass of `Plugin` to attempt to add. :param override: If true, override an already added plugin. """ if not inspect.isclass(klass): raise TypeError('plugin must be a class; got %r' % klass) # Find the base class or raise SubclassError: found = False for base in self.bases: if not issubclass(klass, base): continue sub_d = self.__plugins.setdefault(base, {}) found = True if sub_d.get(klass.__name__) is klass: continue # Check override: if klass.__name__ in sub_d: if not override: # Must use override=True to override: raise errors.PluginOverrideError( base=base.__name__, name=klass.__name__, plugin=klass, ) else: if override: # There was nothing already registered to override: raise errors.PluginMissingOverrideError( base=base.__name__, name=klass.__name__, plugin=klass, ) # The plugin is okay, add to sub_d: sub_d[klass.__name__] = klass if not found: raise errors.PluginSubclassError( plugin=klass, bases=self.bases, ) def finalize(self): """ Finalize the registration, instantiate the plugins. `API.bootstrap` will automatically be called if it hasn't been already. """ self.__doing('finalize') self.__do_if_not_done('load_plugins') production_mode = self.is_production_mode() plugins = {} plugin_info = {} for base in self.bases: name = base.__name__ sub_d = self.__plugins.get(base, {}) members = [] for klass in sub_d.values(): try: instance = plugins[klass] except KeyError: instance = plugins[klass] = klass(self) members.append(instance) plugin_info.setdefault( '%s.%s' % (klass.__module__, klass.__name__), []).append(name) if not production_mode: assert not hasattr(self, name) setattr(self, name, NameSpace(members)) for klass, instance in plugins.items(): if not production_mode: assert instance.api is self if klass.finalize_early or not self.env.plugins_on_demand: instance.ensure_finalized() if not production_mode: assert islocked(instance) self.__finalized = True self.plugins = tuple((k, tuple(v)) for k, v in plugin_info.items()) if not production_mode: lock(self)
def promote_check(installer): options = installer installer._enrollment_performed = False installer._top_dir = tempfile.mkdtemp("ipa") # check selinux status, http and DS ports, NTP conflicting services common_check(options.no_ntp) client_fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) if not client_fstore.has_files(): # One-step replica installation if options.password and options.admin_password: raise ScriptError("--password and --admin-password options are " "mutually exclusive") ensure_enrolled(installer) else: if (options.domain_name or options.server or options.realm_name or options.host_name or options.password or options.keytab): print("IPA client is already configured on this system, ignoring " "the --domain, --server, --realm, --hostname, --password " "and --keytab options.") # The NTP configuration can not be touched on pre-installed client: if options.no_ntp or options.ntp_servers or options.ntp_pool: raise ScriptError( "NTP configuration cannot be updated during promotion") sstore = sysrestore.StateFile(paths.SYSRESTORE) fstore = sysrestore.FileStore(paths.SYSRESTORE) env = Env() env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None) env._finalize_core(**dict(constants.DEFAULT_CONFIG)) # pylint: disable=no-member xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host)) api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=ipaldap.realm_to_ldapi_uri(env.realm), xmlrpc_uri=xmlrpc_uri) # pylint: enable=no-member api.finalize() config = ReplicaConfig() config.realm_name = api.env.realm config.host_name = api.env.host config.domain_name = api.env.domain config.master_host_name = api.env.server if not api.env.ca_host or api.env.ca_host == api.env.host: # ca_host has not been configured explicitly, prefer source master config.ca_host_name = api.env.server else: # default to ca_host from IPA config config.ca_host_name = api.env.ca_host config.kra_host_name = config.ca_host_name config.ca_ds_port = 389 config.setup_ca = options.setup_ca config.setup_kra = options.setup_kra config.dir = installer._top_dir config.basedn = api.env.basedn config.hidden_replica = options.hidden_replica http_pkcs12_file = None http_pkcs12_info = None http_ca_cert = None dirsrv_pkcs12_file = None dirsrv_pkcs12_info = None dirsrv_ca_cert = None pkinit_pkcs12_file = None pkinit_pkcs12_info = None pkinit_ca_cert = None if options.http_cert_files: if options.http_pin is None: options.http_pin = installutils.read_password( "Enter Apache Server private key unlock", confirm=False, validate=False, retry=False) if options.http_pin is None: raise ScriptError( "Apache Server private key unlock password required") http_pkcs12_file, http_pin, http_ca_cert = load_pkcs12( cert_files=options.http_cert_files, key_password=options.http_pin, key_nickname=options.http_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) http_pkcs12_info = (http_pkcs12_file.name, http_pin) if options.dirsrv_cert_files: if options.dirsrv_pin is None: options.dirsrv_pin = installutils.read_password( "Enter Directory Server private key unlock", confirm=False, validate=False, retry=False) if options.dirsrv_pin is None: raise ScriptError( "Directory Server private key unlock password required") dirsrv_pkcs12_file, dirsrv_pin, dirsrv_ca_cert = load_pkcs12( cert_files=options.dirsrv_cert_files, key_password=options.dirsrv_pin, key_nickname=options.dirsrv_cert_name, ca_cert_files=options.ca_cert_files, host_name=config.host_name) dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin) if options.pkinit_cert_files: if options.pkinit_pin is None: options.pkinit_pin = installutils.read_password( "Enter Kerberos KDC private key unlock", confirm=False, validate=False, retry=False) if options.pkinit_pin is None: raise ScriptError( "Kerberos KDC private key unlock password required") pkinit_pkcs12_file, pkinit_pin, pkinit_ca_cert = load_pkcs12( cert_files=options.pkinit_cert_files, key_password=options.pkinit_pin, key_nickname=options.pkinit_cert_name, ca_cert_files=options.ca_cert_files, realm_name=config.realm_name) pkinit_pkcs12_info = (pkinit_pkcs12_file.name, pkinit_pin) if (options.http_cert_files and options.dirsrv_cert_files and http_ca_cert != dirsrv_ca_cert): raise RuntimeError("Apache Server SSL certificate and Directory " "Server SSL certificate are not signed by the same" " CA certificate") if (options.http_cert_files and options.pkinit_cert_files and http_ca_cert != pkinit_ca_cert): raise RuntimeError("Apache Server SSL certificate and PKINIT KDC " "certificate are not signed by the same CA " "certificate") installutils.verify_fqdn(config.host_name, options.no_host_dns) installutils.verify_fqdn(config.master_host_name, options.no_host_dns) ccache = os.environ['KRB5CCNAME'] kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env), paths.KRB5_KEYTAB, ccache) cafile = paths.IPA_CA_CRT if not os.path.isfile(cafile): raise RuntimeError("CA cert file is not available! Please reinstall" "the client and try again.") ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name) xmlrpc_uri = 'https://{}/ipa/xml'.format( ipautil.format_netloc(config.master_host_name)) remote_api = create_api(mode=None) remote_api.bootstrap(in_server=True, context='installer', confdir=paths.ETC_IPA, ldap_uri=ldapuri, xmlrpc_uri=xmlrpc_uri) remote_api.finalize() installer._remote_api = remote_api with rpc_client(remote_api) as client: check_remote_version(client, parse_version(api.env.version)) check_remote_fips_mode(client, api.env.fips_mode) conn = remote_api.Backend.ldap2 replman = None try: # Try out authentication conn.connect(ccache=ccache) replman = ReplicationManager(config.realm_name, config.master_host_name, None) promotion_check_ipa_domain(conn, remote_api.env.basedn) # Make sure that domain fulfills minimal domain level # requirement domain_level = current_domain_level(remote_api) check_domain_level_is_supported(domain_level) if domain_level < constants.MIN_DOMAIN_LEVEL: raise RuntimeError( "Cannot promote this client to a replica. The domain level " "must be raised to {mindomainlevel} before the replica can be " "installed".format( mindomainlevel=constants.MIN_DOMAIN_LEVEL )) # Check authorization result = remote_api.Command['hostgroup_find']( cn=u'ipaservers', host=[unicode(api.env.host)] )['result'] add_to_ipaservers = not result if add_to_ipaservers: if options.password and not options.admin_password: raise errors.ACIError(info="Not authorized") if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: installutils.check_creds(options, config.realm_name) installer._ccache = os.environ.get('KRB5CCNAME') finally: os.environ['KRB5CCNAME'] = ccache conn.disconnect() conn.connect(ccache=installer._ccache) try: result = remote_api.Command['hostgroup_show']( u'ipaservers', all=True, rights=True )['result'] if 'w' not in result['attributelevelrights']['member']: raise errors.ACIError(info="Not authorized") finally: conn.disconnect() conn.connect(ccache=ccache) # Check that we don't already have a replication agreement if replman.get_replication_agreement(config.host_name): msg = ("A replication agreement for this host already exists. " "It needs to be removed.\n" "Run this command:\n" " %% ipa-replica-manage del {host} --force" .format(host=config.host_name)) raise ScriptError(msg, rval=3) # Detect if the other master can handle replication managers # cn=replication managers,cn=sysaccounts,cn=etc,$SUFFIX dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'), ('cn', 'etc'), ipautil.realm_to_suffix(config.realm_name)) try: conn.get_entry(dn) except errors.NotFound: msg = ("The Replication Managers group is not available in " "the domain. Replica promotion requires the use of " "Replication Managers to be able to replicate data. " "Upgrade the peer master or use the ipa-replica-prepare " "command on the master and use a prep file to install " "this replica.") logger.error("%s", msg) raise ScriptError(rval=3) dns_masters = remote_api.Object['dnsrecord'].get_dns_masters() if dns_masters: if not options.no_host_dns: logger.debug('Check forward/reverse DNS resolution') resolution_ok = ( check_dns_resolution(config.master_host_name, dns_masters) and check_dns_resolution(config.host_name, dns_masters)) if not resolution_ok and installer.interactive: if not ipautil.user_input("Continue?", False): raise ScriptError(rval=0) else: logger.debug('No IPA DNS servers, ' 'skipping forward/reverse resolution check') entry_attrs = conn.get_ipa_config() subject_base = entry_attrs.get('ipacertificatesubjectbase', [None])[0] if subject_base is not None: config.subject_base = DN(subject_base) # Find any server with a CA ca_host = find_providing_server( 'CA', conn, [config.ca_host_name] ) if ca_host is not None: config.ca_host_name = ca_host ca_enabled = True if options.dirsrv_cert_files: logger.error("Certificates could not be provided when " "CA is present on some master.") raise ScriptError(rval=3) else: if options.setup_ca: logger.error("The remote master does not have a CA " "installed, can't set up CA") raise ScriptError(rval=3) ca_enabled = False if not options.dirsrv_cert_files: logger.error("Cannot issue certificates: a CA is not " "installed. Use the --http-cert-file, " "--dirsrv-cert-file options to provide " "custom certificates.") raise ScriptError(rval=3) # Find any server with a KRA kra_host = find_providing_server( 'KRA', conn, [config.kra_host_name] ) if kra_host is not None: config.kra_host_name = kra_host kra_enabled = True else: if options.setup_kra: logger.error("There is no active KRA server in the domain, " "can't setup a KRA clone") raise ScriptError(rval=3) kra_enabled = False if ca_enabled: options.realm_name = config.realm_name options.host_name = config.host_name ca.install_check(False, config, options) if kra_enabled: try: kra.install_check(remote_api, config, options) except RuntimeError as e: raise ScriptError(e) if options.setup_dns: dns.install_check(False, remote_api, True, options, config.host_name) config.ips = dns.ip_addresses else: config.ips = installutils.get_server_ip_address( config.host_name, not installer.interactive, False, options.ip_addresses) # check addresses here, dns module is doing own check no_matching_interface_for_ip_address_warning(config.ips) if options.setup_adtrust: adtrust.install_check(False, options, remote_api) except errors.ACIError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nInsufficient privileges to promote the server." "\nPossible issues:" "\n- A user has insufficient privileges" "\n- This client has insufficient privileges " "to become an IPA replica") except errors.LDAPError: logger.debug("%s", traceback.format_exc()) raise ScriptError("\nUnable to connect to LDAP server %s" % config.master_host_name) finally: if replman and replman.conn: replman.conn.unbind() if conn.isconnected(): conn.disconnect() # check connection if not options.skip_conncheck: if add_to_ipaservers: # use user's credentials when the server host is not ipaservers if installer._ccache is None: del os.environ['KRB5CCNAME'] else: os.environ['KRB5CCNAME'] = installer._ccache try: replica_conn_check( config.master_host_name, config.host_name, config.realm_name, options.setup_ca, 389, options.admin_password, principal=options.principal, ca_cert_file=cafile) finally: if add_to_ipaservers: os.environ['KRB5CCNAME'] = ccache installer._ca_enabled = ca_enabled installer._kra_enabled = kra_enabled installer._ca_file = cafile installer._fstore = fstore installer._sstore = sstore installer._config = config installer._add_to_ipaservers = add_to_ipaservers installer._dirsrv_pkcs12_file = dirsrv_pkcs12_file installer._dirsrv_pkcs12_info = dirsrv_pkcs12_info installer._http_pkcs12_file = http_pkcs12_file installer._http_pkcs12_info = http_pkcs12_info installer._pkinit_pkcs12_file = pkinit_pkcs12_file installer._pkinit_pkcs12_info = pkinit_pkcs12_info
# You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # """ WSGI appliction for IPA server. """ from ipaplatform.paths import paths from ipalib import api from ipalib.config import Env from ipalib.constants import DEFAULT_CONFIG # Determine what debug level is configured. We can only do this # by reading in the configuration file(s). The server always reads # default.conf and will also read in `context'.conf. env = Env() env._bootstrap(context='server', log=None, confdir=paths.ETC_IPA) env._finalize_core(**dict(DEFAULT_CONFIG)) # Initialize the API with the proper debug level api.bootstrap(context='server', confdir=paths.ETC_IPA, debug=env.debug, log=None) try: api.finalize() except Exception as e: api.log.error('Failed to start IPA: %s' % e) else: api.log.info('*** PROCESS START ***') # This is the WSGI callable: def application(environ, start_response):
class API(ReadOnly): """ Dynamic API object through which `Plugin` instances are accessed. """ def __init__(self): super(API, self).__init__() self.__plugins = set() self.__plugins_by_key = {} self.__default_map = {} self.__instances = {} self.__next = {} self.__done = set() self.env = Env() @property def bases(self): raise NotImplementedError @property def packages(self): raise NotImplementedError def __len__(self): """ Return the number of plugin namespaces in this API object. """ return len(self.bases) def __iter__(self): """ Iterate (in ascending order) through plugin namespace names. """ return (base.__name__ for base in self.bases) def __contains__(self, name): """ Return True if this API object contains plugin namespace ``name``. :param name: The plugin namespace name to test for membership. """ return name in set(self) def __getitem__(self, name): """ Return the plugin namespace corresponding to ``name``. :param name: The name of the plugin namespace you wish to retrieve. """ if name in self: try: return getattr(self, name) except AttributeError: pass raise KeyError(name) def __call__(self): """ Iterate (in ascending order by name) through plugin namespaces. """ for name in self: try: yield getattr(self, name) except AttributeError: raise KeyError(name) def is_production_mode(self): """ If the object has self.env.mode defined and that mode is production return True, otherwise return False. """ return getattr(self.env, 'mode', None) == 'production' def __doing(self, name): if name in self.__done: raise Exception( '%s.%s() already called' % (self.__class__.__name__, name) ) self.__done.add(name) def __do_if_not_done(self, name): if name not in self.__done: getattr(self, name)() def isdone(self, name): return name in self.__done def bootstrap(self, parser=None, **overrides): """ Initialize environment variables and logging. """ self.__doing('bootstrap') self.log_mgr = log_mgr log = log_mgr.root_logger self.log = log self.env._bootstrap(**overrides) self.env._finalize_core(**dict(DEFAULT_CONFIG)) # Add the argument parser if not parser: parser = self.build_global_parser() self.parser = parser # If logging has already been configured somewhere else (like in the # installer), don't add handlers or change levels: if log_mgr.configure_state != 'default' or self.env.validate_api: return log_mgr.default_level = 'info' log_mgr.configure_from_env(self.env, configure_state='api') # Add stderr handler: level = 'info' if self.env.debug: level = 'debug' else: if self.env.context == 'cli': if self.env.verbose > 0: level = 'info' else: level = 'warning' if 'console' in log_mgr.handlers: log_mgr.remove_handler('console') log_mgr.create_log_handlers([dict(name='console', stream=sys.stderr, level=level, format=LOGGING_FORMAT_STDERR)]) # Add file handler: if self.env.mode in ('dummy', 'unit_test'): return # But not if in unit-test mode if self.env.log is None: return log_dir = path.dirname(self.env.log) if not path.isdir(log_dir): try: os.makedirs(log_dir) except OSError: log.error('Could not create log_dir %r', log_dir) return level = 'info' if self.env.debug: level = 'debug' try: log_mgr.create_log_handlers([dict(name='file', filename=self.env.log, level=level, format=LOGGING_FORMAT_FILE)]) except IOError as e: log.error('Cannot open log file %r: %s', self.env.log, e) return def build_global_parser(self, parser=None, context=None): """ Add global options to an optparse.OptionParser instance. """ def config_file_callback(option, opt, value, parser): if not ipautil.file_exists(value): parser.error( _("%(filename)s: file not found") % dict(filename=value)) parser.values.conf = value if parser is None: parser = optparse.OptionParser( add_help_option=False, formatter=IPAHelpFormatter(), usage='%prog [global-options] COMMAND [command-options]', description='Manage an IPA domain', version=('VERSION: %s, API_VERSION: %s' % (VERSION, API_VERSION)), epilog='\n'.join([ 'See "ipa help topics" for available help topics.', 'See "ipa help <TOPIC>" for more information on a ' 'specific topic.', 'See "ipa help commands" for the full list of commands.', 'See "ipa <COMMAND> --help" for more information on a ' 'specific command.', ])) parser.disable_interspersed_args() parser.add_option("-h", "--help", action="help", help='Show this help message and exit') parser.add_option('-e', dest='env', metavar='KEY=VAL', action='append', help='Set environment variable KEY to VAL', ) parser.add_option('-c', dest='conf', metavar='FILE', action='callback', callback=config_file_callback, type='string', help='Load configuration from FILE.', ) parser.add_option('-d', '--debug', action='store_true', help='Produce full debuging output', ) parser.add_option('--delegate', action='store_true', help='Delegate the TGT to the IPA server', ) parser.add_option('-v', '--verbose', action='count', help='Produce more verbose output. A second -v displays the XML-RPC request', ) if context == 'cli': parser.add_option('-a', '--prompt-all', action='store_true', help='Prompt for ALL values (even if optional)' ) parser.add_option('-n', '--no-prompt', action='store_false', dest='interactive', help='Prompt for NO values (even if required)' ) parser.add_option('-f', '--no-fallback', action='store_false', dest='fallback', help='Only use the server configured in /etc/ipa/default.conf' ) return parser def bootstrap_with_global_options(self, parser=None, context=None): parser = self.build_global_parser(parser, context) (options, args) = parser.parse_args() overrides = {} if options.env is not None: assert type(options.env) is list for item in options.env: try: (key, value) = item.split('=', 1) except ValueError: # FIXME: this should raise an IPA exception with an # error code. # --Jason, 2008-10-31 pass overrides[str(key.strip())] = value.strip() for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive', 'fallback', 'delegate'): value = getattr(options, key, None) if value is not None: overrides[key] = value if hasattr(options, 'prod'): overrides['webui_prod'] = options.prod if context is not None: overrides['context'] = context self.bootstrap(parser, **overrides) return (options, args) def load_plugins(self): """ Load plugins from all standard locations. `API.bootstrap` will automatically be called if it hasn't been already. """ self.__doing('load_plugins') self.__do_if_not_done('bootstrap') if self.env.mode in ('dummy', 'unit_test'): return for package in self.packages: self.add_package(package) # FIXME: This method has no unit test def add_package(self, package): """ Add plugin modules from the ``package``. :param package: A package from which to add modules. """ package_name = package.__name__ package_file = package.__file__ package_dir = path.dirname(path.abspath(package_file)) parent = sys.modules[package_name.rpartition('.')[0]] parent_dir = path.dirname(path.abspath(parent.__file__)) if parent_dir == package_dir: raise errors.PluginsPackageError( name=package_name, file=package_file ) self.log.debug("importing all plugin modules in %s...", package_name) modules = getattr(package, 'modules', find_modules_in_dir(package_dir)) modules = ['.'.join((package_name, name)) for name in modules] for name in modules: self.log.debug("importing plugin module %s", name) try: module = importlib.import_module(name) except errors.SkipPluginModule as e: self.log.debug("skipping plugin module %s: %s", name, e.reason) continue except Exception as e: if self.env.startup_traceback: import traceback self.log.error("could not load plugin module %s\n%s", name, traceback.format_exc()) raise try: self.add_module(module) except errors.PluginModuleError as e: self.log.debug("%s", e) def add_module(self, module): """ Add plugins from the ``module``. :param module: A module from which to add plugins. """ try: register = module.register except AttributeError: pass else: if isinstance(register, Registry): for kwargs in register: self.add_plugin(**kwargs) return raise errors.PluginModuleError(name=module.__name__) def add_plugin(self, plugin, override=False, no_fail=False): """ Add the plugin ``plugin``. :param plugin: A subclass of `Plugin` to attempt to add. :param override: If true, override an already added plugin. """ if not callable(plugin): raise TypeError('plugin must be callable; got %r' % plugin) # Find the base class or raise SubclassError: for base in plugin.bases: if issubclass(base, self.bases): break else: raise errors.PluginSubclassError( plugin=plugin, bases=self.bases, ) # Check override: prev = self.__plugins_by_key.get(plugin.full_name) if prev: if not override: if no_fail: return else: # Must use override=True to override: raise errors.PluginOverrideError( base=base.__name__, name=plugin.name, plugin=plugin, ) self.__plugins.remove(prev) self.__next[plugin] = prev else: if override: if no_fail: return else: # There was nothing already registered to override: raise errors.PluginMissingOverrideError( base=base.__name__, name=plugin.name, plugin=plugin, ) # The plugin is okay, add to sub_d: self.__plugins.add(plugin) self.__plugins_by_key[plugin.full_name] = plugin def finalize(self): """ Finalize the registration, instantiate the plugins. `API.bootstrap` will automatically be called if it hasn't been already. """ self.__doing('finalize') self.__do_if_not_done('load_plugins') if self.env.env_confdir is not None: if self.env.env_confdir == self.env.confdir: self.log.info( "IPA_CONFDIR env sets confdir to '%s'.", self.env.confdir) for plugin in self.__plugins: if not self.env.validate_api: if plugin.full_name not in DEFAULT_PLUGINS: continue else: try: default_version = self.__default_map[plugin.name] except KeyError: pass else: # Technicall plugin.version is not an API version. The # APIVersion class can handle plugin versions. It's more # lean than pkg_resource.parse_version(). version = ipautil.APIVersion(plugin.version) default_version = ipautil.APIVersion(default_version) if version < default_version: continue self.__default_map[plugin.name] = plugin.version production_mode = self.is_production_mode() for base in self.bases: for plugin in self.__plugins: if not any(issubclass(b, base) for b in plugin.bases): continue if not self.env.plugins_on_demand: self._get(plugin) name = base.__name__ if not production_mode: assert not hasattr(self, name) setattr(self, name, APINameSpace(self, base)) for instance in six.itervalues(self.__instances): if not production_mode: assert instance.api is self if not self.env.plugins_on_demand: instance.ensure_finalized() if not production_mode: assert islocked(instance) self.__finalized = True if not production_mode: lock(self) def _get(self, plugin): if not callable(plugin): raise TypeError('plugin must be callable; got %r' % plugin) if plugin not in self.__plugins: raise KeyError(plugin) try: instance = self.__instances[plugin] except KeyError: instance = self.__instances[plugin] = plugin(self) return instance def get_plugin_next(self, plugin): if not callable(plugin): raise TypeError('plugin must be callable; got %r' % plugin) return self.__next[plugin]
# # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # """ WSGI appliction for IPA server. """ from ipaplatform.paths import paths from ipalib import api from ipalib.config import Env from ipalib.constants import DEFAULT_CONFIG # Determine what debug level is configured. We can only do this # by reading in the configuration file(s). The server always reads # default.conf and will also read in `context'.conf. env = Env() env._bootstrap(context='server', log=None, confdir=paths.ETC_IPA) env._finalize_core(**dict(DEFAULT_CONFIG)) # Initialize the API with the proper debug level api.bootstrap(context='server', confdir=paths.ETC_IPA, debug=env.debug, log=None) try: api.finalize() except Exception as e: api.log.error('Failed to start IPA: %s' % e) else: api.log.info('*** PROCESS START ***')