Skip to content

Commit 71ce0f8

Browse files
authored
Merge branch 'main' into p4-install
2 parents cb733d9 + f8478c6 commit 71ce0f8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3534
-153
lines changed

.github/workflows/lint-and-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
max-parallel: 5
1212
matrix:
13-
python-version: ['3.x']
13+
python-version: ['3.13']
1414
steps:
1515
- uses: actions/checkout@v4
1616
- name: Set up Python ${{ matrix.python-version }}

INSTALL.md

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,35 @@ server=https://patchman.example.com
140140
141141
# Options to curl
142142
curl_options="--insecure --connect-timeout 60 --max-time 300"
143-
144143
```
144+
145145
* *server* needs to point the URL where the local patchman server is running
146146
* *--insecure* in the curl options tells the client to ignore certificates.
147147
If the patchman server is set up correctly with certificates this flag can
148148
be removed to increase security.
149149

150+
Patchman supports two report protocol versions:
151+
152+
### Protocol 1 (Text)
153+
The original text-based protocol. Uses multipart form data to upload package
154+
and repository information. No additional dependencies required on the client.
155+
156+
### Protocol 2 (JSON)
157+
A JSON-based REST API. Provides better error handling, structured validation,
158+
and easier debugging. Requires `jq` on the client.
159+
160+
To use Protocol 2, update your `patchman-client.conf`:
161+
162+
```shell
163+
protocol=2
164+
```
165+
166+
Or use the `-p 2` command line option:
167+
168+
```shell
169+
$ patchman-client -s http://patchman.example.org -p 2
170+
```
171+
150172
## Configure Database
151173

152174
The default database backend is sqlite. However, this is not recommended for
@@ -394,7 +416,7 @@ setsebool -P httpd_can_network_connect 1
394416

395417
There is a systemd unit file for celery to make the service persistent over reboot:
396418

397-
`etc/systemd/system/patchman-celery.service`
419+
`etc/systemd/system/patchman-celery-worker.service`
398420

399421
If installing from prebuilt packages, this should be enabled by default.
400422

@@ -428,7 +450,7 @@ CACHES = {
428450
}
429451
```
430452

431-
#### Memcacached
453+
#### Memcached
432454

433455
Install Memcached
434456

