From ffe2d6c8b96e4ae2b3b48938dc27dc2109e68a17 Mon Sep 17 00:00:00 2001
From: Victor Seva <vseva@sipwise.com>
Date: Tue, 17 Nov 2020 09:59:48 +0100
Subject: [PATCH] TT#100201 build: support trunk with more than one
 distribution

* Makefile: add extra test summary
* migrate to structlog, this is an ongoing effort
* panel: remove any "trunk" build

Change-Id: I6f60541248f2970e0fe0784f2eb6eff5dec40721
---
 Makefile                                      |   2 +-
 build/fixtures/config/trunk.yml               |   5 +
 .../commands/create_fake_buildrelease.py      |   9 +-
 build/models/br.py                            |  39 +-
 build/tasks.py                                |  37 +-
 build/test/test_commands.py                   |   4 +-
 build/test/test_models.py                     |  39 +-
 build/test/test_utils.py                      |  49 ++-
 build/utils.py                                |  43 +-
 panel/views.py                                |   2 +-
 release_dashboard/views/build.py              |   2 +-
 .../fixtures/test_model_queries_trunk.yaml    | 399 ++++++++++++++++++
 repoapi/models/jbi.py                         |   5 +-
 repoapi/test/test_model_queries.py            |   8 +
 14 files changed, 571 insertions(+), 72 deletions(-)
 create mode 100644 repoapi/fixtures/test_model_queries_trunk.yaml

diff --git a/Makefile b/Makefile
index 79ff865..90c9540 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ venv_dev: requirements/dev.txt
 ###################################
 
 test:
-	RESULTS=$(RESULTS) pytest-3 --junitxml=$(RESULTS)/junit.xml \
+	RESULTS=$(RESULTS) pytest-3 -ra --junitxml=$(RESULTS)/junit.xml \
 		--cov=. --cov-report=xml:$(RESULTS)/coverage.xml --pep8
 
 test_pylint:
diff --git a/build/fixtures/config/trunk.yml b/build/fixtures/config/trunk.yml
index 0e7930d..2e8df16 100644
--- a/build/fixtures/config/trunk.yml
+++ b/build/fixtures/config/trunk.yml
@@ -1,8 +1,13 @@
 distris:
  - release-trunk-buster
+ - release-trunk-bullseye
 
 debian_release: buster
 
+release-trunk-bullseye:
+ # TT#44190 needed by ngcp-panel
+ - grafana: 7.3.1
+
 release-trunk-buster:
  # TT#32916 TT#44119 bcg729 for G.729 codec support for rtpengine
  - bcg729: 1.0.4+git20180222-0.1~bpo10+1
diff --git a/build/management/commands/create_fake_buildrelease.py b/build/management/commands/create_fake_buildrelease.py
index a9ad562..59b6d38 100644
--- a/build/management/commands/create_fake_buildrelease.py
+++ b/build/management/commands/create_fake_buildrelease.py
@@ -19,6 +19,7 @@ from django.core.management.base import CommandError
 
 from build.models import BuildRelease
 from build.models.br import regex_mrXX
+from build.utils import ReleaseConfig
 
 
 class Command(BaseCommand):
@@ -32,6 +33,12 @@ class Command(BaseCommand):
         if not regex_mrXX.match(ver):
             raise CommandError("'{}'' not mrX.Y version".format(ver))
         release = "release-{}".format(ver)
-        if BuildRelease.objects.release(release).count() > 0:
+        config = ReleaseConfig(release)
+        if (
+            BuildRelease.objects.release(
+                release, config.debian_release
+            ).count()
+            > 0
+        ):
             raise CommandError("'{}' has already instances".format(release))
         BuildRelease.objects.create_build_release(uuid.uuid4(), ver, fake=True)
diff --git a/build/models/br.py b/build/models/br.py
index 22472a3..a31f2b8 100644
--- a/build/models/br.py
+++ b/build/models/br.py
@@ -13,9 +13,9 @@
 # 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 datetime
-import logging
 import re
 
+import structlog
 from django.db import models
 from django.db.models import Q
 from django.forms.models import model_to_dict
@@ -27,7 +27,7 @@ from build.utils import get_simple_release
 from build.utils import ReleaseConfig
 from repoapi.models import JenkinsBuildInfo
 
