]> git.ipfire.org Git - pbs.git/blame - src/buildservice/repository.py
Fix rendering repo page if no packages in a repo
[pbs.git] / src / buildservice / repository.py
CommitLineData
9137135a
MT
1#!/usr/bin/python
2
9729c5b3 3import logging
f6e6ff79
MT
4import os.path
5
612ee16e
MT
6import pakfire
7
9729c5b3
MT
8log = logging.getLogger("repositories")
9log.propagate = 1
10
2c909128
MT
11from . import base
12from . import logs
9137135a 13
9729c5b3 14from .constants import *
e459cbba
MT
15from .decorators import *
16
9137135a 17class Repositories(base.Object):
e459cbba
MT
18 def _get_repository(self, query, *args):
19 res = self.db.get(query, *args)
9137135a 20
e459cbba
MT
21 if res:
22 return Repository(self.backend, res.id, data=res)
9137135a 23
e459cbba
MT
24 def _get_repositories(self, query, *args):
25 res = self.db.query(query, *args)
26
27 for row in res:
28 yield Repository(self.backend, row.id, data=row)
29
30 def __iter__(self):
31 repositories = self._get_repositories("SELECT * FROM repositories \
8cb14e63 32 WHERE deleted IS FALSE ORDER BY distro_id, name")
9137135a 33
e459cbba
MT
34 return iter(repositories)
35
d629da45
MT
36 def create(self, distro, name, description):
37 return self._get_repository("INSERT INTO repositories(distro_id, name, description) \
38 VALUES(%s, %s, %s) RETURNING *", distro.id, name, description)
39
e459cbba
MT
40 def get_by_id(self, repo_id):
41 return self._get_repository("SELECT * FROM repositories \
42 WHERE id = %s", repo_id)
9137135a 43
f6e6ff79
MT
44 def get_history(self, limit=None, offset=None, build=None, repo=None, user=None):
45 query = "SELECT * FROM repositories_history"
46 args = []
9137135a 47
f6e6ff79 48 query += " ORDER BY time DESC"
9137135a 49
f6e6ff79
MT
50 if limit:
51 if offset:
52 query += " LIMIT %s,%s"
53 args += [offset, limit,]
54 else:
55 query += " LIMIT %s"
56 args += [limit,]
9137135a 57
f6e6ff79
MT
58 entries = []
59 for entry in self.db.query(query, *args):
60 entry = logs.RepositoryLogEntry(self.pakfire, entry)
61 entries.append(entry)
9137135a 62
f6e6ff79 63 return entries
9137135a 64
9729c5b3
MT
65 def remaster(self):
66 """
67 Remasters all repositories
68 """
69 for repo in self:
70 # Skip all repositories that don't need an update
71 if not repo.needs_update:
72 log.debug("Repository %s does not need an update" % repo)
73 continue
74
75 with self.db.transaction():
76 repo.remaster()
77
612ee16e
MT
78 def cleanup(self):
79 """
80 Cleans up all repositories
81 """
82 for repo in self:
83 with self.db.transaction():
84 repo.cleanup()
85
9137135a 86
d629da45
MT
87class Repository(base.DataObject):
88 table = "repositories"
f6e6ff79 89
d629da45
MT
90 def __eq__(self, other):
91 if isinstance(other, self.__class__):
92 return self.id == other.id
f6e6ff79 93
d629da45
MT
94 def __lt__(self, other):
95 if isinstance(other, self.__class__):
96 return self.parent_id == other.id
9137135a 97
d629da45
MT
98 def __iter__(self):
99 builds = self.backend.builds._get_builds("SELECT builds.* FROM repositories_builds \
100 LEFT JOIN builds ON repositories_builds.build_id = builds.id \
78f9cb76 101 WHERE repositories_builds.repo_id = %s", self.id)
9137135a 102
d629da45 103 return iter(builds)
f6e6ff79 104
d629da45
MT
105 def __len__(self):
106 res = self.db.get("SELECT COUNT(*) AS len FROM repositories_builds \
107 WHERE repo_id = %s", self.id)
9137135a 108
d629da45 109 return res.len
9137135a 110
64c4b654
MT
111 def __nonzero__(self):
112 return True
113
d629da45 114 @lazy_property
9137135a 115 def next(self):
d629da45
MT
116 return self.backend.repos._get_repository("SELECT * FROM repositories \
117 WHERE parent_id = %s", self.id)
9137135a 118
d629da45 119 @lazy_property
f6e6ff79 120 def parent(self):
d629da45
MT
121 if self.data.parent_id:
122 return self.backend.repos._get_repository("SELECT * FROM repositories \
123 WHERE id = %s", self.data.parent_id)
9137135a 124
e459cbba 125 @lazy_property
9137135a 126 def distro(self):
e459cbba 127 return self.backend.distros.get_by_id(self.data.distro_id)
9137135a 128
5bc58fba
MT
129 def set_priority(self, priority):
130 self._set_attribute("priority", priority)
131
132 priority = property(lambda s: s.data.priority, set_priority)
133
a7a18be1
MT
134 def get_user(self):
135 if self.data.user_id:
136 return self.backend.users.get_by_id(self.data.user_id)
137
138 def set_user(self, user):
139 self._set_attribute("user_id", user.id)
140
141 user = property(get_user, set_user)
142
9137135a
MT
143 @property
144 def info(self):
145 return {
146 "id" : self.id,
147 "distro" : self.distro.info,
148 "name" : self.name,
149 "arches" : self.arches,
150 }
151
152 @property
5286089b 153 def basepath(self):
9c025f7b 154 return os.path.join(
f6e6ff79
MT
155 self.distro.identifier,
156 self.identifier,
9c025f7b 157 )
5286089b
MT
158
159 @property
160 def path(self):
612ee16e 161 return os.path.join(REPOS_DIR, self.basepath)
5286089b
MT
162
163 @property
164 def url(self):
9c025f7b 165 return "/".join((
bb6107cc
MT
166 self.settings.get("baseurl", "https://pakfire.ipfire.org"),
167 "repositories",
5286089b 168 self.basepath,
9c025f7b 169 ))
f6e6ff79 170
f6e6ff79
MT
171 @property
172 def mirrorlist(self):
9c025f7b 173 return "/".join((
bb6107cc 174 self.settings.get("baseurl", "https://pakfire.ipfire.org"),
f6e6ff79
MT
175 "distro", self.distro.identifier,
176 "repo", self.identifier,
177 "mirrorlist?arch=%{arch}"
9c025f7b 178 ))
f6e6ff79 179
5286089b 180 def get_conf(self, local=False):
f6e6ff79
MT
181 lines = [
182 "[repo:%s]" % self.identifier,
183 "description = %s - %s" % (self.distro.name, self.summary),
184 "enabled = 1",
d79bf90f 185 "baseurl = %s/%%{arch}" % (self.path if local else self.url),
f6e6ff79
MT
186 ]
187
5286089b 188 if self.mirrored and not local:
ce88a1bb
MT
189 lines.append("mirrors = %s" % self.mirrorlist)
190
5bc58fba
MT
191 if self.priority:
192 lines.append("priority = %s" % self.priority)
f6e6ff79
MT
193
194 return "\n".join(lines)
9137135a
MT
195
196 @property
197 def name(self):
198 return self.data.name
199
200 @property
f6e6ff79
MT
201 def identifier(self):
202 return self.name.lower()
9137135a
MT
203
204 @property
f6e6ff79
MT
205 def type(self):
206 return self.data.type
9137135a
MT
207
208 @property
f6e6ff79
MT
209 def summary(self):
210 lines = self.description.splitlines()
9137135a 211
f6e6ff79
MT
212 if lines:
213 return lines[0]
9137135a 214
f6e6ff79 215 return "N/A"
9137135a 216
f6e6ff79
MT
217 @property
218 def description(self):
219 return self.data.description or ""
9137135a 220
f6e6ff79
MT
221 @property
222 def parent_id(self):
223 return self.data.parent_id
9137135a 224
d629da45 225 @lazy_property
f6e6ff79
MT
226 def key(self):
227 if not self.data.key_id:
228 return
9137135a 229
d629da45 230 return self.pakfire.keys.get_by_id(self.data.key_id)
9137135a 231
f6e6ff79
MT
232 @property
233 def arches(self):
9729c5b3 234 return self.distro.arches + ["src"]
f6e6ff79 235
ce88a1bb
MT
236 def set_mirrored(self, mirrored):
237 self._set_attribute("mirrored", mirrored)
238
239 mirrored = property(lambda s: s.data.mirrored, set_mirrored)
9137135a 240
f6e6ff79 241 def set_enabled_for_builds(self, state):
d629da45 242 self._set_attribute("enabled_for_builds", state)
9137135a 243
d629da45 244 enabled_for_builds = property(lambda s: s.data.enabled_for_builds, set_enabled_for_builds)
9137135a
MT
245
246 @property
f6e6ff79
MT
247 def score_needed(self):
248 return self.data.score_needed
9137135a
MT
249
250 @property
f6e6ff79
MT
251 def time_min(self):
252 return self.data.time_min
9137135a
MT
253
254 @property
f6e6ff79
MT
255 def time_max(self):
256 return self.data.time_max
9137135a 257
e67c0fc7
MT
258 def set_update_forced(self, update_forced):
259 self._set_attribute("update_forced", update_forced)
260
261 update_forced = property(lambda s: s.data.update_forced, set_update_forced)
262
f6e6ff79
MT
263 def _log_build(self, action, build, from_repo=None, to_repo=None, user=None):
264 user_id = None
265 if user:
266 user_id = user.id
9137135a 267
f6e6ff79
MT
268 from_repo_id = None
269 if from_repo:
270 from_repo_id = from_repo.id
9137135a 271
f6e6ff79
MT
272 to_repo_id = None
273 if to_repo:
274 to_repo_id = to_repo.id
9137135a 275
f6e6ff79
MT
276 self.db.execute("INSERT INTO repositories_history(action, build_id, from_repo_id, to_repo_id, user_id, time) \
277 VALUES(%s, %s, %s, %s, %s, NOW())", action, build.id, from_repo_id, to_repo_id, user_id)
9137135a 278
f6e6ff79
MT
279 def add_build(self, build, user=None, log=True):
280 self.db.execute("INSERT INTO repositories_builds(repo_id, build_id, time_added)"
281 " VALUES(%s, %s, NOW())", self.id, build.id)
9137135a 282
f6e6ff79
MT
283 # Update bug status.
284 build._update_bugs_helper(self)
9137135a 285
f6e6ff79
MT
286 if log:
287 self._log_build("added", build, to_repo=self, user=user)
9137135a 288
f6e6ff79
MT
289 def rem_build(self, build, user=None, log=True):
290 self.db.execute("DELETE FROM repositories_builds \
291 WHERE repo_id = %s AND build_id = %s", self.id, build.id)
9137135a 292
e67c0fc7
MT
293 # Force regenerating the index
294 self.update_forced = True
295
f6e6ff79
MT
296 if log:
297 self._log_build("removed", build, from_repo=self, user=user)
9137135a 298
f6e6ff79
MT
299 def move_build(self, build, to_repo, user=None, log=True):
300 self.db.execute("UPDATE repositories_builds SET repo_id = %s, time_added = NOW() \
301 WHERE repo_id = %s AND build_id = %s", to_repo.id, self.id, build.id)
9137135a 302
e67c0fc7
MT
303 # Force regenerating the index
304 self.update_forced = True
305
f6e6ff79
MT
306 # Update bug status.
307 build._update_bugs_helper(to_repo)
9137135a 308
f6e6ff79
MT
309 if log:
310 self._log_build("moved", build, from_repo=self, to_repo=to_repo,
311 user=user)
9137135a 312
f6e6ff79
MT
313 def get_builds(self, limit=None, offset=None):
314 query = "SELECT build_id AS id FROM repositories_builds \
315 WHERE repo_id = %s ORDER BY time_added DESC"
316 args = [self.id,]
9137135a 317
f6e6ff79
MT
318 if limit:
319 if offset:
320 query += " LIMIT %s,%s"
321 args += [offset, limit,]
322 else:
323 query += " LIMIT %s"
324 args += [limit,]
325
326 _builds = []
327 for build in self.db.query(query, *args):
2c909128 328 build = self.pakfire.builds.get_by_id(build.id)
f6e6ff79
MT
329 build._repo = self
330
331 _builds.append(build)
332
333 return _builds
334
5c596c6e
MT
335 def get_builds_by_name(self, name):
336 """
337 Returns an ordered list of all builds that match this name
338 """
339 builds = self.backend.builds._get_builds("SELECT builds.* FROM repositories_builds \
340 LEFT JOIN builds ON repositories_builds.build_id = builds.id \
341 LEFT JOIN packages ON builds.pkg_id = packages.id \
342 WHERE repositories_builds.repo_id = %s AND packages.name = %s", self.id, name)
343
344 return sorted(builds)
345
83be3106 346 def get_packages(self, arch):
612ee16e
MT
347 if arch == "src":
348 return self.backend.packages._get_packages("SELECT packages.* FROM repositories_builds \
349 LEFT JOIN builds ON repositories_builds.build_id = builds.id \
350 LEFT JOIN packages ON builds.pkg_id = packages.id \
351 WHERE repositories_builds.repo_id = %s", self.id)
352
353 return self.backend.packages._get_packages("SELECT packages.* FROM repositories_builds \
354 LEFT JOIN builds ON repositories_builds.build_id = builds.id \
355 LEFT JOIN jobs ON builds.id = jobs.build_id \
356 LEFT JOIN jobs_packages ON jobs.id = jobs_packages.job_id \
357 LEFT JOIN packages ON jobs_packages.pkg_id = packages.id \
358 WHERE repositories_builds.repo_id = %s \
359 AND (jobs.arch = %s OR jobs.arch = %s) \
360 AND (packages.arch = %s OR packages.arch = %s)",
361 self.id, arch, "noarch", arch, "noarch")
9137135a 362
9729c5b3
MT
363 @property
364 def unpushed_builds(self):
365 return self.backend.builds._get_builds("SELECT builds.* FROM repositories \
366 LEFT JOIN repositories_builds ON repositories.id = repositories_builds.repo_id \
367 LEFT JOIN builds ON repositories_builds.build_id = builds.id \
368 WHERE repositories.id = %s \
369 AND repositories_builds.time_added >= repositories.last_update", self.id)
f6e6ff79
MT
370
371 def get_obsolete_builds(self):
f6e6ff79
MT
372 return self.pakfire.builds.get_obsolete(self)
373
9729c5b3 374 @property
f6e6ff79 375 def needs_update(self):
9729c5b3 376 if self.unpushed_builds:
9137135a
MT
377 return True
378
9137135a
MT
379 return False
380
f6e6ff79
MT
381 def updated(self):
382 self.db.execute("UPDATE repositories SET last_update = NOW() \
383 WHERE id = %s", self.id)
9137135a 384
e67c0fc7
MT
385 # Reset forced update flag
386 self.update_forced = False
387
9729c5b3
MT
388 def remaster(self):
389 log.info("Going to update repository %s..." % self.name)
390
9729c5b3
MT
391 for arch in self.arches:
392 changed = False
393
612ee16e
MT
394 repo_path = os.path.join(self.path, arch)
395 log.debug(" Path: %s" % repo_path)
9729c5b3
MT
396
397 if not os.path.exists(repo_path):
398 os.makedirs(repo_path)
399
612ee16e
MT
400 # Get all packages that are to be included in this repository
401 packages = []
402 for p in self.get_packages(arch):
403 path = os.path.join(repo_path, p.filename)
404 packages.append(path)
9729c5b3 405
612ee16e
MT
406 # Nothing to do if the package already exists
407 if os.path.exists(path):
9729c5b3
MT
408 continue
409
612ee16e
MT
410 # Copy the package into the repository
411 log.info("Adding %s..." % p)
412 p.copy(repo_path)
9729c5b3 413
612ee16e 414 # XXX need to sign the new package here
9729c5b3 415
612ee16e 416 # The repository has been changed
9729c5b3
MT
417 changed = True
418
612ee16e 419 # No need to regenerate the index if the repository hasn't changed
e67c0fc7 420 if not changed and not self.update_forced:
9729c5b3
MT
421 continue
422
423 # Find the key to sign the package.
424 key_id = None
612ee16e 425 if self.key:
9729c5b3
MT
426 key_id = self.key.fingerprint
427
428 # Create package index.
429 p = pakfire.PakfireServer(arch=arch)
612ee16e 430 p.repo_create(repo_path, packages,
9729c5b3
MT
431 name="%s - %s.%s" % (self.distro.name, self.name, arch),
432 key_id=key_id)
433
e67c0fc7
MT
434 # Update the timestamp when we started at last
435 self.updated()
436
612ee16e
MT
437 def cleanup(self):
438 log.info("Cleaning up repository %s..." % self.name)
439
440 for arch in self.arches:
441 repo_path = os.path.join(self.path, arch)
442
443 # Get a list of all files in the repository directory right now
444 filelist = [e for e in os.listdir(repo_path)
445 if os.path.isfile(os.path.join(repo_path, e))]
9729c5b3 446
612ee16e
MT
447 # Get a list of all packages that should be in the repository
448 # and remove them from the filelist
449 for p in self.get_packages(arch):
9729c5b3 450 try:
612ee16e
MT
451 filelist.remove(p.filename)
452 except ValueError:
453 pass
454
455 # For any files that do not belong into the repository
456 # any more, we will just delete them
457 for filename in filelist:
458 path = os.path.join(repo_path, filename)
459 self.backend.delete_file(path)
9729c5b3 460
f6e6ff79
MT
461 def get_history(self, **kwargs):
462 kwargs.update({
463 "repo" : self,
464 })
9137135a 465
f6e6ff79 466 return self.pakfire.repos.get_history(**kwargs)
9137135a 467
f6e6ff79 468 def get_build_times(self):
f6e6ff79 469 times = []
e459cbba 470 for arch in self.arches:
677714db
MT
471 if arch == "src":
472 continue
473
e459cbba 474 time = self.db.get("SELECT SUM(jobs.time_finished - jobs.time_started) AS time FROM jobs \
f6e6ff79
MT
475 JOIN builds ON builds.id = jobs.build_id \
476 JOIN repositories_builds ON builds.id = repositories_builds.build_id \
e459cbba 477 WHERE (jobs.arch = %s OR jobs.arch = %s) AND \
677714db 478 jobs.test IS FALSE AND repositories_builds.repo_id = %s", arch, "noarch", self.id)
f6e6ff79 479
14ebe2d7 480 times.append((arch, time.time.total_seconds() if time.time else 0))
f6e6ff79
MT
481
482 return times
9137135a 483
9137135a 484
d629da45
MT
485class RepositoryAux(base.DataObject):
486 table = "repositories_aux"
f6e6ff79
MT
487
488 @property
489 def name(self):
490 return self.data.name
491
492 @property
493 def description(self):
494 return self.data.description or ""
495
496 @property
497 def url(self):
498 return self.data.url
499
500 @property
501 def identifier(self):
502 return self.name.lower()
503
504 @property
505 def distro(self):
d629da45 506 return self.pakfire.distros.get_by_id(self.data.distro_id)
f6e6ff79 507
5286089b 508 def get_conf(self, local=False):
f6e6ff79
MT
509 lines = [
510 "[repo:%s]" % self.identifier,
511 "description = %s - %s" % (self.distro.name, self.name),
512 "enabled = 1",
513 "baseurl = %s" % self.url,
514 "priority = 0",
515 ]
516
517 return "\n".join(lines)