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 db
23 from coprs import models
24 from coprs import helpers
25 from coprs.constants import DEFAULT_BUILD_TIMEOUT, MAX_BUILD_TIMEOUT
26 from coprs.exceptions import MalformedArgumentException, ActionInProgressException, InsufficientRightsException, \
27 UnrepeatableBuildException, RequestCannotBeExecuted, DuplicateException
28
29 from coprs.logic import coprs_logic
30 from coprs.logic import users_logic
31 from coprs.logic.actions_logic import ActionsLogic
32 from coprs.models import BuildChroot
33 from .coprs_logic import MockChrootsLogic
34 from coprs.logic.packages_logic import PackagesLogic
35
36 log = app.logger
40 @classmethod
41 - def get(cls, build_id):
43
44 @classmethod
55
56 @classmethod
67
68 @classmethod
98
99 @classmethod
107
108 @classmethod
129
130 @classmethod
132 query = text("""
133 SELECT COUNT(*) as result
134 FROM build_chroot JOIN build on build.id = build_chroot.build_id
135 WHERE
136 build.submitted_on < :end
137 AND (
138 build_chroot.started_on > :start
139 OR (build_chroot.started_on is NULL AND build_chroot.status = :status)
140 -- for currently pending builds we need to filter on status=pending because there might be
141 -- failed builds that have started_on=NULL
142 )
143 AND NOT build.canceled
144 """)
145
146 res = db.engine.execute(query, start=start, end=end, status=StatusEnum("pending"))
147 return res.first().result
148
149 @classmethod
151 query = text("""
152 SELECT COUNT(*) as result
153 FROM build_chroot
154 WHERE
155 started_on < :end
156 AND (ended_on > :start OR (ended_on is NULL AND status = :status))
157 -- for currently running builds we need to filter on status=running because there might be failed
158 -- builds that have ended_on=NULL
159 """)
160
161 res = db.engine.execute(query, start=start, end=end, status=StatusEnum("running"))
162 return res.first().result
163
164 @classmethod
181
182 @classmethod
184 data = [["pending"], ["running"], ["avg running"], ["time"]]
185 params = cls.get_graph_parameters(type)
186 cached_data = cls.get_cached_graph_data(params)
187 data[0].extend(cached_data["pending"])
188 data[1].extend(cached_data["running"])
189
190 for i in range(len(data[0]) - 1, params["steps"]):
191 step_start = params["start"] + i * params["step"]
192 step_end = step_start + params["step"]
193 pending = cls.get_pending_jobs_bucket(step_start, step_end)
194 running = cls.get_running_jobs_bucket(step_start, step_end)
195 data[0].append(pending)
196 data[1].append(running)
197 cls.cache_graph_data(type, time=step_start, pending=pending, running=running)
198
199 running_total = 0
200 for i in range(1, params["steps"] + 1):
201 running_total += data[1][i]
202
203 data[2].extend([running_total * 1.0 / params["steps"]] * (len(data[0]) - 1))
204
205 for i in range(params["start"], params["end"], params["step"]):
206 data[3].append(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(i)))
207
208 return data
209
210 @classmethod
225
226 @classmethod
245
246 @classmethod
248 if type is "10min":
249
250 step = 600
251 steps = 144
252 elif type is "30min":
253
254 step = 1800
255 steps = 48
256 elif type is "24h":
257
258 step = 86400
259 steps = 90
260
261 end = int(time.time())
262 end = end - (end % step)
263 start = end - (steps * step)
264
265 return {
266 "type": type,
267 "step": step,
268 "steps": steps,
269 "start": start,
270 "end": end,
271 }
272
273 @classmethod
285
286 @classmethod
295
296 @classmethod
317
318 @classmethod
327
328 @classmethod
331
332 @classmethod
335
336 @classmethod
341
342 @classmethod
349
350 @classmethod
352 if db.engine.url.drivername == "sqlite":
353 return
354
355 status_to_order = """
356 CREATE OR REPLACE FUNCTION status_to_order (x integer)
357 RETURNS integer AS $$ BEGIN
358 RETURN CASE WHEN x = 3 THEN 1
359 WHEN x = 6 THEN 2
360 WHEN x = 7 THEN 3
361 WHEN x = 4 THEN 4
362 WHEN x = 0 THEN 5
363 WHEN x = 1 THEN 6
364 WHEN x = 5 THEN 7
365 WHEN x = 2 THEN 8
366 WHEN x = 8 THEN 9
367 WHEN x = 9 THEN 10
368 ELSE x
369 END; END;
370 $$ LANGUAGE plpgsql;
371 """
372
373 order_to_status = """
374 CREATE OR REPLACE FUNCTION order_to_status (x integer)
375 RETURNS integer AS $$ BEGIN
376 RETURN CASE WHEN x = 1 THEN 3
377 WHEN x = 2 THEN 6
378 WHEN x = 3 THEN 7
379 WHEN x = 4 THEN 4
380 WHEN x = 5 THEN 0
381 WHEN x = 6 THEN 1
382 WHEN x = 7 THEN 5
383 WHEN x = 8 THEN 2
384 WHEN x = 9 THEN 8
385 WHEN x = 10 THEN 9
386 ELSE x
387 END; END;
388 $$ LANGUAGE plpgsql;
389 """
390
391 db.engine.connect()
392 db.engine.execute(status_to_order)
393 db.engine.execute(order_to_status)
394
395 @classmethod
397 query_select = """
398 SELECT build.id, build.source_status, MAX(package.name) AS pkg_name, build.pkg_version, build.submitted_on,
399 MIN(statuses.started_on) AS started_on, MAX(statuses.ended_on) AS ended_on, order_to_status(MIN(statuses.st)) AS status,
400 build.canceled, MIN("group".name) AS group_name, MIN(copr.name) as copr_name, MIN("user".username) as user_name, build.copr_id
401 FROM build
402 LEFT OUTER JOIN package
403 ON build.package_id = package.id
404 LEFT OUTER JOIN (SELECT build_chroot.build_id, started_on, ended_on, status_to_order(status) AS st FROM build_chroot) AS statuses
405 ON statuses.build_id=build.id
406 LEFT OUTER JOIN copr
407 ON copr.id = build.copr_id
408 LEFT OUTER JOIN copr_dir
409 ON build.copr_dir_id = copr_dir.id
410 LEFT OUTER JOIN "user"
411 ON copr.user_id = "user".id
412 LEFT OUTER JOIN "group"
413 ON copr.group_id = "group".id
414 WHERE build.copr_id = :copr_id
415 AND (:dirname = '' OR :dirname = copr_dir.name)
416 GROUP BY
417 build.id
418 ORDER BY
419 build.id DESC;
420 """
421
422 if db.engine.url.drivername == "sqlite":
423 def sqlite_status_to_order(x):
424 if x == 3:
425 return 1
426 elif x == 6:
427 return 2
428 elif x == 7:
429 return 3
430 elif x == 4:
431 return 4
432 elif x == 0:
433 return 5
434 elif x == 1:
435 return 6
436 elif x == 5:
437 return 7
438 elif x == 2:
439 return 8
440 elif x == 8:
441 return 9
442 elif x == 9:
443 return 10
444 return 1000
445
446 def sqlite_order_to_status(x):
447 if x == 1:
448 return 3
449 elif x == 2:
450 return 6
451 elif x == 3:
452 return 7
453 elif x == 4:
454 return 4
455 elif x == 5:
456 return 0
457 elif x == 6:
458 return 1
459 elif x == 7:
460 return 5
461 elif x == 8:
462 return 2
463 elif x == 9:
464 return 8
465 elif x == 10:
466 return 9
467 return 1000
468
469 conn = db.engine.connect()
470 conn.connection.create_function("status_to_order", 1, sqlite_status_to_order)
471 conn.connection.create_function("order_to_status", 1, sqlite_order_to_status)
472 statement = text(query_select)
473 statement.bindparams(bindparam("copr_id", Integer))
474 statement.bindparams(bindparam("dirname", String))
475 result = conn.execute(statement, {"copr_id": copr.id, "dirname": dirname})
476 else:
477 statement = text(query_select)
478 statement.bindparams(bindparam("copr_id", Integer))
479 statement.bindparams(bindparam("dirname", String))
480 result = db.engine.execute(statement, {"copr_id": copr.id, "dirname": dirname})
481
482 return result
483
484 @classmethod
487
488 @classmethod
496
497 @classmethod
500
501 @classmethod
504
505 @classmethod
508 skip_import = False
509 git_hashes = {}
510
511 if source_build.source_type == helpers.BuildSourceEnum('upload'):
512 if source_build.repeatable:
513 skip_import = True
514 for chroot in source_build.build_chroots:
515 git_hashes[chroot.name] = chroot.git_hash
516 else:
517 raise UnrepeatableBuildException("Build sources were not fully imported into CoprDistGit.")
518
519 build = cls.create_new(user, copr, source_build.source_type, source_build.source_json, chroot_names,
520 pkgs=source_build.pkgs, git_hashes=git_hashes, skip_import=skip_import,
521 srpm_url=source_build.srpm_url, copr_dirname=source_build.copr_dir.name, **build_options)
522 build.package_id = source_build.package_id
523 build.pkg_version = source_build.pkg_version
524 return build
525
526 @classmethod
527 - def create_new_from_url(cls, user, copr, url, chroot_names=None,
528 copr_dirname=None, **build_options):
542
543 @classmethod
544 - def create_new_from_scm(cls, user, copr, scm_type, clone_url,
545 committish='', subdirectory='', spec='', srpm_build_method='rpkg',
546 chroot_names=None, copr_dirname=None, **build_options):
547 """
548 :type user: models.User
549 :type copr: models.Copr
550
551 :type chroot_names: List[str]
552
553 :rtype: models.Build
554 """
555 source_type = helpers.BuildSourceEnum("scm")
556 source_json = json.dumps({"type": scm_type,
557 "clone_url": clone_url,
558 "committish": committish,
559 "subdirectory": subdirectory,
560 "spec": spec,
561 "srpm_build_method": srpm_build_method})
562 return cls.create_new(user, copr, source_type, source_json, chroot_names, copr_dirname=copr_dirname, **build_options)
563
564 @classmethod
565 - def create_new_from_pypi(cls, user, copr, pypi_package_name, pypi_package_version, spec_template,
566 python_versions, chroot_names=None, copr_dirname=None, **build_options):
584
585 @classmethod
598
599 @classmethod
600 - def create_new_from_custom(cls, user, copr, script, script_chroot=None, script_builddeps=None,
601 script_resultdir=None, chroot_names=None, copr_dirname=None, **kwargs):
602 """
603 :type user: models.User
604 :type copr: models.Copr
605 :type script: str
606 :type script_chroot: str
607 :type script_builddeps: str
608 :type script_resultdir: str
609 :type chroot_names: List[str]
610 :rtype: models.Build
611 """
612 source_type = helpers.BuildSourceEnum("custom")
613 source_dict = {
614 'script': script,
615 'chroot': script_chroot,
616 'builddeps': script_builddeps,
617 'resultdir': script_resultdir,
618 }
619
620 return cls.create_new(user, copr, source_type, json.dumps(source_dict),
621 chroot_names, copr_dirname=copr_dirname, **kwargs)
622
623 @classmethod
624 - def create_new_from_upload(cls, user, copr, f_uploader, orig_filename,
625 chroot_names=None, copr_dirname=None, **build_options):
626 """
627 :type user: models.User
628 :type copr: models.Copr
629 :param f_uploader(file_path): function which stores data at the given `file_path`
630 :return:
631 """
632 tmp = tempfile.mkdtemp(dir=app.config["STORAGE_DIR"])
633 tmp_name = os.path.basename(tmp)
634 filename = secure_filename(orig_filename)
635 file_path = os.path.join(tmp, filename)
636 f_uploader(file_path)
637
638
639 pkg_url = "{baseurl}/tmp/{tmp_dir}/{filename}".format(
640 baseurl=app.config["PUBLIC_COPR_BASE_URL"],
641 tmp_dir=tmp_name,
642 filename=filename)
643
644
645 source_type = helpers.BuildSourceEnum("upload")
646 source_json = json.dumps({"url": pkg_url, "pkg": filename, "tmp": tmp_name})
647 srpm_url = None if pkg_url.endswith('.spec') else pkg_url
648
649 try:
650 build = cls.create_new(user, copr, source_type, source_json,
651 chroot_names, pkgs=pkg_url, srpm_url=srpm_url,
652 copr_dirname=copr_dirname, **build_options)
653 except Exception:
654 shutil.rmtree(tmp)
655 raise
656
657 return build
658
659 @classmethod
660 - def create_new(cls, user, copr, source_type, source_json, chroot_names=None, pkgs="",
661 git_hashes=None, skip_import=False, background=False, batch=None,
662 srpm_url=None, copr_dirname=None, **build_options):
663 """
664 :type user: models.User
665 :type copr: models.Copr
666 :type chroot_names: List[str]
667 :type source_type: int value from helpers.BuildSourceEnum
668 :type source_json: str in json format
669 :type pkgs: str
670 :type git_hashes: dict
671 :type skip_import: bool
672 :type background: bool
673 :type batch: models.Batch
674 :rtype: models.Build
675 """
676 chroots = None
677 if chroot_names:
678 chroots = []
679 for chroot in copr.active_chroots:
680 if chroot.name in chroot_names:
681 chroots.append(chroot)
682
683 build = cls.add(
684 user=user,
685 pkgs=pkgs,
686 copr=copr,
687 chroots=chroots,
688 source_type=source_type,
689 source_json=source_json,
690 enable_net=build_options.get("enable_net", copr.build_enable_net),
691 background=background,
692 git_hashes=git_hashes,
693 skip_import=skip_import,
694 batch=batch,
695 srpm_url=srpm_url,
696 copr_dirname=copr_dirname,
697 )
698
699 if user.proven:
700 if "timeout" in build_options:
701 build.timeout = build_options["timeout"]
702
703 return build
704
705 @classmethod
706 - def add(cls, user, pkgs, copr, source_type=None, source_json=None,
707 repos=None, chroots=None, timeout=None, enable_net=True,
708 git_hashes=None, skip_import=False, background=False, batch=None,
709 srpm_url=None, copr_dirname=None):
710
711 if chroots is None:
712 chroots = []
713
714 coprs_logic.CoprsLogic.raise_if_unfinished_blocking_action(
715 copr, "Can't build while there is an operation in progress: {action}")
716 users_logic.UsersLogic.raise_if_cant_build_in_copr(
717 user, copr,
718 "You don't have permissions to build in this copr.")
719
720 if not repos:
721 repos = copr.repos
722
723
724 if pkgs and (" " in pkgs or "\n" in pkgs or "\t" in pkgs or pkgs.strip() != pkgs):
725 raise MalformedArgumentException("Trying to create a build using src_pkg "
726 "with bad characters. Forgot to split?")
727
728
729 if not source_type or not source_json:
730 source_type = helpers.BuildSourceEnum("link")
731 source_json = json.dumps({"url":pkgs})
732
733 if skip_import and srpm_url:
734 chroot_status = StatusEnum("pending")
735 source_status = StatusEnum("succeeded")
736 else:
737 chroot_status = StatusEnum("waiting")
738 source_status = StatusEnum("pending")
739
740 copr_dir = None
741 if copr_dirname:
742 if not copr_dirname.startswith(copr.name+':') and copr_dirname != copr.name:
743 raise MalformedArgumentException("Copr dirname not starting with copr name.")
744 copr_dir = coprs_logic.CoprDirsLogic.get_or_create(copr, copr_dirname)
745
746 build = models.Build(
747 user=user,
748 pkgs=pkgs,
749 copr=copr,
750 repos=repos,
751 source_type=source_type,
752 source_json=source_json,
753 source_status=source_status,
754 submitted_on=int(time.time()),
755 enable_net=bool(enable_net),
756 is_background=bool(background),
757 batch=batch,
758 srpm_url=srpm_url,
759 copr_dir=copr_dir,
760 )
761
762 if timeout:
763 build.timeout = timeout or DEFAULT_BUILD_TIMEOUT
764
765 db.session.add(build)
766
767 for chroot in chroots:
768
769 git_hash = None
770 if git_hashes:
771 git_hash = git_hashes.get(chroot.name)
772 buildchroot = models.BuildChroot(
773 build=build,
774 status=chroot_status,
775 mock_chroot=chroot,
776 git_hash=git_hash,
777 )
778 db.session.add(buildchroot)
779
780 return build
781
782 @classmethod
783 - def rebuild_package(cls, package, source_dict_update={}, copr_dir=None, update_callback=None,
784 scm_object_type=None, scm_object_id=None,
785 scm_object_url=None, submitted_by=None):
786
787 source_dict = package.source_json_dict
788 source_dict.update(source_dict_update)
789 source_json = json.dumps(source_dict)
790
791 if not copr_dir:
792 copr_dir = package.copr.main_dir
793
794 build = models.Build(
795 user=None,
796 pkgs=None,
797 package=package,
798 copr=package.copr,
799 repos=package.copr.repos,
800 source_status=StatusEnum("pending"),
801 source_type=package.source_type,
802 source_json=source_json,
803 submitted_on=int(time.time()),
804 enable_net=package.copr.build_enable_net,
805 timeout=DEFAULT_BUILD_TIMEOUT,
806 copr_dir=copr_dir,
807 update_callback=update_callback,
808 scm_object_type=scm_object_type,
809 scm_object_id=scm_object_id,
810 scm_object_url=scm_object_url,
811 submitted_by=submitted_by,
812 )
813 db.session.add(build)
814
815 status = StatusEnum("waiting")
816 for chroot in package.chroots:
817 buildchroot = models.BuildChroot(
818 build=build,
819 status=status,
820 mock_chroot=chroot,
821 git_hash=None
822 )
823 db.session.add(buildchroot)
824
825 cls.process_update_callback(build)
826 return build
827
828
829 terminal_states = {StatusEnum("failed"), StatusEnum("succeeded"), StatusEnum("canceled")}
830
831 @classmethod
843
844
845 @classmethod
847 """
848 Deletes the locally stored data for build purposes. This is typically
849 uploaded srpm file, uploaded spec file or webhook POST content.
850 """
851
852 data = json.loads(build.source_json)
853 if 'tmp' in data:
854 tmp = data["tmp"]
855 storage_path = app.config["STORAGE_DIR"]
856 try:
857 shutil.rmtree(os.path.join(storage_path, tmp))
858 except:
859 pass
860
861
862 @classmethod
864 """
865 :param build:
866 :param upd_dict:
867 example:
868 {
869 "builds":[
870 {
871 "id": 1,
872 "copr_id": 2,
873 "started_on": 1390866440
874 },
875 {
876 "id": 2,
877 "copr_id": 1,
878 "status": 0,
879 "chroot": "fedora-18-x86_64",
880 "result_dir": "baz",
881 "ended_on": 1390866440
882 }]
883 }
884 """
885 log.info("Updating build {} by: {}".format(build.id, upd_dict))
886
887
888 pkg_name = upd_dict.get('pkg_name', None)
889 if pkg_name:
890 if not PackagesLogic.get(build.copr_dir.id, pkg_name).first():
891 try:
892 package = PackagesLogic.add(
893 build.copr.user, build.copr_dir,
894 pkg_name, build.source_type, build.source_json)
895 db.session.add(package)
896 db.session.commit()
897 except (IntegrityError, DuplicateException) as e:
898 app.logger.exception(e)
899 db.session.rollback()
900 return
901 build.package = PackagesLogic.get(build.copr_dir.id, pkg_name).first()
902
903 for attr in ["built_packages", "srpm_url", "pkg_version"]:
904 value = upd_dict.get(attr, None)
905 if value:
906 setattr(build, attr, value)
907
908
909 if str(upd_dict.get("task_id")) == str(build.task_id):
910 build.result_dir = upd_dict.get("result_dir", "")
911
912 new_status = upd_dict.get("status")
913 if new_status == StatusEnum("succeeded"):
914 new_status = StatusEnum("importing")
915 chroot_status=StatusEnum("waiting")
916 if not build.build_chroots:
917
918
919 for chroot in build.package.chroots:
920 buildchroot = models.BuildChroot(
921 build=build,
922 status=chroot_status,
923 mock_chroot=chroot,
924 git_hash=None,
925 )
926 db.session.add(buildchroot)
927 else:
928 for buildchroot in build.build_chroots:
929 buildchroot.status = chroot_status
930 db.session.add(buildchroot)
931
932 build.source_status = new_status
933 if new_status == StatusEnum("failed") or \
934 new_status == StatusEnum("skipped"):
935 for ch in build.build_chroots:
936 ch.status = new_status
937 ch.ended_on = upd_dict.get("ended_on") or time.time()
938 ch.started_on = upd_dict.get("started_on", ch.ended_on)
939 db.session.add(ch)
940
941 if new_status == StatusEnum("failed"):
942 build.fail_type = FailTypeEnum("srpm_build_error")
943
944 cls.process_update_callback(build)
945 db.session.add(build)
946 return
947
948 if "chroot" in upd_dict:
949
950 for build_chroot in build.build_chroots:
951 if build_chroot.name == upd_dict["chroot"]:
952 build_chroot.result_dir = upd_dict.get("result_dir", "")
953
954 if "status" in upd_dict and build_chroot.status not in BuildsLogic.terminal_states:
955 build_chroot.status = upd_dict["status"]
956
957 if upd_dict.get("status") in BuildsLogic.terminal_states:
958 build_chroot.ended_on = upd_dict.get("ended_on") or time.time()
959
960 if upd_dict.get("status") == StatusEnum("starting"):
961 build_chroot.started_on = upd_dict.get("started_on") or time.time()
962
963 db.session.add(build_chroot)
964
965
966
967 if (build.module
968 and upd_dict.get("status") == StatusEnum("succeeded")
969 and all(b.status == StatusEnum("succeeded") for b in build.module.builds)):
970 ActionsLogic.send_build_module(build.copr, build.module)
971
972 cls.process_update_callback(build)
973 db.session.add(build)
974
975 @classmethod
990
991 @classmethod
993 headers = {
994 'Authorization': 'token {}'.format(build.copr.scm_api_auth.get('api_key'))
995 }
996
997 if build.srpm_url:
998 progress = 50
999 else:
1000 progress = 10
1001
1002 state_table = {
1003 'failed': ('failure', 0),
1004 'succeeded': ('success', 100),
1005 'canceled': ('canceled', 0),
1006 'running': ('pending', progress),
1007 'pending': ('pending', progress),
1008 'skipped': ('error', 0),
1009 'starting': ('pending', progress),
1010 'importing': ('pending', progress),
1011 'forked': ('error', 0),
1012 'waiting': ('pending', progress),
1013 'unknown': ('error', 0),
1014 }
1015
1016 build_url = os.path.join(
1017 app.config['PUBLIC_COPR_BASE_URL'],
1018 'coprs', build.copr.full_name.replace('@', 'g/'),
1019 'build', str(build.id)
1020 )
1021
1022 data = {
1023 'username': 'Copr build',
1024 'comment': '#{}'.format(build.id),
1025 'url': build_url,
1026 'status': state_table[build.state][0],
1027 'percent': state_table[build.state][1],
1028 'uid': str(build.id),
1029 }
1030
1031 log.debug('Sending data to Pagure API: %s', pprint.pformat(data))
1032 response = requests.post(api_url, data=data, headers=headers)
1033 log.debug('Pagure API response: %s', response.text)
1034
1035 @classmethod
1058
1059 @classmethod
1073
1074 @classmethod
1075 - def delete_build(cls, user, build, send_delete_action=True):
1086
1087 @classmethod
1106
1107 @classmethod
1120
1121 @classmethod
1140
1141 @classmethod
1149
1150 @classmethod
1153
1154 @classmethod
1157
1158 @classmethod
1160 dirs = (
1161 db.session.query(
1162 models.CoprDir.id,
1163 models.Package.id,
1164 models.Package.max_builds)
1165 .join(models.Build, models.Build.copr_dir_id==models.CoprDir.id)
1166 .join(models.Package)
1167 .filter(models.Package.max_builds > 0)
1168 .group_by(
1169 models.CoprDir.id,
1170 models.Package.max_builds,
1171 models.Package.id)
1172 .having(func.count(models.Build.id) > models.Package.max_builds)
1173 )
1174
1175 for dir_id, package_id, limit in dirs.all():
1176 delete_builds = (
1177 models.Build.query.filter(
1178 models.Build.copr_dir_id==dir_id,
1179 models.Build.package_id==package_id)
1180 .order_by(desc(models.Build.id))
1181 .offset(limit)
1182 .all()
1183 )
1184
1185 for build in delete_builds:
1186 try:
1187 cls.delete_build(build.copr.user, build)
1188 except ActionInProgressException:
1189
1190 log.error("Build(id={}) delete failed, unfinished action.".format(build.id))
1191
1192 @classmethod
1208
1211 @classmethod
1220
1221 @classmethod
1232
1233 @classmethod
1236
1237 @classmethod
1240
1241 @classmethod
1244
1245 @classmethod
1248
1249 @classmethod
1252
1255 @classmethod
1257 query = """
1258 SELECT
1259 package.id as package_id,
1260 package.name AS package_name,
1261 build.id AS build_id,
1262 build_chroot.status AS build_chroot_status,
1263 build.pkg_version AS build_pkg_version,
1264 mock_chroot.id AS mock_chroot_id,
1265 mock_chroot.os_release AS mock_chroot_os_release,
1266 mock_chroot.os_version AS mock_chroot_os_version,
1267 mock_chroot.arch AS mock_chroot_arch
1268 FROM package
1269 JOIN (SELECT
1270 MAX(build.id) AS max_build_id_for_chroot,
1271 build.package_id AS package_id,
1272 build_chroot.mock_chroot_id AS mock_chroot_id
1273 FROM build
1274 JOIN build_chroot
1275 ON build.id = build_chroot.build_id
1276 WHERE build.copr_id = {copr_id}
1277 AND build_chroot.status != 2
1278 GROUP BY build.package_id,
1279 build_chroot.mock_chroot_id) AS max_build_ids_for_a_chroot
1280 ON package.id = max_build_ids_for_a_chroot.package_id
1281 JOIN build
1282 ON build.id = max_build_ids_for_a_chroot.max_build_id_for_chroot
1283 JOIN build_chroot
1284 ON build_chroot.mock_chroot_id = max_build_ids_for_a_chroot.mock_chroot_id
1285 AND build_chroot.build_id = max_build_ids_for_a_chroot.max_build_id_for_chroot
1286 JOIN mock_chroot
1287 ON mock_chroot.id = max_build_ids_for_a_chroot.mock_chroot_id
1288 JOIN copr_dir ON build.copr_dir_id=copr_dir.id WHERE copr_dir.main IS TRUE
1289 ORDER BY package.name ASC, package.id ASC, mock_chroot.os_release ASC, mock_chroot.os_version ASC, mock_chroot.arch ASC
1290 """.format(copr_id=copr.id)
1291 rows = db.session.execute(query)
1292 return rows
1293