From 24dd8bcfbd7697c7cd179eecd0e6f0e506711653 Mon Sep 17 00:00:00 2001 From: Victor Seva Date: Thu, 9 Jun 2022 22:55:17 +0200 Subject: [PATCH] TT#15305 release_dashboard: hotfix from release directly * don't use any info from Projects, just from ReleaseConfig Change-Id: I703f14d9ae0e3781adf1fb8f47147ee24c3c01a0 --- .../release_dashboard/js/hotfix_release.js | 70 +++++++++++++++++ .../release_dashboard/build_release.html | 10 ++- .../release_dashboard/hotfix_release.html | 66 ++++++++++++++++ release_dashboard/test/test_views.py | 78 +++++++++++++++++-- release_dashboard/urls.py | 10 +++ release_dashboard/views/__init__.py | 3 +- release_dashboard/views/build.py | 38 +++++++++ 7 files changed, 265 insertions(+), 10 deletions(-) create mode 100644 release_dashboard/static/release_dashboard/js/hotfix_release.js create mode 100644 release_dashboard/templates/release_dashboard/hotfix_release.html diff --git a/release_dashboard/static/release_dashboard/js/hotfix_release.js b/release_dashboard/static/release_dashboard/js/hotfix_release.js new file mode 100644 index 0000000..630ce15 --- /dev/null +++ b/release_dashboard/static/release_dashboard/js/hotfix_release.js @@ -0,0 +1,70 @@ +/** + * + * + */ +$( "button.hotfix" ).click( function( e ) { + + // don't send the form + e.preventDefault(); + var button = $( this ); + var id = button.attr( "id" ).replace( "hotfix_", "" ); + var repo = id; + var span = $( "span#hotfix_error_" + id ); + var links = $( "#links_" + id ); + var push = $( "select#push_" + id + " option:selected" ).val(); + var empty = $( "input#empty_" + id ).prop( "checked" ); + + $.ajax( { + url: repo + "/", + type: "POST", + data: JSON.stringify( { push: push, empty: empty } ), + contentType: "application/json; charset=utf-8", + dataType: "json", + success: successFunc, + error: errorFunc, + /* eslint-disable-next-line no-undef */ // at csrf.js + beforeSend: csrftokenFunc + } ); + + button.attr( "disabled", "disabled" ); + span.html( "processing" ); + span.show(); + links.addClass( "hidden" ); + + function successFunc( data, _status ) { + span.html( "" ); + $( "#link_done_" + id ).attr( "href", data.urls[ 0 ] ); + $( "#link_latest_" + id ).attr( "href", data.urls[ 1 ] ); + links.removeClass( "hidden" ); + 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 ); + var links = $( "#links_" + 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( "" ); + links.addClass( "hidden" ); +} ); + +$( document ).ready( function() { + $( "td.version > select option[value^=\"branch/mr\"]" ).each( function() { + $( this ).change(); +} ); +} ); diff --git a/release_dashboard/templates/release_dashboard/build_release.html b/release_dashboard/templates/release_dashboard/build_release.html index 0c7cab5..9719fe5 100644 --- a/release_dashboard/templates/release_dashboard/build_release.html +++ b/release_dashboard/templates/release_dashboard/build_release.html @@ -61,13 +61,19 @@
-

Projects to build

+
+

Projects to build

+ +
    {% for prj in config.projects %}
  • {{ prj }} + {% if prj in build_deps %}{% endif %}
  • {% endfor %}
