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 copr_common.enums import StatusEnum
15 from coprs import db
16 from coprs import exceptions
17 from coprs import forms
18 from coprs import helpers
19 from coprs import models
20 from coprs.helpers import fix_protocol_for_backend, generate_build_config
21 from coprs.logic.api_logic import MonitorWrapper
22 from coprs.logic.builds_logic import BuildsLogic
23 from coprs.logic.complex_logic import ComplexLogic
24 from coprs.logic.users_logic import UsersLogic
25 from coprs.logic.packages_logic import PackagesLogic
26 from coprs.logic.modules_logic import ModulesLogic, ModuleProvider, ModuleBuildFacade
27
28 from coprs.views.misc import login_required, api_login_required
29
30 from coprs.views.api_ns import api_ns
31
32 from coprs.logic import builds_logic
33 from coprs.logic import coprs_logic
34 from coprs.logic.coprs_logic import CoprsLogic
35 from coprs.logic.actions_logic import ActionsLogic
36
37 from coprs.exceptions import (ActionInProgressException,
38 InsufficientRightsException,
39 DuplicateException,
40 LegacyApiError,
41 NoPackageSourceException,
42 UnknownSourceTypeException)
55 return wrapper
56
60 """
61 Render the home page of the api.
62 This page provides information on how to call/use the API.
63 """
64
65 return flask.render_template("api.html")
66
67
68 @api_ns.route("/new/", methods=["GET", "POST"])
69 @login_required
70 -def api_new_token():
89
92 infos = []
93
94
95 proxyuser_keys = ["username"]
96 allowed = list(form.__dict__.keys()) + proxyuser_keys
97 for post_key in flask.request.form.keys():
98 if post_key not in allowed:
99 infos.append("Unknown key '{key}' received.".format(key=post_key))
100 return infos
101
102
103 @api_ns.route("/status")
104 -def api_status():
114
115
116 @api_ns.route("/coprs/<username>/new/", methods=["POST"])
117 @api_login_required
118 -def api_new_copr(username):
119 """
120 Receive information from the user on how to create its new copr,
121 check their validity and create the corresponding copr.
122
123 :arg name: the name of the copr to add
124 :arg chroots: a comma separated list of chroots to use
125 :kwarg repos: a comma separated list of repository that this copr
126 can use.
127 :kwarg initial_pkgs: a comma separated list of initial packages to
128 build in this new copr
129
130 """
131
132 form = forms.CoprFormFactory.create_form_cls()(csrf_enabled=False)
133 infos = []
134
135
136 infos.extend(validate_post_keys(form))
137
138 if form.validate_on_submit():
139 group = ComplexLogic.get_group_by_name_safe(username[1:]) if username[0] == "@" else None
140
141 auto_prune = True
142 if "auto_prune" in flask.request.form:
143 auto_prune = form.auto_prune.data
144
145 use_bootstrap_container = True
146 if "use_bootstrap_container" in flask.request.form:
147 use_bootstrap_container = form.use_bootstrap_container.data
148
149 try:
150 copr = CoprsLogic.add(
151 name=form.name.data.strip(),
152 repos=" ".join(form.repos.data.split()),
153 user=flask.g.user,
154 selected_chroots=form.selected_chroots,
155 description=form.description.data,
156 instructions=form.instructions.data,
157 check_for_duplicates=True,
158 disable_createrepo=form.disable_createrepo.data,
159 unlisted_on_hp=form.unlisted_on_hp.data,
160 build_enable_net=form.build_enable_net.data,
161 group=group,
162 persistent=form.persistent.data,
163 auto_prune=auto_prune,
164 use_bootstrap_container=use_bootstrap_container,
165 )
166 infos.append("New project was successfully created.")
167
168 if form.initial_pkgs.data:
169 pkgs = form.initial_pkgs.data.split()
170 for pkg in pkgs:
171 builds_logic.BuildsLogic.add(
172 user=flask.g.user,
173 pkgs=pkg,
174 srpm_url=pkg,
175 copr=copr)
176
177 infos.append("Initial packages were successfully "
178 "submitted for building.")
179
180 output = {"output": "ok", "message": "\n".join(infos)}
181 db.session.commit()
182 except (exceptions.DuplicateException,
183 exceptions.NonAdminCannotCreatePersistentProject,
184 exceptions.NonAdminCannotDisableAutoPrunning) as err:
185 db.session.rollback()
186 raise LegacyApiError(str(err))
187
188 else:
189 errormsg = "Validation error\n"
190 if form.errors:
191 for field, emsgs in form.errors.items():
192 errormsg += "- {0}: {1}\n".format(field, "\n".join(emsgs))
193
194 errormsg = errormsg.replace('"', "'")
195 raise LegacyApiError(errormsg)
196
197 return flask.jsonify(output)
198
199
200 @api_ns.route("/coprs/<username>/<coprname>/delete/", methods=["POST"])
201 @api_login_required
202 @api_req_with_copr
203 -def api_copr_delete(copr):
225
226
227 @api_ns.route("/coprs/<username>/<coprname>/fork/", methods=["POST"])
228 @api_login_required
229 @api_req_with_copr
230 -def api_copr_fork(copr):
231 """ Fork the project and builds in it
232 """
233 form = forms.CoprForkFormFactory\
234 .create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)(csrf_enabled=False)
235
236 if form.validate_on_submit() and copr:
237 try:
238 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0]
239 if flask.g.user.name != form.owner.data and not dstgroup:
240 return LegacyApiError("There is no such group: {}".format(form.owner.data))
241
242 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup)
243 if created:
244 msg = ("Forking project {} for you into {}.\nPlease be aware that it may take a few minutes "
245 "to duplicate backend data.".format(copr.full_name, fcopr.full_name))
246 elif not created and form.confirm.data == True:
247 msg = ("Updating packages in {} from {}.\nPlease be aware that it may take a few minutes "
248 "to duplicate backend data.".format(copr.full_name, fcopr.full_name))
249 else:
250 raise LegacyApiError("You are about to fork into existing project: {}\n"
251 "Please use --confirm if you really want to do this".format(fcopr.full_name))
252
253 output = {"output": "ok", "message": msg}
254 db.session.commit()
255
256 except (exceptions.ActionInProgressException,
257 exceptions.InsufficientRightsException) as err:
258 db.session.rollback()
259 raise LegacyApiError(str(err))
260 else:
261 raise LegacyApiError("Invalid request: {0}".format(form.errors))
262
263 return flask.jsonify(output)
264
265
266 @api_ns.route("/coprs/")
267 @api_ns.route("/coprs/<username>/")
268 -def api_coprs_by_owner(username=None):
269 """ Return the list of coprs owned by the given user.
270 username is taken either from GET params or from the URL itself
271 (in this order).
272
273 :arg username: the username of the person one would like to the
274 coprs of.
275
276 """
277 username = flask.request.args.get("username", None) or username
278 if username is None:
279 raise LegacyApiError("Invalid request: missing `username` ")
280
281 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
282
283 if username.startswith("@"):
284 group_name = username[1:]
285 query = CoprsLogic.get_multiple()
286 query = CoprsLogic.filter_by_group_name(query, group_name)
287 else:
288 query = CoprsLogic.get_multiple_owned_by_username(username)
289
290 query = CoprsLogic.join_builds(query)
291 query = CoprsLogic.set_query_order(query)
292
293 repos = query.all()
294 output = {"output": "ok", "repos": []}
295 for repo in repos:
296 yum_repos = {}
297 for build in repo.builds:
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.copr.repo_url, 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(models.Build.copr_id == copr.id).first()
332
333 if build:
334 for chroot in copr.active_chroots:
335 release = release_tmpl.format(chroot=chroot)
336 yum_repos[release] = fix_protocol_for_backend(
337 os.path.join(build.copr.repo_url, release + '/'))
338
339 output["detail"] = {
340 "name": copr.name,
341 "additional_repos": copr.repos,
342 "yum_repos": yum_repos,
343 "description": copr.description,
344 "instructions": copr.instructions,
345 "last_modified": builds_logic.BuildsLogic.last_modified(copr),
346 "auto_createrepo": copr.auto_createrepo,
347 "persistent": copr.persistent,
348 "unlisted_on_hp": copr.unlisted_on_hp,
349 "auto_prune": copr.auto_prune,
350 "use_bootstrap_container": copr.use_bootstrap_container,
351 }
352 return flask.jsonify(output)
353
354
355 @api_ns.route("/auth_check/", methods=["POST"])
356 @api_login_required
357 -def api_auth_check():
358 output = {"output": "ok"}
359 return flask.jsonify(output)
360
361
362 @api_ns.route("/coprs/<username>/<coprname>/new_webhook_secret/", methods=["POST"])
363 @api_login_required
364 @api_req_with_copr
365 -def new_webhook_secret(copr):
378
379
380 @api_ns.route("/coprs/<username>/<coprname>/new_build/", methods=["POST"])
381 @api_login_required
382 @api_req_with_copr
383 -def copr_new_build(copr):
395 return process_creating_new_build(copr, form, create_new_build)
396
397
398 @api_ns.route("/coprs/<username>/<coprname>/new_build_upload/", methods=["POST"])
399 @api_login_required
400 @api_req_with_copr
401 -def copr_new_build_upload(copr):
412 return process_creating_new_build(copr, form, create_new_build)
413
414
415 @api_ns.route("/coprs/<username>/<coprname>/new_build_pypi/", methods=["POST"])
416 @api_login_required
417 @api_req_with_copr
418 -def copr_new_build_pypi(copr):
436 return process_creating_new_build(copr, form, create_new_build)
437
438
439 @api_ns.route("/coprs/<username>/<coprname>/new_build_tito/", methods=["POST"])
440 @api_login_required
441 @api_req_with_copr
442 -def copr_new_build_tito(copr):
460 return process_creating_new_build(copr, form, create_new_build)
461
462
463 @api_ns.route("/coprs/<username>/<coprname>/new_build_mock/", methods=["POST"])
464 @api_login_required
465 @api_req_with_copr
466 -def copr_new_build_mock(copr):
484 return process_creating_new_build(copr, form, create_new_build)
485
486
487 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
488 @api_login_required
489 @api_req_with_copr
490 -def copr_new_build_rubygems(copr):
501 return process_creating_new_build(copr, form, create_new_build)
502
503
504 @api_ns.route("/coprs/<username>/<coprname>/new_build_custom/", methods=["POST"])
505 @api_login_required
506 @api_req_with_copr
507 -def copr_new_build_custom(copr):
520 return process_creating_new_build(copr, form, create_new_build)
521
522
523 @api_ns.route("/coprs/<username>/<coprname>/new_build_scm/", methods=["POST"])
524 @api_login_required
525 @api_req_with_copr
526 -def copr_new_build_scm(copr):
527 form = forms.BuildFormScmFactory(copr.active_chroots)(csrf_enabled=False)
528
529 def create_new_build():
530 return BuildsLogic.create_new_from_scm(
531 flask.g.user,
532 copr,
533 scm_type=form.scm_type.data,
534 clone_url=form.clone_url.data,
535 committish=form.committish.data,
536 subdirectory=form.subdirectory.data,
537 spec=form.spec.data,
538 srpm_build_method=form.srpm_build_method.data,
539 chroot_names=form.selected_chroots,
540 background=form.background.data,
541 )
542 return process_creating_new_build(copr, form, create_new_build)
543
544
545 @api_ns.route("/coprs/<username>/<coprname>/new_build_distgit/", methods=["POST"])
546 @api_login_required
547 @api_req_with_copr
548 -def copr_new_build_distgit(copr):
564 return process_creating_new_build(copr, form, create_new_build)
565
568 infos = []
569
570
571 infos.extend(validate_post_keys(form))
572
573 if not form.validate_on_submit():
574 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors))
575
576 if not flask.g.user.can_build_in(copr):
577 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}"
578 .format(flask.g.user.username, copr.full_name))
579
580
581 try:
582
583
584 build = create_new_build()
585 db.session.commit()
586 ids = [build.id] if type(build) != list else [b.id for b in build]
587 infos.append("Build was added to {0}:".format(copr.name))
588 for build_id in ids:
589 infos.append(" " + flask.url_for("coprs_ns.copr_build_redirect",
590 build_id=build_id,
591 _external=True))
592
593 except (ActionInProgressException, InsufficientRightsException) as e:
594 raise LegacyApiError("Invalid request: {}".format(e))
595
596 output = {"output": "ok",
597 "ids": ids,
598 "message": "\n".join(infos)}
599
600 return flask.jsonify(output)
601
602
603 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
604 -def build_status(build_id):
609
610
611 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"])
612 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
613 -def build_detail(build_id):
614 build = ComplexLogic.get_build_safe(build_id)
615
616 chroots = {}
617 results_by_chroot = {}
618 for chroot in build.build_chroots:
619 chroots[chroot.name] = chroot.state
620 results_by_chroot[chroot.name] = chroot.result_dir_url
621
622 built_packages = None
623 if build.built_packages:
624 built_packages = build.built_packages.split("\n")
625
626 output = {
627 "output": "ok",
628 "status": build.state,
629 "project": build.copr_name,
630 "project_dirname": build.copr_dirname,
631 "owner": build.copr.owner_name,
632 "results": build.copr.repo_url,
633 "built_pkgs": built_packages,
634 "src_version": build.pkg_version,
635 "chroots": chroots,
636 "submitted_on": build.submitted_on,
637 "started_on": build.min_started_on,
638 "ended_on": build.max_ended_on,
639 "src_pkg": build.pkgs,
640 "submitted_by": build.user.name if build.user else None,
641 "results_by_chroot": results_by_chroot
642 }
643 return flask.jsonify(output)
644
645
646 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
647 @api_login_required
648 -def cancel_build(build_id):
659
660
661 @api_ns.route("/coprs/delete_build/<int:build_id>/", methods=["POST"])
662 @api_login_required
663 -def delete_build(build_id):
674
675
676 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
677 @api_login_required
678 @api_req_with_copr
679 -def copr_modify(copr):
680 form = forms.CoprModifyForm(csrf_enabled=False)
681
682 if not form.validate_on_submit():
683 raise LegacyApiError("Invalid request: {0}".format(form.errors))
684
685
686
687 if form.description.raw_data and len(form.description.raw_data):
688 copr.description = form.description.data
689 if form.instructions.raw_data and len(form.instructions.raw_data):
690 copr.instructions = form.instructions.data
691 if form.repos.raw_data and len(form.repos.raw_data):
692 copr.repos = form.repos.data
693 if form.disable_createrepo.raw_data and len(form.disable_createrepo.raw_data):
694 copr.disable_createrepo = form.disable_createrepo.data
695
696 if "unlisted_on_hp" in flask.request.form:
697 copr.unlisted_on_hp = form.unlisted_on_hp.data
698 if "build_enable_net" in flask.request.form:
699 copr.build_enable_net = form.build_enable_net.data
700 if "auto_prune" in flask.request.form:
701 copr.auto_prune = form.auto_prune.data
702 if "use_bootstrap_container" in flask.request.form:
703 copr.use_bootstrap_container = form.use_bootstrap_container.data
704 if "chroots" in flask.request.form:
705 coprs_logic.CoprChrootsLogic.update_from_names(
706 flask.g.user, copr, form.chroots.data)
707
708 try:
709 CoprsLogic.update(flask.g.user, copr)
710 if copr.group:
711 _ = copr.group.id
712 db.session.commit()
713 except (exceptions.ActionInProgressException,
714 exceptions.InsufficientRightsException,
715 exceptions.NonAdminCannotDisableAutoPrunning) as e:
716 db.session.rollback()
717 raise LegacyApiError("Invalid request: {}".format(e))
718
719 output = {
720 'output': 'ok',
721 'description': copr.description,
722 'instructions': copr.instructions,
723 'repos': copr.repos,
724 'chroots': [c.name for c in copr.mock_chroots],
725 }
726
727 return flask.jsonify(output)
728
729
730 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
731 @api_login_required
732 @api_req_with_copr
733 -def copr_modify_chroot(copr, chrootname):
747
748
749 @api_ns.route('/coprs/<username>/<coprname>/chroot/edit/<chrootname>/', methods=["POST"])
750 @api_login_required
751 @api_req_with_copr
752 -def copr_edit_chroot(copr, chrootname):
753 form = forms.ModifyChrootForm(csrf_enabled=False)
754 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname)
755
756 if not form.validate_on_submit():
757 raise LegacyApiError("Invalid request: {0}".format(form.errors))
758 else:
759 buildroot_pkgs = repos = comps_xml = comps_name = None
760 if "buildroot_pkgs" in flask.request.form:
761 buildroot_pkgs = form.buildroot_pkgs.data
762 if "repos" in flask.request.form:
763 repos = form.repos.data
764 if form.upload_comps.has_file():
765 comps_xml = form.upload_comps.data.stream.read()
766 comps_name = form.upload_comps.data.filename
767 if form.delete_comps.data:
768 coprs_logic.CoprChrootsLogic.remove_comps(flask.g.user, chroot)
769 coprs_logic.CoprChrootsLogic.update_chroot(
770 flask.g.user, chroot, buildroot_pkgs, repos, comps=comps_xml, comps_name=comps_name)
771 db.session.commit()
772
773 output = {
774 "output": "ok",
775 "message": "Edit chroot operation was successful.",
776 "chroot": chroot.to_dict(),
777 }
778 return flask.jsonify(output)
779
780
781 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
782 @api_req_with_copr
783 -def copr_chroot_details(copr, chrootname):
788
789 @api_ns.route('/coprs/<username>/<coprname>/chroot/get/<chrootname>/', methods=["GET"])
790 @api_req_with_copr
791 -def copr_get_chroot(copr, chrootname):
795
799 """ Return the list of coprs found in search by the given text.
800 project is taken either from GET params or from the URL itself
801 (in this order).
802
803 :arg project: the text one would like find for coprs.
804
805 """
806 project = flask.request.args.get("project", None) or project
807 if not project:
808 raise LegacyApiError("No project found.")
809
810 try:
811 query = CoprsLogic.get_multiple_fulltext(project)
812
813 repos = query.all()
814 output = {"output": "ok", "repos": []}
815 for repo in repos:
816 output["repos"].append({"username": repo.user.name,
817 "coprname": repo.name,
818 "description": repo.description})
819 except ValueError as e:
820 raise LegacyApiError("Server error: {}".format(e))
821
822 return flask.jsonify(output)
823
827 """ Return list of coprs which are part of playground """
828 query = CoprsLogic.get_playground()
829 repos = query.all()
830 output = {"output": "ok", "repos": []}
831 for repo in repos:
832 output["repos"].append({"username": repo.owner_name,
833 "coprname": repo.name,
834 "chroots": [chroot.name for chroot in repo.active_chroots]})
835
836 jsonout = flask.jsonify(output)
837 jsonout.status_code = 200
838 return jsonout
839
840
841 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
842 @api_req_with_copr
843 -def monitor(copr):
847
848
849
850 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
851 @api_login_required
852 @api_req_with_copr
853 -def copr_add_package(copr, source_type_text):
855
856
857 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
858 @api_login_required
859 @api_req_with_copr
860 -def copr_edit_package(copr, package_name, source_type_text):
867
908
911 params = {}
912 if flask.request.args.get('with_latest_build'):
913 params['with_latest_build'] = True
914 if flask.request.args.get('with_latest_succeeded_build'):
915 params['with_latest_succeeded_build'] = True
916 if flask.request.args.get('with_all_builds'):
917 params['with_all_builds'] = True
918 return params
919
922 """
923 A lagging generator to stream JSON so we don't have to hold everything in memory
924 This is a little tricky, as we need to omit the last comma to make valid JSON,
925 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
926 """
927 packages = query.__iter__()
928 try:
929 prev_package = next(packages)
930 except StopIteration:
931
932 yield '{"packages": []}'
933 raise StopIteration
934
935 yield '{"packages": ['
936
937 for package in packages:
938 yield json.dumps(prev_package.to_dict(**params)) + ', '
939 prev_package = package
940
941 yield json.dumps(prev_package.to_dict(**params)) + ']}'
942
943
944 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
945 @api_req_with_copr
946 -def copr_list_packages(copr):
950
951
952
953 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
954 @api_req_with_copr
955 -def copr_get_package(copr, package_name):
964
965
966 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
967 @api_login_required
968 @api_req_with_copr
969 -def copr_delete_package(copr, package_name):
986
987
988 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
989 @api_login_required
990 @api_req_with_copr
991 -def copr_reset_package(copr, package_name):
1008
1009
1010 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
1011 @api_login_required
1012 @api_req_with_copr
1013 -def copr_build_package(copr, package_name):
1014 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False)
1015
1016 try:
1017 package = PackagesLogic.get(copr.main_dir.id, package_name)[0]
1018 except IndexError:
1019 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name))
1020
1021 if form.validate_on_submit():
1022 try:
1023 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data)
1024 db.session.commit()
1025 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e:
1026 raise LegacyApiError(str(e))
1027 else:
1028 raise LegacyApiError(form.errors)
1029
1030 return flask.jsonify({
1031 "output": "ok",
1032 "ids": [build.id],
1033 "message": "Build was added to {0}.".format(copr.name)
1034 })
1035
1036
1037 @api_ns.route("/coprs/<username>/<coprname>/module/build/", methods=["POST"])
1038 @api_login_required
1039 @api_req_with_copr
1040 -def copr_build_module(copr):
1063
1064
1065 @api_ns.route("/coprs/<username>/<coprname>/build-config/<chroot>/", methods=["GET"])
1066 @api_ns.route("/g/<group_name>/<coprname>/build-config/<chroot>/", methods=["GET"])
1067 @api_req_with_copr
1068 -def copr_build_config(copr, chroot):
1069 """
1070 Generate build configuration.
1071 """
1072 output = {
1073 "output": "ok",
1074 "build_config": generate_build_config(copr, chroot),
1075 }
1076
1077 if not output['build_config']:
1078 raise LegacyApiError('Chroot not found.')
1079
1080 return flask.jsonify(output)
1081