MT#3762 release_dashboard base

* fix duplicate rule in Makefile
* Implement just hotfix for now
* /var/lib/repoapi/gerrit.ini needed [gerrit REST HTTP password]

Change-Id: I80286e9d97846cf037566eaca74079362fa6e54a
changes/11/8411/11
Victor Seva 10 years ago
parent 8f0ff90375
commit 16a3bcc4e0

@ -28,7 +28,7 @@ migrate: venv_prod
./manage.py migrate --settings="repoapi.settings.prod"
chown www-data:www-data $(VAR_DIR)/db.sqlite3
shell_dev: venv_prod
shell: venv_prod
source $(VAR_DIR)/venv_prod/bin/activate && \
./manage.py shell --settings="repoapi.settings.prod"

1
debian/install vendored

@ -2,6 +2,7 @@ hotfix usr/share/repoapi
Makefile usr/share/repoapi
manage.py usr/share/repoapi
panel usr/share/repoapi
release_dashboard usr/share/repoapi
repoapi usr/share/repoapi
repoapi.ini etc/uwsgi/apps-available
requirements usr/share/repoapi

3
debian/rules vendored

@ -16,6 +16,9 @@ override_dh_auto_test:
override_dh_auto_install:
echo "fakesecretkey" > .secret_key
echo "[gerrit]" > gerrit.ini
echo "HTTP_USER=fake" >> gerrit.ini
echo "HTTP_PASSWD=fakeHTTPpass" >> gerrit.ini
VAR_DIR=$(shell pwd) make deploy
make clean

@ -31,7 +31,9 @@
<ul class="nav navbar-nav navbar-right">
<li><a href="/docs/">API docs</a></li>
<li><a href="https://jenkins.mgm.sipwise.com">Jenkins</a></li>
<li><a href="http://jenkins.mgm.sipwise.com:5000/">Release Dashboard</a></li>
{% block applist%}
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
{% endblock %}
</ul>
</nav>
</div>

@ -0,0 +1,22 @@
# Copyright (C) 2016 The Sipwise Team - http://sipwise.com
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
# 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 General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib import admin
from . import models
@admin.register(models.Project)
class ProjectAdmin(admin.ModelAdmin):
pass

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-09-23 11:48
from __future__ import unicode_literals
from django.db import migrations, models
from django_extensions.db.fields import ModificationDateTimeField
from django_extensions.db.fields.json import JSONField
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Project',
fields=[
('id', models.AutoField(
auto_created=True, primary_key=True,
serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True)),
('json_tags', JSONField(null=True)),
('json_branches', JSONField(null=True)),
('modified', ModificationDateTimeField(
auto_now=True, null=True)),
],
),
]

@ -0,0 +1,88 @@
# Copyright (C) 2016 The Sipwise Team - http://sipwise.com
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
# 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 General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import re
import json
from django.db import models
from django_extensions.db.fields.json import JSONField
from django_extensions.db.fields import ModificationDateTimeField
logger = logging.getLogger(__name__)
class Project(models.Model):
name = models.CharField(max_length=50, unique=True, null=False)
json_tags = JSONField(null=True)
json_branches = JSONField(null=True)
modified = ModificationDateTimeField(null=True)
@classmethod
def _filter_values(cls, values, val_ok_filter, regex=None):
res = set()
for value in values:
logger.debug("ref[%s]", value["ref"])
match = re.search(val_ok_filter, value["ref"])
if match:
val_ok = match.group(1)
if regex is not None:
if re.search(regex, val_ok):
res.add(val_ok)
logger.debug("val_ok[%s] regex", val_ok)
else:
logger.debug("val_ok[%s]", val_ok)
res.add(val_ok)
return sorted(res, reverse=True)
@classmethod
def _get_filtered_json(cls, text):
"""gerrit responds with malformed json
https://gerrit-review.googlesource.com/Documentation/rest-api.html#output
"""
logging.debug("json[:5]: %s", text[:5])
return json.loads(text[5:])
def __str__(self):
return self.name
@property
def tags(self):
return Project._filter_values(self.json_tags, '^refs/tags/(.+)$')
@tags.setter
def tags(self, value):
self.json_tags = Project._get_filtered_json(value)
@property
def branches(self):
return Project._filter_values(self.json_branches, '^refs/heads/(.+)$')
@branches.setter
def branches(self, value):
self.json_branches = Project._get_filtered_json(value)
def filter_tags(self, regex):
return Project._filter_values(self.json_tags,
'^refs/tags/(.+)$', regex)
def filter_branches(self, regex):
return Project._filter_values(self.json_branches,
'^refs/heads/(.+)$', regex)
def branches_mrXX(self):
return self.filter_branches(r'^mr[0-9]+\.[0-9]+$')
def branches_mrXXX(self):
return self.filter_branches(r'^mr[0-9]+\.[0-9]+\.[0-9]+$')

