mirror of https://github.com/sipwise/repoapi.git
* provide info of actual images and tags * refresh docker images/tag via release_dashboard * keep that info in db * fix Dockerfile documentation * cleanup thanks to flake8 Change-Id: I743482da9b4d50f7b1832cad324882f3a49cbcf0changes/94/13994/10
parent
01411e6ea1
commit
731dd69df0
File diff suppressed because one or more lines are too long
@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2017-06-24 23:00
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields.json
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('release_dashboard', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DockerImage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('project', models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='release_dashboard.Project')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DockerTag',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('manifests', django_extensions.db.fields.json.JSONField()),
|
||||
('image', models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='release_dashboard.DockerImage')),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='dockertag',
|
||||
unique_together=set([('name', 'image')]),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,32 @@
|
||||
# Copyright (C) 2017 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 rest_framework import serializers
|
||||
from . import models
|
||||
|
||||
|
||||
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Project
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DockerImageSerializer(serializers.HyperlinkedModelSerializer):
|
||||
project = serializers.StringRelatedField()
|
||||
|
||||
class Meta:
|
||||
model = models.DockerImage
|
||||
fields = '__all__'
|
||||
@ -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/docker/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/docker/refresh/' + project + '/',
|
||||
type: 'POST',
|
||||
success: successFunc,
|
||||
error: errorFunc,
|
||||
beforeSend: csrftokenFunc
|
||||
});
|
||||
|
||||
//deactivate button
|
||||
button.attr("disabled", "disabled");
|
||||
span.html('processing');
|
||||
span.show();
|
||||
|
||||
});
|
||||
@ -0,0 +1,56 @@
|
||||
<div class="container">
|
||||
<form method="POST" class="form-inline">{% csrf_token %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Actions</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<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.branches %}
|
||||
<option value="branch/{{ v }}">branch/{{ v }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button id="main" type="submit" class="btn btn-default">Submit</button>
|
||||
</div>
|
||||
<div id="select_text_info" class="panel-footer"></div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Data</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-2">Project</th>
|
||||
<th class="col-md-2">Version</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in projects %}
|
||||
<tr class="repo">
|
||||
<th class="col-md-2">
|
||||
<label for="version_{{ p.name }}">{{ p.name }}</label>
|
||||
</th>
|
||||
<td class="version col-md-2">
|
||||
<select class="form-control select-version" id="version_{{ p.name }}" name="version_{{ p.name }}"
|
||||
project="{{ p.name }}">
|
||||
<option value="ignore">ignore</option>
|
||||
{% for b in p.branches %}
|
||||
<option value="branch/{{ b }}">branch/{{ b }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -0,0 +1,43 @@
|
||||
<div class="container">
|
||||
<form method="POST" class="form-inline">{% csrf_token %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Data</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>Project</th>
|
||||
<th>Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i in images %}
|
||||
<tr>
|
||||
<th><label>{{ i.name }}</label></th>
|
||||
<td>{{ i.project.name }}</td>
|
||||
<td>
|
||||
{% for tag in i.dockertag_set.all %}
|
||||
<a href="{{URL_BASE}}{{i.name}}/manifests/{{tag.name}}">
|
||||
{% if tag.name|length > 10 %}
|
||||
<span class="label label-warning"
|
||||
{% else %}
|
||||
<span class="label label-success"
|
||||
{% endif %}
|
||||
data-toggle="tooltip" title="{{tag.date|date:"DATETIME_FORMAT"}}">
|
||||
{{ tag.name|truncatechars:10 }}
|
||||
</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -0,0 +1,10 @@
|
||||
{% extends "release_dashboard/base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% block title %}Build docker images per project{% endblock %}
|
||||
{% block navlist %}
|
||||
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
|
||||
<li><a href="{% url 'release_dashboard:docker_images'%}">Docker Images</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% include "release_dashboard/docker_content.html" %}
|
||||
{% endblock %}
|
||||
@ -0,0 +1,40 @@
|
||||
<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>
|
||||
@ -0,0 +1,14 @@
|
||||
{% extends "release_dashboard/base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% block title %}Refresh Docker{% endblock %}
|
||||
{% block navlist %}
|
||||
<li><a href="{% url 'release_dashboard:index'%}">Release Dashboard</a></li>
|
||||
<li><a href="{% url 'release_dashboard:refresh_docker_all'%}">Refresh Docker Info</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% include "release_dashboard/refresh_content.html" %}
|
||||
{% endblock %}
|
||||
{% block extrajs %}
|
||||
<script src="{% static "release_dashboard/js/csrf.js" %}"></script>
|
||||
<script src="{% static "release_dashboard/js/refresh_docker_info.js" %}"></script>
|
||||
{% endblock %}
|
||||
@ -0,0 +1,93 @@
|
||||
# Copyright (C) 2017 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/>.
|
||||
|
||||
from django.test import TestCase
|
||||
from release_dashboard.models import Project, DockerImage, DockerTag
|
||||
import datetime
|
||||
|
||||
|
||||
class DockerImageTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.proj = Project.objects.create(name="fake")
|
||||
|
||||
def test_create(self):
|
||||
image = DockerImage.objects.create(
|
||||
name='fake-jessie', project=self.proj)
|
||||
self.assertItemsEqual(self.proj.dockerimage_set.all(),
|
||||
[image, ])
|
||||
|
||||
def test_remove_image(self):
|
||||
image = DockerImage.objects.create(
|
||||
name='fake-jessie', project=self.proj)
|
||||
self.assertItemsEqual(self.proj.dockerimage_set.all(),
|
||||
[image, ])
|
||||
image.delete()
|
||||
self.assertTrue(Project.objects.filter(name="fake").exists())
|
||||
|
||||
def test_remove_project(self):
|
||||
image = DockerImage.objects.create(
|
||||
name='fake-jessie', project=self.proj)
|
||||
self.assertItemsEqual(self.proj.dockerimage_set.all(), [image, ])
|
||||
self.proj.delete()
|
||||
self.assertFalse(Project.objects.filter(name="fake").exists())
|
||||
self.assertFalse(DockerImage.objects.filter(name="fake").exists())
|
||||
|
||||
def test_filter_images(self):
|
||||
images = ['fake-jessie', 'other', 'ngcp-fake', 'fake-more']
|
||||
images_ok = ['fake-jessie', 'ngcp-fake', 'fake-more']
|
||||
self.assertItemsEqual(
|
||||
self.proj.filter_docker_images(images), images_ok)
|
||||
|
||||
def test_image_tags(self):
|
||||
image = DockerImage.objects.create(
|
||||
name='fake-jessie', project=self.proj)
|
||||
self.assertItemsEqual(image.tags, [])
|
||||
DockerTag.objects.create(
|
||||
name='latest',
|
||||
image=image,
|
||||
manifests='{}')
|
||||
self.assertItemsEqual(image.tags, ['latest', ])
|
||||
DockerTag.objects.create(
|
||||
name='mr5.4',
|
||||
image=image,
|
||||
manifests='{}')
|
||||
self.assertItemsEqual(image.tags, ['latest', 'mr5.4'])
|
||||
|
||||
|
||||
class DockerImageTest2Case(TestCase):
|
||||
fixtures = ['test_model_fixtures', ]
|
||||
|
||||
def setUp(self):
|
||||
self.images_with_tags = [
|
||||
DockerImage.objects.get(name='data-hal-jessie'),
|
||||
DockerImage.objects.get(name='documentation-jessie'),
|
||||
DockerImage.objects.get(name='ngcp-panel-selenium'),
|
||||
DockerImage.objects.get(name='ngcp-panel-tests-rest-api-jessie'),
|
||||
DockerImage.objects.get(name='ngcp-panel-tests-selenium-jessie'),
|
||||
]
|
||||
|
||||
def test_images_with_tags(self):
|
||||
self.assertItemsEqual(
|
||||
DockerImage.objects.images_with_tags(),
|
||||
self.images_with_tags)
|
||||
|
||||
def test_date(self):
|
||||
tag = DockerTag.objects.get(
|
||||
name='latest',
|
||||
image__name='ngcp-panel-tests-selenium-jessie')
|
||||
self.assertEqual(
|
||||
tag.date,
|
||||
datetime.datetime(2016, 11, 07, 20, 30, 25))
|
||||
@ -0,0 +1,100 @@
|
||||
# Copyright (C) 2017 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/>.
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
from release_dashboard import tasks
|
||||
from release_dashboard.models import Project, DockerImage
|
||||
from mock import patch, call
|
||||
|
||||
DOCKER_REST_CATALOG = """
|
||||
{
|
||||
"repositories":[
|
||||
"data-hal-jessie",
|
||||
"data-hal-selenium-jessie",
|
||||
"other",
|
||||
"one"]
|
||||
}
|
||||
"""
|
||||
|
||||
DOCKER_REST_FAKE_TAGS = {
|
||||
'data-hal-jessie': """{
|
||||
"name": "data-hal-jessie",
|
||||
"tags":[
|
||||
"I3a899",
|
||||
"latest"]
|
||||
}""",
|
||||
'data-hal-selenium-jessie': """{
|
||||
"name":"data-hal-selenium-jessie",
|
||||
"tags":["If53a9","latest"]
|
||||
}"""
|
||||
}
|
||||
|
||||
|
||||
def fake_tag(url):
|
||||
if url == "data-hal-jessie/tags/list":
|
||||
return DOCKER_REST_FAKE_TAGS['data-hal-jessie']
|
||||
elif url == "_catalog":
|
||||
return DOCKER_REST_CATALOG
|
||||
elif url == "data-hal-selenium-jessie/tags/list":
|
||||
return DOCKER_REST_FAKE_TAGS['data-hal-selenium-jessie']
|
||||
else:
|
||||
return "{}"
|
||||
|
||||
|
||||
@override_settings(CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||
@override_settings(DOCKER_REGISTRY_URL='{}')
|
||||
@override_settings(DEBUG=False)
|
||||
class TasksDockerTestCase(TestCase):
|
||||
|
||||
@patch('release_dashboard.utils.docker.get_docker_info',
|
||||
side_effect=fake_tag)
|
||||
def test_docker_fetch_info(self, gdi):
|
||||
proj = Project.objects.create(name="data-hal")
|
||||
self.assertEquals(proj.name, "data-hal")
|
||||
image = DockerImage.objects.create(
|
||||
name='data-hal-jessie', project=proj)
|
||||
self.assertItemsEqual(proj.dockerimage_set.all(), [image, ])
|
||||
result = tasks.docker_fetch_info.delay('data-hal-jessie')
|
||||
self.assertTrue(result.successful())
|
||||
image = DockerImage.objects.get(name='data-hal-jessie')
|
||||
calls = [
|
||||
call("data-hal-jessie/tags/list"),
|
||||
call("data-hal-jessie/manifests/I3a899"),
|
||||
call("data-hal-jessie/manifests/latest"),
|
||||
]
|
||||
gdi.assert_has_calls(calls)
|
||||
self.assertItemsEqual(image.tags, ["I3a899", "latest"])
|
||||
|
||||
@patch('release_dashboard.utils.docker.get_docker_info',
|
||||
side_effect=fake_tag)
|
||||
def test_docker_fetch_all(self, gdi):
|
||||
result = tasks.docker_fetch_all.delay()
|
||||
self.assertTrue(result.successful())
|
||||
proj = Project.objects.get(name="data-hal")
|
||||
images = [DockerImage.objects.get(name='data-hal-jessie'),
|
||||
DockerImage.objects.get(name='data-hal-selenium-jessie')]
|
||||
self.assertItemsEqual(proj.dockerimage_set.all(), images)
|
||||
self.assertItemsEqual(images[0].tags, ["I3a899", "latest"])
|
||||
self.assertItemsEqual(images[1].tags, ["If53a9", "latest"])
|
||||
calls = [
|
||||
call("_catalog"),
|
||||
call("data-hal-jessie/tags/list"),
|
||||
call("data-hal-jessie/manifests/I3a899"),
|
||||
call("data-hal-jessie/manifests/latest"),
|
||||
call("data-hal-selenium-jessie/tags/list"),
|
||||
call("data-hal-selenium-jessie/manifests/If53a9"),
|
||||
call("data-hal-selenium-jessie/manifests/latest"),
|
||||
]
|
||||
gdi.assert_has_calls(calls)
|
||||
@ -0,0 +1,83 @@
|
||||
# Copyright (C) 2017 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/>.
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test import override_settings
|
||||
from release_dashboard.utils import docker
|
||||
from mock import patch, call
|
||||
|
||||
DOCKER_REST_CATALOG = """
|
||||
{
|
||||
"repositories":[
|
||||
"fake-jessie",
|
||||
"fake-selenium-jessie",
|
||||
"other",
|
||||
"one"]
|
||||
}
|
||||
"""
|
||||
|
||||
DOCKER_REST_FAKE_TAGS = {
|
||||
'fake-jessie': """{
|
||||
"name": "fake-jessie",
|
||||
"tags":[
|
||||
"I3a899b8945688c2ef3a4be6ba6c4c1d4cbf6d548",
|
||||
"latest"]
|
||||
}""",
|
||||
'other': """{"name": "other", "tags":[]}""",
|
||||
}
|
||||
|
||||
|
||||
def fake_tag(url):
|
||||
if url == "fake-jessie/tags/list":
|
||||
return DOCKER_REST_FAKE_TAGS['fake-jessie']
|
||||
elif url == "other/tags/list":
|
||||
return DOCKER_REST_FAKE_TAGS['other']
|
||||
|
||||
|
||||
@override_settings(DOCKER_REGISTRY_URL='{}')
|
||||
@override_settings(DEBUG=False)
|
||||
class UtilsDockerTestCase(TestCase):
|
||||
|
||||
@patch('release_dashboard.utils.docker.get_docker_info')
|
||||
def test_get_docker_repositories(self, gdi):
|
||||
gdi.return_value = DOCKER_REST_CATALOG
|
||||
self.assertItemsEqual(
|
||||
docker.get_docker_repositories(),
|
||||
['fake-jessie',
|
||||
'fake-selenium-jessie',
|
||||
'other',
|
||||
'one']
|
||||
)
|
||||
|
||||
@patch('release_dashboard.utils.docker.get_docker_info',
|
||||
side_effect=fake_tag)
|
||||
def test_get_docker_tags(self, gdi):
|
||||
self.assertItemsEqual(
|
||||
docker.get_docker_tags('fake-jessie'),
|
||||
["I3a899b8945688c2ef3a4be6ba6c4c1d4cbf6d548",
|
||||
"latest"])
|
||||
calls = [
|
||||
call("fake-jessie/tags/list"),
|
||||
]
|
||||
gdi.assert_has_calls(calls)
|
||||
|
||||
@patch('release_dashboard.utils.docker.get_docker_info',
|
||||
side_effect=fake_tag)
|
||||
def test_get_docker_tags_empty(self, gdi):
|
||||
self.assertItemsEqual(docker.get_docker_tags('other'), [])
|
||||
calls = [
|
||||
call("other/tags/list"),
|
||||
]
|
||||
gdi.assert_has_calls(calls)
|
||||
@ -0,0 +1,26 @@
|
||||
# Copyright (C) 2017 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 release_dashboard.models import Project
|
||||
|
||||
|
||||
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)
|
||||
@ -0,0 +1,108 @@
|
||||
# Copyright (C) 2017 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 urllib
|
||||
import requests
|
||||
import json
|
||||
from django.conf import settings
|
||||
from repoapi.utils import openurl
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
docker_url = ("{base}/job/build-project-docker/buildWithParameters?"
|
||||
"token={token}&project={project}&branch={branch}")
|
||||
|
||||
|
||||
def trigger_docker_build(project, branch):
|
||||
if branch == "ignore":
|
||||
logger.debug("ignoring request to trigger project %s due"
|
||||
" to request of version 'ignore'", project)
|
||||
return
|
||||
branch = branch.split("branch/")[1]
|
||||
params = {
|
||||
'base': settings.JENKINS_URL,
|
||||
'token': urllib.quote(settings.JENKINS_TOKEN),
|
||||
'project': project,
|
||||
'branch': urllib.quote(branch),
|
||||
}
|
||||
|
||||
url = docker_url.format(**params)
|
||||
if settings.DEBUG:
|
||||
logger.debug("Debug mode, would trigger: %s", url)
|
||||
else:
|
||||
openurl(url)
|
||||
return "{base}/job/build-project-docker/".format(**params)
|
||||
|
||||
|
||||
def get_docker_info(url):
|
||||
if settings.DEBUG:
|
||||
logger.debug("Debug mode, would trigger: %s", url)
|
||||
else:
|
||||
logger.debug("trigger: %s", url)
|
||||
response = requests.get(url)
|
||||
logger.debug("response: %s" % response)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
|
||||
|
||||
def get_docker_repositories():
|
||||
if settings.DEBUG:
|
||||
result = json.loads(settings.DOCKER_REGISTRY)
|
||||
return result['repositories']
|
||||
else:
|
||||
url = settings.DOCKER_REGISTRY_URL.format("_catalog")
|
||||
try:
|
||||
info = get_docker_info(url)
|
||||
logger.debug("response: %s" % info)
|
||||
result = json.loads(info)
|
||||
return result['repositories']
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return []
|
||||
|
||||
|
||||
def get_docker_tags(image):
|
||||
if settings.DEBUG:
|
||||
try:
|
||||
return settings.DOCKER_IMAGES[image]
|
||||
except Exception as e:
|
||||
return []
|
||||
else:
|
||||
url = settings.DOCKER_REGISTRY_URL.format("%s/tags/list" % image)
|
||||
try:
|
||||
info = get_docker_info(url)
|
||||
logger.debug("response: %s" % info)
|
||||
result = json.loads(info)
|
||||
return result['tags']
|
||||
except Exception as e:
|
||||
logger.error('image: %s %s' % (image, e))
|
||||
return []
|
||||
|
||||
|
||||
def get_docker_manifests(image, tag):
|
||||
if settings.DEBUG:
|
||||
return '{}'
|
||||
else:
|
||||
dru = settings.DOCKER_REGISTRY_URL
|
||||
url = dru.format("%s/manifests/%s" % (image, tag))
|
||||
try:
|
||||
info = get_docker_info(url)
|
||||
logger.debug("response: %s" % info)
|
||||
result = json.loads(info)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error('image: %s tag:%s %s' % (image, tag, e))
|
||||
return None
|
||||
@ -0,0 +1,62 @@
|
||||
# Copyright (C) 2017 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 re
|
||||
from release_dashboard.utils import get_tags, get_branches
|
||||
|
||||
regex_hotfix = re.compile(r'^mr[0-9]+\.[0-9]+\.[0-9]+$')
|
||||
regex_mr = re.compile(r'^mr.+$')
|
||||
regex_master = re.compile(r'^master$')
|
||||
|
||||
|
||||
def _projects_versions(projects, regex=None,
|
||||
tags=True, branches=True, master=False):
|
||||
res = []
|
||||
for project in projects:
|
||||
info = {
|
||||
'name': project,
|
||||
}
|
||||
if tags:
|
||||
info['tags'] = get_tags(project, regex)
|
||||
if branches:
|
||||
info['branches'] = get_branches(project, regex)
|
||||
if master:
|
||||
info['branches'].append('master')
|
||||
res.append(info)
|
||||
return res
|
||||
|
||||
|
||||
def _common_versions(context, tags=True, branches=True):
|
||||
common_versions = {'tags': set(), 'branches': set()}
|
||||
|
||||
for project in context['projects']:
|
||||
if tags:
|
||||
common_versions['tags'] |= set(project['tags'])
|
||||
if branches:
|
||||
common_versions['branches'] |= set(project['branches'])
|
||||
context['common_versions'] = {
|
||||
'tags': sorted(common_versions['tags'], reverse=True),
|
||||
'branches': sorted(common_versions['branches'], reverse=True),
|
||||
}
|
||||
|
||||
|
||||
def _hash_versions(data, projects):
|
||||
result = {}
|
||||
for i in projects:
|
||||
try:
|
||||
result[i] = data["version_{0}".format(i)]
|
||||
except (KeyError, AttributeError):
|
||||
pass
|
||||
return result
|
||||
@ -0,0 +1,131 @@
|
||||
# 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
|
||||
from django.shortcuts import render
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.conf import settings
|
||||
from release_dashboard.utils import docker
|
||||
from release_dashboard.forms.docker import BuildDockerForm
|
||||
from release_dashboard.forms import docker_projects
|
||||
from release_dashboard.tasks import docker_fetch_info, docker_fetch_all
|
||||
from release_dashboard.models import DockerImage
|
||||
from . import _projects_versions, _common_versions, _hash_versions
|
||||
from . import regex_mr
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_docker_tags(project, tag=None):
|
||||
repos = docker.get_docker_repositories()
|
||||
r = re.compile(".*%s.*" % project)
|
||||
project_repos = filter(r.match, repos)
|
||||
logger.debug("%s: %s" % (project, project_repos))
|
||||
docker_tags = []
|
||||
for image in project_repos:
|
||||
res = {'name': image}
|
||||
tags = docker.get_docker_tags(image)
|
||||
if tag:
|
||||
logger.degug("non filtered tags: %s" % tags)
|
||||
tags = filter(re.compile(tag).match, tags)
|
||||
res['tags'] = tags
|
||||
docker_tags.append(res)
|
||||
logger.debug("docker_tags: %s" % docker_tags)
|
||||
return docker_tags
|
||||
|
||||
|
||||
def _build_docker_logic(form, projects):
|
||||
result = _hash_versions(form.cleaned_data, projects)
|
||||
context = {'projects': []}
|
||||
for pro in projects:
|
||||
try:
|
||||
logger.debug(
|
||||
"trying to trigger docker image at branch %s for project %s",
|
||||
result[pro], pro)
|
||||
url = docker.trigger_docker_build(pro, result[pro])
|
||||
context['projects'].append(
|
||||
{'name': pro, 'url': url})
|
||||
except KeyError:
|
||||
logger.error("Houston, we have a problem with"
|
||||
"trigger for %s", pro)
|
||||
context['projects'].append(
|
||||
{'name': pro, 'url': None})
|
||||
return context
|
||||
|
||||
|
||||
def build_docker_images(request):
|
||||
if request.method == "POST":
|
||||
form = BuildDockerForm(request.POST)
|
||||
if form.is_valid():
|
||||
context = _build_docker_logic(form, docker_projects)
|
||||
else:
|
||||
context = {'error': 'form validation error'}
|
||||
return render(request,
|
||||
'release_dashboard/build_result.html',
|
||||
context)
|
||||
else:
|
||||
context = {
|
||||
'projects': _projects_versions(
|
||||
docker_projects,
|
||||
regex_mr,
|
||||
False,
|
||||
True,
|
||||
True,
|
||||
),
|
||||
'common_versions': {
|
||||
'tags': [],
|
||||
'branches': ['master', ]
|
||||
},
|
||||
'docker': True,
|
||||
}
|
||||
_common_versions(context, False, True)
|
||||
return render(request,
|
||||
'release_dashboard/build_docker.html',
|
||||
context)
|
||||
|
||||
|
||||
def refresh_all(request):
|
||||
if request.method == "POST":
|
||||
res = docker_fetch_all.delay()
|
||||
return JsonResponse({'url': '/flower/task/%s' % res.id})
|
||||
else:
|
||||
projects = []
|
||||
for project in docker_projects:
|
||||
info = {
|
||||
'name': project,
|
||||
'tags': None
|
||||
}
|
||||
projects.append(info)
|
||||
return render(request, 'release_dashboard/refresh_docker.html',
|
||||
{'projects': projects})
|
||||
|
||||
|
||||
@require_http_methods(["POST", ])
|
||||
def refresh(request, project):
|
||||
res = docker_fetch_info.delay(project)
|
||||
return JsonResponse({'url': '/flower/task/%s' % res.id})
|
||||
|
||||
|
||||
@require_http_methods(["GET", ])
|
||||
def docker_images(request):
|
||||
images = DockerImage.objects.images_with_tags
|
||||
context = {
|
||||
'images': images,
|
||||
'URL_BASE': settings.DOCKER_REGISTRY_URL.format(''),
|
||||
}
|
||||
return render(request, 'release_dashboard/docker_images.html',
|
||||
context)
|
||||
Loading…
Reference in new issue