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 if build.results:
298 for chroot in repo.active_chroots:
299 release = release_tmpl.format(chroot=chroot)
300 yum_repos[release] = fix_protocol_for_backend(
301 os.path.join(build.results, release + '/'))
302 break
303
304 output["repos"].append({"name": repo.name,
305 "additional_repos": repo.repos,
306 "yum_repos": yum_repos,
307 "description": repo.description,
308 "instructions": repo.instructions,
309 "persistent": repo.persistent,
310 "unlisted_on_hp": repo.unlisted_on_hp,
311 "auto_prune": repo.auto_prune,
312 })
313
314 return flask.jsonify(output)
315
320 """ Return detail of one project.
321
322 :arg username: the username of the person one would like to the
323 coprs of.
324 :arg coprname: the name of project.
325
326 """
327 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
328 output = {"output": "ok", "detail": {}}
329 yum_repos = {}
330
331 build = models.Build.query.filter(
332 models.Build.copr_id == copr.id, models.Build.results != None).first()
333
334 if build:
335 for chroot in copr.active_chroots:
336 release = release_tmpl.format(chroot=chroot)
337 yum_repos[release] = fix_protocol_for_backend(
338 os.path.join(build.results, release + '/'))
339
340 output["detail"] = {
341 "name": copr.name,
342 "additional_repos": copr.repos,
343 "yum_repos": yum_repos,
344 "description": copr.description,
345 "instructions": copr.instructions,
346 "last_modified": builds_logic.BuildsLogic.last_modified(copr),
347 "auto_createrepo": copr.auto_createrepo,
348 "persistent": copr.persistent,
349 "unlisted_on_hp": copr.unlisted_on_hp,
350 "auto_prune": copr.auto_prune,
351 "use_bootstrap_container": copr.use_bootstrap_container,
352 }
353 return flask.jsonify(output)
354
355
356 @api_ns.route("/auth_check/", methods=["POST"])
357 @api_login_required
358 -def api_auth_check():
359 output = {"output": "ok"}
360 return flask.jsonify(output)
361
362
363 @api_ns.route("/coprs/<username>/<coprname>/new_build/", methods=["POST"])
364 @api_login_required
365 @api_req_with_copr
366 -def copr_new_build(copr):
378 return process_creating_new_build(copr, form, create_new_build)
379
380
381 @api_ns.route("/coprs/<username>/<coprname>/new_build_upload/", methods=["POST"])
382 @api_login_required
383 @api_req_with_copr
384 -def copr_new_build_upload(copr):
395 return process_creating_new_build(copr, form, create_new_build)
396
397
398 @api_ns.route("/coprs/<username>/<coprname>/new_build_pypi/", methods=["POST"])
399 @api_login_required
400 @api_req_with_copr
401 -def copr_new_build_pypi(copr):
418 return process_creating_new_build(copr, form, create_new_build)
419
420
421 @api_ns.route("/coprs/<username>/<coprname>/new_build_tito/", methods=["POST"])
422 @api_login_required
423 @api_req_with_copr
424 -def copr_new_build_tito(copr):
442 return process_creating_new_build(copr, form, create_new_build)
443
444
445 @api_ns.route("/coprs/<username>/<coprname>/new_build_mock/", methods=["POST"])
446 @api_login_required
447 @api_req_with_copr
448 -def copr_new_build_mock(copr):
466 return process_creating_new_build(copr, form, create_new_build)
467
468
469 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
470 @api_login_required
471 @api_req_with_copr
472 -def copr_new_build_rubygems(copr):
483 return process_creating_new_build(copr, form, create_new_build)
484
485
486 @api_ns.route("/coprs/<username>/<coprname>/new_build_custom/", methods=["POST"])
487 @api_login_required
488 @api_req_with_copr
489 -def copr_new_build_custom(copr):
502 return process_creating_new_build(copr, form, create_new_build)
503
504
505 @api_ns.route("/coprs/<username>/<coprname>/new_build_scm/", methods=["POST"])
506 @api_login_required
507 @api_req_with_copr
508 -def copr_new_build_scm(copr):
509 form = forms.BuildFormScmFactory(copr.active_chroots)(csrf_enabled=False)
510
511 def create_new_build():
512 return BuildsLogic.create_new_from_scm(
513 flask.g.user,
514 copr,
515 scm_type=form.scm_type.data,
516 clone_url=form.clone_url.data,
517 committish=form.committish.data,
518 subdirectory=form.subdirectory.data,
519 spec=form.spec.data,
520 srpm_build_method=form.srpm_build_method.data,
521 chroot_names=form.selected_chroots,
522 background=form.background.data,
523 )
524 return process_creating_new_build(copr, form, create_new_build)
525
526
527 @api_ns.route("/coprs/<username>/<coprname>/new_build_distgit/", methods=["POST"])
528 @api_login_required
529 @api_req_with_copr
530 -def copr_new_build_distgit(copr):
546 return process_creating_new_build(copr, form, create_new_build)
547
550 infos = []
551
552
553 infos.extend(validate_post_keys(form))
554
555 if not form.validate_on_submit():
556 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors))
557
558 if not flask.g.user.can_build_in(copr):
559 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}"
560 .format(flask.g.user.username, copr.full_name))
561
562
563 try:
564
565
566 build = create_new_build()
567 db.session.commit()
568 ids = [build.id] if type(build) != list else [b.id for b in build]
569 infos.append("Build was added to {0}:".format(copr.name))
570 for build_id in ids:
571 infos.append(" " + flask.url_for("coprs_ns.copr_build_redirect",
572 build_id=build_id,
573 _external=True))
574
575 except (ActionInProgressException, InsufficientRightsException) as e:
576 raise LegacyApiError("Invalid request: {}".format(e))
577
578 output = {"output": "ok",
579 "ids": ids,
580 "message": "\n".join(infos)}
581
582 return flask.jsonify(output)
583
584
585 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
586 @api_login_required
587 -def build_status(build_id):
592
593
594 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"])
595 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
596 -def build_detail(build_id):
597 build = ComplexLogic.get_build_safe(build_id)
598
599 chroots = {}
600 results_by_chroot = {}
601 for chroot in build.build_chroots:
602 chroots[chroot.name] = chroot.state
603 results_by_chroot[chroot.name] = chroot.result_dir_url
604
605 built_packages = None
606 if build.built_packages:
607 built_packages = build.built_packages.split("\n")
608
609 output = {
610 "output": "ok",
611 "status": build.state,
612 "project": build.copr.name,
613 "owner": build.copr.owner_name,
614 "results": build.results,
615 "built_pkgs": built_packages,
616 "src_version": build.pkg_version,
617 "chroots": chroots,
618 "submitted_on": build.submitted_on,
619 "started_on": build.min_started_on,
620 "ended_on": build.max_ended_on,
621 "src_pkg": build.pkgs,
622 "submitted_by": build.user.name if build.user else None,
623 "results_by_chroot": results_by_chroot
624 }
625 return flask.jsonify(output)
626
627
628 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
629 @api_login_required
630 -def cancel_build(build_id):
641
642
643 @api_ns.route("/coprs/delete_build/<int:build_id>/", methods=["POST"])
644 @api_login_required
645 -def delete_build(build_id):
656
657
658 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
659 @api_login_required
660 @api_req_with_copr
661 -def copr_modify(copr):
662 form = forms.CoprModifyForm(csrf_enabled=False)
663
664 if not form.validate_on_submit():
665 raise LegacyApiError("Invalid request: {0}".format(form.errors))
666
667
668
669 if form.description.raw_data and len(form.description.raw_data):
670 copr.description = form.description.data
671 if form.instructions.raw_data and len(form.instructions.raw_data):
672 copr.instructions = form.instructions.data
673 if form.repos.raw_data and len(form.repos.raw_data):
674 copr.repos = form.repos.data
675 if form.disable_createrepo.raw_data and len(form.disable_createrepo.raw_data):
676 copr.disable_createrepo = form.disable_createrepo.data
677
678 if "unlisted_on_hp" in flask.request.form:
679 copr.unlisted_on_hp = form.unlisted_on_hp.data
680 if "build_enable_net" in flask.request.form:
681 copr.build_enable_net = form.build_enable_net.data
682 if "auto_prune" in flask.request.form:
683 copr.auto_prune = form.auto_prune.data
684 if "use_bootstrap_container" in flask.request.form:
685 copr.use_bootstrap_container = form.use_bootstrap_container.data
686 if "chroots" in flask.request.form:
687 coprs_logic.CoprChrootsLogic.update_from_names(
688 flask.g.user, copr, form.chroots.data)
689
690 try:
691 CoprsLogic.update(flask.g.user, copr)
692 if copr.group:
693 _ = copr.group.id
694 db.session.commit()
695 except (exceptions.ActionInProgressException,
696 exceptions.InsufficientRightsException,
697 exceptions.NonAdminCannotDisableAutoPrunning) as e:
698 db.session.rollback()
699 raise LegacyApiError("Invalid request: {}".format(e))
700
701 output = {
702 'output': 'ok',
703 'description': copr.description,
704 'instructions': copr.instructions,
705 'repos': copr.repos,
706 'chroots': [c.name for c in copr.mock_chroots],
707 }
708
709 return flask.jsonify(output)
710
711
712 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
713 @api_login_required
714 @api_req_with_copr
715 -def copr_modify_chroot(copr, chrootname):
729
730
731 @api_ns.route('/coprs/<username>/<coprname>/chroot/edit/<chrootname>/', methods=["POST"])
732 @api_login_required
733 @api_req_with_copr
734 -def copr_edit_chroot(copr, chrootname):
735 form = forms.ModifyChrootForm(csrf_enabled=False)
736 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname)
737
738 if not form.validate_on_submit():
739 raise LegacyApiError("Invalid request: {0}".format(form.errors))
740 else:
741 buildroot_pkgs = repos = comps_xml = comps_name = None
742 if "buildroot_pkgs" in flask.request.form:
743 buildroot_pkgs = form.buildroot_pkgs.data
744 if "repos" in flask.request.form:
745 repos = form.repos.data
746 if form.upload_comps.has_file():
747 comps_xml = form.upload_comps.data.stream.read()
748 comps_name = form.upload_comps.data.filename
749 if form.delete_comps.data:
750 coprs_logic.CoprChrootsLogic.remove_comps(flask.g.user, chroot)
751 coprs_logic.CoprChrootsLogic.update_chroot(
752 flask.g.user, chroot, buildroot_pkgs, repos, comps=comps_xml, comps_name=comps_name)
753 db.session.commit()
754
755 output = {
756 "output": "ok",
757 "message": "Edit chroot operation was successful.",
758 "chroot": chroot.to_dict(),
759 }
760 return flask.jsonify(output)
761
762
763 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
764 @api_req_with_copr
765 -def copr_chroot_details(copr, chrootname):
770
771 @api_ns.route('/coprs/<username>/<coprname>/chroot/get/<chrootname>/', methods=["GET"])
772 @api_req_with_copr
773 -def copr_get_chroot(copr, chrootname):
777
781 """ Return the list of coprs found in search by the given text.
782 project is taken either from GET params or from the URL itself
783 (in this order).
784
785 :arg project: the text one would like find for coprs.
786
787 """
788 project = flask.request.args.get("project", None) or project
789 if not project:
790 raise LegacyApiError("No project found.")
791
792 try:
793 query = CoprsLogic.get_multiple_fulltext(project)
794
795 repos = query.all()
796 output = {"output": "ok", "repos": []}
797 for repo in repos:
798 output["repos"].append({"username": repo.user.name,
799 "coprname": repo.name,
800 "description": repo.description})
801 except ValueError as e:
802 raise LegacyApiError("Server error: {}".format(e))
803
804 return flask.jsonify(output)
805
809 """ Return list of coprs which are part of playground """
810 query = CoprsLogic.get_playground()
811 repos = query.all()
812 output = {"output": "ok", "repos": []}
813 for repo in repos:
814 output["repos"].append({"username": repo.owner_name,
815 "coprname": repo.name,
816 "chroots": [chroot.name for chroot in repo.active_chroots]})
817
818 jsonout = flask.jsonify(output)
819 jsonout.status_code = 200
820 return jsonout
821
822
823 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
824 @api_req_with_copr
825 -def monitor(copr):
829
830
831
832 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
833 @api_login_required
834 @api_req_with_copr
835 -def copr_add_package(copr, source_type_text):
837
838
839 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
840 @api_login_required
841 @api_req_with_copr
842 -def copr_edit_package(copr, package_name, source_type_text):
848
885
888 params = {}
889 if flask.request.args.get('with_latest_build'):
890 params['with_latest_build'] = True
891 if flask.request.args.get('with_latest_succeeded_build'):
892 params['with_latest_succeeded_build'] = True
893 if flask.request.args.get('with_all_builds'):
894 params['with_all_builds'] = True
895 return params
896
899 """
900 A lagging generator to stream JSON so we don't have to hold everything in memory
901 This is a little tricky, as we need to omit the last comma to make valid JSON,
902 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
903 """
904 packages = query.__iter__()
905 try:
906 prev_package = next(packages)
907 except StopIteration:
908
909 yield '{"packages": []}'
910 raise StopIteration
911
912 yield '{"packages": ['
913
914 for package in packages:
915 yield json.dumps(prev_package.to_dict(**params)) + ', '
916 prev_package = package
917
918 yield json.dumps(prev_package.to_dict(**params)) + ']}'
919
920
921 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
922 @api_req_with_copr
923 -def copr_list_packages(copr):
927
928
929
930 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
931 @api_req_with_copr
932 -def copr_get_package(copr, package_name):
940
941
942 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
943 @api_login_required
944 @api_req_with_copr
945 -def copr_delete_package(copr, package_name):
962
963
964 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
965 @api_login_required
966 @api_req_with_copr
967 -def copr_reset_package(copr, package_name):
984
985
986 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
987 @api_login_required
988 @api_req_with_copr
989 -def copr_build_package(copr, package_name):
990 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False)
991
992 try:
993 package = PackagesLogic.get(copr.id, package_name)[0]
994 except IndexError:
995 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name))
996
997 if form.validate_on_submit():
998 try:
999 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data)
1000 db.session.commit()
1001 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e:
1002 raise LegacyApiError(str(e))
1003 else:
1004 raise LegacyApiError(form.errors)
1005
1006 return flask.jsonify({
1007 "output": "ok",
1008 "ids": [build.id],
1009 "message": "Build was added to {0}.".format(copr.name)
1010 })
1011
1012
1013 @api_ns.route("/coprs/<username>/<coprname>/module/build/", methods=["POST"])
1014 @api_login_required
1015 @api_req_with_copr
1016 -def copr_build_module(copr):
1039
1040
1041 @api_ns.route("/coprs/<username>/<coprname>/build-config/<chroot>/", methods=["GET"])
1042 @api_ns.route("/g/<group_name>/<coprname>/build-config/<chroot>/", methods=["GET"])
1043 @api_req_with_copr
1044 -def copr_build_config(copr, chroot):
1045 """
1046 Generate build configuration.
1047 """
1048 output = {
1049 "output": "ok",
1050 "build_config": generate_build_config(copr, chroot),
1051 }
1052
1053 if not output['build_config']:
1054 raise LegacyApiError('Chroot not found.')
1055
1056 return flask.jsonify(output)
1057