@ -0,0 +1,27 @@
/**
*
*/
$('select#common_select').change(function() {
var selected_version = $('select#common_select option:selected').val();
var ignored = $('.version option[value="ignore"]' );
var version = "";
if(selected_version.match(/^branch/)) {
version = selected_version.replace(
/^branch\/(mr[0-9]+\.[0-9]+(\.[0-9]+)?)$/g, "$1");
}
else {
version = selected_version.replace(
/^tag\/(mr[0-9]+\.[0-9]+\.[0-9]+)(\.[0-9]+)?$/g, "$1");
}
// set ignored for all
ignored.prop('selected', true);
ignored.each(function(){ $(this).change(); });
$('tr.repo option[value="ignore"]').closest('tr').children('td,th').css('background-color','#F78181');
var selected = $('.version option[value="'+ selected_version + '"]' )
selected.prop('selected', true);
selected.each(function(){ $(this).change(); });
$('tr.repo option[value="'+ selected_version + '"]').closest('tr').children('td,th').css('background-color','white');
var text = "Selected " + selected.length + " of " + ignored.length;
$('#select_text_info').text(text);
$('input#version_release').val("release-" + version);
});

@ -0,0 +1,31 @@
/**
*
*
*/
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
function csrftokenFunc(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');

@ -0,0 +1,61 @@
/**
*
*
*/
$('button.hotfix').click(function(e){
// don't send the form
e.preventDefault();
var button = $(this);
var id = button.attr('id').replace('hotfix_','');
var branch = $('select#version_' + id + ' option:selected').val().replace('branch/', '');
var repo = id;
var span = $('span#hotfix_error_' + id);
var push = $('select#push_' + id + ' option:selected').val();
$.ajax({
url: branch + '/' + repo + '/',
type: 'POST',
data: JSON.stringify({push: push }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: successFunc,
error: errorFunc,
beforeSend: csrftokenFunc
});
button.attr("disabled", "disabled");
span.html('processing');
span.show();
function successFunc(data, status) {
span.html('');
span.append('<a href="' + data.url + '">Done</a>');
button.removeAttr("disabled");
}
function errorFunc(jqXHR, status, error) {
span.html(error);
button.removeAttr("disabled");
}
});
$('td.version > select').change(function() {
var id = $(this).attr('id').replace('version_','');
var version = $(this).val();
var button = $('button#hotfix_' + id);
var span = $('span#hotfix_error_' + id);
if (version.match(/^branch\/mr[0-9]+\.[0-9]+\.[0-9]+$/)) {
button.html("Release hotfix");
button.removeAttr("disabled");
}
else {
button.html("Select branch to hotfix");
button.attr("disabled", "disabled");
}
span.html('');
});
$( document ).ready(function() {
$('td.version > select option[value^="branch/mr"]').each(function(){ $(this).change(); });
});

@ -0,0 +1,66 @@
/**
* refresh info
*/
$('button#refresh_all').click(function(e){
// don't send the form
e.preventDefault();
var button = $(this);
var span = $('span#refresh_all_error');
$.ajax({
url: '/release_panel/refresh/',
type: 'POST',
success: successFunc,
error: errorFunc,
beforeSend: csrftokenFunc
});
function successFunc(data, status) {
span.html('');
span.append('<a href="' + data.url + '">Done</a><br/>');
span.append('This will take a while. Refresh the page in a few');
}
function errorFunc(jqXHR, status, error) {
span.html(error);
button.removeAttr("disabled");
}
//deactivate button
button.attr("disabled", "disabled");
span.html('processing');
span.show();
});
$('button.refresh').click(function(e){
// don't send the form
e.preventDefault();
var button = $(this);
var project = button.attr('id').replace('refresh_','');
var span = $('span#refresh_error_' + project );
function successFunc(data, status) {
span.html('');
span.append('<a href="' + data.url + '">Done</a>');
button.removeAttr("disabled");
}
function errorFunc(jqXHR, status, error) {
span.html(error);
button.removeAttr("disabled");
}
$.ajax({
url: '/release_panel/refresh/' + project + '/',
type: 'POST',
success: successFunc,
error: errorFunc,
beforeSend: csrftokenFunc
});
//deactivate button
button.attr("disabled", "disabled");
span.html('processing');
span.show();
});

@ -0,0 +1,38 @@
# Copyright (C) 2016 The Sipwise Team - http://sipwise.com
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
# 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 General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
import logging
from celery import shared_task
from release_dashboard.models import Project
from django.conf import settings
from .utils import get_gerrit_tags, get_gerrit_branches
logger = logging.getLogger(__name__)
rd_settings = settings.RELEASE_DASHBOARD_SETTINGS
@shared_task(ignore_result=True)
def gerrit_fetch_info(projectname):
project, _ = Project.objects.get_or_create(name=projectname)
project.tags = get_gerrit_tags(projectname)
project.branches = get_gerrit_branches(projectname)
project.save()
@shared_task(ignore_result=True)
def gerrit_fetch_all():
for project in rd_settings['projects']:
gerrit_fetch_info.delay(project)

@ -0,0 +1,4 @@
{% extends "panel/base.html" %}
{% block applist %}
<li><a href="{% url 'panel:index' %}">Panel</a></li>
{% endblock %}

@ -0,0 +1,79 @@
{% extends "release_dashboard/base.html" %}
{% load staticfiles %}
{% block title %}Build release{% endblock %}
{% block navlist %}
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
<li><a href="{% url 'release_dashboard:build_release'%}">Build release</a></li>
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Actions</h3>
</div>
<div class="panel-body">
<form class="form-inline">{% csrf_token %}
<div class="form-group">
<label for="common_select">Common selection</label>
<select class="form-control" id="common_select"
name="common_select">
<option value="ignore">ignore</option>
{% for v in common_versions.tags %}
<option value="tag/{{ v }}">tag/{{ v }}</option>
{% endfor %}
{% for v in common_versions.branches %}
<option value="branch/{{ v }}">branch/{{ v }}</option>
{% endfor %}
</select>
<span id="select_text_info"></span>
</div>
<div class="form-group">
<label for="version_release">Release version</label>
<input class="form-control" id="version_release"
name="version_release" type="text" value="">
</div>
<div class="form-group">
<label for="distribution">Debian release</label>
<select class="form-control" id="distribution"
name="distribution">
{% for d in debian %}
<option value="{{ d }}">{{ d }}</option>
{% endfor %}
</select>
</div>
</form>
</div>
</div>
<div>
<table class="table table-condensed">
<thead>
<tr>
<th>Project</th>
<th>Version</th>
</tr>
</thead>
<tbody>
{% for p in projects %}
<tr class="repo">
<th><label for="version_{{ p.name }}">{{ p.name }}</label></th>
<td class="version">
<select id="version_{{ p.name }}" name="version_{{ p.name }}">
<option value="ignore">ignore</option>
{% for t in p.tags %}
<option value="tag/{{ t }}">tag/{{ t }}</option>
{% endfor %}
{% for b in p.branches %}
<option value="branch/{{ b }}">branch/{{ b }}</option>
{% endfor %}
</select>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extrajs %}
<script src="{% static "release_dashboard/js/build.js" %}"></script>
{% endblock %}

@ -0,0 +1,79 @@
{% extends "release_dashboard/base.html" %}
{% load staticfiles %}
{% block title %}Build dependences{% endblock %}
{% block navlist %}
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
<li><a href="{% url 'release_dashboard:build_deps'%}">Build dependences</a></li>
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Actions</h3>
</div>
<div class="panel-body">
<form class="form-inline">{% csrf_token %}
<div class="form-group">
<label for="common_select">Common selection</label>
<select class="form-control" id="common_select"
name="common_select">
<option value="ignore">ignore</option>
{% for v in common_versions.tags %}
<option value="tags/{{ v }}">tags/{{ v }}</option>
{% endfor %}
{% for v in common_versions.branches %}
<option value="branch/{{ v }}">branch/{{ v }}</option>
{% endfor %}
</select>
<span id="select_text_info"></span>
</div>
<div class="form-group">
<label for="version_release">Release version</label>
<input class="form-control" id="version_release"
name="version_release" type="text" value="">
</div>
<div class="form-group">
<label for="distribution">Debian release</label>
<select class="form-control" id="distribution"
name="distribution">
{% for d in debian %}
<option value="{{ d }}">{{ d }}</option>
{% endfor %}
</select>
</div>
</form>
</div>
</div>
<div>
<table class="table table-condensed">
<thead>
<tr>
<th>Project</th>
<th>Version</th>
</tr>
</thead>
<tbody>
{% for p in projects %}
<tr class="repo">
<th><label for="version_{{ p.name }}">{{ p.name }}</label></th>
<td class="version">
<select id="version_{{ p.name }}" name="version_{{ p.name }}">
<option value="ignore">ignore</option>
{% for t in p.tags %}
<option value="tag/{{ t }}">tag/{{ t }}</option>
{% endfor %}
{% for b in p.branches %}
<option value="branch/{{ b }}">branch/{{ b }}</option>
{% endfor %}
</select>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extrajs %}
<script src="{% static "release_dashboard/js/build.js" %}"></script>
{% endblock %}