-logger = logging.getLogger(__name__)
+logger = structlog.get_logger(__name__)
 
 regex_mrXXX = re.compile(r"^mr[0-9]+\.[0-9]+\.[0-9]+$")
 regex_mrXX = re.compile(r"^mr[0-9]+\.[0-9]+$")
@@ -37,16 +37,20 @@ regex_mrXX_up = re.compile(r"^release-mr[0-9]+\.[0-9]+-update$")
 class BuildReleaseManager(models.Manager):
     _jbi = JenkinsBuildInfo.objects
 
-    def release(self, version):
+    def release(self, version, distribution):
         qs = self.get_queryset()
         return qs.filter(
-            Q(release=version) | Q(release="{}-update".format(version))
+            Q(release=version, distribution=distribution)
+            | Q(release="{}-update".format(version), distribution=distribution)
         )
 
     def create_build_release(self, uuid, release, fake=False):
+        log = logger.bind(uuid=str(uuid), release=release, fake=fake)
         config = ReleaseConfig(release)
         qs = self.get_queryset()
-        br = qs.filter(release=config.release)
+        br = qs.filter(
+            release=config.release, distribution=config.debian_release
+        )
         release_ok = config.release
         if br.exists():
             if regex_mrXXX.match(config.branch):
@@ -58,7 +62,7 @@ class BuildReleaseManager(models.Manager):
                     "release[mrX.Y]:{} has already a build, "
                     "set {} as release"
                 )
-                logger.info(msg.format(config.branch, release_ok))
+                log.info(msg.format(config.branch, release_ok))
         projects = ",".join(config.projects)
         if fake:
             start_date = timezone.make_aware(datetime.datetime(1977, 1, 1))
@@ -277,6 +281,7 @@ class BuildRelease(models.Model):
         return "branch/{}".format(self.branch)
 
     def _next(self):
+        log = logger.bind(release=self)
         if self.built_projects is None:
             return self.build_deps[0][0]
         if self.done:
@@ -293,8 +298,10 @@ class BuildRelease(models.Model):
                         deps_missing.append(prj)
             else:
                 if len(deps_missing) > 0:
-                    msg = "release {} has build_deps {} missing"
-                    logger.info(msg.format(self, deps_missing))
+                    log.info(
+                        "release has build_deps missing",
+                        deps_missing=deps_missing,
+                    )
                     return None
         for prj in self.projects_list:
             if prj not in built_list and prj not in t_list:
@@ -303,15 +310,19 @@ class BuildRelease(models.Model):
     @property
     def next(self):
         failed_projects = self.failed_projects_list
+        log = logger.bind(release=self)
         if any(job in failed_projects for job in settings.BUILD_RELEASE_JOBS):
-            msg = "release {} has failed release_jobs, stop sending jobs"
-            logger.info(msg.format(self))
+            log.info(
+                "release has failed release_jobs, stop sending jobs",
+                failed_projects=failed_projects,
+            )
             return
         res = self._next()
         if res is not None:
             if res in failed_projects:
