Skip to content

Commit 040deda

Browse files
committed
Add 'sync' command to Python client
Signed-off-by: Scott K Logan <[email protected]>
1 parent 1a84c85 commit 040deda

File tree

2 files changed

+150
-16
lines changed

2 files changed

+150
-16
lines changed

src/python/client.c

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,31 @@ sequence_to_str_array(PyObject *sequence)
127127
return res;
128128
}
129129

130+
static gchar *
131+
parse_arch_list(PyObject *arches)
132+
{
133+
gchar *arch_list = NULL;
134+
135+
if (1 != PySequence_Check(arches)) {
136+
PyErr_SetString(PyExc_TypeError, "arches must be an iterable");
137+
return NULL;
138+
}
139+
140+
const gchar **arch_array = sequence_to_str_array(arches);
141+
if (NULL == arch_array) {
142+
return NULL;
143+
}
144+
145+
arch_list = g_strjoinv(" ", (gchar **)arch_array);
146+
g_free(arch_array);
147+
if (NULL == arch_list) {
148+
PyErr_NoMemory();
149+
return NULL;
150+
}
151+
152+
return arch_list;
153+
}
154+
130155
static PyObject *
131156
client_add(ClientObject *self, PyObject *args)
132157
{
@@ -141,18 +166,10 @@ client_add(ClientObject *self, PyObject *args)
141166
}
142167

143168
if (arches != NULL && arches != Py_None) {
144-
if (1 != PySequence_Check(arches)) {
145-
PyErr_SetString(PyExc_TypeError, "arches must be an iterable");
146-
return NULL;
147-
}
148-
149-
const gchar **arch_array = sequence_to_str_array(arches);
150-
if (NULL == arch_array) {
169+
arch_list = parse_arch_list(arches);
170+
if (NULL == arch_list) {
151171
return NULL;
152172
}
153-
154-
arch_list = g_strjoinv(" ", (gchar **)arch_array);
155-
g_free(arch_array);
156173
}
157174

158175
cmd = g_strjoin(" ", "ADD", package, arch_list, NULL);
@@ -268,6 +285,49 @@ client_set_invalidate_family(ClientObject *self, PyObject *args)
268285
return set_option(self, "invalidate_family", invalidate_family);
269286
}
270287

