1 import base64
2 import datetime
3 from functools import wraps
4 import os
5 import flask
6 import sqlalchemy
7 import json
8 import requests
9 from requests.exceptions import RequestException, InvalidSchema
10 from wtforms import ValidationError
11
12 from werkzeug import secure_filename
13
14 from coprs import db
15 from coprs import exceptions
16 from coprs import forms
17 from coprs import helpers
18 from coprs import models
19 from coprs.helpers import fix_protocol_for_backend, generate_build_config
20 from coprs.logic.api_logic import MonitorWrapper
21 from coprs.logic.builds_logic import BuildsLogic
22 from coprs.logic.complex_logic import ComplexLogic
23 from coprs.logic.users_logic import UsersLogic
24 from coprs.logic.packages_logic import PackagesLogic
25 from coprs.logic.modules_logic import ModulesLogic, ModuleProvider, ModuleBuildFacade
26
27 from coprs.views.misc import login_required, api_login_required
28
29 from coprs.views.api_ns import api_ns
30
31 from coprs.logic import builds_logic
32 from coprs.logic import coprs_logic
33 from coprs.logic.coprs_logic import CoprsLogic
34 from coprs.logic.actions_logic import ActionsLogic
35
36 from coprs.exceptions import (ActionInProgressException,
37 InsufficientRightsException,
38 DuplicateException,
39 LegacyApiError,
40 NoPackageSourceException,
41 UnknownSourceTypeException)
54 return wrapper
55
59 """
60 Render the home page of the api.
61 This page provides information on how to call/use the API.
62 """
63
64 return flask.render_template("api.html")
65
66
67 @api_ns.route("/new/", methods=["GET", "POST"])
68 @login_required
69 -def api_new_token():
88
91 infos = []
92
93
94 proxyuser_keys = ["username"]
95 allowed = list(form.__dict__.keys()) + proxyuser_keys
96 for post_key in flask.request.form.keys():
97 if post_key not in allowed:
98 infos.append("Unknown key '{key}' received.".format(key=post_key))
99 return infos
100
101
102 @api_ns.route("/status")
103 -def api_status():
113
114
115 @api_ns.route("/coprs/<username>/new/", methods=["POST"])
116 @api_login_required
117 -def api_new_copr(username):
118 """
119 Receive information from the user on how to create its new copr,
120 check their validity and create the corresponding copr.
121
122 :arg name: the name of the copr to add
123 :arg chroots: a comma separated list of chroots to use
124 :kwarg repos: a comma separated list of repository that this copr
125 can use.
126 :kwarg initial_pkgs: a comma separated list of initial packages to
127 build in this new copr
128
129 """
130
131 form = forms.CoprFormFactory.create_form_cls()(csrf_enabled=False)
132 infos = []
133
134
135 infos.extend(validate_post_keys(form))
136
137 if form.validate_on_submit():
138 group = ComplexLogic.get_group_by_name_safe(username[1:]) if username[0] == "@" else None
139
140 auto_prune = True
141 if "auto_prune" in flask.request.form:
142 auto_prune = form.auto_prune.data
143
144 use_bootstrap_container = True
145 if "use_bootstrap_container" in flask.request.form:
146 use_bootstrap_container = form.use_bootstrap_container.data
147
148 try:
149 copr = CoprsLogic.add(
150 name=form.name.data.strip(),
151 repos=" ".join(form.repos.data.split()),
152 user=flask.g.user,
153 selected_chroots=form.selected_chroots,
154 description=form.description.data,
155 instructions=form.instructions.data,
156 check_for_duplicates=True,
157 disable_createrepo=form.disable_createrepo.data,
158 unlisted_on_hp=form.unlisted_on_hp.data,
159 build_enable_net=form.build_enable_net.data,
160 group=group,
161 persistent=form.persistent.data,
162 auto_prune=auto_prune,
163 use_bootstrap_container=use_bootstrap_container,
164 )
165 infos.append("New project was successfully created.")
166
167 if form.initial_pkgs.data:
168 pkgs = form.initial_pkgs.data.split()
169 for pkg in pkgs:
170 builds_logic.BuildsLogic.add(
171 user=flask.g.user,
172 pkgs=pkg,
173 srpm_url=pkg,
174 copr=copr)
175
176 infos.append("Initial packages were successfully "
177 "submitted for building.")
178
179 output = {"output": "ok", "message": "\n".join(infos)}
180 db.session.commit()
181 except (exceptions.DuplicateException,
182 exceptions.NonAdminCannotCreatePersistentProject,
183 exceptions.NonAdminCannotDisableAutoPrunning) as err:
184 db.session.rollback()
185 raise LegacyApiError(str(err))
186
187 else:
188 errormsg = "Validation error\n"
189 if form.errors:
190 for field, emsgs in form.errors.items():
191 errormsg += "- {0}: {1}\n".format(field, "\n".join(emsgs))
192
193 errormsg = errormsg.replace('"', "'")
194 raise LegacyApiError(errormsg)
195
196 return flask.jsonify(output)
197
198
199 @api_ns.route("/coprs/<username>/<coprname>/delete/", methods=["POST"])
200 @api_login_required
201 @api_req_with_copr
202 -def api_copr_delete(copr):
224
225
226 @api_ns.route("/coprs/<username>/<coprname>/fork/", methods=["POST"])
227 @api_login_required
228 @api_req_with_copr
229 -def api_copr_fork(copr):
230 """ Fork the project and builds in it
231 """
232 form = forms.CoprForkFormFactory\
233 .create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)(csrf_enabled=False)
234
235 if form.validate_on_submit() and copr:
236 try:
237 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0]
238 if flask.g.user.name != form.owner.data and not dstgroup:
239 return LegacyApiError("There is no such group: {}".format(form.owner.data))
240
241 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup)
242 if created:
243 msg = ("Forking project {} for you into {}.\nPlease be aware that it may take a few minutes "
244 "to duplicate backend data.".format(copr.full_name, fcopr.full_name))
245 elif not created and form.confirm.data == True:
246 msg = ("Updating packages in {} from {}.\nPlease be aware that it may take a few minutes "
247 "to duplicate backend data.".format(copr.full_name, fcopr.full_name))
248 else:
249 raise LegacyApiError("You are about to fork into existing project: {}\n"
250 "Please use --confirm if you really want to do this".format(fcopr.full_name))
251
252 output = {"output": "ok", "message": msg}
253 db.session.commit()
254
255 except (exceptions.ActionInProgressException,
256 exceptions.InsufficientRightsException) as err:
257 db.session.rollback()
258 raise LegacyApiError(str(err))
259 else:
260 raise LegacyApiError("Invalid request: {0}".format(form.errors))
261
262 return flask.jsonify(output)
263
264
265 @api_ns.route("/coprs/")
266 @api_ns.route("/coprs/<username>/")
267 -def api_coprs_by_owner(username=None):
268 """ Return the list of coprs owned by the given user.
269 username is taken either from GET params or from the URL itself
270 (in this order).
271
272 :arg username: the username of the person one would like to the
273 coprs of.
274
275 """
276 username = flask.request.args.get("username", None) or username
277 if username is None:
278 raise LegacyApiError("Invalid request: missing `username` ")
279
280 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
281
282 if username.startswith("@"):
283 group_name = username[1:]
284 query = CoprsLogic.get_multiple()
285 query = CoprsLogic.filter_by_group_name(query, group_name)
286 else:
287 query = CoprsLogic.get_multiple_owned_by_username(username)
288
289 query = CoprsLogic.join_builds(query)
290 query = CoprsLogic.set_query_order(query)
291
292 repos = query.all()
293 output = {"output": "ok", "repos": []}
294 for repo in repos:
295 yum_repos = {}
296 for build in repo.builds:
297 for chroot in repo.active_chroots:
298 release = release_tmpl.format(chroot=chroot)
299 yum_repos[release] = fix_protocol_for_backend(
300 os.path.join(build.copr.repo_url, release + '/'))
301 break
302
303 output["repos"].append({"name": repo.name,
304 "additional_repos": repo.repos,
305 "yum_repos": yum_repos,
306 "description": repo.description,
307 "instructions": repo.instructions,
308 "persistent": repo.persistent,
309 "unlisted_on_hp": repo.unlisted_on_hp,
310 "auto_prune": repo.auto_prune,
311 })
312
313 return flask.jsonify(output)
314
319 """ Return detail of one project.
320
321 :arg username: the username of the person one would like to the
322 coprs of.
323 :arg coprname: the name of project.
324
325 """
326 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
327 output = {"output": "ok", "detail": {}}
328 yum_repos = {}
329
330 build = models.Build.query.filter(models.Build.copr_id == copr.id).first()
331
332 if build:
333 for chroot in copr.active_chroots:
334 release = release_tmpl.format(chroot=chroot)
335 yum_repos[release] = fix_protocol_for_backend(
336 os.path.join(build.copr.repo_url, release + '/'))
337
338 output["detail"] = {
339 "name": copr.name,
340 "additional_repos": copr.repos,
341 "yum_repos": yum_repos,
342 "description": copr.description,
343 "instructions": copr.instructions,
344 "last_modified": builds_logic.BuildsLogic.last_modified(copr),
345 "auto_createrepo": copr.auto_createrepo,
346 "persistent": copr.persistent,
347 "unlisted_on_hp": copr.unlisted_on_hp,
348 "auto_prune": copr.auto_prune,
349 "use_bootstrap_container": copr.use_bootstrap_container,
350 }
351 return flask.jsonify(output)
352
353
354 @api_ns.route("/auth_check/", methods=["POST"])
355 @api_login_required
356 -def api_auth_check():
357 output = {"output": "ok"}
358 return flask.jsonify(output)
359
360
361 @api_ns.route("/coprs/<username>/<coprname>/new_build/", methods=["POST"])
362 @api_login_required
363 @api_req_with_copr
364 -def copr_new_build(copr):
376 return process_creating_new_build(copr, form, create_new_build)
377
378
379 @api_ns.route("/coprs/<username>/<coprname>/new_build_upload/", methods=["POST"])
380 @api_login_required
381 @api_req_with_copr
382 -def copr_new_build_upload(copr):
393 return process_creating_new_build(copr, form, create_new_build)
394
395
396 @api_ns.route("/coprs/<username>/<coprname>/new_build_pypi/", methods=["POST"])
397 @api_login_required
398 @api_req_with_copr
399 -def copr_new_build_pypi(copr):
416 return process_creating_new_build(copr, form, create_new_build)
417
418
419 @api_ns.route("/coprs/<username>/<coprname>/new_build_tito/", methods=["POST"])
420 @api_login_required
421 @api_req_with_copr
422 -def copr_new_build_tito(copr):
440 return process_creating_new_build(copr, form, create_new_build)
441
442
443 @api_ns.route("/coprs/<username>/<coprname>/new_build_mock/", methods=["POST"])
444 @api_login_required
445 @api_req_with_copr
446 -def copr_new_build_mock(copr):
464 return process_creating_new_build(copr, form, create_new_build)
465
466
467 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
468 @api_login_required
469 @api_req_with_copr
470 -def copr_new_build_rubygems(copr):
481 return process_creating_new_build(copr, form, create_new_build)
482
483
484 @api_ns.route("/coprs/<username>/<coprname>/new_build_custom/", methods=["POST"])
485 @api_login_required
486 @api_req_with_copr
487 -def copr_new_build_custom(copr):
500 return process_creating_new_build(copr, form, create_new_build)
501
502
503 @api_ns.route("/coprs/<username>/<coprname>/new_build_scm/", methods=["POST"])
504 @api_login_required
505 @api_req_with_copr
506 -def copr_new_build_scm(copr):
507 form = forms.BuildFormScmFactory(copr.active_chroots)(csrf_enabled=False)
508
509 def create_new_build():
510 return BuildsLogic.create_new_from_scm(
511 flask.g.user,
512 copr,
513 scm_type=form.scm_type.data,
514 clone_url=form.clone_url.data,
515 committish=form.committish.data,
516 subdirectory=form.subdirectory.data,
517 spec=form.spec.data,
518 srpm_build_method=form.srpm_build_method.data,
519 chroot_names=form.selected_chroots,
520 background=form.background.data,
521 )
522 return process_creating_new_build(copr, form, create_new_build)
523
524
525 @api_ns.route("/coprs/<username>/<coprname>/new_build_distgit/", methods=["POST"])
526 @api_login_required
527 @api_req_with_copr
528 -def copr_new_build_distgit(copr):
544 return process_creating_new_build(copr, form, create_new_build)
545
548 infos = []
549
550
551 infos.extend(validate_post_keys(form))
552
553 if not form.validate_on_submit():
554 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors))
555
556 if not flask.g.user.can_build_in(copr):
557 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}"
558 .format(flask.g.user.username, copr.full_name))
559
560
561 try:
562
563
564 build = create_new_build()
565 db.session.commit()
566 ids = [build.id] if type(build) != list else [b.id for b in build]
567 infos.append("Build was added to {0}:".format(copr.name))
568 for build_id in ids:
569 infos.append(" " + flask.url_for("coprs_ns.copr_build_redirect",
570 build_id=build_id,
571 _external=True))
572
573 except (ActionInProgressException, InsufficientRightsException) as e:
574 raise LegacyApiError("Invalid request: {}".format(e))
575
576 output = {"output": "ok",
577 "ids": ids,
578 "message": "\n".join(infos)}
579
580 return flask.jsonify(output)
581
582
583 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
584 -def build_status(build_id):
589
590
591 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"])
592 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
593 -def build_detail(build_id):
594 build = ComplexLogic.get_build_safe(build_id)
595
596 chroots = {}
597 results_by_chroot = {}
598 for chroot in build.build_chroots:
599 chroots[chroot.name] = chroot.state
600 results_by_chroot[chroot.name] = chroot.result_dir_url
601
602 built_packages = None
603 if build.built_packages:
604 built_packages = build.built_packages.split("\n")
605
606 output = {
607 "output": "ok",
608 "status": build.state,
609 "project": build.copr.name,
610 "owner": build.copr.owner_name,
611 "results": build.copr.repo_url,
612 "built_pkgs": built_packages,
613 "src_version": build.pkg_version,
614 "chroots": chroots,
615 "submitted_on": build.submitted_on,
616 "started_on": build.min_started_on,
617 "ended_on": build.max_ended_on,
618 "src_pkg": build.pkgs,
619 "submitted_by": build.user.name if build.user else None,
620 "results_by_chroot": results_by_chroot
621 }
622 return flask.jsonify(output)
623
624
625 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
626 @api_login_required
627 -def cancel_build(build_id):
638
639
640 @api_ns.route("/coprs/delete_build/<int:build_id>/", methods=["POST"])
641 @api_login_required
642 -def delete_build(build_id):
653
654
655 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
656 @api_login_required
657 @api_req_with_copr
658 -def copr_modify(copr):
659 form = forms.CoprModifyForm(csrf_enabled=False)
660
661 if not form.validate_on_submit():
662 raise LegacyApiError("Invalid request: {0}".format(form.errors))
663
664
665
666 if form.description.raw_data and len(form.description.raw_data):
667 copr.description = form.description.data
668 if form.instructions.raw_data and len(form.instructions.raw_data):
669 copr.instructions = form.instructions.data
670 if form.repos.raw_data and len(form.repos.raw_data):
671 copr.repos = form.repos.data
672 if form.disable_createrepo.raw_data and len(form.disable_createrepo.raw_data):
673 copr.disable_createrepo = form.disable_createrepo.data
674
675 if "unlisted_on_hp" in flask.request.form:
676 copr.unlisted_on_hp = form.unlisted_on_hp.data
677 if "build_enable_net" in flask.request.form:
678 copr.build_enable_net = form.build_enable_net.data
679 if "auto_prune" in flask.request.form:
680 copr.auto_prune = form.auto_prune.data
681 if "use_bootstrap_container" in flask.request.form:
682 copr.use_bootstrap_container = form.use_bootstrap_container.data
683 if "chroots" in flask.request.form:
684 coprs_logic.CoprChrootsLogic.update_from_names(
685 flask.g.user, copr, form.chroots.data)
686
687 try:
688 CoprsLogic.update(flask.g.user, copr)
689 if copr.group:
690 _ = copr.group.id
691 db.session.commit()
692 except (exceptions.ActionInProgressException,
693 exceptions.InsufficientRightsException,
694 exceptions.NonAdminCannotDisableAutoPrunning) as e:
695 db.session.rollback()
696 raise LegacyApiError("Invalid request: {}".format(e))
697
698 output = {
699 'output': 'ok',
700 'description': copr.description,
701 'instructions': copr.instructions,
702 'repos': copr.repos,
703 'chroots': [c.name for c in copr.mock_chroots],
704 }
705
706 return flask.jsonify(output)
707
708
709 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
710 @api_login_required
711 @api_req_with_copr
712 -def copr_modify_chroot(copr, chrootname):
726
727
728 @api_ns.route('/coprs/<username>/<coprname>/chroot/edit/<chrootname>/', methods=["POST"])
729 @api_login_required
730 @api_req_with_copr
731 -def copr_edit_chroot(copr, chrootname):
732 form = forms.ModifyChrootForm(csrf_enabled=False)
733 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname)
734
735 if not form.validate_on_submit():
736 raise LegacyApiError("Invalid request: {0}".format(form.errors))
737 else:
738 buildroot_pkgs = repos = comps_xml = comps_name = None
739 if "buildroot_pkgs" in flask.request.form:
740 buildroot_pkgs = form.buildroot_pkgs.data
741 if "repos" in flask.request.form:
742 repos = form.repos.data
743 if form.upload_comps.has_file():
744 comps_xml = form.upload_comps.data.stream.read()
745 comps_name = form.upload_comps.data.filename
746 if form.delete_comps.data:
747 coprs_logic.CoprChrootsLogic.remove_comps(flask.g.user, chroot)
748 coprs_logic.CoprChrootsLogic.update_chroot(
749 flask.g.user, chroot, buildroot_pkgs, repos, comps=comps_xml, comps_name=comps_name)
750 db.session.commit()
751
752 output = {
753 "output": "ok",
754 "message": "Edit chroot operation was successful.",
755 "chroot": chroot.to_dict(),
756 }
757 return flask.jsonify(output)
758
759
760 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
761 @api_req_with_copr
762 -def copr_chroot_details(copr, chrootname):
767
768 @api_ns.route('/coprs/<username>/<coprname>/chroot/get/<chrootname>/', methods=["GET"])
769 @api_req_with_copr
770 -def copr_get_chroot(copr, chrootname):
774
778 """ Return the list of coprs found in search by the given text.
779 project is taken either from GET params or from the URL itself
780 (in this order).
781
782 :arg project: the text one would like find for coprs.
783
784 """
785 project = flask.request.args.get("project", None) or project
786 if not project:
787 raise LegacyApiError("No project found.")
788
789 try:
790 query = CoprsLogic.get_multiple_fulltext(project)
791
792 repos = query.all()
793 output = {"output": "ok", "repos": []}
794 for repo in repos:
795 output["repos"].append({"username": repo.user.name,
796 "coprname": repo.name,
797 "description": repo.description})
798 except ValueError as e:
799 raise LegacyApiError("Server error: {}".format(e))
800
801 return flask.jsonify(output)
802
806 """ Return list of coprs which are part of playground """
807 query = CoprsLogic.get_playground()
808 repos = query.all()
809 output = {"output": "ok", "repos": []}
810 for repo in repos:
811 output["repos"].append({"username": repo.owner_name,
812 "coprname": repo.name,
813 "chroots": [chroot.name for chroot in repo.active_chroots]})
814
815 jsonout = flask.jsonify(output)
816 jsonout.status_code = 200
817 return jsonout
818
819
820 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
821 @api_req_with_copr
822 -def monitor(copr):
826
827
828
829 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
830 @api_login_required
831 @api_req_with_copr
832 -def copr_add_package(copr, source_type_text):
834
835
836 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
837 @api_login_required
838 @api_req_with_copr
839 -def copr_edit_package(copr, package_name, source_type_text):
845
882
885 params = {}
886 if flask.request.args.get('with_latest_build'):
887 params['with_latest_build'] = True
888 if flask.request.args.get('with_latest_succeeded_build'):
889 params['with_latest_succeeded_build'] = True
890 if flask.request.args.get('with_all_builds'):
891 params['with_all_builds'] = True
892 return params
893
896 """
897 A lagging generator to stream JSON so we don't have to hold everything in memory
898 This is a little tricky, as we need to omit the last comma to make valid JSON,
899 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
900 """
901 packages = query.__iter__()
902 try:
903 prev_package = next(packages)
904 except StopIteration:
905
906 yield '{"packages": []}'
907 raise StopIteration
908
909 yield '{"packages": ['
910
911 for package in packages:
912 yield json.dumps(prev_package.to_dict(**params)) + ', '
913 prev_package = package
914
915 yield json.dumps(prev_package.to_dict(**params)) + ']}'
916
917
918 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
919 @api_req_with_copr
920 -def copr_list_packages(copr):
924
925
926
927 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
928 @api_req_with_copr
929 -def copr_get_package(copr, package_name):
937
938
939 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
940 @api_login_required
941 @api_req_with_copr
942 -def copr_delete_package(copr, package_name):
959
960
961 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
962 @api_login_required
963 @api_req_with_copr
964 -def copr_reset_package(copr, package_name):
981
982
983 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
984 @api_login_required
985 @api_req_with_copr
986 -def copr_build_package(copr, package_name):
987 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False)
988
989 try:
990 package = PackagesLogic.get(copr.id, package_name)[0]
991 except IndexError:
992 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name))
993
994 if form.validate_on_submit():
995 try:
996 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data)
997 db.session.commit()
998 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e:
999 raise LegacyApiError(str(e))
1000 else:
1001 raise LegacyApiError(form.errors)
1002
1003 return flask.jsonify({
1004 "output": "ok",
1005 "ids": [build.id],
1006 "message": "Build was added to {0}.".format(copr.name)
1007 })
1008
1009
1010 @api_ns.route("/coprs/<username>/<coprname>/module/build/", methods=["POST"])
1011 @api_login_required
1012 @api_req_with_copr
1013 -def copr_build_module(copr):
1036
1037
1038 @api_ns.route("/coprs/<username>/<coprname>/build-config/<chroot>/", methods=["GET"])
1039 @api_ns.route("/g/<group_name>/<coprname>/build-config/<chroot>/", methods=["GET"])
1040 @api_req_with_copr
1041 -def copr_build_config(copr, chroot):
1042 """
1043 Generate build configuration.
1044 """
1045 output = {
1046 "output": "ok",
1047 "build_config": generate_build_config(copr, chroot),
1048 }
1049
1050 if not output['build_config']:
1051 raise LegacyApiError('Chroot not found.')
1052
1053 return flask.jsonify(output)
1054