@ -0,0 +1,58 @@
{% extends "release_dashboard/base.html" %}
{% load staticfiles %}
{% block title %}Hotfixes{% endblock %}
{% block navlist %}
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
<li><a href="{% url 'release_dashboard:hotfix'%}">Hotfixes</a></li>
{% endblock %}
{% block content %}
<div class="container">
<table class="table table-condensed">
<thead>
<tr>
<th>Project</th>
<th>Version</th>
<th>Push</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<form class="form-inline">{% csrf_token %}</form>
{% for p in projects %}
<tr class="repo">
<th><label for="version_{{ p.name }}">{{ p.name }}</label></th>
<td class="version">
<select class="form-control" id="version_{{ p.name }}" name="version_{{ p.name }}">
<option value="ignore">ignore</option>
{% for t in p.tags %}
<option value="tag/{{ t }}">tag/{{ t }}</option>
{% endfor %}
{% for b in p.branches %}
<option value="branch/{{ b }}">branch/{{ b }}</option>
{% endfor %}
</select>
</td>
<td>
<select class="form-control" id="push_{{ p.name }}">
<option value="yes" selected="selected">yes</option>
<option value="no">no</option>
</select>
</td>
<td>
<button type="button" id="hotfix_{{ p.name }}" class="btn btn-warning hotfix" disabled="disabled">Select branch to hotfix</button>
</td>
<td>
<span class="text-danger" id="hotfix_error_{{ p.name }}"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extrajs %}
<script src="{% static "release_dashboard/js/csrf.js" %}"></script>
<script src="{% static "release_dashboard/js/hotfix.js" %}"></script>
{% endblock %}

