repositories: enabled support for maintenance commands.

- maintenance commands does things like git gc or mercurial verify
to check integrity of repositories.
This commit is contained in:
Marcin Kuzminski 2017-03-16 15:09:20 +01:00
parent e959f4e89a
commit 8f4818db12
8 changed files with 294 additions and 1 deletions

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016-2017 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
def includeme(config):
config.add_route(
name='repo_maintenance',
pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
config.add_route(
name='repo_maintenance_execute',
pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
# Scan module for configuration decorators.
config.scan()

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2011-2017 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

View file

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2011-2017 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
from pyramid.view import view_config
from rhodecode.apps._base import RepoAppView
from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
NotAnonymous)
from rhodecode.lib import repo_maintenance
log = logging.getLogger(__name__)
class RepoMaintenanceView(RepoAppView):
def load_default_context(self):
c = self._get_local_tmpl_context()
# TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
c.repo_info = self.db_repo
self._register_global_c(c)
return c
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator('repository.admin')
@view_config(
route_name='repo_maintenance', request_method='GET',
renderer='rhodecode:templates/admin/repos/repo_edit.mako')
def repo_maintenance(self):
c = self.load_default_context()
c.active = 'maintenance'
maintenance = repo_maintenance.RepoMaintenance()
c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
return self._get_template_context(c)
@LoginRequired()
@NotAnonymous()
@HasRepoPermissionAnyDecorator('repository.admin')
@view_config(
route_name='repo_maintenance_execute', request_method='GET',
renderer='json', xhr=True)
def repo_maintenance_execute(self):
c = self.load_default_context()
c.active = 'maintenance'
_ = self.request.translate
maintenance = repo_maintenance.RepoMaintenance()
executed_types = maintenance.execute(self.db_repo)
return executed_types

View file

@ -288,6 +288,7 @@ def includeme(config):
config.include('rhodecode.apps.admin')
config.include('rhodecode.apps.channelstream')
config.include('rhodecode.apps.login')
config.include('rhodecode.apps.repository')
config.include('rhodecode.apps.user_profile')
config.include('rhodecode.apps.my_account')
config.include('rhodecode.apps.svn_support')

View file

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2017 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
log = logging.getLogger(__name__)
class MaintenanceTask(object):
human_name = 'undefined'
def __init__(self, db_repo):
self.db_repo = db_repo
def run(self):
"""Execute task and return task human value"""
raise NotImplementedError()
class GitGC(MaintenanceTask):
human_name = 'GIT Garbage collect'
def _count_objects(self, repo):
stdout, stderr = repo.run_git_command(
['count-objects', '-v'], fail_on_stderr=False)
errors = ' '
objects = ' '.join(stdout.splitlines())
if stderr:
errors = '\nSTD ERR:' + '\n'.join(stderr.splitlines())
return objects + errors
def run(self):
output = []
instance = self.db_repo.scm_instance()
objects = self._count_objects(instance)
output.append(objects)
log.debug('GIT objects:%s', objects)
stdout, stderr = instance.run_git_command(
['gc', '--aggressive'], fail_on_stderr=False)
out = 'executed git gc --aggressive'
if stderr:
out = ''.join(stderr.splitlines())
elif stdout:
out = ''.join(stdout.splitlines())
output.append(out)
objects = self._count_objects(instance)
log.debug('GIT objects:%s', objects)
output.append(objects)
return '\n'.join(output)
class HGVerify(MaintenanceTask):
human_name = 'HG Verify repo'
def run(self):
instance = self.db_repo.scm_instance()
res = instance.verify()
return res
class RepoMaintenance(object):
"""
Performs maintenance of repository based on it's type
"""
tasks = {
'hg': [HGVerify],
'git': [GitGC],
'svn': [],
}
def get_tasks_for_repo(self, db_repo):
"""
fetches human names of tasks pending for execution for given type of repo
"""
tasks = []
for task in self.tasks[db_repo.repo_type]:
tasks.append(task.human_name)
return tasks
def execute(self, db_repo):
executed_tasks = []
for task in self.tasks[db_repo.repo_type]:
executed_tasks.append(task(db_repo).run())
return executed_tasks

View file

@ -71,6 +71,9 @@
<li class="${'active' if c.active=='integrations' else ''}">
<a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
</li>
<li class="${'active' if c.active=='maintenance' else ''}">
<a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
</li>
## TODO: dan: replace repo navigation with navlist registry like with
## admin menu. First must find way to allow runtime configuration
## it to account for the c.repo_info.repo_type != 'svn' call above

View file

@ -0,0 +1,58 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${_('Maintenance')}</h3>
</div>
<div class="panel-body">
<p>
% if c.executable_tasks:
${_('Perform maintenance tasks for this repo, following tasks will be performed')}:
<ol>
% for task in c.executable_tasks:
<li>${task}</li>
% endfor
</ol>
% else:
${_('No maintenance tasks for this repo available')}
% endif
</p>
<div id="results" style="display:none; padding: 10px 0px;"></div>
% if c.executable_tasks:
<div class="form">
<div class="fields">
<button class="btn btn-small btn-primary" onclick="executeTask();return false">
${_('Run Maintenance')}
</button>
</div>
</div>
% endif
</div>
</div>
<script>
executeTask = function() {
var btn = $(this);
$('#results').show();
$('#results').html('<h4>${_('Performing Maintenance')}...</h4>');
btn.attr('disabled', 'disabled');
btn.addClass('disabled');
var url = "${h.route_path('repo_maintenance_execute', repo_name=c.repo_info.repo_name)}";
var success = function (data) {
var displayHtml = $('<pre></pre>');
$(displayHtml).append(data);
$('#results').html(displayHtml);
btn.removeAttr('disabled');
btn.removeClass('disabled');
};
ajaxGET(url, success, null);
}
</script>

View file

@ -209,7 +209,7 @@ ${h.end_form()}
<%text filter="h">
<script>
// Server announcement displayed on the top of the page.
// This can be used to send a global maintainance messages or other
// This can be used to send a global maintenance messages or other
// important messages to all users of the RhodeCode Enterprise system.
$(document).ready(function(e){