Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions errata/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# along with Patchman. If not, see <http://www.gnu.org/licenses/>

from errata.models import Erratum
from hosts.utils import refresh_host_update_counts
from packages.models import PackageUpdate
from patchman.signals import pbar_start, pbar_update
from util import fetch_concurrently, run_concurrently, tz_aware_datetime
Expand Down Expand Up @@ -72,6 +73,7 @@ def mark_errata_security_updates(concurrent_processing=True, max_workers=25):
for i, e in enumerate(Erratum.objects.all()):
e.scan_for_security_updates()
pbar_update.send(sender=None, index=i + 1)
refresh_host_update_counts()


def scan_security_worker(pk):
Expand Down
37 changes: 36 additions & 1 deletion hosts/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@
from unittest.mock import patch

from django.test import TestCase
from django.utils import timezone

from arch.models import MachineArchitecture
from arch.models import MachineArchitecture, PackageArchitecture
from domains.models import Domain
from errata.models import Erratum
from errata.utils import mark_errata_security_updates
from hosts.models import Host
from hosts.utils import get_or_create_host
from operatingsystems.models import OSRelease, OSVariant
from packages.models import Package, PackageName, PackageUpdate
from reports.models import Report


Expand Down Expand Up @@ -54,3 +59,33 @@ def test_protocol2_keeps_spaces_within_tags(self, _):

def test_protocol2_empty_tags(self, _):
self.assertEqual(self.host_tags('2', ''), [])


class RefreshHostUpdateCountsTests(TestCase):

def test_counts_refreshed_after_errata_security_marking(self):
os_release = OSRelease.objects.create(name='Debian 13')
host = Host.objects.create(
hostname='h.example.com', ipaddress='192.0.2.1',
osvariant=OSVariant.objects.create(name='Debian 13', osrelease=os_release),
kernel='6.12.0', arch=MachineArchitecture.objects.create(name='x86_64'),
domain=Domain.objects.create(name='example.com'),
lastreport=timezone.now(),
)
pkg_arch = PackageArchitecture.objects.create(name='amd64')
name = PackageName.objects.create(name='curl')
old = Package.objects.create(name=name, arch=pkg_arch, epoch='', version='1', release='1', packagetype='D')
new = Package.objects.create(name=name, arch=pkg_arch, epoch='', version='2', release='1', packagetype='D')
update = PackageUpdate.objects.create(oldpackage=old, newpackage=new, security=False)
host.updates.add(update)
host.refresh_from_db()
self.assertEqual((host.sec_updates_count, host.bug_updates_count), (0, 1))

erratum = Erratum.objects.create(
name='DSA-0000-1', e_type='security', issue_date=timezone.now(), synopsis='curl',
)
erratum.fixed_packages.add(new)
mark_errata_security_updates(concurrent_processing=False)

host.refresh_from_db()
self.assertEqual((host.sec_updates_count, host.bug_updates_count), (1, 0))
23 changes: 23 additions & 0 deletions hosts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from socket import gaierror, gethostbyaddr, herror

from django.db import IntegrityError, transaction
from django.db.models import Count, OuterRef, Subquery
from django.db.models.functions import Coalesce
from taggit.models import Tag
from taggit.utils import parse_tags

Expand All @@ -37,6 +39,27 @@ def update_rdns(host):
host.save()


def refresh_host_update_counts():
""" Recalculate cached security/bugfix update counts for all hosts.
Needed when PackageUpdate.security changes, as that does not fire
the Host.updates m2m_changed signal.
"""
from hosts.models import Host
through = Host.updates.through

def count_updates(security):
counts = through.objects.filter(
host_id=OuterRef('pk'),
packageupdate__security=security,
).values('host_id').annotate(c=Count('pk')).values('c')
return Coalesce(Subquery(counts), 0)

Host.objects.update(
sec_updates_count=count_updates(True),
bug_updates_count=count_updates(False),
)


def get_report_tags(report):
""" Protocol 2 sends a list (stored comma-joined), protocol 1 sends the
raw config string, which may be comma or space delimited
Expand Down
Loading