@@ -84,6 +90,7 @@ $( document ).ready(function() { if (builds > 0 && release.match(/^release-mr[0-9]+\.[0-9]+\.[0-9]+$/)) { $( "#build_button" ).attr("disabled", "disabled"); console.debug("mrX.X.X release, can't be built twice"); + $( "#hotfix" ).toggleClass("hidden"); } }); @@ -98,6 +105,7 @@ function delete_build_release( id ) { $("#br_" + id).remove(); if ( $(".build_release").length === 0 ) { $( "#build_button" ).removeAttr("disabled"); + $( "#hotfix" ).toggleClass("hidden"); } } diff --git a/release_dashboard/templates/release_dashboard/hotfix_release.html b/release_dashboard/templates/release_dashboard/hotfix_release.html new file mode 100644 index 0000000..11b5153 --- /dev/null +++ b/release_dashboard/templates/release_dashboard/hotfix_release.html @@ -0,0 +1,66 @@ +{% extends "release_dashboard/base.html" %} +{% load static %} +{% block title %}Release Dashboard{% endblock %} +{% block navlist %} +
  • Release Dashboard
  • +
  • Build Release
  • +
  • {{ config.release }}
  • +{% endblock %} +{% block content %} +
    + + + + + + + + + + + + + {% csrf_token %} + {% for p in config.projects %} + + + + + + + + + {% endfor %} + +
    ProjectVersionPushEmpty
    + + + + + + + + + + +
    +
    +
    +{% endblock %} +{% block extrajs %} + + +{% endblock %} \ No newline at end of file diff --git a/release_dashboard/test/test_views.py b/release_dashboard/test/test_views.py index 5b94a01..080510f 100644 --- a/release_dashboard/test/test_views.py +++ b/release_dashboard/test/test_views.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 The Sipwise Team - http://sipwise.com +# Copyright (C) 2020-2022 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 @@ -68,6 +68,68 @@ class TestHotfix(TestCase): self.assertEqual(res.context["projects"], expected) +class TestHotfixRelease(TestCase): + def test_no_login(self): + res = self.client.get( + reverse( + "release_dashboard:hotfix_release", args=["release-mr7.5.2"] + ) + ) + self.assertNotEqual(res.status_code, 200) + + def test_login_ok(self): + user = User.objects.create_user(username="test") + self.client.force_login(user) + res = self.client.get( + reverse( + "release_dashboard:hotfix_release", args=["release-mr7.5.2"] + ) + ) + self.assertEqual(res.status_code, 200) + + def test_no_mrXXX(self): + user = User.objects.create_user(username="test") + self.client.force_login(user) + res = self.client.get( + reverse("release_dashboard:hotfix_release", args=["release-mr7.5"]) + ) + self.assertNotEqual(res.status_code, 200) + + res = self.client.get( + reverse( + "release_dashboard:hotfix_release", + args=["release-trunk-buster"], + ) + ) + self.assertNotEqual(res.status_code, 200) + + def test_project_ok(self): + user = User.objects.create_user(username="test") + self.client.force_login(user) + res = self.client.post( + reverse( + "release_dashboard:hotfix_release_build", + args=["release-mr7.5.2", "data-hal"], + ), + {"push": "no"}, + content_type="application/json", + ) + self.assertEqual(res.status_code, 200) + + def test_project_wrong(self): + user = User.objects.create_user(username="test") + self.client.force_login(user) + res = self.client.post( + reverse( + "release_dashboard:hotfix_release_build", + args=["release-mr7.5.2", "fake-project"], + ), + {"push": "no"}, + content_type="application/json", + ) + self.assertNotEqual(res.status_code, 200) + + class TestDocker(TestCase): def test_no_login(self): res = self.client.get(reverse("release_dashboard:docker_images")) @@ -81,17 +143,17 @@ class TestDocker(TestCase): class TestBuildRelease(BaseTest): - fixtures = ['test_build_release'] + fixtures = ["test_build_release"] def test_no_login(self): - url = reverse("release_dashboard:build_release", args=['trunk']) + url = reverse("release_dashboard:build_release", args=["trunk"]) res = self.client.get(url) self.assertNotEqual(res.status_code, 200) def test_login_ok(self): user = User.objects.create_user(username="test") self.client.force_login(user) - url = reverse("release_dashboard:build_release", args=['trunk']) + url = reverse("release_dashboard:build_release", args=["trunk"]) res = self.client.get(url) self.assertEqual(res.status_code, 200) @@ -99,10 +161,10 @@ class TestBuildRelease(BaseTest): user = User.objects.create_user(username="test") self.client.force_login(user) # no build yet - url = reverse("release_dashboard:build_release", args=['mr8.1']) + url = reverse("release_dashboard:build_release", args=["mr8.1"]) res = self.client.get(url) self.assertEqual(res.status_code, 200) - self.assertTrue(res.context['done']) + self.assertTrue(res.context["done"]) def test_context_not_done(self): user = User.objects.create_user(username="test") @@ -112,7 +174,7 @@ class TestBuildRelease(BaseTest): ) br.built_projects = None br.save() - url = reverse("release_dashboard:build_release", args=['trunk']) + url = reverse("release_dashboard:build_release", args=["trunk"]) res = self.client.get(url) self.assertEqual(res.status_code, 200) - self.assertFalse(res.context['done']) + self.assertFalse(res.context["done"]) diff --git a/release_dashboard/urls.py b/release_dashboard/urls.py index 0d97d98..a148746 100644 --- a/release_dashboard/urls.py +++ b/release_dashboard/urls.py @@ -27,6 +27,16 @@ urlpatterns = [ build.build_release, name="build_release", ), + re_path( + r"^hotfix_release/(?P[^/]+)/$", + build.hotfix_release, + name="hotfix_release", + ), + re_path( + r"^hotfix_release/(?P[^/]+)/(?P[^/]+)/$", + build.hotfix_release_build, + name="hotfix_release_build", + ), re_path(r"^hotfix/$", build.hotfix, name="hotfix"), re_path( r"^hotfix/(?P[^/]+)/(?P[^/]+)/$", build.hotfix_build diff --git a/release_dashboard/views/__init__.py b/release_dashboard/views/__init__.py index e346beb..d1b7b76 100644 --- a/release_dashboard/views/__init__.py +++ b/release_dashboard/views/__init__.py @@ -17,10 +17,11 @@ import re from django.views.generic.base import TemplateView from natsort import humansorted +from ..conf import settings from ..utils import get_branches from ..utils import get_tags -regex_hotfix = re.compile(r"^mr[0-9]+\.[0-9]+\.[0-9]+$") +regex_hotfix = re.compile(settings.RELEASE_DASHBOARD_FILTER_MRXXX) regex_mr = re.compile(r"^mr.+$") diff --git a/release_dashboard/views/build.py b/release_dashboard/views/build.py index 57bc79c..31c80aa 100644 --- a/release_dashboard/views/build.py +++ b/release_dashboard/views/build.py @@ -112,6 +112,44 @@ def hotfix(request): return render(request, "release_dashboard/hotfix.html", context) +@login_required +@require_http_methods(["POST"]) +def hotfix_release_build(request, release, project): + release_config = ReleaseConfig(release) + if project not in release_config.projects: + error = f"project:{project} not in {release_config.release}" + logger.error(error) + return HttpResponseNotFound(error) + + if not regex_hotfix.match(release_config.branch): + error = f"branch:{release_config.branch} not valid. Not mrX.X.X format" + logger.error(error) + return HttpResponseNotFound(error) + logger.debug(body=request.body) + json_data = json.loads(request.body.decode("utf-8")) + push = json_data.get("push", "no") + empty = json_data.get("empty", False) + if push == "no": + logger.warn(f"dry-run for {project}:{release_config.branch}") + urls = build.trigger_hotfix( + project, release_config.branch, request.user, push, empty + ) + return JsonResponse({"urls": urls}) + + +@login_required +def hotfix_release(request, release): + release_config = ReleaseConfig(release) + if not regex_hotfix.match(release_config.branch): + error = ( + "branch:%s not valid. Not mrX.X.X format" % release_config.branch + ) + logger.error(error) + return HttpResponseNotFound(error) + context = {"config": release_config} + return render(request, "release_dashboard/hotfix_release.html", context) + + @login_required def refresh_all(request): if request.method == "POST":