mirror of https://github.com/sipwise/repoapi.git
* fix duplicate rule in Makefile * Implement just hotfix for now * /var/lib/repoapi/gerrit.ini needed [gerrit REST HTTP password] Change-Id: I80286e9d97846cf037566eaca74079362fa6e54achanges/11/8411/11
parent
8f0ff90375
commit
16a3bcc4e0
@ -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})
|
Loading…
Reference in new issue