-                logger.error(
-                    "project: %s marked as failed, stop sending jobs", res
+                log.error(
+                    "project marked as failed, stop sending jobs",
+                    project=res,
                 )
             else:
                 return res
@@ -328,5 +339,7 @@ class BuildRelease(models.Model):
     @property
     def config(self):
         if getattr(self, "_config", None) is None:
-            self._config = ReleaseConfig(self.release)
+            self._config = ReleaseConfig(
+                self.release, distribution=self.distribution
+            )
         return self._config
diff --git a/build/tasks.py b/build/tasks.py
index 39074ef..7d9522d 100644
--- a/build/tasks.py
+++ b/build/tasks.py
@@ -12,8 +12,8 @@
 #
 # 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 structlog
 from celery import shared_task
-from celery.utils.log import get_task_logger
 
 from .conf import settings
 from build.models.br import BuildRelease
@@ -21,36 +21,37 @@ from build.utils import trigger_build
 from build.utils import trigger_copy_deps
 from repoapi.celery import app
 
-logger = get_task_logger(__name__)
+logger = structlog.get_logger(__name__)
 
 
 @app.task(bind=True)
 def build_release(self, pk):
+    log = logger.bind(pk=pk)
     br = BuildRelease.objects
     try:
         instance = br.get(id=pk)
     except BuildRelease.DoesNotExist as exc:
+        log.warn("BuildRelease not found")
         raise self.retry(countdown=60 * 5, exc=exc)
     if instance.release == "trunk":
         release = "release-trunk-{}".format(instance.distribution)
     else:
         release = instance.release
     url = trigger_copy_deps(
-        release=release,
-        internal=True,
-        release_uuid=instance.uuid,
+        release=release, internal=True, release_uuid=instance.uuid
+    )
+    log.info(
+        "BuildRelease copy_deps triggered", instance=str(instance), url=url
     )
-    logger.info("%s triggered" % url)
 
 
 @shared_task(ignore_result=True)
 def build_project(pk, project):
+    log = logger.bind(project=project, pk=pk)
     try:
         br = BuildRelease.objects.get(id=pk)
     except BuildRelease.DoesNotExist:
-        logger.error(
-            "can't trigger %s on unknown release with id:%s", project, pk
-        )
+        log.error("can't trigger project on unknown release")
         return
     url = trigger_build(
         "{}-get-code".format(project),
@@ -60,15 +61,16 @@ def build_project(pk, project):
         trigger_distribution=br.distribution,
     )
     br.pool_size += 1
-    logger.info("%s triggered" % url)
+    log.info("project triggered", url=url, pool_size=br.pool_size)
 
 
 @shared_task(ignore_result=True)
 def build_resume(pk):
+    log = logger.bind(pk=pk)
     try:
         br = BuildRelease.objects.get(id=pk)
     except BuildRelease.DoesNotExist:
-        logger.error("can't resume on unknown release with id:%s", pk)
+        log.error("can't resume on unknown release")
         return
     params = {
         "release_uuid": br.uuid,
@@ -77,21 +79,16 @@ def build_resume(pk):
         "trigger_distribution": br.distribution,
     }
     size = settings.BUILD_POOL - br.pool_size
+    log.bind(size=size, pool_size=br.pool_size, br=str(br))
     if size <= 0:
-        logger.info(
-            "BuildRelease:%s No more room for new builds,"
-            " wait for next slot",
-            br,
-        )
+        log.info("No more room for new builds, wait for next slot")
     for step in range(size):
         prj = br.next
         if prj:
             params["project"] = "{}-get-code".format(prj)
-            logger.debug(
-                "trigger:%s for BuildRelease:%s", params["project"], br
-            )
+            log.debug("trigger project", project=params["project"])
             trigger_build(**params)
             br.append_triggered(prj)
         else:
-            logger.debug("BuildRelease:%s has no next", br)
+            log.debug("BuildRelease has no next")
             break
diff --git a/build/test/test_commands.py b/build/test/test_commands.py
index 67ae7e0..1a179b0 100644
--- a/build/test/test_commands.py
+++ b/build/test/test_commands.py
@@ -32,10 +32,10 @@ class createFakeBuildReleaseTest(TestCase):
 
     def test_mrXX_ok(self):
         self.assertEqual(
-            BuildRelease.objects.release("release-mr7.5").count(), 0
+            BuildRelease.objects.release("release-mr7.5", "buster").count(), 0
         )
         call_command("create_fake_buildrelease", "mr7.5")
-        qs = BuildRelease.objects.release("release-mr7.5")
+        qs = BuildRelease.objects.release("release-mr7.5", "buster")
         self.assertEqual(qs.count(), 1)
         br = qs.first()
         self.assertTrue(br.done)
diff --git a/build/test/test_models.py b/build/test/test_models.py
index 80eaa36..d4939ad 100644
--- a/build/test/test_models.py
+++ b/build/test/test_models.py
@@ -41,6 +41,17 @@ class BuildReleaseManagerTestCase(BaseTest):
         self.assertNotEqual(len(br.projects), 0)
         self.assertIn("sipwise-base", br.projects)
 
+    def test_create_release_trunk(self, dlf):
+        br = BuildRelease.objects.create_build_release(
+            "AAA", "release-trunk-bullseye"
+        )
+        self.assertEqual(br.release, "trunk")
+        self.assertEqual(br.distribution, "bullseye")
+        self.assertIsNone(br.tag)
+        self.assertEqual(br.branch, "master")
+        self.assertNotEqual(len(br.projects), 0)
+        self.assertIn("sipwise-base", br.projects)
+
     def test_create_mrXX(self, dlf):
         BuildRelease.objects.filter(release="release-mr8.1").delete()
         self.assertFalse(
@@ -88,7 +99,7 @@ class BuildReleaseManagerTestCase(BaseTest):
         prev = BuildRelease.objects.filter(release="release-mr8.1")
         br = BuildRelease.objects.create_build_release("BBB", "mr8.1")
         self.assertEqual(br.release, "release-mr8.1-update")
-        qs = BuildRelease.objects.release("release-mr8.1")
+        qs = BuildRelease.objects.release("release-mr8.1", "buster")
         self.assertEqual(qs.count(), prev.count() + 1)
 
     def test_create_fake(self, dlf):
@@ -102,9 +113,7 @@ class BuildReleaseManagerTestCase(BaseTest):
 
 
 class BuildReleaseTestCase(BaseTest):
-    fixtures = [
-        "test_models",
-    ]
+    fixtures = ["test_models"]
     release_uuid = "dbe569f7-eab6-4532-a6d1-d31fb559649b"
 
     def test_distribution(self):
@@ -121,14 +130,12 @@ class BuildReleaseTestCase(BaseTest):
     def test_built_projects_list(self):
         build = BuildRelease.objects.get(uuid=self.release_uuid)
         self.assertCountEqual(
-            build.built_projects_list, ["kamailio", "lua-ngcp-kamailio"],
+            build.built_projects_list, ["kamailio", "lua-ngcp-kamailio"]
         )
 
     def test_queued_projects_list(self):
         build = BuildRelease.objects.get(uuid=self.release_uuid)
-        self.assertCountEqual(
-            build.queued_projects_list, ["ngcp-panel"],
-        )
+        self.assertCountEqual(build.queued_projects_list, ["ngcp-panel"])
 
     def test_config(self):
         build = BuildRelease.objects.get(uuid=self.release_uuid)
@@ -189,9 +196,7 @@ class BuildReleaseTestCase(BaseTest):
 
 
 class BuildReleaseStepsTest(BaseTest):
-    fixtures = [
-        "test_models",
-    ]
+    fixtures = ["test_models"]
     release = "release-mr8.1"
     release_uuid = "dbe569f7-eab6-4532-a6d1-d31fb559648b"
 
@@ -388,9 +393,7 @@ class BuildReleaseStepsTest(BaseTest):
 @patch("repoapi.utils.dlfile")
 @patch("build.models.build_resume")
 class JBIManageTest(BaseTest):
-    fixtures = [
-        "test_models",
-    ]
+    fixtures = ["test_models"]
     release = "release-mr8.1"
     release_uuid = "dbe569f7-eab6-4532-a6d1-d31fb559648b"
 
@@ -461,9 +464,7 @@ class JBIManageTest(BaseTest):
 
 
 class BRManageTest(BaseTest):
-    fixtures = [
-        "test_models",
-    ]
+    fixtures = ["test_models"]
 
     @patch("build.tasks.trigger_copy_deps")
     @patch("build.models.build_resume")
@@ -483,9 +484,7 @@ class BRManageTest(BaseTest):
 
 
 class BuildReleaseRetriggerTest(BaseTest):
-    fixtures = [
-        "test_models",
-    ]
+    fixtures = ["test_models"]
     release = "release-mr8.1"
     release_uuid = "dbe569f7-eab6-4532-a6d1-d31fb559648b"
 
diff --git a/build/test/test_utils.py b/build/test/test_utils.py
index 6964a7c..2239cd6 100644
--- a/build/test/test_utils.py
+++ b/build/test/test_utils.py
@@ -13,20 +13,42 @@
 # 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 unittest.mock import patch
 
 from django.test import override_settings
 from django.test import SimpleTestCase
-from mock import patch
 
 from build import exceptions as err
 from build.conf import settings
 from build.utils import get_common_release
 from build.utils import get_simple_release
+from build.utils import is_release_trunk
 from build.utils import ReleaseConfig
 from build.utils import trigger_build
 from build.utils import trigger_copy_deps
 
 
+class SimpleIsReleaseTrunkTest(SimpleTestCase):
+    def test_trunk(self):
+        ok, val = is_release_trunk("trunk")
+        self.assertFalse(ok)
+        self.assertIsNone(val)
+
+    def test_mrXX(self):
+        ok, val = is_release_trunk("release-mr8.5")
+        self.assertFalse(ok)
+        self.assertIsNone(val)
+
+    def test_release_trunk(self):
+        ok, val = is_release_trunk("release-trunk-buster")
+        self.assertTrue(ok)
+        self.assertEqual(val, "buster")
+
+        ok, val = is_release_trunk("release-trunk-bullseye")
+        self.assertTrue(ok)
+        self.assertEqual(val, "bullseye")
+
+
 class SimpleReleaseTest(SimpleTestCase):
     def test_trunk(self):
         val = get_simple_release("release-trunk-buster")
@@ -81,7 +103,8 @@ class ReleaseConfigTestCase(SimpleTestCase):
     @override_settings(BUILD_RELEASES_SKIP=["mr0.1"])
     def test_supported_releases(self):
         supported = [
-            "trunk",
+            "release-trunk-buster",
+            "release-trunk-bullseye",
             "mr8.1.2",
             "mr8.1",
             "mr7.5.3",
@@ -95,12 +118,17 @@ class ReleaseConfigTestCase(SimpleTestCase):
     @patch.object(ReleaseConfig, "supported_releases")
     def test_supported_releases_dict(self, sr):
         res_ok = [
-            {"release": "trunk", "base": "master"},
+            {"release": "release-trunk-buster", "base": "master"},
             {"release": "mr8.0", "base": "mr8.0"},
             {"release": "mr8.0.1", "base": "mr8.0"},
             {"release": "mr7.5.1", "base": "mr7.5"},
         ]
-        sr.return_value = ["trunk", "mr8.0", "mr8.0.1", "mr7.5.1"]
+        sr.return_value = [
+            "release-trunk-buster",
+            "mr8.0",
+            "mr8.0.1",
+            "mr7.5.1",
+        ]
         res = ReleaseConfig.supported_releases_dict()
         self.assertListEqual(res, res_ok)
 
@@ -119,6 +147,19 @@ class ReleaseConfigTestCase(SimpleTestCase):
         self.assertEqual(rc.debian_release, "buster")
         self.assertEqual(len(rc.projects), 73)
 
+    def test_debian_release_value(self):
+        rc = ReleaseConfig("trunk")
+        self.assertEqual(rc.debian_release, "buster")
+
+        rc = ReleaseConfig("release-trunk-bullseye")
+        self.assertEqual(rc.debian_release, "bullseye")
+
+        rc = ReleaseConfig("trunk", "bullseye")
+        self.assertEqual(rc.debian_release, "bullseye")
+        # distribution parameter is only used with trunk
+        rc = ReleaseConfig("release-mr8.1-update", "bullseye")
+        self.assertEqual(rc.debian_release, "buster")
+
     def test_release_value(self):
         rc = ReleaseConfig("trunk")
         self.assertEqual(rc.release, "trunk")
diff --git a/build/utils.py b/build/utils.py
index 91c2cbd..fca63be 100644
--- a/build/utils.py
+++ b/build/utils.py
@@ -40,6 +40,15 @@ project_url = (
 copy_deps_url = base_url + "&release={release}&internal={internal}"
 re_release = re.compile(r"^release-(mr[0-9]+\.[0-9]+(\.[0-9]+)?)$")
 re_release_common = re.compile(r"^(release-)?(mr[0-9]+\.[0-9]+)(\.[0-9]+)?$")
+re_release_trunk = re.compile(r"^release-trunk-(\w+)$")
+
+
+def is_release_trunk(version):
+    match = re_release_trunk.search(version)
+    if match:
+        return (True, match.group(1))
+    else:
+        return (False, None)
 
 
 def get_simple_release(version):
@@ -158,6 +167,15 @@ class ReleaseConfig(object):
                 return list_prj.pop(0)
             raise StopIteration
 
+    @classmethod
+    def load_config(cls, config_path):
+        try:
+            with open(config_path) as f:
+                return load(f, Loader=Loader)
+        except IOError:
+            msg = "could not read configuration file '{}'"
+            raise err.NoConfigReleaseFile(msg.format(config_path))
+
     @classmethod
     def supported_releases(cls):
         skip_files = ["{}.yml".format(x) for x in settings.BUILD_RELEASES_SKIP]
@@ -165,12 +183,21 @@ class ReleaseConfig(object):
         for root, dirs, files in os.walk(
             settings.BUILD_REPOS_SCRIPTS_CONFIG_DIR
         ):
+            if "trunk.yml" in files:
+                files.remove("trunk.yml")
+                cfg = cls.load_config(
+                    os.path.join(
+                        settings.BUILD_REPOS_SCRIPTS_CONFIG_DIR, "trunk.yml"
+                    )
+                )
+                for dist in cfg["distris"]:
+                    res.append(dist)
             for name in files:
                 path_name = Path(name)
                 if path_name.suffix != ".yml":
                     continue
                 if name not in skip_files:
-                    res.append(Path(name).stem)
+                    res.append(path_name.stem)
         res.sort(reverse=True)
         return res
 
@@ -182,7 +209,10 @@ class ReleaseConfig(object):
             for version in sr
         ]
 
-    def __init__(self, name):
+    def __init__(self, name, distribution=None):
+        ok, self.distribution = is_release_trunk(name)
+        if not ok and name == "trunk":
+            self.distribution = distribution
         filename = get_simple_release(name)
         if filename is None:
             filename = name
@@ -190,12 +220,7 @@ class ReleaseConfig(object):
         self.config_path = os.path.join(
             settings.BUILD_REPOS_SCRIPTS_CONFIG_DIR, self.config_file
         )
-        try:
-            with open(self.config_path) as f:
-                self.config = load(f, Loader=Loader)
-        except IOError:
-            msg = "could not read configuration file '{}'"
-            raise err.NoConfigReleaseFile(msg.format(self.config_path))
+        self.config = self.load_config(self.config_path)
         try:
             self.jenkins_jobs = self.config["jenkins-jobs"]
         except KeyError:
@@ -241,6 +266,8 @@ class ReleaseConfig(object):
 
     @property
     def debian_release(self):
+        if self.distribution:
+            return self.distribution
         return self.config["debian_release"]
 
     @property
diff --git a/panel/views.py b/panel/views.py
index f83a505..d2998cf 100644
--- a/panel/views.py
+++ b/panel/views.py
@@ -23,7 +23,7 @@ from repoapi.models import JenkinsBuildInfo as jbi
 
 
 def index(request):
-    context = {"releases": humansorted(jbi.objects.releases())}
+    context = {"releases": humansorted(jbi.objects.releases(), reverse=True)}
     return render(request, "panel/index.html", context)
 
 
diff --git a/release_dashboard/views/build.py b/release_dashboard/views/build.py
index a39fb24..dac388d 100644
--- a/release_dashboard/views/build.py
+++ b/release_dashboard/views/build.py
@@ -69,7 +69,7 @@ def build_release(request, release):
         )
     else:
         build_releases = BuildRelease.objects.release(
-            release_config.release
+            release_config.release, release_config.debian_release
         ).order_by("-start_date")
         if build_releases.count() == 0:
             done = True
diff --git a/repoapi/fixtures/test_model_queries_trunk.yaml b/repoapi/fixtures/test_model_queries_trunk.yaml
new file mode 100644
index 0000000..ea166b5
--- /dev/null
+++ b/repoapi/fixtures/test_model_queries_trunk.yaml
@@ -0,0 +1,399 @@
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23955
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-get-code
+        buildnumber: 35
+        date: 2020-11-19 18:07:20.752111+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-get-code/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: null
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23956
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-manage-docker
+        buildnumber: 34
+        date: 2020-11-19 18:07:21.586985+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-manage-docker/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23957
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-tap-test
+        buildnumber: 34
+        date: 2020-11-19 18:07:29.112762+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-tap-test/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23958
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-unit-tests-docker
+        buildnumber: 34
+        date: 2020-11-19 18:07:35.561203+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-unit-tests-docker/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23959
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-source-tests
+        buildnumber: 35
+        date: 2020-11-19 18:07:35.899654+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-source-tests/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23960
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-source
+        buildnumber: 31
+        date: 2020-11-19 18:07:37.676427+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-source/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23961
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-binaries
+        buildnumber: 31
+        date: 2020-11-19 18:08:07.137439+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-binaries/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 23962
+    fields:
+        tag: 167c4889-7eca-40c1-ada3-e444fca78d1f
+        projectname: data-hal
+        jobname: data-hal-repos
+        buildnumber: 31
+        date: 2020-11-19 18:08:07.759922+00:00
+        result: FAILURE
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/data-hal-repos/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: trunk
+        param_release_uuid: 62b4ccfe-20c6-4ed9-bb5b-c29893dc2e3a
+        param_distribution: bullseye
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 8687921 Release new version 9.2.0.0+0~mr9.2.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14192
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-get-code
+        buildnumber: 6
+        date: 2020-03-28 08:32:26.520724+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-get-code/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: null
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14193
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-manage-docker
+        buildnumber: 6
+        date: 2020-03-28 08:32:27.025884+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-manage-docker/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14194
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-tap-test
+        buildnumber: 6
+        date: 2020-03-28 08:32:37.511295+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-tap-test/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14195
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-dist-test
+        buildnumber: 6
+        date: 2020-03-28 08:32:44.277122+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-dist-test/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14196
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-source-tests
+        buildnumber: 6
+        date: 2020-03-28 08:32:44.465908+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-source-tests/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14197
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-docs
+        buildnumber: 6
+        date: 2020-03-28 08:32:45.212242+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-docs/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14198
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-source
+        buildnumber: 6
+        date: 2020-03-28 08:32:46.692498+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-source/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14200
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-binaries
+        buildnumber: 6
+        date: 2020-03-28 08:33:07.359523+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-binaries/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14201
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-repos
+        buildnumber: 6
+        date: 2020-03-28 08:33:08.430006+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-repos/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14202
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-docker-ppa
+        buildnumber: 5
+        date: 2020-03-28 08:33:08.747639+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-docker-ppa/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
+-   model: repoapi.jenkinsbuildinfo
+    pk: 14208
+    fields:
+        tag: c8726604-79d3-4c62-bd18-50f86124dd33
+        projectname: sipwise-base
+        jobname: sipwise-base-piuparts
+        buildnumber: 5
+        date: 2020-03-28 08:33:34.732237+00:00
+        result: SUCCESS
+        job_url: https://jenkins-dev.mgm.sipwise.com/job/sipwise-base-piuparts/
+        gerrit_patchset: none_might-be-direct-push
+        gerrit_change: null
+        gerrit_eventtype: null
+        param_tag: none
+        param_branch: master
+        param_release: release-trunk-buster
+        param_release_uuid: 2b0d0640-a0be-4b44-98be-0c55cc6f6610
+        param_distribution: buster
+        param_ppa: $ppa
+        repo_name: null
+        git_commit_msg: 99e3227 Release new version 8.4.0.0+0~mr8.4.0.0
diff --git a/repoapi/models/jbi.py b/repoapi/models/jbi.py
index b8ee0d6..1da9f60 100644
--- a/repoapi/models/jbi.py
+++ b/repoapi/models/jbi.py
@@ -23,6 +23,7 @@ from urllib.parse import urlparse
 
 import structlog
 from django.db import models
+from django.db.models import Q
 from django.forms.models import model_to_dict
 
 from debian import deb822
@@ -62,7 +63,8 @@ class JenkinsBuildInfoManager(models.Manager):
 
     def releases(self, flat=True):
         qs = self.get_queryset().exclude(
-            jobname__in=settings.BUILD_RELEASE_JOBS
+            Q(param_release__contains="trunk")
+            | Q(jobname__in=settings.BUILD_RELEASE_JOBS)
         )
         res = qs.filter(tag__isnull=False).values("param_release").distinct()
         if res.exists():
@@ -70,6 +72,7 @@ class JenkinsBuildInfoManager(models.Manager):
                 return res.values_list("param_release", flat=True)
             else:
                 return res.values("param_release")
+        return []
 
     def is_release(self, release):
         res = self.get_queryset().filter(
diff --git a/repoapi/test/test_model_queries.py b/repoapi/test/test_model_queries.py
index 50d368e..d329dac 100644
--- a/repoapi/test/test_model_queries.py
+++ b/repoapi/test/test_model_queries.py
@@ -103,6 +103,14 @@ class JBIQueriesTestCase(BaseTest):
         self.assertEqual(JenkinsBuildInfo.objects.count(), prev_count - 1)
 
 
+class JBIQueriesTrunk(BaseTest):
+    fixtures = ["test_model_queries_trunk.yaml"]
+
+    def test_releases(self):
+        releases = JenkinsBuildInfo.objects.releases()
+        self.assertEqual(releases, [])
+
+
 class JBIQueriesUUIDTest(BaseTest):
     fixtures = ["test_model_queries_uuid.json"]
     release = "release-mr8.1"