def _do_import(self, filename=None): permsys = PermissionSystem(self.env) try: with file_or_std(filename, 'rb') as f: encoding = stream_encoding(f) linesep = os.linesep if filename else '\n' reader = csv.reader(f, lineterminator=linesep) for row in reader: if len(row) < 2: raise AdminCommandError( _( "Invalid row %(line)d. Expected <user>, " "<action>, [action], [...]", line=reader.line_num)) user = to_unicode(row[0], encoding) actions = [ to_unicode(action, encoding) for action in row[1:] ] if user.isupper(): raise AdminCommandError( _( "Invalid user %(user)s on line %(line)d: All " "upper-cased tokens are reserved for permission " "names.", user=user, line=reader.line_num)) old_actions = self.get_user_perms(user) for action in set(actions) - set(old_actions): permsys.grant_permission(user, action) except csv.Error, e: raise AdminCommandError( _("Cannot import from %(filename)s line %(line)d: %(error)s ", filename=path_to_unicode(filename or 'stdin'), line=reader.line_num, error=e))
def _do_create(self, name, type, owner, dir=None): rm = RepositoryManager(self.env) base_directory = rm.get_base_directory(type) directory = os.path.join(base_directory, dir or name) if os.path.lexists(directory): raise AdminCommandError( _('Directory "%(name)s" already exists', name=directory)) rap = RepositoryAdminPanel(self.env) prefixes = [ os.path.join(self.env.path, prefix) for prefix in rap.allowed_repository_dir_prefixes ] if prefixes and not any( is_path_below(directory, prefix) for prefix in prefixes): add_warning( req, _( "The repository directory must be located " "below one of the following directories: " "%(dirs)s", dirs=', '.join(prefixes))) if rm.get_repository(name): raise AdminCommandError( _('Repository "%(name)s" already ' 'exists', name=name)) repo = {'name': name, 'type': type, 'owner': owner, 'dir': directory} rm.create(repo)
def _do_remove(self, section, option=None): if option and not self.config.has_option(section, option): raise AdminCommandError( _("Option '%(option)s' doesn't exist in section" " '%(section)s'", option=option, section=section)) elif section not in self.config: raise AdminCommandError( _("Section '%(section)s' doesn't exist", section=section)) self.config.remove(section, option) self.config.save() if section == 'inherit' and option == 'file': self.config.parse_if_needed(force=True) # Full reload
def _do_remove_managed(self, name, delete): rm = RepositoryManager(self.env) repository = rm.get_repository(name) if not repository: raise AdminCommandError( _('Repository "%(name)s" does not exists', name=name)) rm.remove(repository, as_bool(delete))
def _do_add(self, user, *actions): permsys = PermissionSystem(self.env) if user.isupper(): raise AdminCommandError(_('All upper-cased tokens are reserved ' 'for permission names')) for action in actions: permsys.grant_permission(user, action)
def _do_get(self, section, option): if not self.config.has_option(section, option): raise AdminCommandError( _("Option '%(option)s' doesn't exist in section '%(section)s'", option=option, section=section)) printout(self.config.get(section, option))
def _do_remove(self, user, *actions): permsys = PermissionSystem(self.env) rows = permsys.get_all_permissions() for action in actions: found = False for u, a in rows: if user in (u, '*') and action in (a, '*'): permsys.revoke_permission(u, a) found = True if not found: if user in self.get_user_list() and \ action in permsys.get_user_permissions(user): msg = _( "Cannot remove permission %(action)s for user " "%(user)s. The permission is granted through " "a meta-permission or group.", action=action, user=user) else: msg = _( "Cannot remove permission %(action)s for user " "%(user)s. The user has not been granted the " "permission.", action=action, user=user) raise AdminCommandError(msg)
def _do_remove(self, identifier): # Get downloads API component. api = self.env[DownloadsApi] # Create context. context = Context('downloads-consoleadmin') db = self.env.get_db_cnx() context.cursor = db.cursor() context.req = FakeRequest(self.env, self.consoleadmin_user) # Get download by ID or filename. try: download_id = int(identifier) download = api.get_download(context, download_id) except ValueError: download = api.get_download_by_file(context, identifier) # Check if download exists. if not download: raise AdminCommandError( _('Invalid download identifier: %(value)s', value=identifier)) # Delete download by ID. api.delete_download(context, download_id) # Commit changes in DB. db.commit()
def _do_add(self, user, *actions): permsys = PermissionSystem(self.env) if user.isupper(): raise AdminCommandError( _("All upper-cased tokens are reserved " "for permission names")) def grant_actions_atomically(actions): action = None try: with self.env.db_transaction: for action in actions: permsys.grant_permission(user, action) except PermissionExistsError as e: printout(e) return action except TracError as e: raise AdminCommandError(e) # An exception rolls back the atomic transaction so it's # necessary to retry the transaction after removing the # failed action from the list. actions_to_grant = list(actions) while actions_to_grant: action_that_failed = grant_actions_atomically(actions_to_grant) if action_that_failed is None: break else: actions_to_grant.remove(action_that_failed)
def _do_hotcopy(self, dest, no_db=None): if no_db not in (None, '--no-database'): raise AdminCommandError(_("Invalid argument '%(arg)s'", arg=no_db), show_usage=True) if os.path.exists(dest): raise TracError( _("hotcopy can't overwrite existing '%(dest)s'", dest=path_to_unicode(dest))) import shutil # Bogus statement to lock the database while copying files with self.env.db_transaction as db: db("UPDATE system SET name=NULL WHERE name IS NULL") printout( _("Hotcopying %(src)s to %(dst)s ...", src=path_to_unicode(self.env.path), dst=path_to_unicode(dest))) db_str = self.env.config.get('trac', 'database') prefix, db_path = db_str.split(':', 1) skip = [] if prefix == 'sqlite': db_path = os.path.join(self.env.path, os.path.normpath(db_path)) # don't copy the journal (also, this would fail on Windows) skip = [ db_path + '-journal', db_path + '-stmtjrnl', db_path + '-shm', db_path + '-wal' ] if no_db: skip.append(db_path) try: copytree(self.env.path, dest, symlinks=1, skip=skip) retval = 0 except shutil.Error as e: retval = 1 printerr( _("The following errors happened while copying " "the environment:")) for (src, dst, err) in e.args[0]: if src in err: printerr(' %s' % err) else: printerr(" %s: '%s'" % (err, path_to_unicode(src))) # db backup for non-sqlite if prefix != 'sqlite' and not no_db: printout(_("Backing up database ...")) sql_backup = os.path.join(dest, 'db', '%s-db-backup.sql' % prefix) self.env.backup(sql_backup) printout(_("Hotcopy done.")) return retval
def _do_add(self, user, *actions): permsys = PermissionSystem(self.env) if user.isupper(): raise AdminCommandError( _('All upper-cased tokens are reserved ' 'for permission names')) for action in actions: try: permsys.grant_permission(user, action) except self.env.db_exc.IntegrityError: printout( _( "The user %(user)s already has permission " "%(action)s.", user=user, action=action)) except TracError as e: raise AdminCommandError(e)
def _do_deploy(self, dest): from trac.web.chrome import Chrome target = os.path.normpath(dest) chrome_target = os.path.join(target, 'htdocs') script_target = os.path.join(target, 'cgi-bin') # Check source and destination to avoid recursively copying files for provider in Chrome(self.env).template_providers: paths = list(provider.get_htdocs_dirs() or []) if not paths: continue for key, root in paths: if not root: continue source = os.path.normpath(root) dest = os.path.join(chrome_target, key) if os.path.exists(source) and is_path_below(dest, source): raise AdminCommandError( _( "Resources cannot be deployed to a target " "directory that is equal to or below the source " "directory '%(source)s'.\n\nPlease choose a " "different target directory and try again.", source=source)) # Copy static content makedirs(target, overwrite=True) makedirs(chrome_target, overwrite=True) printout(_("Copying resources from:")) for provider in Chrome(self.env).template_providers: paths = list(provider.get_htdocs_dirs() or []) if not paths: continue printout(' %s.%s' % (provider.__module__, provider.__class__.__name__)) for key, root in paths: if not root: continue source = os.path.normpath(root) printout(' ', source) if os.path.exists(source): dest = os.path.join(chrome_target, key) copytree(source, dest, overwrite=True) # Create and copy scripts makedirs(script_target, overwrite=True) printout(_("Creating scripts.")) data = {'env': self.env, 'executable': sys.executable} for script in ('cgi', 'fcgi', 'wsgi'): dest = os.path.join(script_target, 'trac.' + script) template = Chrome(self.env).load_template('deploy_trac.' + script, 'text') stream = template.generate(**data) with open(dest, 'w') as out: stream.render('text', out=out, encoding='utf-8')
def _handle_change(self, command, downloads_dir_name, can_be_moved): try: env_name = self.env.project_identifier except AttributeError as e: # In case of trac admin commands, project_identifier is not found env_name = self.env.path.split('/')[-1] self.env.project_identifier = env_name download_config = FilesDownloadConfig(env_name) if downloads_dir_name is None: files_core = FilesCoreComponent(self.env) downloads_dir_name = files_core.default_downloads_directory node_factory = FileSystemNode(download_config.base_path) old_node, old_dir_exists = self.get_dir_data(download_config, node_factory) if command == 'downloads-dir-create': if old_dir_exists: raise AdminCommandError( _('Project already has existing downloads directory')) node = FileSystemNode.from_path(downloads_dir_name, node_factory) if node.exists(): raise AdminCommandError( _('The given downloads directory already exists')) msg_handler = lambda msg: printout(msg) try: self.handle_change(download_config, downloads_dir_name, can_be_moved, node_factory, msg_handler, msg_handler) except TracError as e: raise AdminCommandError(str(e)) if command == 'downloads-dir-create': files_core = FilesCoreComponent(self.env) mapped_node_factory, mapped_download_config = files_core.files_node_factory_and_config( ) created_node = MappedFileNode.from_path( download_config.downloads_dir, mapped_node_factory) files_notifier = FilesEventNotifier(self.env) files_notifier.node_created('trac', created_node)
def grant_actions_atomically(actions): action = None try: with self.env.db_transaction: for action in actions: permsys.grant_permission(user, action) except PermissionExistsError as e: printout(e) return action except TracError as e: raise AdminCommandError(e)
def _do_export(self, resource, name, destination=None): realm, id_ = self.split_resource(resource) attachment = Attachment(self.env, realm, id_, name) if destination is not None: if os.path.isdir(destination): destination = os.path.join(destination, name) if os.path.isfile(destination): raise AdminCommandError(_("File '%(name)s' exists", name=path_to_unicode(destination))) with attachment.open() as input: with file_or_std(destination, 'wb') as output: shutil.copyfileobj(input, output)
def _do_remove(self, user, *actions): permsys = PermissionSystem(self.env) rows = permsys.get_all_permissions() for action in actions: found = False for u, a in rows: if user in (u, '*') and action in (a, '*'): permsys.revoke_permission(u, a) found = True if not found: raise AdminCommandError( _("Cannot remove permission %(action)s for user %(user)s.", action=action, user=user))
def _do_upgrade(self, no_backup=None): if no_backup not in (None, '-b', '--no-backup'): raise AdminCommandError(_("Invalid arguments"), show_usage=True) if not self.env.needs_upgrade(): printout(_("Database is up to date, no upgrade necessary.")) return try: self.env.upgrade(backup=no_backup is None) except BackupError as e: printerr( _("The pre-upgrade backup failed.\nUse '--no-backup' to " "upgrade without doing a backup.\n")) raise e.args[0] except Exception: printerr( _("The upgrade failed. Please fix the issue and try " "again.\n")) raise # Remove wiki-macros if it is empty and warn if it isn't wiki_macros = os.path.join(self.env.path, 'wiki-macros') try: entries = os.listdir(wiki_macros) except OSError: pass else: if entries: printerr( _("Warning: the wiki-macros directory in the " "environment is non-empty, but Trac\n" "doesn't load plugins from there anymore. " "Please remove it by hand.")) else: try: os.rmdir(wiki_macros) except OSError as e: printerr( _( "Error while removing wiki-macros: %(err)s\n" "Trac doesn't load plugins from wiki-macros " "anymore. Please remove it by hand.", err=exception_to_unicode(e))) printout( _( "Upgrade done.\n\n" "You may want to upgrade the Trac documentation now by " "running:\n\n trac-admin %(path)s wiki upgrade", path=path_to_unicode(self.env.path)))
def _do_set(self, reponame, key, value): if key not in self.repository_attrs: raise AdminCommandError(_('Invalid key "%(key)s"', key=key)) if key == 'dir': value = os.path.abspath(value) self.modify_repository(reponame, {key: value}) if not reponame: reponame = '(default)' if key == 'dir': printout(_('You should now run "repository resync %(name)s".', name=reponame)) elif key == 'type': printout(_('You may have to run "repository resync %(name)s".', name=reponame))
def _do_export(self, filename=None): try: with file_or_std(filename, 'w') as f: linesep = os.linesep if filename else '\n' writer = csv.writer(f, lineterminator=linesep) users = self.get_user_list() for user in sorted(users): actions = sorted(self.get_user_perms(user)) writer.writerow([s for s in [user] + actions]) f.flush() except IOError as e: raise AdminCommandError( _("Cannot export to %(filename)s: %(error)s", filename=path_to_unicode(filename or 'stdout'), error=e.strerror))
def _do_upgrade(self, no_backup=None): if no_backup not in (None, '-b', '--no-backup'): raise AdminCommandError(_("Invalid arguments"), show_usage=True) if not self.env.needs_upgrade(): printout(_("Database is up to date, no upgrade necessary.")) return try: self.env.upgrade(backup=no_backup is None) except TracError, e: raise TracError( _( "Backup failed: %(msg)s.\nUse '--no-backup' to " "upgrade without doing a backup.", msg=unicode(e)))
def _notify_admins(self, permission, email_path, debug='false'): is_debug = debug.lower() in ('true', 'yes') if permission != 'TRAC_ADMIN': raise AdminCommandError('Only TRAC_ADMIN permission is supported') # A standard thing to do in IAdminCommandProviders (otherwise, # accessing project_identifier would give errors) if not hasattr(self.env, 'project_identifier'): MultiProjectEnvironmentInit(self.env).environment_needs_upgrade(None) env_name = self.env.project_identifier if env_name == self.env.config.get('multiproject', 'sys_home_project_name'): raise AdminCommandError('Command does not support home project') if not os.path.exists(email_path): raise AdminCommandError(_("Email template was not found!")) project = Project.get(self.env) email_template = '' try: with open(email_path) as fd: email_template = fd.read() except OSError as e: raise AdminCommandError(_("Error with opening file %(path)s: %(error_msg)s", path=email_path, error_msg=e)) except Exception as e: raise AdminCommandError(_("Unknown error when parsing file %(path)s: %(error_msg)s", path=email_path, error_msg=e)) email_template = [i.strip() for i in email_template.split('\n', 1) if i] if not len(email_template) > 1 or not all(email_template): raise AdminCommandError(_("Email template %(path)s was invalid.", path=email_path)) subject, body = email_template text_template = NewTextTemplate(body) admins = project.get_admin_email_addresses() data = {'env_name': env_name} if is_debug: printout('## DEBUG MODE - NOT SENDING EMAILS ##') printout("project: {0}".format(env_name)) printout("to: {0}".format(','.join(admins))) printout("subject: {0}".format(subject)) printout("----------------------------") printout(text_template.generate(**data)) printout("----------------------------") if not is_debug: notifier = EmailNotifier(self.env, subject=subject, data=data) notifier.template = text_template notifier.notify(admins) printout('Emails sent')
def _do_export(self, resource, name, destination=None): (realm, id) = self.split_resource(resource) attachment = Attachment(self.env, realm, id, name) if destination is not None: if os.path.isdir(destination): destination = os.path.join(destination, name) if os.path.isfile(destination): raise AdminCommandError(_("File '%(name)s' exists", name=path_to_unicode(destination))) with attachment.open() as input: output = open(destination, "wb") if destination is not None \ else sys.stdout try: shutil.copyfileobj(input, output) finally: if destination is not None: output.close()
def _do_role_list(self, repos): repository = RepositoryManager(self.env).get_repository(repos, True) if not repository: raise AdminCommandError(_("Not a managed repository")) columns = [] if repository.is_forkable: columns.append(_("Maintainers")) values = list( izip_longest(sorted(repository.maintainers()), sorted(repository.writers()), sorted(repository.readers()), fillvalue='')) else: values = list( izip_longest(sorted(repository.writers()), sorted(repository.readers()), fillvalue='')) columns.append(_("Writers")) columns.append(_("Readers")) print_table(values, columns)
def deploy_htdocs(env, dest=None, config_file=None, also_common=False, path=None): """ :param Environment env: Environment instance, may be home or normal project. :param str dest: Destination where to put files. Defaults to [multiproject] static_htdocs_path. """ keys = set() # construct list of enabled pugins in home project and normal project enabled_plugins = [] project_ini = env.config.get('multiproject', 'global_conf_path') home_ini = os.path.join( env.config.get('multiproject', 'sys_projects_root'), env.config.get('multiproject', 'sys_home_project_name'), 'conf', 'trac.ini') enabled_plugins.extend(_get_enabled_components(project_ini)) enabled_plugins.extend(_get_enabled_components(home_ini)) env = MockEnvironment(config_file, enabled_plugins, path) if dest is None: dest = env.config.get('multiproject', 'static_htdocs_path', default=None) if dest is None: raise AdminCommandError( 'Destination not given and ' '[multiproject] static_htdocs_path configuration is not set') chrome_target = dest # A slightly edited snippet from trac.env.EnvironmentAdmin._do_deploy chrome = Chrome(env) os.path.normpath(dest) for provider in chrome.template_providers: paths = list(provider.get_htdocs_dirs() or []) if not len(paths): continue for key, root in paths: if key == 'site': continue if key == 'common' and not also_common: continue keys.add(key) source = os.path.normpath(root) if os.path.exists(source): dest = os.path.join(chrome_target, key) copytree(source, dest, overwrite=True) printout(' Static htdocs deployed to directory %s ' % chrome_target) dirs = sorted([ str(dir) for dir in env.config.getlist( 'multiproject', 'static_htdocs_plugin_dirs', default=['*']) ]) if len(dirs) == 1 and dirs[0] == '*': pass elif dirs and set(dirs) != keys: printout( ' Warning: [multiproject] static_htdocs_plugin_dirs is not up-to-date!' ) extra_dirs = [dir for dir in dirs if dir not in keys] if extra_dirs: printout( ' It contains the following extra directories, which should be removed:' ) printout(' %s' % extra_dirs) extra_keys = [key for key in keys if key not in dirs] if extra_keys: printout( ' The urls of the htdocs of the following plugins are not changed ' ) printout(' to use [multiproject] static_htdocs_location:') printout(' %s' % sorted(extra_keys)) printout( ' To fix these errors, change the configuration to be as follows:' ) printout(' "static_htdocs_plugin_dirs = %s"' % ','.join(sorted(list(keys)))) elif not dirs: printout( ' To use the static htdocs of the global plugins as static files, ' ) printout(' change the configuration to be as follows:') printout(' "static_htdocs_plugin_dirs = %s"' % ','.join(sorted(list(keys))))
def _do_add(self, filename, *arguments): # Get downloads API component. api = self.env[DownloadsApi] # Create context. context = Context('downloads-consoleadmin') db = self.env.get_db_cnx() context.cursor = db.cursor() context.req = FakeRequest(self.env, self.consoleadmin_user) # Convert relative path to absolute. if not os.path.isabs(filename): filename = os.path.join(self.path, filename) # Open file object. file, filename, file_size = self._get_file(filename) # Create download dictionary from arbitrary attributes. download = { 'file': filename, 'size': file_size, 'time': to_timestamp(datetime.now(utc)), 'count': 0 } # Read optional attributes from arguments. for argument in arguments: # Check correct format. argument = argument.split("=") if len(argument) != 2: AdminCommandError( _('Invalid format of download attribute:' ' %(value)s', value=argument)) name, value = argument # Check known arguments. if not name in ('description', 'author', 'tags', 'component', 'version', 'architecture', 'platform', 'type'): raise AdminCommandError( _('Invalid download attribute:' ' %(value)s', value=name)) # Transform architecture, platform and type name to ID. if name == 'architecture': value = api.get_architecture_by_name(context, value)['id'] elif name == 'platform': value = api.get_platform_by_name(context, value)['id'] elif name == 'type': value = api.get_type_by_name(context, value)['id'] # Add attribute to download. download[name] = value self.log.debug(download) # Upload file to DB and file storage. api._add_download(context, download, file) # Close input file and commit changes in DB. file.close() db.commit()
_( "Invalid row %(line)d. Expected <user>, " "<action>, [action], [...]", line=reader.line_num)) user = to_unicode(row[0], encoding) actions = [ to_unicode(action, encoding) for action in row[1:] ] if user.isupper(): raise AdminCommandError( _( "Invalid user %(user)s on line %(line)d: All " "upper-cased tokens are reserved for permission " "names.", user=user, line=reader.line_num)) old_actions = self.get_user_perms(user) for action in set(actions) - set(old_actions): permsys.grant_permission(user, action) except csv.Error, e: raise AdminCommandError( _("Cannot import from %(filename)s line %(line)d: %(error)s ", filename=path_to_unicode(filename or 'stdin'), line=reader.line_num, error=e)) except IOError, e: raise AdminCommandError( _("Cannot import from %(filename)s: %(error)s", filename=path_to_unicode(filename or 'stdin'), error=e.strerror))
def split_resource(self, resource): result = resource.split(':', 1) if len(result) != 2: raise AdminCommandError( _("Invalid resource identifier '%(id)s'", id=resource)) return result