@ -0,0 +1,36 @@
{% extends "release_dashboard/base.html" %}
{% block title %}Release Dashboard{% endblock %}
{% block navlist %}
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Actions</h3>
</div>
<div class="panel-body">
<ul class="list-group">
<!-- Not implemented
<li class="list-group-item">
<a href="{% url 'release_dashboard:build_deps'%}">
Build release dependences</a>
</li>
<li class="list-group-item">
<a href="{% url 'release_dashboard:build_release'%}">
Build release</a>
</li>
-->
<li class="list-group-item">
<a href="{% url 'release_dashboard:hotfix'%}">
Hotfixes</a>
</li>
<li class="list-group-item">
<a href="{% url 'release_dashboard:refresh_all'%}">
Refresh tag/branch Info</a>
</li>
</ul>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,53 @@
{% extends "release_dashboard/base.html" %}
{% load staticfiles %}
{% block title %}Hotfixes{% endblock %}
{% block navlist %}
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
<li><a href="{% url 'release_dashboard:refresh_all'%}">Refresh Info</a></li>
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Actions</h3>
</div>
<div class="panel-body">
<form class="form-inline">{% csrf_token %}
<div class="form-group">
<button type="button" class="btn btn-danger" id="refresh_all">Refresh All</button>
<span style="display:none" id="refresh_all_error"></span>
</div>
</form>
</div>
</div>
<div>
<table class="table table-condensed">
<thead>
<tr>
<th>Project</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<form class="form-inline">{% csrf_token %}</form>
{% for p in projects %}
<tr class="repo">
<th><label for="version_{{ p.name }}">{{ p.name }}</label></th>
<td>
<button type="button" class="btn btn-info refresh" id="refresh_{{ p.name }}" class="refresh">Refresh</button>
</td>
<td>
<span style="display:none" class="text-danger" id="refresh_error_{{ p.name }}"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extrajs %}
<script src="{% static "release_dashboard/js/csrf.js" %}"></script>
<script src="{% static "release_dashboard/js/refresh_info.js" %}"></script>
{% endblock %}

