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