@@ -460,3 +482,55 @@ To test the installation, run the client locally on the patchman server:
460482
```shell
461483
patchman-client -s http://127.0.0.1/patchman/
462484
```
485+
486+
# API Key Authentication
487+
488+
For Protocol 2, API key authentication is available using
489+
[djangorestframework-api-key](https://florimondmanca.github.io/djangorestframework-api-key/).
490+
Keys are hashed in the database and cannot be retrieved after creation.
491+
492+
### Server-side setup
493+
494+
1. Create an API key:
495+
496+
```shell
497+
$ patchman-manage create_api_key "clients"
498+
Created API key: clients
499+
500+
Key: abc123...
501+
502+
Add this to your patchman-client.conf:
503+
api_key=abc123...
504+
505+
Save this key as it cannot be retrieved later.
506+
```
507+
508+
2. List existing keys:
509+
510+
```shell
511+
$ patchman-manage list_api_keys
512+
```
513+
514+
3. Revoke a key:
515+
516+
```shell
517+
$ patchman-manage revoke_api_key <prefix-or-name>
518+
```
519+
520+
521+
API keys can also be managed via the Django admin interface.
522+
523+
### Client-side setup
524+
525+
Add the API key to `patchman-client.conf`:
526+
527+
```shell
528+
protocol=2
529+
api_key=abc123...
530+
```
531+
532+
Or use the `-k` command line option:
533+
534+
```shell
535+
$ patchman-client -s http://patchman.example.org -p 2 -k abc123...
536+
```

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ required to report packages, `yum`, `dnf`, `zypper` and/or `apt` are required
107107
to report repositories. These packages are normally installed by default on
108108
most systems. `which`, `mktemp`, `flock` and `curl` are also required.
109109
110+
For Protocol 2 (JSON-based reports), `jq` is required. If `jq` is not available,
111+
the client will automatically fall back to Protocol 1 (text-based reports).
112+
110113
deb-based OS's do not always change the kernel version when a kernel update is
111114
installed, so the `update-notifier-common` package can optionally be installed
112115
to enable this functionality. rpm-based OS's can tell if a reboot is required

arch/tests/__init__.py

Whitespace-only changes.

arch/tests/test_api.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Copyright 2025 Marcus Furlong <furlongm@gmail.com>
2+
#
3+
# This file is part of Patchman.
4+
#
5+
# Patchman is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, version 3 only.
8+
#
9+
# Patchman is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with Patchman. If not, see <http://www.gnu.org/licenses/>
16+
17+
from django.contrib.auth.models import User
18+
from django.test import TestCase, override_settings
19+
from rest_framework import status
20+
from rest_framework.test import APITestCase
21+
22+
from arch.models import MachineArchitecture, PackageArchitecture
23+
24+
25+
@override_settings(
26+
CELERY_TASK_ALWAYS_EAGER=True,
27+
CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
28+
)
29+
class ArchitectureAPITests(APITestCase):
30+
"""Tests for the Architecture API endpoints."""
31+
32+
def setUp(self):
33+
"""Set up test data."""
34+
self.user = User.objects.create_user(
35+
username='testuser', password='testpass'
36+
)
37+
self.client.force_authenticate(user=self.user)
38+
39+
self.machine_arch = MachineArchitecture.objects.create(name='x86_64')
40+
self.pkg_arch = PackageArchitecture.objects.create(name='amd64')
41+
42+
def test_list_machine_architectures(self):
43+
"""Test listing machine architectures."""
44+
response = self.client.get('/api/machine-architecture/')
45+
self.assertEqual(response.status_code, status.HTTP_200_OK)
46+
self.assertEqual(len(response.data['results']), 1)
47+
48+
def test_retrieve_machine_architecture(self):
49+
"""Test retrieving a machine architecture."""
50+
response = self.client.get(f'/api/machine-architecture/{self.machine_arch.id}/')
51+
self.assertEqual(response.status_code, status.HTTP_200_OK)
52+
self.assertEqual(response.data['name'], 'x86_64')
53+
54+
def test_list_package_architectures(self):
55+
"""Test listing package architectures."""
56+
response = self.client.get('/api/package-architecture/')
57+
self.assertEqual(response.status_code, status.HTTP_200_OK)
58+
self.assertEqual(len(response.data['results']), 1)
59+
60+
def test_retrieve_package_architecture(self):
61+
"""Test retrieving a package architecture."""
62+
response = self.client.get(f'/api/package-architecture/{self.pkg_arch.id}/')
63+
self.assertEqual(response.status_code, status.HTTP_200_OK)
64+
self.assertEqual(response.data['name'], 'amd64')
65+
66+
67+
@override_settings(
68+
CELERY_TASK_ALWAYS_EAGER=True,
69+
CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
70+
)
71+
class MachineArchitectureModelTests(TestCase):
72+
"""Tests for the MachineArchitecture model."""
73+
74+
def test_machine_architecture_creation(self):
75+
"""Test creating a machine architecture."""
76+
arch = MachineArchitecture.objects.create(name='aarch64')
77+
self.assertEqual(arch.name, 'aarch64')
78+
79+
def test_machine_architecture_string_representation(self):
80+
"""Test MachineArchitecture __str__ method."""
81+
arch = MachineArchitecture.objects.create(name='i686')
82+
self.assertEqual(str(arch), 'i686')
83+
84+
def test_machine_architecture_unique_name(self):
85+
"""Test that machine architecture names must be unique."""
86+
MachineArchitecture.objects.create(name='unique-arch')
87+
from django.db import IntegrityError
88+
with self.assertRaises(IntegrityError):
89+
MachineArchitecture.objects.create(name='unique-arch')
90+
91+
92+
@override_settings(
93+
CELERY_TASK_ALWAYS_EAGER=True,
94+
CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
95+
)
96+
class PackageArchitectureModelTests(TestCase):
97+
"""Tests for the PackageArchitecture model."""
98+
99+
def test_package_architecture_creation(self):
100+
"""Test creating a package architecture."""
101+
arch = PackageArchitecture.objects.create(name='arm64')
102+
self.assertEqual(arch.name, 'arm64')
103+
104+
def test_package_architecture_string_representation(self):
105+
"""Test PackageArchitecture __str__ method."""
106+
arch = PackageArchitecture.objects.create(name='noarch')
107+
self.assertEqual(str(arch), 'noarch')
108+
109+
def test_package_architecture_unique_name(self):
110+
"""Test that package architecture names must be unique."""
111+
PackageArchitecture.objects.create(name='all')
112+
from django.db import IntegrityError
113+
with self.assertRaises(IntegrityError):
114+
PackageArchitecture.objects.create(name='all')
115+
116+
def test_common_architectures(self):
117+
"""Test creating common architecture types."""
118+
archs = ['x86_64', 'amd64', 'i386', 'i686', 'noarch', 'all', 'arm64', 'aarch64']
119+
for arch_name in archs:
120+
arch = PackageArchitecture.objects.create(name=arch_name)
121+
self.assertEqual(str(arch), arch_name)

0 commit comments

Comments
 (0)