@ -0,0 +1,155 @@
# Copyright (C) 2016 The Sipwise Team - http://sipwise.com
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
# 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 General Public License along
# with this prograproj. If not, see <http://www.gnu.org/licenses/>.
import copy
from django.test import TestCase
from release_dashboard.models import Project
GERRIT_REST_TAGS = """
)]}'
[
{
"ref": "refs/tags/mr2.0.0"
},
{
"ref": "refs/tags/mr1.0.0"
}
]
"""
FILTERED_TAGS = [
{
"ref": "refs/tags/mr2.0.0"
},
{
"ref": "refs/tags/mr1.0.0"
}
]
GERRIT_REST_BRANCHES = """
)]}'
[
{
"ref": "refs/heads/master"
},
{
"ref": "refs/heads/vseva/1789"
}
]
"""
FILTERED_BRANCHES = [
{
"ref": "refs/heads/master"
},
{
"ref": "refs/heads/vseva/1789"
}
]
class ProjectTestCase(TestCase):
def test_create(self):
proj = Project.objects.create(name="fake")
self.assertEquals(proj.name, "fake")
def test_tags(self):
proj = Project.objects.create(name="fake", json_tags=FILTERED_TAGS)
self.assertEquals(proj.name, "fake")
self.assertIsInstance(proj.tags, list)
self.assertItemsEqual(proj.tags, ["mr2.0.0", "mr1.0.0", ])
def test_tags_null(self):
proj = Project.objects.create(name="fake")
self.assertEquals(proj.name, "fake")
self.assertIsInstance(proj.tags, list)
self.assertItemsEqual(proj.tags, [])
def test_branches(self):
proj = Project.objects.create(name="fake",
json_branches=FILTERED_BRANCHES)
self.assertEquals(proj.name, "fake")
self.assertIsInstance(proj.branches, list)
self.assertItemsEqual(proj.branches, ["vseva/1789", "master"])
def test_branches_null(self):
proj = Project.objects.create(name="fake")
self.assertEquals(proj.name, "fake")
self.assertIsInstance(proj.branches, list)
self.assertItemsEqual(proj.branches, [])
def test_filtered_json(self):
res = Project._get_filtered_json(GERRIT_REST_TAGS)
self.assertEquals(res, FILTERED_TAGS)
def test_filter_values(self):
values = copy.deepcopy(FILTERED_TAGS)
res = Project._filter_values(FILTERED_TAGS, '^refs/tags/(.+)$')
self.assertEquals(res, ['mr2.0.0', 'mr1.0.0'])
values.append({"ref": "no/no"})
res = Project._filter_values(values, '^refs/tags/(.+)$')
self.assertEquals(res, ['mr2.0.0', 'mr1.0.0'])
def test_filter_values_regex(self):
values = copy.deepcopy(FILTERED_TAGS)
res = Project._filter_values(FILTERED_TAGS, '^refs/tags/(.+)$',
r'^mr[0-9]+\.[0-9]+\.[0-9]+$')
self.assertEquals(res, ['mr2.0.0', 'mr1.0.0'])
values.append({"ref": "refs/tags/3.7.8"})
res = Project._filter_values(values, '^refs/tags/(.+)$',
r'^mr[0-9]+\.[0-9]+\.[0-9]+$')
self.assertEquals(res, ['mr2.0.0', 'mr1.0.0'])
res = Project._filter_values(values, '^refs/tags/(.+)$',
r'^[0-9]+\.[0-9]+\.[0-9]+$')
self.assertEquals(res, ['3.7.8'])
def test_tags_set(self):
proj = Project.objects.create(name="fake")
self.assertEquals(proj.name, "fake")
self.assertIsInstance(proj.tags, list)
self.assertItemsEqual(proj.tags, [])
proj.tags = GERRIT_REST_TAGS
self.assertItemsEqual(proj.tags, ["mr2.0.0", "mr1.0.0"])
def test_branches_set(self):
proj = Project.objects.create(name="fake")
self.assertEquals(proj.name, "fake")
self.assertIsInstance(proj.branches, list)
self.assertItemsEqual(proj.branches, [])
proj.branches = GERRIT_REST_BRANCHES
self.assertItemsEqual(proj.branches, ["master", "vseva/1789"])
def test_branches_mrXX(self):
tmp = [
{"ref": "refs/heads/master"},
{"ref": "refs/heads/mr0.1"},
{"ref": "refs/heads/mr0.1.1"},
{"ref": "refs/heads/vseva/nono"},
]
proj = Project.objects.create(name="fake",
json_branches=tmp)
self.assertEquals(proj.name, "fake")
self.assertItemsEqual(proj.branches_mrXX(),
["mr0.1", ])
def test_branches_mrXXX(self):
tmp = [
{"ref": "refs/heads/master"},
{"ref": "refs/heads/mr0.1"},
{"ref": "refs/heads/mr0.1.1"},
{"ref": "refs/heads/vseva/nono"},
]
proj = Project.objects.create(name="fake",
json_branches=tmp)
self.assertEquals(proj.name, "fake")
self.assertItemsEqual(proj.branches_mrXXX(),
["mr0.1.1", ])

