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 import or_
11 from sqlalchemy import and_
12 from sqlalchemy import func, desc
13 from sqlalchemy.sql import false,true
14 from werkzeug.utils import secure_filename
15 from sqlalchemy import bindparam, Integer, String
16 from sqlalchemy.exc import IntegrityError
17
18 from copr_common.enums import FailTypeEnum, StatusEnum
19 from coprs import app
20 from coprs import db
21 from coprs import models
22 from coprs import helpers
23 from coprs.constants import DEFAULT_BUILD_TIMEOUT, MAX_BUILD_TIMEOUT
24 from coprs.exceptions import MalformedArgumentException, ActionInProgressException, InsufficientRightsException, \
25 UnrepeatableBuildException, RequestCannotBeExecuted, DuplicateException
26
27 from coprs.logic import coprs_logic
28 from coprs.logic import users_logic
29 from coprs.logic.actions_logic import ActionsLogic
30 from coprs.models import BuildChroot
31 from .coprs_logic import MockChrootsLogic
32 from coprs.logic.packages_logic import PackagesLogic
33
34 log = app.logger
38 @classmethod
39 - def get(cls, build_id):
41
42 @classmethod
53
54 @classmethod
65
66 @classmethod
85
86 @classmethod
94
95 @classmethod
116
117 @classmethod
119 query = text("""
120 SELECT COUNT(*) as result
121 FROM build_chroot JOIN build on build.id = build_chroot.build_id
122 WHERE
123 build.submitted_on < :end
124 AND (
125 build_chroot.started_on > :start
126 OR (build_chroot.started_on is NULL AND build_chroot.status = :status)
127 -- for currently pending builds we need to filter on status=pending because there might be
128 -- failed builds that have started_on=NULL
129 )
130 AND NOT build.canceled
131 """)
132
133 res = db.engine.execute(query, start=start, end=end, status=StatusEnum("pending"))
134 return res.first().result
135
136 @classmethod
138 query = text("""
139 SELECT COUNT(*) as result
140 FROM build_chroot
141 WHERE
142 started_on < :end
143 AND (ended_on > :start OR (ended_on is NULL AND status = :status))
144 -- for currently running builds we need to filter on status=running because there might be failed
145 -- builds that have ended_on=NULL
146 """)
147
148 res = db.engine.execute(query, start=start, end=end, status=StatusEnum("running"))
149 return res.first().result
150
151 @classmethod
168
169 @classmethod
171 data = [["pending"], ["running"], ["avg running"], ["time"]]
172 params = cls.get_graph_parameters(type)
173 cached_data = cls.get_cached_graph_data(params)
174 data[0].extend(cached_data["pending"])
175 data[1].extend(cached_data["running"])
176
177 for i in range(len(data[0]) - 1, params["steps"]):
178 step_start = params["start"] + i * params["step"]
179 step_end = step_start + params["step"]
180 pending = cls.get_pending_jobs_bucket(step_start, step_end)
181 running = cls.get_running_jobs_bucket(step_start, step_end)
182 data[0].append(pending)
183 data[1].append(running)
184 cls.cache_graph_data(type, time=step_start, pending=pending, running=running)
185
186 running_total = 0
187 for i in range(1, params["steps"] + 1):
188 running_total += data[1][i]
189
190 data[2].extend([running_total * 1.0 / params["steps"]] * (len(data[0]) - 1))
191
192 for i in range(params["start"], params["end"], params["step"]):
193 data[3].append(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(i)))
194
195 return data
196
197 @classmethod
212
213 @classmethod
232
233 @classmethod
235 if type is "10min":
236
237 step = 600
238 steps = 144
239 elif type is "30min":
240
241 step = 1800
242 steps = 48
243 elif type is "24h":
244
245 step = 86400
246 steps = 90
247
248 end = int(time.time())
249 end = end - (end % step)
250 start = end - (steps * step)
251
252 return {
253 "type": type,
254 "step": step,
255 "steps": steps,
256 "start": start,
257 "end": end,
258 }
259
260 @classmethod
272
273 @classmethod
282
283 @classmethod
299
300 @classmethod
309
310 @classmethod
313
314 @classmethod
317
318 @classmethod
323
324 @classmethod
331
332 @classmethod
334 if db.engine.url.drivername == "sqlite":
335 return
336
337 status_to_order = """
338 CREATE OR REPLACE FUNCTION status_to_order (x integer)
339 RETURNS integer AS $$ BEGIN
340 RETURN CASE WHEN x = 3 THEN 1
341 WHEN x = 6 THEN 2
342 WHEN x = 7 THEN 3
343 WHEN x = 4 THEN 4
344 WHEN x = 0 THEN 5
345 WHEN x = 1 THEN 6
346 WHEN x = 5 THEN 7
347 WHEN x = 2 THEN 8
348 WHEN x = 8 THEN 9
349 WHEN x = 9 THEN 10
350 ELSE x
351 END; END;
352 $$ LANGUAGE plpgsql;
353 """
354
355 order_to_status = """
356 CREATE OR REPLACE FUNCTION order_to_status (x integer)
357 RETURNS integer AS $$ BEGIN
358 RETURN CASE WHEN x = 1 THEN 3
359 WHEN x = 2 THEN 6
360 WHEN x = 3 THEN 7
361 WHEN x = 4 THEN 4
362 WHEN x = 5 THEN 0
363 WHEN x = 6 THEN 1
364 WHEN x = 7 THEN 5
365 WHEN x = 8 THEN 2
366 WHEN x = 9 THEN 8
367 WHEN x = 10 THEN 9
368 ELSE x
369 END; END;
370 $$ LANGUAGE plpgsql;
371 """
372
373 db.engine.connect()
374 db.engine.execute(status_to_order)
375 db.engine.execute(order_to_status)
376
377 @classmethod
379 query_select = """
380 SELECT build.id, build.source_status, MAX(package.name) AS pkg_name, build.pkg_version, build.submitted_on,
381 MIN(statuses.started_on) AS started_on, MAX(statuses.ended_on) AS ended_on, order_to_status(MIN(statuses.st)) AS status,
382 build.canceled, MIN("group".name) AS group_name, MIN(copr.name) as copr_name, MIN("user".username) as user_name, build.copr_id
383 FROM build
384 LEFT OUTER JOIN package
385 ON build.package_id = package.id
386 LEFT OUTER JOIN (SELECT build_chroot.build_id, started_on, ended_on, status_to_order(status) AS st FROM build_chroot) AS statuses
387 ON statuses.build_id=build.id
388 LEFT OUTER JOIN copr
389 ON copr.id = build.copr_id
390 LEFT OUTER JOIN copr_dir
391 ON build.copr_dir_id = copr_dir.id
392 LEFT OUTER JOIN "user"
393 ON copr.user_id = "user".id
394 LEFT OUTER JOIN "group"
395 ON copr.group_id = "group".id
396 WHERE build.copr_id = :copr_id
397 AND (:dirname = '' OR :dirname = copr_dir.name)
398 GROUP BY
399 build.id
400 ORDER BY
401 build.id DESC;
402 """
403
404 if db.engine.url.drivername == "sqlite":
405 def sqlite_status_to_order(x):
406 if x == 3:
407 return 1
408 elif x == 6:
409 return 2
410 elif x == 7:
411 return 3
412 elif x == 4:
413 return 4
414 elif x == 0:
415 return 5
416 elif x == 1:
417 return 6
418 elif x == 5:
419 return 7
420 elif x == 2:
421 return 8
422 elif x == 8:
423 return 9
424 elif x == 9:
425 return 10
426 return 1000
427
428 def sqlite_order_to_status(x):
429 if x == 1:
430 return 3
431 elif x == 2:
432 return 6
433 elif x == 3:
434 return 7
435 elif x == 4:
436 return 4
437 elif x == 5:
438 return 0
439 elif x == 6:
440 return 1
441 elif x == 7:
442 return 5
443 elif x == 8:
444 return 2
445 elif x == 9:
446 return 8
447 elif x == 10:
448 return 9
449 return 1000
450
451 conn = db.engine.connect()
452 conn.connection.create_function("status_to_order", 1, sqlite_status_to_order)
453 conn.connection.create_function("order_to_status", 1, sqlite_order_to_status)
454 statement = text(query_select)
455 statement.bindparams(bindparam("copr_id", Integer))
456 statement.bindparams(bindparam("dirname", String))
457 result = conn.execute(statement, {"copr_id": copr.id, "dirname": dirname})
458 else:
459 statement = text(query_select)
460 statement.bindparams(bindparam("copr_id", Integer))
461 statement.bindparams(bindparam("dirname", String))
462 result = db.engine.execute(statement, {"copr_id": copr.id, "dirname": dirname})
463
464 return result
465
466 @classmethod
469
470 @classmethod
478
479 @classmethod
482
483 @classmethod
486
487 @classmethod
490 skip_import = False
491 git_hashes = {}
492
493 if source_build.source_type == helpers.BuildSourceEnum('upload'):
494 if source_build.repeatable:
495 skip_import = True
496 for chroot in source_build.build_chroots:
497 git_hashes[chroot.name] = chroot.git_hash
498 else:
499 raise UnrepeatableBuildException("Build sources were not fully imported into CoprDistGit.")
500
501 build = cls.create_new(user, copr, source_build.source_type, source_build.source_json, chroot_names,
502 pkgs=source_build.pkgs, git_hashes=git_hashes, skip_import=skip_import,
503 srpm_url=source_build.srpm_url, copr_dirname=source_build.copr_dir.name, **build_options)
504 build.package_id = source_build.package_id
505 build.pkg_version = source_build.pkg_version
506 return build
507
508 @classmethod
509 - def create_new_from_url(cls, user, copr, url, chroot_names=None,
510 copr_dirname=None, **build_options):
524
525 @classmethod
526 - def create_new_from_scm(cls, user, copr, scm_type, clone_url,
527 committish='', subdirectory='', spec='', srpm_build_method='rpkg',
528 chroot_names=None, copr_dirname=None, **build_options):
529 """
530 :type user: models.User
531 :type copr: models.Copr
532
533 :type chroot_names: List[str]
534
535 :rtype: models.Build
536 """
537 source_type = helpers.BuildSourceEnum("scm")
538 source_json = json.dumps({"type": scm_type,
539 "clone_url": clone_url,
540 "committish": committish,
541 "subdirectory": subdirectory,
542 "spec": spec,
543 "srpm_build_method": srpm_build_method})
544 return cls.create_new(user, copr, source_type, source_json, chroot_names, copr_dirname=copr_dirname, **build_options)
545
546 @classmethod
547 - def create_new_from_pypi(cls, user, copr, pypi_package_name, pypi_package_version, spec_template,
548 python_versions, chroot_names=None, copr_dirname=None, **build_options):
566
567 @classmethod
580
581 @classmethod
582 - def create_new_from_custom(cls, user, copr, script, script_chroot=None, script_builddeps=None,
583 script_resultdir=None, chroot_names=None, copr_dirname=None, **kwargs):
584 """
585 :type user: models.User
586 :type copr: models.Copr
587 :type script: str
588 :type script_chroot: str
589 :type script_builddeps: str
590 :type script_resultdir: str
591 :type chroot_names: List[str]
592 :rtype: models.Build
593 """
594 source_type = helpers.BuildSourceEnum("custom")
595 source_dict = {
596 'script': script,
597 'chroot': script_chroot,
598 'builddeps': script_builddeps,
599 'resultdir': script_resultdir,
600 }
601
602 return cls.create_new(user, copr, source_type, json.dumps(source_dict),
603 chroot_names, copr_dirname=copr_dirname, **kwargs)
604
605 @classmethod
606 - def create_new_from_upload(cls, user, copr, f_uploader, orig_filename,
607 chroot_names=None, copr_dirname=None, **build_options):
608 """
609 :type user: models.User
610 :type copr: models.Copr
611 :param f_uploader(file_path): function which stores data at the given `file_path`
612 :return:
613 """
614 tmp = tempfile.mkdtemp(dir=app.config["STORAGE_DIR"])
615 tmp_name = os.path.basename(tmp)
616 filename = secure_filename(orig_filename)
617 file_path = os.path.join(tmp, filename)
618 f_uploader(file_path)
619
620
621 pkg_url = "{baseurl}/tmp/{tmp_dir}/{filename}".format(
622 baseurl=app.config["PUBLIC_COPR_BASE_URL"],
623 tmp_dir=tmp_name,
624 filename=filename)
625
626
627 source_type = helpers.BuildSourceEnum("upload")
628 source_json = json.dumps({"url": pkg_url, "pkg": filename, "tmp": tmp_name})
629 srpm_url = None if pkg_url.endswith('.spec') else pkg_url
630
631 try:
632 build = cls.create_new(user, copr, source_type, source_json,
633 chroot_names, pkgs=pkg_url, srpm_url=srpm_url,
634 copr_dirname=copr_dirname, **build_options)
635 except Exception:
636 shutil.rmtree(tmp)
637 raise
638
639 return build
640
641 @classmethod
642 - def create_new(cls, user, copr, source_type, source_json, chroot_names=None, pkgs="",
643 git_hashes=None, skip_import=False, background=False, batch=None,
644 srpm_url=None, copr_dirname=None, **build_options):
645 """
646 :type user: models.User
647 :type copr: models.Copr
648 :type chroot_names: List[str]
649 :type source_type: int value from helpers.BuildSourceEnum
650 :type source_json: str in json format
651 :type pkgs: str
652 :type git_hashes: dict
653 :type skip_import: bool
654 :type background: bool
655 :type batch: models.Batch
656 :rtype: models.Build
657 """
658 chroots = None
659 if chroot_names:
660 chroots = []
661 for chroot in copr.active_chroots:
662 if chroot.name in chroot_names:
663 chroots.append(chroot)
664
665 build = cls.add(
666 user=user,
667 pkgs=pkgs,
668 copr=copr,
669 chroots=chroots,
670 source_type=source_type,
671 source_json=source_json,
672 enable_net=build_options.get("enable_net", copr.build_enable_net),
673 background=background,
674 git_hashes=git_hashes,
675 skip_import=skip_import,
676 batch=batch,
677 srpm_url=srpm_url,
678 copr_dirname=copr_dirname,
679 )
680
681 if user.proven:
682 if "timeout" in build_options:
683 build.timeout = build_options["timeout"]
684
685 return build
686
687 @classmethod
688 - def add(cls, user, pkgs, copr, source_type=None, source_json=None,
689 repos=None, chroots=None, timeout=None, enable_net=True,
690 git_hashes=None, skip_import=False, background=False, batch=None,
691 srpm_url=None, copr_dirname=None):
692
693 if chroots is None:
694 chroots = []
695
696 coprs_logic.CoprsLogic.raise_if_unfinished_blocking_action(
697 copr, "Can't build while there is an operation in progress: {action}")
698 users_logic.UsersLogic.raise_if_cant_build_in_copr(
699 user, copr,
700 "You don't have permissions to build in this copr.")
701
702 if not repos:
703 repos = copr.repos
704
705
706 if pkgs and (" " in pkgs or "\n" in pkgs or "\t" in pkgs or pkgs.strip() != pkgs):
707 raise MalformedArgumentException("Trying to create a build using src_pkg "
708 "with bad characters. Forgot to split?")
709
710
711 if not source_type or not source_json:
712 source_type = helpers.BuildSourceEnum("link")
713 source_json = json.dumps({"url":pkgs})
714
715 if skip_import and srpm_url:
716 chroot_status = StatusEnum("pending")
717 source_status = StatusEnum("succeeded")
718 else:
719 chroot_status = StatusEnum("waiting")
720 source_status = StatusEnum("pending")
721
722 copr_dir = None
723 if copr_dirname:
724 if not copr_dirname.startswith(copr.name+':') and copr_dirname != copr.name:
725 raise MalformedArgumentException("Copr dirname not starting with copr name.")
726 copr_dir = coprs_logic.CoprDirsLogic.get_or_create(copr, copr_dirname)
727
728 build = models.Build(
729 user=user,
730 pkgs=pkgs,
731 copr=copr,
732 repos=repos,
733 source_type=source_type,
734 source_json=source_json,
735 source_status=source_status,
736 submitted_on=int(time.time()),
737 enable_net=bool(enable_net),
738 is_background=bool(background),
739 batch=batch,
740 srpm_url=srpm_url,
741 copr_dir=copr_dir,
742 )
743
744 if timeout:
745 build.timeout = timeout or DEFAULT_BUILD_TIMEOUT
746
747 db.session.add(build)
748
749 for chroot in chroots:
750
751 git_hash = None
752 if git_hashes:
753 git_hash = git_hashes.get(chroot.name)
754 buildchroot = models.BuildChroot(
755 build=build,
756 status=chroot_status,
757 mock_chroot=chroot,
758 git_hash=git_hash,
759 )
760 db.session.add(buildchroot)
761
762 return build
763
764 @classmethod
765 - def rebuild_package(cls, package, source_dict_update={}, copr_dir=None, update_callback=None,
766 scm_object_type=None, scm_object_id=None,
767 scm_object_url=None, submitted_by=None):
768
769 source_dict = package.source_json_dict
770 source_dict.update(source_dict_update)
771 source_json = json.dumps(source_dict)
772
773 if not copr_dir:
774 copr_dir = package.copr.main_dir
775
776 build = models.Build(
777 user=None,
778 pkgs=None,
779 package=package,
780 copr=package.copr,
781 repos=package.copr.repos,
782 source_status=StatusEnum("pending"),
783 source_type=package.source_type,
784 source_json=source_json,
785 submitted_on=int(time.time()),
786 enable_net=package.copr.build_enable_net,
787 timeout=DEFAULT_BUILD_TIMEOUT,
788 copr_dir=copr_dir,
789 update_callback=update_callback,
790 scm_object_type=scm_object_type,
791 scm_object_id=scm_object_id,
792 scm_object_url=scm_object_url,
793 submitted_by=submitted_by,
794 )
795 db.session.add(build)
796
797 status = StatusEnum("waiting")
798 for chroot in package.chroots:
799 buildchroot = models.BuildChroot(
800 build=build,
801 status=status,
802 mock_chroot=chroot,
803 git_hash=None
804 )
805 db.session.add(buildchroot)
806
807 cls.process_update_callback(build)
808 return build
809
810
811 terminal_states = {StatusEnum("failed"), StatusEnum("succeeded"), StatusEnum("canceled")}
812
813 @classmethod
825
826
827 @classmethod
829 """
830 Deletes the locally stored data for build purposes. This is typically
831 uploaded srpm file, uploaded spec file or webhook POST content.
832 """
833
834 data = json.loads(build.source_json)
835 if 'tmp' in data:
836 tmp = data["tmp"]
837 storage_path = app.config["STORAGE_DIR"]
838 try:
839 shutil.rmtree(os.path.join(storage_path, tmp))
840 except:
841 pass
842
843
844 @classmethod
846 """
847 :param build:
848 :param upd_dict:
849 example:
850 {
851 "builds":[
852 {
853 "id": 1,
854 "copr_id": 2,
855 "started_on": 1390866440
856 },
857 {
858 "id": 2,
859 "copr_id": 1,
860 "status": 0,
861 "chroot": "fedora-18-x86_64",
862 "result_dir": "baz",
863 "ended_on": 1390866440
864 }]
865 }
866 """
867 log.info("Updating build {} by: {}".format(build.id, upd_dict))
868
869
870 pkg_name = upd_dict.get('pkg_name', None)
871 if pkg_name:
872 if not PackagesLogic.get(build.copr_dir.id, pkg_name).first():
873 try:
874 package = PackagesLogic.add(
875 build.copr.user, build.copr_dir,
876 pkg_name, build.source_type, build.source_json)
877 db.session.add(package)
878 db.session.commit()
879 except (IntegrityError, DuplicateException) as e:
880 app.logger.exception(e)
881 db.session.rollback()
882 return
883 build.package = PackagesLogic.get(build.copr_dir.id, pkg_name).first()
884
885 for attr in ["built_packages", "srpm_url", "pkg_version"]:
886 value = upd_dict.get(attr, None)
887 if value:
888 setattr(build, attr, value)
889
890
891 if str(upd_dict.get("task_id")) == str(build.task_id):
892 build.result_dir = upd_dict.get("result_dir", "")
893
894 new_status = upd_dict.get("status")
895 if new_status == StatusEnum("succeeded"):
896 new_status = StatusEnum("importing")
897 chroot_status=StatusEnum("waiting")
898 if not build.build_chroots:
899
900
901 for chroot in build.package.chroots:
902 buildchroot = models.BuildChroot(
903 build=build,
904 status=chroot_status,
905 mock_chroot=chroot,
906 git_hash=None,
907 )
908 db.session.add(buildchroot)
909 else:
910 for buildchroot in build.build_chroots:
911 buildchroot.status = chroot_status
912 db.session.add(buildchroot)
913
914 build.source_status = new_status
915 if new_status == StatusEnum("failed") or \
916 new_status == StatusEnum("skipped"):
917 for ch in build.build_chroots:
918 ch.status = new_status
919 ch.ended_on = upd_dict.get("ended_on") or time.time()
920 db.session.add(ch)
921
922 if new_status == StatusEnum("failed"):
923 build.fail_type = FailTypeEnum("srpm_build_error")
924
925 cls.process_update_callback(build)
926 db.session.add(build)
927 return
928
929 if "chroot" in upd_dict:
930
931 for build_chroot in build.build_chroots:
932 if build_chroot.name == upd_dict["chroot"]:
933 build_chroot.result_dir = upd_dict.get("result_dir", "")
934
935 if "status" in upd_dict and build_chroot.status not in BuildsLogic.terminal_states:
936 build_chroot.status = upd_dict["status"]
937
938 if upd_dict.get("status") in BuildsLogic.terminal_states:
939 build_chroot.ended_on = upd_dict.get("ended_on") or time.time()
940
941 if upd_dict.get("status") == StatusEnum("starting"):
942 build_chroot.started_on = upd_dict.get("started_on") or time.time()
943
944 db.session.add(build_chroot)
945
946
947
948 if (build.module
949 and upd_dict.get("status") == StatusEnum("succeeded")
950 and all(b.status == StatusEnum("succeeded") for b in build.module.builds)):
951 ActionsLogic.send_build_module(build.copr, build.module)
952
953 cls.process_update_callback(build)
954 db.session.add(build)
955
956 @classmethod
971
972 @classmethod
974 headers = {
975 'Authorization': 'token {}'.format(build.copr.scm_api_auth.get('api_key'))
976 }
977
978 if build.srpm_url:
979 progress = 50
980 else:
981 progress = 10
982
983 state_table = {
984 'failed': ('failure', 0),
985 'succeeded': ('success', 100),
986 'canceled': ('canceled', 0),
987 'running': ('pending', progress),
988 'pending': ('pending', progress),
989 'skipped': ('error', 0),
990 'starting': ('pending', progress),
991 'importing': ('pending', progress),
992 'forked': ('error', 0),
993 'waiting': ('pending', progress),
994 'unknown': ('error', 0),
995 }
996
997 build_url = os.path.join(
998 app.config['PUBLIC_COPR_BASE_URL'],
999 'coprs', build.copr.full_name.replace('@', 'g/'),
1000 'build', str(build.id)
1001 )
1002
1003 data = {
1004 'username': 'Copr build',
1005 'comment': '#{}'.format(build.id),
1006 'url': build_url,
1007 'status': state_table[build.state][0],
1008 'percent': state_table[build.state][1],
1009 'uid': str(build.id),
1010 }
1011
1012 log.debug('Sending data to Pagure API: %s', pprint.pformat(data))
1013 response = requests.post(api_url, data=data, headers=headers)
1014 log.debug('Pagure API response: %s', response.text)
1015
1016 @classmethod
1039
1040 @classmethod
1041 - def delete_build(cls, user, build, send_delete_action=True):
1062
1063 @classmethod
1076
1077 @classmethod
1096
1097 @classmethod
1105
1106 @classmethod
1109
1110 @classmethod
1113
1114 @classmethod
1116 dirs = (
1117 db.session.query(
1118 models.CoprDir.id,
1119 models.Package.id,
1120 models.Package.max_builds)
1121 .join(models.Build, models.Build.copr_dir_id==models.CoprDir.id)
1122 .join(models.Package)
1123 .filter(models.Package.max_builds > 0)
1124 .group_by(
1125 models.CoprDir.id,
1126 models.Package.max_builds,
1127 models.Package.id)
1128 .having(func.count(models.Build.id) > models.Package.max_builds)
1129 )
1130
1131 for dir_id, package_id, limit in dirs.all():
1132 delete_builds = (
1133 models.Build.query.filter(
1134 models.Build.copr_dir_id==dir_id,
1135 models.Build.package_id==package_id)
1136 .order_by(desc(models.Build.id))
1137 .offset(limit)
1138 .all()
1139 )
1140
1141 for build in delete_builds:
1142 try:
1143 cls.delete_build(build.copr.user, build)
1144 except ActionInProgressException:
1145
1146 log.error("Build(id={}) delete failed, unfinished action.".format(build.id))
1147
1148 @classmethod
1164
1167 @classmethod
1176
1177 @classmethod
1188
1189 @classmethod
1192
1193 @classmethod
1196
1197 @classmethod
1200
1201 @classmethod
1204
1205 @classmethod
1208
1211 @classmethod
1213 query = """
1214 SELECT
1215 package.id as package_id,
1216 package.name AS package_name,
1217 build.id AS build_id,
1218 build_chroot.status AS build_chroot_status,
1219 build.pkg_version AS build_pkg_version,
1220 mock_chroot.id AS mock_chroot_id,
1221 mock_chroot.os_release AS mock_chroot_os_release,
1222 mock_chroot.os_version AS mock_chroot_os_version,
1223 mock_chroot.arch AS mock_chroot_arch
1224 FROM package
1225 JOIN (SELECT
1226 MAX(build.id) AS max_build_id_for_chroot,
1227 build.package_id AS package_id,
1228 build_chroot.mock_chroot_id AS mock_chroot_id
1229 FROM build
1230 JOIN build_chroot
1231 ON build.id = build_chroot.build_id
1232 WHERE build.copr_id = {copr_id}
1233 AND build_chroot.status != 2
1234 GROUP BY build.package_id,
1235 build_chroot.mock_chroot_id) AS max_build_ids_for_a_chroot
1236 ON package.id = max_build_ids_for_a_chroot.package_id
1237 JOIN build
1238 ON build.id = max_build_ids_for_a_chroot.max_build_id_for_chroot
1239 JOIN build_chroot
1240 ON build_chroot.mock_chroot_id = max_build_ids_for_a_chroot.mock_chroot_id
1241 AND build_chroot.build_id = max_build_ids_for_a_chroot.max_build_id_for_chroot
1242 JOIN mock_chroot
1243 ON mock_chroot.id = max_build_ids_for_a_chroot.mock_chroot_id
1244 ORDER BY package.name ASC, package.id ASC, mock_chroot.os_release ASC, mock_chroot.os_version ASC, mock_chroot.arch ASC
1245 """.format(copr_id=copr.id)
1246 rows = db.session.execute(query)
1247 return rows
1248