1 import tempfile
2 import shutil
3 import json
4 import os
5 import pprint
6 import time
7 import requests
8
9 from sqlalchemy.sql import text
10 from sqlalchemy.sql.expression import not_
11 from sqlalchemy.orm import joinedload
12 from sqlalchemy import or_
13 from sqlalchemy import and_
14 from sqlalchemy import func, desc
15 from sqlalchemy.sql import false,true
16 from werkzeug.utils import secure_filename
17 from sqlalchemy import bindparam, Integer, String
18 from sqlalchemy.exc import IntegrityError
19
20 from copr_common.enums import FailTypeEnum, StatusEnum
21 from coprs import app
22 from coprs import cache
23 from coprs import db
24 from coprs import models
25 from coprs import helpers
26 from coprs.constants import DEFAULT_BUILD_TIMEOUT, MAX_BUILD_TIMEOUT
27 from coprs.exceptions import MalformedArgumentException, ActionInProgressException, InsufficientRightsException, \
28 UnrepeatableBuildException, RequestCannotBeExecuted, DuplicateException
29
30 from coprs.logic import coprs_logic
31 from coprs.logic import users_logic
32 from coprs.logic.actions_logic import ActionsLogic
33 from coprs.models import BuildChroot
34 from .coprs_logic import MockChrootsLogic
35 from coprs.logic.packages_logic import PackagesLogic
36
37 log = app.logger
41 @classmethod
42 - def get(cls, build_id):
44
45 @classmethod
56
57 @classmethod
68
69 @classmethod
70 @cache.memoize(timeout=2*60)
100
101 @classmethod
106
107 @classmethod
115
116 @classmethod
137
138 @classmethod
140 query = text("""
141 SELECT COUNT(*) as result
142 FROM build_chroot JOIN build on build.id = build_chroot.build_id
143 WHERE
144 build.submitted_on < :end
145 AND (
146 build_chroot.started_on > :start
147 OR (build_chroot.started_on is NULL AND build_chroot.status = :status)
148 -- for currently pending builds we need to filter on status=pending because there might be
149 -- failed builds that have started_on=NULL
150 )
151 AND NOT build.canceled
152 """)
153
154 res = db.engine.execute(query, start=start, end=end, status=StatusEnum("pending"))
155 return res.first().result
156
157 @classmethod
159 query = text("""
160 SELECT COUNT(*) as result
161 FROM build_chroot
162 WHERE
163 started_on < :end
164 AND (ended_on > :start OR (ended_on is NULL AND status = :status))
165 -- for currently running builds we need to filter on status=running because there might be failed
166 -- builds that have ended_on=NULL
167 """)
168
169 res = db.engine.execute(query, start=start, end=end, status=StatusEnum("running"))
170 return res.first().result
171
172 @classmethod
189
190 @classmethod
192 data = [["pending"], ["running"], ["avg running"], ["time"]]
193 params = cls.get_graph_parameters(type)
194 cached_data = cls.get_cached_graph_data(params)
195 data[0].extend(cached_data["pending"])
196 data[1].extend(cached_data["running"])
197
198 for i in range(len(data[0]) - 1, params["steps"]):
199 step_start = params["start"] + i * params["step"]
200 step_end = step_start + params["step"]
201 pending = cls.get_pending_jobs_bucket(step_start, step_end)
202 running = cls.get_running_jobs_bucket(step_start, step_end)
203 data[0].append(pending)
204 data[1].append(running)
205 cls.cache_graph_data(type, time=step_start, pending=pending, running=running)
206
207 running_total = 0
208 for i in range(1, params["steps"] + 1):
209 running_total += data[1][i]
210
211 data[2].extend([running_total * 1.0 / params["steps"]] * (len(data[0]) - 1))
212
213 for i in range(params["start"], params["end"], params["step"]):
214 data[3].append(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(i)))
215
216 return data
217
218 @classmethod
233
234 @classmethod
253
254 @classmethod
256 if type is "10min":
257
258 step = 600
259 steps = 144
260 elif type is "30min":
261
262 step = 1800
263 steps = 48
264 elif type is "24h":
265
266 step = 86400
267 steps = 90
268
269 end = int(time.time())
270 end = end - (end % step)
271 start = end - (steps * step)
272
273 return {
274 "type": type,
275 "step": step,
276 "steps": steps,
277 "start": start,
278 "end": end,
279 }
280
281 @classmethod
293
294 @classmethod
303
304 @classmethod
325
326 @classmethod
335
336 @classmethod
339
340 @classmethod
343
344 @classmethod
349
350 @classmethod
357
358 @classmethod
360 if db.engine.url.drivername == "sqlite":
361 return
362
363 status_to_order = """
364 CREATE OR REPLACE FUNCTION status_to_order (x integer)
365 RETURNS integer AS $$ BEGIN
366 RETURN CASE WHEN x = 3 THEN 1
367 WHEN x = 6 THEN 2
368 WHEN x = 7 THEN 3
369 WHEN x = 4 THEN 4
370 WHEN x = 0 THEN 5
371 WHEN x = 1 THEN 6
372 WHEN x = 5 THEN 7
373 WHEN x = 2 THEN 8
374 WHEN x = 8 THEN 9
375 WHEN x = 9 THEN 10
376 ELSE x
377 END; END;
378 $$ LANGUAGE plpgsql;
379 """
380
381 order_to_status = """
382 CREATE OR REPLACE FUNCTION order_to_status (x integer)
383 RETURNS integer AS $$ BEGIN
384 RETURN CASE WHEN x = 1 THEN 3
385 WHEN x = 2 THEN 6
386 WHEN x = 3 THEN 7
387 WHEN x = 4 THEN 4
388 WHEN x = 5 THEN 0
389 WHEN x = 6 THEN 1
390 WHEN x = 7 THEN 5
391 WHEN x = 8 THEN 2
392 WHEN x = 9 THEN 8
393 WHEN x = 10 THEN 9
394 ELSE x
395 END; END;
396 $$ LANGUAGE plpgsql;
397 """
398
399 db.engine.connect()
400 db.engine.execute(status_to_order)
401 db.engine.execute(order_to_status)
402
403 @classmethod
405 query_select = """
406 SELECT build.id, build.source_status, MAX(package.name) AS pkg_name, build.pkg_version, build.submitted_on,
407 MIN(statuses.started_on) AS started_on, MAX(statuses.ended_on) AS ended_on, order_to_status(MIN(statuses.st)) AS status,
408 build.canceled, MIN("group".name) AS group_name, MIN(copr.name) as copr_name, MIN("user".username) as user_name, build.copr_id
409 FROM build
410 LEFT OUTER JOIN package
411 ON build.package_id = package.id
412 LEFT OUTER JOIN (SELECT build_chroot.build_id, started_on, ended_on, status_to_order(status) AS st FROM build_chroot) AS statuses
413 ON statuses.build_id=build.id
414 LEFT OUTER JOIN copr
415 ON copr.id = build.copr_id
416 LEFT OUTER JOIN copr_dir
417 ON build.copr_dir_id = copr_dir.id
418 LEFT OUTER JOIN "user"
419 ON copr.user_id = "user".id
420 LEFT OUTER JOIN "group"
421 ON copr.group_id = "group".id
422 WHERE build.copr_id = :copr_id
423 AND (:dirname = '' OR :dirname = copr_dir.name)
424 GROUP BY
425 build.id
426 ORDER BY
427 build.id DESC;
428 """
429
430 if db.engine.url.drivername == "sqlite":
431 def sqlite_status_to_order(x):
432 if x == 3:
433 return 1
434 elif x == 6:
435 return 2
436 elif x == 7:
437 return 3
438 elif x == 4:
439 return 4
440 elif x == 0:
441 return 5
442 elif x == 1:
443 return 6
444 elif x == 5:
445 return 7
446 elif x == 2:
447 return 8
448 elif x == 8:
449 return 9
450 elif x == 9:
451 return 10
452 return 1000
453
454 def sqlite_order_to_status(x):
455 if x == 1:
456 return 3
457 elif x == 2:
458 return 6
459 elif x == 3:
460 return 7
461 elif x == 4:
462 return 4
463 elif x == 5:
464 return 0
465 elif x == 6:
466 return 1
467 elif x == 7:
468 return 5
469 elif x == 8:
470 return 2
471 elif x == 9:
472 return 8
473 elif x == 10:
474 return 9
475 return 1000
476
477 conn = db.engine.connect()
478 conn.connection.create_function("status_to_order", 1, sqlite_status_to_order)
479 conn.connection.create_function("order_to_status", 1, sqlite_order_to_status)
480 statement = text(query_select)
481 statement.bindparams(bindparam("copr_id", Integer))
482 statement.bindparams(bindparam("dirname", String))
483 result = conn.execute(statement, {"copr_id": copr.id, "dirname": dirname})
484 else:
485 statement = text(query_select)
486 statement.bindparams(bindparam("copr_id", Integer))
487 statement.bindparams(bindparam("dirname", String))
488 result = db.engine.execute(statement, {"copr_id": copr.id, "dirname": dirname})
489
490 return result
491
492 @classmethod
495
496 @classmethod
504
505 @classmethod
508
509 @classmethod
512
513 @classmethod
516 skip_import = False
517 git_hashes = {}
518
519 if source_build.source_type == helpers.BuildSourceEnum('upload'):
520 if source_build.repeatable:
521 skip_import = True
522 for chroot in source_build.build_chroots:
523 git_hashes[chroot.name] = chroot.git_hash
524 else:
525 raise UnrepeatableBuildException("Build sources were not fully imported into CoprDistGit.")
526
527 build = cls.create_new(user, copr, source_build.source_type, source_build.source_json, chroot_names,
528 pkgs=source_build.pkgs, git_hashes=git_hashes, skip_import=skip_import,
529 srpm_url=source_build.srpm_url, copr_dirname=source_build.copr_dir.name, **build_options)
530 build.package_id = source_build.package_id
531 build.pkg_version = source_build.pkg_version
532 build.resubmitted_from_id = source_build.id
533
534 return build
535
536 @classmethod
537 - def create_new_from_url(cls, user, copr, url, chroot_names=None,
538 copr_dirname=None, **build_options):
552
553 @classmethod
554 - def create_new_from_scm(cls, user, copr, scm_type, clone_url,
555 committish='', subdirectory='', spec='', srpm_build_method='rpkg',
556 chroot_names=None, copr_dirname=None, **build_options):
557 """
558 :type user: models.User
559 :type copr: models.Copr
560
561 :type chroot_names: List[str]
562
563 :rtype: models.Build
564 """
565 source_type = helpers.BuildSourceEnum("scm")
566 source_json = json.dumps({"type": scm_type,
567 "clone_url": clone_url,
568 "committish": committish,
569 "subdirectory": subdirectory,
570 "spec": spec,
571 "srpm_build_method": srpm_build_method})
572 return cls.create_new(user, copr, source_type, source_json, chroot_names, copr_dirname=copr_dirname, **build_options)
573
574 @classmethod
575 - def create_new_from_pypi(cls, user, copr, pypi_package_name, pypi_package_version, spec_template,
576 python_versions, chroot_names=None, copr_dirname=None, **build_options):
594
595 @classmethod
608
609 @classmethod
610 - def create_new_from_custom(cls, user, copr, script, script_chroot=None, script_builddeps=None,
611 script_resultdir=None, chroot_names=None, copr_dirname=None, **kwargs):
612 """
613 :type user: models.User
614 :type copr: models.Copr
615 :type script: str
616 :type script_chroot: str
617 :type script_builddeps: str
618 :type script_resultdir: str
619 :type chroot_names: List[str]
620 :rtype: models.Build
621 """
622 source_type = helpers.BuildSourceEnum("custom")
623 source_dict = {
624 'script': script,
625 'chroot': script_chroot,
626 'builddeps': script_builddeps,
627 'resultdir': script_resultdir,
628 }
629
630 return cls.create_new(user, copr, source_type, json.dumps(source_dict),
631 chroot_names, copr_dirname=copr_dirname, **kwargs)
632
633 @classmethod
634 - def create_new_from_upload(cls, user, copr, f_uploader, orig_filename,
635 chroot_names=None, copr_dirname=None, **build_options):
636 """
637 :type user: models.User
638 :type copr: models.Copr
639 :param f_uploader(file_path): function which stores data at the given `file_path`
640 :return:
641 """
642 tmp = tempfile.mkdtemp(dir=app.config["STORAGE_DIR"])
643 tmp_name = os.path.basename(tmp)
644 filename = secure_filename(orig_filename)
645 file_path = os.path.join(tmp, filename)
646 f_uploader(file_path)
647
648
649 pkg_url = "{baseurl}/tmp/{tmp_dir}/{filename}".format(
650 baseurl=app.config["PUBLIC_COPR_BASE_URL"],
651 tmp_dir=tmp_name,
652 filename=filename)
653
654
655 source_type = helpers.BuildSourceEnum("upload")
656 source_json = json.dumps({"url": pkg_url, "pkg": filename, "tmp": tmp_name})
657 srpm_url = None if pkg_url.endswith('.spec') else pkg_url
658
659 try:
660 build = cls.create_new(user, copr, source_type, source_json,
661 chroot_names, pkgs=pkg_url, srpm_url=srpm_url,
662 copr_dirname=copr_dirname, **build_options)
663 except Exception:
664 shutil.rmtree(tmp)
665 raise
666
667 return build
668
669 @classmethod
670 - def create_new(cls, user, copr, source_type, source_json, chroot_names=None, pkgs="",
671 git_hashes=None, skip_import=False, background=False, batch=None,
672 srpm_url=None, copr_dirname=None, **build_options):
673 """
674 :type user: models.User
675 :type copr: models.Copr
676 :type chroot_names: List[str]
677 :type source_type: int value from helpers.BuildSourceEnum
678 :type source_json: str in json format
679 :type pkgs: str
680 :type git_hashes: dict
681 :type skip_import: bool
682 :type background: bool
683 :type batch: models.Batch
684 :rtype: models.Build
685 """
686 chroots = None
687 if chroot_names:
688 chroots = []
689 for chroot in copr.active_chroots:
690 if chroot.name in chroot_names:
691 chroots.append(chroot)
692
693 build = cls.add(
694 user=user,
695 pkgs=pkgs,
696 copr=copr,
697 chroots=chroots,
698 source_type=source_type,
699 source_json=source_json,
700 enable_net=build_options.get("enable_net", copr.build_enable_net),
701 background=background,
702 git_hashes=git_hashes,
703 skip_import=skip_import,
704 batch=batch,
705 srpm_url=srpm_url,
706 copr_dirname=copr_dirname,
707 )
708
709 if user.proven:
710 if "timeout" in build_options:
711 build.timeout = build_options["timeout"]
712
713 return build
714
715 @classmethod
716 - def add(cls, user, pkgs, copr, source_type=None, source_json=None,
717 repos=None, chroots=None, timeout=None, enable_net=True,
718 git_hashes=None, skip_import=False, background=False, batch=None,
719 srpm_url=None, copr_dirname=None):
720
721 if chroots is None:
722 chroots = []
723
724 coprs_logic.CoprsLogic.raise_if_unfinished_blocking_action(
725 copr, "Can't build while there is an operation in progress: {action}")
726 users_logic.UsersLogic.raise_if_cant_build_in_copr(
727 user, copr,
728 "You don't have permissions to build in this copr.")
729
730 if not repos:
731 repos = copr.repos
732
733
734 if pkgs and (" " in pkgs or "\n" in pkgs or "\t" in pkgs or pkgs.strip() != pkgs):
735 raise MalformedArgumentException("Trying to create a build using src_pkg "
736 "with bad characters. Forgot to split?")
737
738
739 if not source_type or not source_json:
740 source_type = helpers.BuildSourceEnum("link")
741 source_json = json.dumps({"url":pkgs})
742
743 if skip_import and srpm_url:
744 chroot_status = StatusEnum("pending")
745 source_status = StatusEnum("succeeded")
746 else:
747 chroot_status = StatusEnum("waiting")
748 source_status = StatusEnum("pending")
749
750 copr_dir = None
751 if copr_dirname:
752 if not copr_dirname.startswith(copr.name+':') and copr_dirname != copr.name:
753 raise MalformedArgumentException("Copr dirname not starting with copr name.")
754 copr_dir = coprs_logic.CoprDirsLogic.get_or_create(copr, copr_dirname)
755
756 build = models.Build(
757 user=user,
758 pkgs=pkgs,
759 copr=copr,
760 repos=repos,
761 source_type=source_type,
762 source_json=source_json,
763 source_status=source_status,
764 submitted_on=int(time.time()),
765 enable_net=bool(enable_net),
766 is_background=bool(background),
767 batch=batch,
768 srpm_url=srpm_url,
769 copr_dir=copr_dir,
770 )
771
772 if timeout:
773 build.timeout = timeout or DEFAULT_BUILD_TIMEOUT
774
775 db.session.add(build)
776
777 for chroot in chroots:
778
779 git_hash = None
780 if git_hashes:
781 git_hash = git_hashes.get(chroot.name)
782 buildchroot = models.BuildChroot(
783 build=build,
784 status=chroot_status,
785 mock_chroot=chroot,
786 git_hash=git_hash,
787 )
788 db.session.add(buildchroot)
789
790 return build
791
792 @classmethod
793 - def rebuild_package(cls, package, source_dict_update={}, copr_dir=None, update_callback=None,
794 scm_object_type=None, scm_object_id=None,
795 scm_object_url=None, submitted_by=None):
796
797 source_dict = package.source_json_dict
798 source_dict.update(source_dict_update)
799 source_json = json.dumps(source_dict)
800
801 if not copr_dir:
802 copr_dir = package.copr.main_dir
803
804 build = models.Build(
805 user=None,
806 pkgs=None,
807 package=package,
808 copr=package.copr,
809 repos=package.copr.repos,
810 source_status=StatusEnum("pending"),
811 source_type=package.source_type,
812 source_json=source_json,
813 submitted_on=int(time.time()),
814 enable_net=package.copr.build_enable_net,
815 timeout=DEFAULT_BUILD_TIMEOUT,
816 copr_dir=copr_dir,
817 update_callback=update_callback,
818 scm_object_type=scm_object_type,
819 scm_object_id=scm_object_id,
820 scm_object_url=scm_object_url,
821 submitted_by=submitted_by,
822 )
823 db.session.add(build)
824
825 status = StatusEnum("waiting")
826 for chroot in package.chroots:
827 buildchroot = models.BuildChroot(
828 build=build,
829 status=status,
830 mock_chroot=chroot,
831 git_hash=None
832 )
833 db.session.add(buildchroot)
834
835 cls.process_update_callback(build)
836 return build
837
838
839 terminal_states = {StatusEnum("failed"), StatusEnum("succeeded"), StatusEnum("canceled")}
840
841 @classmethod
853
854
855 @classmethod
857 """
858 Deletes the locally stored data for build purposes. This is typically
859 uploaded srpm file, uploaded spec file or webhook POST content.
860 """
861
862 data = json.loads(build.source_json)
863 if 'tmp' in data:
864 tmp = data["tmp"]
865 storage_path = app.config["STORAGE_DIR"]
866 try:
867 shutil.rmtree(os.path.join(storage_path, tmp))
868 except:
869 pass
870
871
872 @classmethod
874 """
875 :param build:
876 :param upd_dict:
877 example:
878 {
879 "builds":[
880 {
881 "id": 1,
882 "copr_id": 2,
883 "started_on": 1390866440
884 },
885 {
886 "id": 2,
887 "copr_id": 1,
888 "status": 0,
889 "chroot": "fedora-18-x86_64",
890 "result_dir": "baz",
891 "ended_on": 1390866440
892 }]
893 }
894 """
895 log.info("Updating build {} by: {}".format(build.id, upd_dict))
896
897
898 pkg_name = upd_dict.get('pkg_name', None)
899 if pkg_name:
900 if not PackagesLogic.get(build.copr_dir.id, pkg_name).first():
901 try:
902 package = PackagesLogic.add(
903 build.copr.user, build.copr_dir,
904 pkg_name, build.source_type, build.source_json)
905 db.session.add(package)
906 db.session.commit()
907 except (IntegrityError, DuplicateException) as e:
908 app.logger.exception(e)
909 db.session.rollback()
910 return
911 build.package = PackagesLogic.get(build.copr_dir.id, pkg_name).first()
912
913 for attr in ["built_packages", "srpm_url", "pkg_version"]:
914 value = upd_dict.get(attr, None)
915 if value:
916 setattr(build, attr, value)
917
918
919 if str(upd_dict.get("task_id")) == str(build.task_id):
920 build.result_dir = upd_dict.get("result_dir", "")
921
922 new_status = upd_dict.get("status")
923 if new_status == StatusEnum("succeeded"):
924 new_status = StatusEnum("importing")
925 chroot_status=StatusEnum("waiting")
926 if not build.build_chroots:
927
928
929 for chroot in build.package.chroots:
930 buildchroot = models.BuildChroot(
931 build=build,
932 status=chroot_status,
933 mock_chroot=chroot,
934 git_hash=None,
935 )
936 db.session.add(buildchroot)
937 else:
938 for buildchroot in build.build_chroots:
939 buildchroot.status = chroot_status
940 db.session.add(buildchroot)
941
942 build.source_status = new_status
943 if new_status == StatusEnum("failed") or \
944 new_status == StatusEnum("skipped"):
945 for ch in build.build_chroots:
946 ch.status = new_status
947 ch.ended_on = upd_dict.get("ended_on") or time.time()
948 ch.started_on = upd_dict.get("started_on", ch.ended_on)
949 db.session.add(ch)
950
951 if new_status == StatusEnum("failed"):
952 build.fail_type = FailTypeEnum("srpm_build_error")
953
954 cls.process_update_callback(build)
955 db.session.add(build)
956 return
957
958 if "chroot" in upd_dict:
959
960 for build_chroot in build.build_chroots:
961 if build_chroot.name == upd_dict["chroot"]:
962 build_chroot.result_dir = upd_dict.get("result_dir", "")
963
964 if "status" in upd_dict and build_chroot.status not in BuildsLogic.terminal_states:
965 build_chroot.status = upd_dict["status"]
966
967 if upd_dict.get("status") in BuildsLogic.terminal_states:
968 build_chroot.ended_on = upd_dict.get("ended_on") or time.time()
969
970 if upd_dict.get("status") == StatusEnum("starting"):
971 build_chroot.started_on = upd_dict.get("started_on") or time.time()
972
973 db.session.add(build_chroot)
974
975
976
977 if (build.module
978 and upd_dict.get("status") == StatusEnum("succeeded")
979 and all(b.status == StatusEnum("succeeded") for b in build.module.builds)):
980 ActionsLogic.send_build_module(build.copr, build.module)
981
982 cls.process_update_callback(build)
983 db.session.add(build)
984
985 @classmethod
1000
1001 @classmethod
1003 headers = {
1004 'Authorization': 'token {}'.format(build.copr.scm_api_auth.get('api_key'))
1005 }
1006
1007 if build.srpm_url:
1008 progress = 50
1009 else:
1010 progress = 10
1011
1012 state_table = {
1013 'failed': ('failure', 0),
1014 'succeeded': ('success', 100),
1015 'canceled': ('canceled', 0),
1016 'running': ('pending', progress),
1017 'pending': ('pending', progress),
1018 'skipped': ('error', 0),
1019 'starting': ('pending', progress),
1020 'importing': ('pending', progress),
1021 'forked': ('error', 0),
1022 'waiting': ('pending', progress),
1023 'unknown': ('error', 0),
1024 }
1025
1026 build_url = os.path.join(
1027 app.config['PUBLIC_COPR_BASE_URL'],
1028 'coprs', build.copr.full_name.replace('@', 'g/'),
1029 'build', str(build.id)
1030 )
1031
1032 data = {
1033 'username': 'Copr build',
1034 'comment': '#{}'.format(build.id),
1035 'url': build_url,
1036 'status': state_table[build.state][0],
1037 'percent': state_table[build.state][1],
1038 'uid': str(build.id),
1039 }
1040
1041 log.debug('Sending data to Pagure API: %s', pprint.pformat(data))
1042 response = requests.post(api_url, data=data, headers=headers)
1043 log.debug('Pagure API response: %s', response.text)
1044
1045 @classmethod
1068
1069 @classmethod
1083
1084 @classmethod
1085 - def delete_build(cls, user, build, send_delete_action=True):
1096
1097 @classmethod
1116
1117 @classmethod
1130
1131 @classmethod
1150
1151 @classmethod
1159
1160 @classmethod
1163
1164 @classmethod
1167
1168 @classmethod
1170 dirs = (
1171 db.session.query(
1172 models.CoprDir.id,
1173 models.Package.id,
1174 models.Package.max_builds)
1175 .join(models.Build, models.Build.copr_dir_id==models.CoprDir.id)
1176 .join(models.Package)
1177 .filter(models.Package.max_builds > 0)
1178 .group_by(
1179 models.CoprDir.id,
1180 models.Package.max_builds,
1181 models.Package.id)
1182 .having(func.count(models.Build.id) > models.Package.max_builds)
1183 )
1184
1185 for dir_id, package_id, limit in dirs.all():
1186 delete_builds = (
1187 models.Build.query.filter(
1188 models.Build.copr_dir_id==dir_id,
1189 models.Build.package_id==package_id)
1190 .order_by(desc(models.Build.id))
1191 .offset(limit)
1192 .all()
1193 )
1194
1195 for build in delete_builds:
1196 try:
1197 cls.delete_build(build.copr.user, build)
1198 except ActionInProgressException:
1199
1200 log.error("Build(id={}) delete failed, unfinished action.".format(build.id))
1201
1202 @classmethod
1218
1221 @classmethod
1230
1231 @classmethod
1242
1243 @classmethod
1246
1247 @classmethod
1250
1251 @classmethod
1254
1255 @classmethod
1258
1259 @classmethod
1262
1265 @classmethod
1267 query = """
1268 SELECT
1269 package.id as package_id,
1270 package.name AS package_name,
1271 build.id AS build_id,
1272 build_chroot.status AS build_chroot_status,
1273 build.pkg_version AS build_pkg_version,
1274 mock_chroot.id AS mock_chroot_id,
1275 mock_chroot.os_release AS mock_chroot_os_release,
1276 mock_chroot.os_version AS mock_chroot_os_version,
1277 mock_chroot.arch AS mock_chroot_arch
1278 FROM package
1279 JOIN (SELECT
1280 MAX(build.id) AS max_build_id_for_chroot,
1281 build.package_id AS package_id,
1282 build_chroot.mock_chroot_id AS mock_chroot_id
1283 FROM build
1284 JOIN build_chroot
1285 ON build.id = build_chroot.build_id
1286 WHERE build.copr_id = {copr_id}
1287 AND build_chroot.status != 2
1288 GROUP BY build.package_id,
1289 build_chroot.mock_chroot_id) AS max_build_ids_for_a_chroot
1290 ON package.id = max_build_ids_for_a_chroot.package_id
1291 JOIN build
1292 ON build.id = max_build_ids_for_a_chroot.max_build_id_for_chroot
1293 JOIN build_chroot
1294 ON build_chroot.mock_chroot_id = max_build_ids_for_a_chroot.mock_chroot_id
1295 AND build_chroot.build_id = max_build_ids_for_a_chroot.max_build_id_for_chroot
1296 JOIN mock_chroot
1297 ON mock_chroot.id = max_build_ids_for_a_chroot.mock_chroot_id
1298 JOIN copr_dir ON build.copr_dir_id=copr_dir.id WHERE copr_dir.main IS TRUE
1299 ORDER BY package.name ASC, package.id ASC, mock_chroot.os_release ASC, mock_chroot.os_version ASC, mock_chroot.arch ASC
1300 """.format(copr_id=copr.id)
1301 rows = db.session.execute(query)
1302 return rows
1303