@ -0,0 +1,28 @@
# Copyright (C) 2015 The Sipwise Team - http://sipwise.com
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
# 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 General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^build_deps/$', views.build_deps, name='build_deps'),
url(r'^build/$', views.build_release, name='build_release'),
url(r'^hotfix/$', views.hotfix, name='hotfix'),
url(r'^hotfix/(?P<branch>[^/]+)/(?P<project>[^/]+)/$',
views.hotfix_build),
url(r'^refresh/$', views.refresh_all, name='refresh_all'),
url(r'^refresh/(?P<project>[^/]+)/$', views.refresh, name='refresh'),
]

@ -0,0 +1,87 @@
# Copyright (C) 2015 The Sipwise Team - http://sipwise.com
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
# 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 General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import uuid
import urllib
import requests
from requests.auth import HTTPDigestAuth
from django.conf import settings
from repoapi.utils import openurl
from release_dashboard.models import Project
logger = logging.getLogger(__name__)
def get_response(url):
auth = HTTPDigestAuth(
settings.GERRIT_REST_HTTP_USER,
settings.GERRIT_REST_HTTP_PASSWD)
response = requests.get(url, auth=auth)
return response
def trigger_hotfix(project, branch, push="yes"):
flow_uuid = uuid.uuid4()
params = {
"base": settings.JENKINS_URL,
'token': urllib.quote(settings.JENKINS_TOKEN),
'action': urllib.quote("--hotfix"),
'branch': urllib.quote(branch),
'project': urllib.quote(project),
'push': urllib.quote(push),
'uuid': flow_uuid,
}
url = ("{base}/job/release-tools-runner/buildWithParameters?"
"token={token}&action={action}&branch={branch}&"
"PROJECTNAME={project}&repository={project}&"
"push={push}&uuid={uuid}".format(**params))
if settings.DEBUG:
logger.debug("Debug mode, would trigger: %s", url)
# raise Exception("debug error")
else:
openurl(url)
return "%s/job/release-tools-runner/" % settings.JENKINS_URL
def get_gerrit_info(url):
if settings.DEBUG:
logger.debug("Debug mode, would trigger: %s", url)
return r")]}'\n[]"
else:
response = get_response(url)
response.raise_for_status()
return response.text
def get_tags(projectname, regex=None):
project, _ = Project.objects.get_or_create(name=projectname)
return project.filter_tags(regex)
def get_branches(projectname, regex=None):
project, _ = Project.objects.get_or_create(name=projectname)
return project.filter_branches(regex)
def get_gerrit_tags(project, regex=None):
url = settings.GERRIT_URL.format("a/projects/%s/tags/" % project)
return get_gerrit_info(url)
def get_gerrit_branches(project, regex=None):
url = settings.GERRIT_URL.format("a/projects/%s/branches/" % project)
return get_gerrit_info(url)