288+
static PyObject *
289+
client_sync(ClientObject *self, PyObject *args, PyObject *kwargs)
290+
{
291+
char *base_url = NULL;
292+
char *pattern = NULL;
293+
PyObject *arches = NULL;
294+
gchar *arch_list = NULL;
295+
gchar *cmd;
296+
PyObject *ret;
297+
298+
static char * keywords[] = {
299+
"base_url",
300+
"pattern",
301+
"arches",
302+
NULL,
303+
};
304+
305+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|zO", keywords, &base_url, &pattern, &arches)) {
306+
return NULL;
307+
}
308+
309+
if (arches != NULL && arches != Py_None) {
310+
arch_list = parse_arch_list(arches);
311+
if (NULL == arch_list) {
312+
return NULL;
313+
}
314+
}
315+
316+
if (NULL == pattern) {
317+
cmd = g_strjoin(" ", "SYNC", base_url, arch_list, NULL);
318+
} else {
319+
cmd = g_strjoin(" ", "SYNC_PATTERN", base_url, pattern, arch_list, NULL);
320+
}
321+
g_free(arch_list);
322+
if (!cmd) {
323+
return PyErr_NoMemory();
324+
}
325+
326+
ret = execute_transaction(self, cmd);
327+
g_free(cmd);
328+
return ret;
329+
}
330+
271331
static PyObject *
272332
client_enter(ClientObject *self, PyObject *args)
273333
{
@@ -299,6 +359,7 @@ static struct PyMethodDef client_methods[] = {
299359
{"disconnect", (PyCFunction)client_disconnect, METH_NOARGS, NULL},
300360
{"set_invalidate_dependants", (PyCFunction)client_set_invalidate_dependants, METH_VARARGS, NULL},
301361
{"set_invalidate_family", (PyCFunction)client_set_invalidate_family, METH_VARARGS, NULL},
362+
{"sync", (PyCFunction)(void(*)(void))client_sync, METH_VARARGS | METH_KEYWORDS, NULL},
302363
{"__enter__", (PyCFunction)client_enter, METH_NOARGS, NULL},
303364
{"__exit__", (PyCFunction)client_disconnect, METH_VARARGS, NULL},
304365
{NULL, NULL, 0, NULL}

test/test_smoke.py

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,41 @@
77
import pytest
88

99
FIXTURES_DIR = Path(__file__).parent / 'fixtures'
10+
POPULATED_REPO = FIXTURES_DIR / 'populated'
11+
POPULATED_RPM = Path(
12+
POPULATED_REPO,
13+
'x86_64',
14+
'Packages',
15+
'r',
16+
'ros-dev-tools-1.0.1-1.el9.noarch.rpm',
17+
)
1018

1119

1220
def test_version():
1321
assert createrepo_agent.__version__
1422

1523

1624
def test_add(tmp_path):
17-
packages_path = FIXTURES_DIR / 'populated' / 'x86_64' / 'Packages'
18-
rpm_path = packages_path / 'r' / 'ros-dev-tools-1.0.1-1.el9.noarch.rpm'
25+
rpm_path = str(POPULATED_RPM)
1926

2027
with createrepo_agent.Server(str(tmp_path)):
2128
with createrepo_agent.Client(str(tmp_path)) as c:
2229
with pytest.raises(TypeError):
23-
c.add(str(rpm_path), 1)
30+
c.add(None)
2431
with pytest.raises(TypeError):
25-
c.add(str(rpm_path), (1,))
32+
c.add(rpm_path, 1)
33+
with pytest.raises(TypeError):
34+
c.add(rpm_path, (1,))
2635
c.set_invalidate_dependants(True)
2736
c.set_invalidate_family(True)
28-
c.add(str(rpm_path), ('x86_64',))
37+
c.add(rpm_path, ('x86_64',))
2938
c.commit()
3039

31-
assert (tmp_path / 'x86_64' / 'repodata' / 'repomd.xml').is_file()
40+
arch_path = tmp_path / 'x86_64'
41+
repomd_path = arch_path / 'repodata' / 'repomd.xml'
42+
43+
assert repomd_path.is_file()
44+
assert (arch_path / 'Packages' / 'r' / POPULATED_RPM.name).is_file()
3245

3346

3447
def test_commit_nothing(tmp_path):
@@ -45,3 +58,63 @@ def test_server_socket_collision(tmp_path):
4558
with pytest.raises(OSError):
4659
with createrepo_agent.Server(str(tmp_path)):
4760
pass
61+
62+
63+
def test_sync_all(tmp_path):
64+
base_url = POPULATED_REPO.as_uri()
65+
with createrepo_agent.Server(str(tmp_path)):
66+
with createrepo_agent.Client(str(tmp_path)) as c:
67+
with pytest.raises(TypeError):
68+
c.sync(None)
69+
with pytest.raises(TypeError):
70+
c.sync(base_url, arches=1)
71+
with pytest.raises(TypeError):
72+
c.sync(base_url, arches=(1,))
73+
c.sync(base_url, arches=('x86_64',))
74+
c.commit()
75+
76+
arch_path = tmp_path / 'x86_64'
77+
repomd_path = arch_path / 'repodata' / 'repomd.xml'
78+
79+
assert repomd_path.is_file()
80+
assert (arch_path / 'Packages' / 'r' / POPULATED_RPM.name).is_file()
81+
82+
# Performing the same operation again results in no changes, so CRA shouldn't
83+
# make any changes to the metadata at all.
84+
85+
old_repomd_contents = repomd_path.read_text()
86+
87+
with createrepo_agent.Server(str(tmp_path)):
88+
with createrepo_agent.Client(str(tmp_path)) as c:
89+
c.sync(base_url, arches=('x86_64',))
90+
c.commit()
91+
92+
assert old_repomd_contents == repomd_path.read_text()
93+
94+
95+
def test_sync_pattern_hit(tmp_path):
96+
base_url = POPULATED_REPO.as_uri()
97+
pattern = POPULATED_RPM.name[:3] + '.*'
98+
with createrepo_agent.Server(str(tmp_path)):
99+
with createrepo_agent.Client(str(tmp_path)) as c:
100+
c.sync(base_url, pattern, ('x86_64',))
101+
c.commit()
102+
103+
arch_path = tmp_path / 'x86_64'
104+
repomd_path = arch_path / 'repodata' / 'repomd.xml'
105+
106+
assert repomd_path.is_file()
107+
assert (arch_path / 'Packages' / 'r' / POPULATED_RPM.name).is_file()
108+
109+
110+
def test_sync_pattern_miss(tmp_path):
111+
base_url = POPULATED_REPO.as_uri()
112+
pattern = 'does-not-match'
113+
with createrepo_agent.Server(str(tmp_path)):
114+
with createrepo_agent.Client(str(tmp_path)) as c:
115+
c.sync(base_url, pattern, ('x86_64',))
116+
c.commit()
117+
118+
arch_path = tmp_path / 'x86_64'
119+
120+
assert not (arch_path / 'Packages' / 'r' / POPULATED_RPM.name).is_file()

0 commit comments

Comments
 (0)