1 import base64
2 import datetime
3 from functools import wraps
4 import json
5 import os
6 import flask
7 import sqlalchemy
8 import json
9 import requests
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
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 UnknownSourceTypeException)
53 return wrapper
54
58 """
59 Render the home page of the api.
60 This page provides information on how to call/use the API.
61 """
62
63 return flask.render_template("api.html")
64
65
66 @api_ns.route("/new/", methods=["GET", "POST"])
87
90 infos = []
91
92
93 proxyuser_keys = ["username"]
94 allowed = form.__dict__.keys() + proxyuser_keys
95 for post_key in flask.request.form.keys():
96 if post_key not in allowed:
97 infos.append("Unknown key '{key}' received.".format(key=post_key))
98 return infos
99
100
101 @api_ns.route("/status")
102 -def api_status():
112
113
114 @api_ns.route("/coprs/<username>/new/", methods=["POST"])
117 """
118 Receive information from the user on how to create its new copr,
119 check their validity and create the corresponding copr.
120
121 :arg name: the name of the copr to add
122 :arg chroots: a comma separated list of chroots to use
123 :kwarg repos: a comma separated list of repository that this copr
124 can use.
125 :kwarg initial_pkgs: a comma separated list of initial packages to
126 build in this new copr
127
128 """
129
130 form = forms.CoprFormFactory.create_form_cls()(csrf_enabled=False)
131 infos = []
132
133
134 infos.extend(validate_post_keys(form))
135
136 if form.validate_on_submit():
137 group = ComplexLogic.get_group_by_name_safe(username[1:]) if username[0] == "@" else None
138
139 auto_prune = True
140 if "auto_prune" in flask.request.form:
141 auto_prune = form.auto_prune.data
142
143 try:
144 copr = CoprsLogic.add(
145 name=form.name.data.strip(),
146 repos=" ".join(form.repos.data.split()),
147 user=flask.g.user,
148 selected_chroots=form.selected_chroots,
149 description=form.description.data,
150 instructions=form.instructions.data,
151 check_for_duplicates=True,
152 disable_createrepo=form.disable_createrepo.data,
153 unlisted_on_hp=form.unlisted_on_hp.data,
154 build_enable_net=form.build_enable_net.data,
155 group=group,
156 persistent=form.persistent.data,
157 auto_prune=auto_prune,
158 )
159 infos.append("New project was successfully created.")
160
161 if form.initial_pkgs.data:
162 pkgs = form.initial_pkgs.data.split()
163 for pkg in pkgs:
164 builds_logic.BuildsLogic.add(
165 user=flask.g.user,
166 pkgs=pkg,
167 srpm_url=pkg,
168 copr=copr)
169
170 infos.append("Initial packages were successfully "
171 "submitted for building.")
172
173 output = {"output": "ok", "message": "\n".join(infos)}
174 db.session.commit()
175 except (exceptions.DuplicateException,
176 exceptions.NonAdminCannotCreatePersistentProject,
177 exceptions.NonAdminCannotDisableAutoPrunning) as err:
178 db.session.rollback()
179 raise LegacyApiError(str(err))
180
181 else:
182 errormsg = "Validation error\n"
183 if form.errors:
184 for field, emsgs in form.errors.items():
185 errormsg += "- {0}: {1}\n".format(field, "\n".join(emsgs))
186
187 errormsg = errormsg.replace('"', "'")
188 raise LegacyApiError(errormsg)
189
190 return flask.jsonify(output)
191
192
193 @api_ns.route("/coprs/<username>/<coprname>/delete/", methods=["POST"])
218
219
220 @api_ns.route("/coprs/<username>/<coprname>/fork/", methods=["POST"])
221 @api_login_required
222 @api_req_with_copr
223 -def api_copr_fork(copr):
224 """ Fork the project and builds in it
225 """
226 form = forms.CoprForkFormFactory\
227 .create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)(csrf_enabled=False)
228
229 if form.validate_on_submit() and copr:
230 try:
231 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0]
232 if flask.g.user.name != form.owner.data and not dstgroup:
233 return LegacyApiError("There is no such group: {}".format(form.owner.data))
234
235 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup)
236 if created:
237 msg = ("Forking project {} for you into {}.\nPlease be aware that it may take a few minutes "
238 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
239 elif not created and form.confirm.data == True:
240 msg = ("Updating packages in {} from {}.\nPlease be aware that it may take a few minutes "
241 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
242 else:
243 raise LegacyApiError("You are about to fork into existing project: {}\n"
244 "Please use --confirm if you really want to do this".format(fcopr.full_name))
245
246 output = {"output": "ok", "message": msg}
247 db.session.commit()
248
249 except (exceptions.ActionInProgressException,
250 exceptions.InsufficientRightsException) as err:
251 db.session.rollback()
252 raise LegacyApiError(str(err))
253 else:
254 raise LegacyApiError("Invalid request: {0}".format(form.errors))
255
256 return flask.jsonify(output)
257
258
259 @api_ns.route("/coprs/")
260 @api_ns.route("/coprs/<username>/")
261 -def api_coprs_by_owner(username=None):
262 """ Return the list of coprs owned by the given user.
263 username is taken either from GET params or from the URL itself
264 (in this order).
265
266 :arg username: the username of the person one would like to the
267 coprs of.
268
269 """
270 username = flask.request.args.get("username", None) or username
271 if username is None:
272 raise LegacyApiError("Invalid request: missing `username` ")
273
274 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
275
276 if username.startswith("@"):
277 group_name = username[1:]
278 query = CoprsLogic.get_multiple()
279 query = CoprsLogic.filter_by_group_name(query, group_name)
280 else:
281 query = CoprsLogic.get_multiple_owned_by_username(username)
282
283 query = CoprsLogic.join_builds(query)
284 query = CoprsLogic.set_query_order(query)
285
286 repos = query.all()
287 output = {"output": "ok", "repos": []}
288 for repo in repos:
289 yum_repos = {}
290 for build in repo.builds:
291 if build.results:
292 for chroot in repo.active_chroots:
293 release = release_tmpl.format(chroot=chroot)
294 yum_repos[release] = fix_protocol_for_backend(
295 os.path.join(build.results, release + '/'))
296 break
297
298 output["repos"].append({"name": repo.name,
299 "additional_repos": repo.repos,
300 "yum_repos": yum_repos,
301 "description": repo.description,
302 "instructions": repo.instructions,
303 "persistent": repo.persistent,
304 "unlisted_on_hp": repo.unlisted_on_hp,
305 "auto_prune": repo.auto_prune,
306 })
307
308 return flask.jsonify(output)
309
314 """ Return detail of one project.
315
316 :arg username: the username of the person one would like to the
317 coprs of.
318 :arg coprname: the name of project.
319
320 """
321 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
322 output = {"output": "ok", "detail": {}}
323 yum_repos = {}
324
325 build = models.Build.query.filter(
326 models.Build.copr_id == copr.id, models.Build.results != None).first()
327
328 if build:
329 for chroot in copr.active_chroots:
330 release = release_tmpl.format(chroot=chroot)
331 yum_repos[release] = fix_protocol_for_backend(
332 os.path.join(build.results, release + '/'))
333
334 output["detail"] = {
335 "name": copr.name,
336 "additional_repos": copr.repos,
337 "yum_repos": yum_repos,
338 "description": copr.description,
339 "instructions": copr.instructions,
340 "last_modified": builds_logic.BuildsLogic.last_modified(copr),
341 "auto_createrepo": copr.auto_createrepo,
342 "persistent": copr.persistent,
343 "unlisted_on_hp": copr.unlisted_on_hp,
344 "auto_prune": copr.auto_prune,
345 }
346 return flask.jsonify(output)
347
348
349 @api_ns.route("/auth_check/", methods=["POST"])
352 output = {"output": "ok"}
353 return flask.jsonify(output)
354
355
356 @api_ns.route("/coprs/<username>/<coprname>/new_build/", methods=["POST"])
357 @api_login_required
358 @api_req_with_copr
359 -def copr_new_build(copr):
371 return process_creating_new_build(copr, form, create_new_build)
372
373
374 @api_ns.route("/coprs/<username>/<coprname>/new_build_upload/", methods=["POST"])
388 return process_creating_new_build(copr, form, create_new_build)
389
390
391 @api_ns.route("/coprs/<username>/<coprname>/new_build_pypi/", methods=["POST"])
411 return process_creating_new_build(copr, form, create_new_build)
412
413
414 @api_ns.route("/coprs/<username>/<coprname>/new_build_tito/", methods=["POST"])
435 return process_creating_new_build(copr, form, create_new_build)
436
437
438 @api_ns.route("/coprs/<username>/<coprname>/new_build_mock/", methods=["POST"])
459 return process_creating_new_build(copr, form, create_new_build)
460
461
462 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
476 return process_creating_new_build(copr, form, create_new_build)
477
478
479 @api_ns.route("/coprs/<username>/<coprname>/new_build_scm/", methods=["POST"])
483 form = forms.BuildFormScmFactory(copr.active_chroots)(csrf_enabled=False)
484
485 def create_new_build():
486 return BuildsLogic.create_new_from_scm(
487 flask.g.user,
488 copr,
489 scm_type=form.scm_type.data,
490 clone_url=form.clone_url.data,
491 committish=form.committish.data,
492 subdirectory=form.subdirectory.data,
493 spec=form.spec.data,
494 srpm_build_method=form.srpm_build_method.data,
495 chroot_names=form.selected_chroots,
496 background=form.background.data,
497 )
498 return process_creating_new_build(copr, form, create_new_build)
499
500
501 @api_ns.route("/coprs/<username>/<coprname>/new_build_distgit/", methods=["POST"])
520 return process_creating_new_build(copr, form, create_new_build)
521
524 infos = []
525
526
527 infos.extend(validate_post_keys(form))
528
529 if not form.validate_on_submit():
530 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors))
531
532 if not flask.g.user.can_build_in(copr):
533 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}"
534 .format(flask.g.user.username, copr.full_name))
535
536
537 try:
538
539
540 build = create_new_build()
541 db.session.commit()
542 ids = [build.id] if type(build) != list else [b.id for b in build]
543 infos.append("Build was added to {0}:".format(copr.name))
544 for build_id in ids:
545 infos.append(" " + flask.url_for("coprs_ns.copr_build_redirect",
546 build_id=build_id,
547 _external=True))
548
549 except (ActionInProgressException, InsufficientRightsException) as e:
550 raise LegacyApiError("Invalid request: {}".format(e))
551
552 output = {"output": "ok",
553 "ids": ids,
554 "message": "\n".join(infos)}
555
556 return flask.jsonify(output)
557
558
559 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
566
567
568 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"])
569 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
571 build = ComplexLogic.get_build_safe(build_id)
572
573 chroots = {}
574 results_by_chroot = {}
575 for chroot in build.build_chroots:
576 chroots[chroot.name] = chroot.state
577 results_by_chroot[chroot.name] = chroot.result_dir_url
578
579 built_packages = None
580 if build.built_packages:
581 built_packages = build.built_packages.split("\n")
582
583 output = {
584 "output": "ok",
585 "status": build.state,
586 "project": build.copr.name,
587 "owner": build.copr.owner_name,
588 "results": build.results,
589 "built_pkgs": built_packages,
590 "src_version": build.pkg_version,
591 "chroots": chroots,
592 "submitted_on": build.submitted_on,
593 "started_on": build.min_started_on,
594 "ended_on": build.max_ended_on,
595 "src_pkg": build.pkgs,
596 "submitted_by": build.user.name if build.user else None,
597 "results_by_chroot": results_by_chroot
598 }
599 return flask.jsonify(output)
600
601
602 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
615
616
617 @api_ns.route("/coprs/delete_build/<int:build_id>/", methods=["POST"])
630
631
632 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
633 @api_login_required
634 @api_req_with_copr
635 -def copr_modify(copr):
636 form = forms.CoprModifyForm(csrf_enabled=False)
637
638 if not form.validate_on_submit():
639 raise LegacyApiError("Invalid request: {0}".format(form.errors))
640
641
642
643 if form.description.raw_data and len(form.description.raw_data):
644 copr.description = form.description.data
645 if form.instructions.raw_data and len(form.instructions.raw_data):
646 copr.instructions = form.instructions.data
647 if form.repos.raw_data and len(form.repos.raw_data):
648 copr.repos = form.repos.data
649 if form.disable_createrepo.raw_data and len(form.disable_createrepo.raw_data):
650 copr.disable_createrepo = form.disable_createrepo.data
651
652 if "unlisted_on_hp" in flask.request.form:
653 copr.unlisted_on_hp = form.unlisted_on_hp.data
654 if "build_enable_net" in flask.request.form:
655 copr.build_enable_net = form.build_enable_net.data
656 if "auto_prune" in flask.request.form:
657 copr.auto_prune = form.auto_prune.data
658 if "chroots" in flask.request.form:
659 coprs_logic.CoprChrootsLogic.update_from_names(
660 flask.g.user, copr, form.chroots.data)
661
662 try:
663 CoprsLogic.update(flask.g.user, copr)
664 if copr.group:
665 _ = copr.group.id
666 db.session.commit()
667 except (exceptions.ActionInProgressException,
668 exceptions.InsufficientRightsException,
669 exceptions.NonAdminCannotDisableAutoPrunning) as e:
670 db.session.rollback()
671 raise LegacyApiError("Invalid request: {}".format(e))
672
673 output = {
674 'output': 'ok',
675 'description': copr.description,
676 'instructions': copr.instructions,
677 'repos': copr.repos,
678 'chroots': [c.name for c in copr.mock_chroots],
679 }
680
681 return flask.jsonify(output)
682
683
684 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
701
702
703 @api_ns.route('/coprs/<username>/<coprname>/chroot/edit/<chrootname>/', methods=["POST"])
704 @api_login_required
705 @api_req_with_copr
706 -def copr_edit_chroot(copr, chrootname):
707 form = forms.ModifyChrootForm(csrf_enabled=False)
708 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname)
709
710 if not form.validate_on_submit():
711 raise LegacyApiError("Invalid request: {0}".format(form.errors))
712 else:
713 buildroot_pkgs = repos = comps_xml = comps_name = None
714 if "buildroot_pkgs" in flask.request.form:
715 buildroot_pkgs = form.buildroot_pkgs.data
716 if "repos" in flask.request.form:
717 repos = form.repos.data
718 if form.upload_comps.has_file():
719 comps_xml = form.upload_comps.data.stream.read()
720 comps_name = form.upload_comps.data.filename
721 if form.delete_comps.data:
722 coprs_logic.CoprChrootsLogic.remove_comps(flask.g.user, chroot)
723 coprs_logic.CoprChrootsLogic.update_chroot(
724 flask.g.user, chroot, buildroot_pkgs, repos, comps=comps_xml, comps_name=comps_name)
725 db.session.commit()
726
727 output = {
728 "output": "ok",
729 "message": "Edit chroot operation was successful.",
730 "chroot": chroot.to_dict(),
731 }
732 return flask.jsonify(output)
733
734
735 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
742
743 @api_ns.route('/coprs/<username>/<coprname>/chroot/get/<chrootname>/', methods=["GET"])
749
753 """ Return the list of coprs found in search by the given text.
754 project is taken either from GET params or from the URL itself
755 (in this order).
756
757 :arg project: the text one would like find for coprs.
758
759 """
760 project = flask.request.args.get("project", None) or project
761 if not project:
762 raise LegacyApiError("No project found.")
763
764 try:
765 query = CoprsLogic.get_multiple_fulltext(project)
766
767 repos = query.all()
768 output = {"output": "ok", "repos": []}
769 for repo in repos:
770 output["repos"].append({"username": repo.user.name,
771 "coprname": repo.name,
772 "description": repo.description})
773 except ValueError as e:
774 raise LegacyApiError("Server error: {}".format(e))
775
776 return flask.jsonify(output)
777
781 """ Return list of coprs which are part of playground """
782 query = CoprsLogic.get_playground()
783 repos = query.all()
784 output = {"output": "ok", "repos": []}
785 for repo in repos:
786 output["repos"].append({"username": repo.owner_name,
787 "coprname": repo.name,
788 "chroots": [chroot.name for chroot in repo.active_chroots]})
789
790 jsonout = flask.jsonify(output)
791 jsonout.status_code = 200
792 return jsonout
793
794
795 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
796 @api_req_with_copr
797 -def monitor(copr):
801
802
803
804 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
805 @api_login_required
806 @api_req_with_copr
807 -def copr_add_package(copr, source_type_text):
809
810
811 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
812 @api_login_required
813 @api_req_with_copr
814 -def copr_edit_package(copr, package_name, source_type_text):
820
857
860 params = {}
861 if flask.request.args.get('with_latest_build'):
862 params['with_latest_build'] = True
863 if flask.request.args.get('with_latest_succeeded_build'):
864 params['with_latest_succeeded_build'] = True
865 if flask.request.args.get('with_all_builds'):
866 params['with_all_builds'] = True
867 return params
868
871 """
872 A lagging generator to stream JSON so we don't have to hold everything in memory
873 This is a little tricky, as we need to omit the last comma to make valid JSON,
874 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
875 """
876 packages = query.__iter__()
877 try:
878 prev_package = next(packages)
879 except StopIteration:
880
881 yield '{"packages": []}'
882 raise StopIteration
883
884 yield '{"packages": ['
885
886 for package in packages:
887 yield json.dumps(prev_package.to_dict(**params)) + ', '
888 prev_package = package
889
890 yield json.dumps(prev_package.to_dict(**params)) + ']}'
891
892
893 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
899
900
901
902 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
912
913
914 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
934
935
936 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
937 @api_login_required
938 @api_req_with_copr
939 -def copr_reset_package(copr, package_name):
956
957
958 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
959 @api_login_required
960 @api_req_with_copr
961 -def copr_build_package(copr, package_name):
962 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False)
963
964 try:
965 package = PackagesLogic.get(copr.id, package_name)[0]
966 except IndexError:
967 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name))
968
969 if form.validate_on_submit():
970 try:
971 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data)
972 db.session.commit()
973 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e:
974 raise LegacyApiError(str(e))
975 else:
976 raise LegacyApiError(form.errors)
977
978 return flask.jsonify({
979 "output": "ok",
980 "ids": [build.id],
981 "message": "Build was added to {0}.".format(copr.name)
982 })
983
984
985 @api_ns.route("/module/build/", methods=["POST"])
988 form = forms.ModuleBuildForm(csrf_enabled=False)
989 if not form.validate_on_submit():
990 raise LegacyApiError(form.errors)
991
992 try:
993 common = {"owner": flask.g.user.name,
994 "copr_owner": form.copr_owner.data,
995 "copr_project": form.copr_project.data}
996 if form.scmurl.data:
997 kwargs = {"json": dict({"scmurl": form.scmurl.data, "branch": form.branch.data}, **common)}
998 else:
999 kwargs = {"data": common, "files": {"yaml": (form.modulemd.data.filename, form.modulemd.data)}}
1000
1001 response = requests.post(flask.current_app.config["MBS_URL"], verify=False, **kwargs)
1002 if response.status_code == 500:
1003 raise LegacyApiError("Error from MBS: {} - {}".format(response.status_code, response.reason))
1004
1005 resp = json.loads(response.content)
1006 if response.status_code != 201:
1007 raise LegacyApiError("Error from MBS: {}".format(resp["message"]))
1008
1009 return flask.jsonify({
1010 "output": "ok",
1011 "message": "Created module {}-{}-{}".format(resp["name"], resp["stream"], resp["version"]),
1012 })
1013
1014 except requests.ConnectionError:
1015 raise LegacyApiError("Can't connect to MBS instance")
1016
1017
1018 @api_ns.route("/coprs/<username>/<coprname>/module/make/", methods=["POST"])
1022 form = forms.ModuleFormUploadFactory(csrf_enabled=False)
1023 if not form.validate_on_submit():
1024
1025 raise LegacyApiError(form.errors)
1026
1027 modulemd = form.modulemd.data.read()
1028 module = ModulesLogic.from_modulemd(modulemd)
1029 try:
1030 ModulesLogic.validate(modulemd)
1031 msg = "Nothing happened"
1032 if form.create.data:
1033 module = ModulesLogic.add(flask.g.user, copr, module)
1034 db.session.flush()
1035 msg = "Module was created"
1036
1037 if form.build.data:
1038 if not module.id:
1039 module = ModulesLogic.get_by_nsv(copr, module.name, module.stream, module.version).one()
1040 ActionsLogic.send_build_module(flask.g.user, copr, module)
1041 msg = "Module build was submitted"
1042 db.session.commit()
1043
1044 return flask.jsonify({
1045 "output": "ok",
1046 "message": msg,
1047 "modulemd": modulemd,
1048 })
1049
1050 except sqlalchemy.exc.IntegrityError:
1051 raise LegacyApiError({"nsv": ["Module {} already exists".format(module.nsv)]})
1052
1053 except sqlalchemy.orm.exc.NoResultFound:
1054 raise LegacyApiError({"nsv": ["Module {} doesn't exist. You need to create it first".format(module.nsv)]})
1055
1056 except ValidationError as ex:
1057 raise LegacyApiError({"nsv": [ex.message]})
1058
1059
1060 @api_ns.route("/coprs/<username>/<coprname>/build-config/<chroot>/", methods=["GET"])
1061 @api_ns.route("/g/<group_name>/<coprname>/build-config/<chroot>/", methods=["GET"])
1064 """
1065 Generate build configuration.
1066 """
1067 output = {
1068 "output": "ok",
1069 "build_config": generate_build_config(copr, chroot),
1070 }
1071
1072 if not output['build_config']:
1073 raise LegacyApiError('Chroot not found.')
1074
1075 return flask.jsonify(output)
1076
1077
1078 @api_ns.route("/module/repo/", methods=["POST"])
1094