@ -0,0 +1,142 @@
# Copyright (C) 2015 The Sipwise Team - http://sipwise.com
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
# 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 General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import re
import json
from django.shortcuts import render
from django.http import HttpResponseNotFound, JsonResponse
from django.conf import settings
from django.views.decorators.http import require_http_methods
from release_dashboard.models import Project
from .utils import get_tags, get_branches, trigger_hotfix
from .tasks import gerrit_fetch_info, gerrit_fetch_all
rd_settings = settings.RELEASE_DASHBOARD_SETTINGS
logger = logging.getLogger(__name__)
regex_hotfix = re.compile(r'^mr[0-9]+\.[0-9]+\.[0-9]+$')
regex_mr = re.compile(r'^mr.+$')
def index(request):
context = {}
return render(request, 'release_dashboard/index.html', context)
def _projects_versions(projects, regex=None, tag_only=False, ):
res = []
for project in projects:
info = {
'name': project,
'tags': get_tags(project, regex),
}
if not tag_only:
info['branches'] = get_branches(project, regex)
res.append(info)
logger.debug(res)
return res
def _common_versions(context):
common_versions = {'tags': set(), 'branches': set()}
for project in context['projects']:
common_versions['tags'] |= set(project['tags'])
common_versions['branches'] |= set(project['branches'])
context['common_versions'] = {
'tags': sorted(common_versions['tags']),
'branches': sorted(common_versions['branches']),
}
@require_http_methods(["POST", ])
def hotfix_build(request, branch, project):
if project not in rd_settings['projects']:
error = "repo:%s not valid" % project
logger.error(error)
return HttpResponseNotFound(error)
if not regex_hotfix.match(branch):
error = "branch:%s not valid. Not mrX.X.X format" % branch
logger.error(error)
return HttpResponseNotFound(error)
proj = Project.objects.get(name=project)
if branch not in proj.branches_mrXXX():
error = "branch:%s not valid" % branch
logger.error(error)
return HttpResponseNotFound(error)
json_data = json.loads(request.body)
if json_data['push'] == 'no':
logger.warn("dryrun for %s:%s", project, branch)
url = trigger_hotfix(project, branch, json_data['push'])
return JsonResponse({'url': url})
def build_deps(request):
context = {
'projects': _projects_versions(
rd_settings['build_deps'],
regex_mr
),
'debian': rd_settings['debian_supported'],
}
_common_versions(context)
return render(request, 'release_dashboard/build_deps.html', context)
def hotfix(request):
context = {
'projects': _projects_versions(
rd_settings['projects'],
regex_hotfix
)
}
return render(request, 'release_dashboard/hotfix.html', context)
def build_release(request):
context = {
'projects': _projects_versions(
rd_settings['projects'],
regex_mr
),
'debian': rd_settings['debian_supported'],
}
_common_versions(context)
return render(request, 'release_dashboard/build.html', context)
def refresh_all(request):
if request.method == "POST":
res = gerrit_fetch_all.delay()
return JsonResponse({'url': '/flower/task/%s' % res.id})
else:
projects = []
for project in rd_settings['projects']:
info = {
'name': project,
'tags': None
}
projects.append(info)
return render(request, 'release_dashboard/refresh.html',
{'projects': projects})
@require_http_methods(["POST", ])
def refresh(request, project):
res = gerrit_fetch_info.delay(project)
return JsonResponse({'url': '/flower/task/%s' % res.id})

