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