@ -25,6 +25,7 @@ PROJECT_APPS = [
'repoapi',
'hotfix',
'panel',
'release_dashboard',
]
INSTALLED_APPS = [
@ -134,6 +135,10 @@ LOGGING = {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
},
'release_dashboard': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
},
},
}
@ -145,3 +150,96 @@ CELERY_ACCEPT_CONTENT = ['json']
CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend'
HOTFIX_ARTIFACT = 'debian_changelog.txt'
RELEASE_DASHBOARD_SETTINGS = {
'debian_supported': ('jessie', 'wheezy', 'squeeze'),
'build_deps': (
"data-hal", "libswrate", "sipwise-base", "mediaproxy-ng",
"ngcp-schema", "rtpengine", "libtcap", "libinewrate"
),
'projects': (
"acc-cdi",
"asterisk",
"asterisk-sounds",
"asterisk-voicemail",
"backup-tools",
"bulk-processor",
"bootenv",
"captagent",
"cdr-exporter",
"cfg-schema",
"check-tools",
"cleanup-tools",
"cloudpbx-devices",
"cloudpbx-sources",
"collectd-mod-redis",
"comx",
"comx-application",
"comx-fileshare-service",
"comx-sip",
"comx-xmpp",
"data-hal",
"db-schema",
"diva-drivers",
"documentation",
"faxserver",
"glusterfs-config",
"heartbeat",
"hylafaxplus",
"iaxmodem",
"installer",
"kamailio",
"kamailio-config-tests",
"keyring",
"kibana",
"klish",
"libinewrate",
"libswrate",
"libtcap",
"license-client",
"lnpd",
"lua-ngcp-kamailio",
"mediaproxy-ng",
"mediaproxy-redis",
"mediator",
"megacli",
"metapackages",
"monitoring-tools",
"netscript",
"ngcp-api-tools",
"ngcp-klish-config",
"ngcp-panel",
"ngcp-prompts",
"ngcp-rtcengine",
"ngcp-schema",
"ngcp-status",
"ngcp-support",
"ngcpcfg",
"ngcpcfg-api",
"ngcpcfg-ha",
"ngrep-sip",
"ossbss",
"prosody",
"pushd",
"rate-o-mat",
"reminder",
"rtpengine",
"rtpengine-redis",
"sems",
"sems-app",
"sems-ha",
"sems-pbx",
"sems-prompts",
"sipsak",
"sipwise-base",
"snmp-agent",
"system-tests",
"system-tools",
"templates",
"upgrade",
"vmnotify",
"voisniff-ng",
"www_admin",
"www_csc"
),
}

@ -19,6 +19,9 @@ import os
# pylint: disable=W0401,W0614,C0413
from .test import *
LOGGING['loggers']['release_dashboard']['level'] = \
os.getenv('DJANGO_LOG_LEVEL', 'DEBUG')
# celery
BROKER_BACKEND = 'amqp'
CELERY_ALWAYS_EAGER = False

@ -15,6 +15,7 @@
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from ConfigParser import RawConfigParser
# pylint: disable=W0401,W0614
from .common import *
@ -53,6 +54,12 @@ LOGGING['loggers']['repoapi']['level'] = os.getenv('DJANGO_LOG_LEVEL', 'INFO')
JENKINS_URL = "https://jenkins.mgm.sipwise.com"
GERRIT_URL = "https://gerrit.mgm.sipwise.com/{}"
gerrit_config = RawConfigParser()
gerrit_config.read(os.path.join(VAR_DIR, 'gerrit.ini'))
GERRIT_REST_HTTP_USER = gerrit_config.get('gerrit', 'HTTP_USER')
GERRIT_REST_HTTP_PASSWD = gerrit_config.get('gerrit', 'HTTP_PASSWD')
GITWEB_URL = "https://git.mgm.sipwise.com/gitweb/?p={}.git;a=commit;h={}"
WORKFRONT_CREDENTIALS = os.path.join(BASE_DIR,
'/etc/jenkins_jobs/workfront.ini')

@ -61,6 +61,8 @@ DJANGO_LOG_LEVEL = 'DEBUG'
JENKINS_URL = "http://localhost"
GERRIT_URL = "https://gerrit.local/{}"
GERRIT_REST_HTTP_USER = 'jenkins'
GERRIT_REST_HTTP_PASSWD = 'verysecrethttppasswd'
GITWEB_URL = "https://git.local/gitweb/?p={}.git;a=commit;h={}"
WORKFRONT_CREDENTIALS = os.path.join(BASE_DIR, '.workfront.ini')

@ -57,4 +57,6 @@ urlpatterns = [
url(r'^docs/', views.schema_view),
url(r'^panel/', include('panel.urls',
namespace='panel')),
url(r'^release_panel/',
include('release_dashboard.urls', namespace='release_dashboard')),
]

Loading…
Cancel
Save