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 use_bootstrap_container = True
144 if "use_bootstrap_container" in flask.request.form:
145 use_bootstrap_container = form.use_bootstrap_container.data
146
147 try:
148 copr = CoprsLogic.add(
149 name=form.name.data.strip(),
150 repos=" ".join(form.repos.data.split()),
151 user=flask.g.user,
152 selected_chroots=form.selected_chroots,
153 description=form.description.data,
154 instructions=form.instructions.data,
155 check_for_duplicates=True,
156 disable_createrepo=form.disable_createrepo.data,
157 unlisted_on_hp=form.unlisted_on_hp.data,
158 build_enable_net=form.build_enable_net.data,
159 group=group,
160 persistent=form.persistent.data,
161 auto_prune=auto_prune,
162 use_bootstrap_container=use_bootstrap_container,
163 )
164 infos.append("New project was successfully created.")
165
166 if form.initial_pkgs.data:
167 pkgs = form.initial_pkgs.data.split()
168 for pkg in pkgs:
169 builds_logic.BuildsLogic.add(
170 user=flask.g.user,
171 pkgs=pkg,
172 srpm_url=pkg,
173 copr=copr)
174
175 infos.append("Initial packages were successfully "
176 "submitted for building.")
177
178 output = {"output": "ok", "message": "\n".join(infos)}
179 db.session.commit()
180 except (exceptions.DuplicateException,
181 exceptions.NonAdminCannotCreatePersistentProject,
182 exceptions.NonAdminCannotDisableAutoPrunning) as err:
183 db.session.rollback()
184 raise LegacyApiError(str(err))
185
186 else:
187 errormsg = "Validation error\n"
188 if form.errors:
189 for field, emsgs in form.errors.items():
190 errormsg += "- {0}: {1}\n".format(field, "\n".join(emsgs))
191
192 errormsg = errormsg.replace('"', "'")
193 raise LegacyApiError(errormsg)
194
195 return flask.jsonify(output)
196
197
198 @api_ns.route("/coprs/<username>/<coprname>/delete/", methods=["POST"])
223
224
225 @api_ns.route("/coprs/<username>/<coprname>/fork/", methods=["POST"])
226 @api_login_required
227 @api_req_with_copr
228 -def api_copr_fork(copr):
229 """ Fork the project and builds in it
230 """
231 form = forms.CoprForkFormFactory\
232 .create_form_cls(copr=copr, user=flask.g.user, groups=flask.g.user.user_groups)(csrf_enabled=False)
233
234 if form.validate_on_submit() and copr:
235 try:
236 dstgroup = ([g for g in flask.g.user.user_groups if g.at_name == form.owner.data] or [None])[0]
237 if flask.g.user.name != form.owner.data and not dstgroup:
238 return LegacyApiError("There is no such group: {}".format(form.owner.data))
239
240 fcopr, created = ComplexLogic.fork_copr(copr, flask.g.user, dstname=form.name.data, dstgroup=dstgroup)
241 if created:
242 msg = ("Forking project {} for you into {}.\nPlease be aware that it may take a few minutes "
243 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
244 elif not created and form.confirm.data == True:
245 msg = ("Updating packages in {} from {}.\nPlease be aware that it may take a few minutes "
246 "to duplicate a backend data.".format(copr.full_name, fcopr.full_name))
247 else:
248 raise LegacyApiError("You are about to fork into existing project: {}\n"
249 "Please use --confirm if you really want to do this".format(fcopr.full_name))
250
251 output = {"output": "ok", "message": msg}
252 db.session.commit()
253
254 except (exceptions.ActionInProgressException,
255 exceptions.InsufficientRightsException) as err:
256 db.session.rollback()
257 raise LegacyApiError(str(err))
258 else:
259 raise LegacyApiError("Invalid request: {0}".format(form.errors))
260
261 return flask.jsonify(output)
262
263
264 @api_ns.route("/coprs/")
265 @api_ns.route("/coprs/<username>/")
266 -def api_coprs_by_owner(username=None):
267 """ Return the list of coprs owned by the given user.
268 username is taken either from GET params or from the URL itself
269 (in this order).
270
271 :arg username: the username of the person one would like to the
272 coprs of.
273
274 """
275 username = flask.request.args.get("username", None) or username
276 if username is None:
277 raise LegacyApiError("Invalid request: missing `username` ")
278
279 release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
280
281 if username.startswith("@"):
282 group_name = username[1:]
283 query = CoprsLogic.get_multiple()
284 query = CoprsLogic.filter_by_group_name(query, group_name)
285 else:
286 query = CoprsLogic.get_multiple_owned_by_username(username)
287
288 query = CoprsLogic.join_builds(query)
289 query = CoprsLogic.set_query_order(query)
290
291 repos = query.all()
292 output = {"output": "ok", "repos": []}
293 for repo in repos:
294 yum_repos = {}
295 for build in repo.builds:
296 if build.results:
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.results, 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(
331 models.Build.copr_id == copr.id, models.Build.results != None).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.results, 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"])
358 output = {"output": "ok"}
359 return flask.jsonify(output)
360
361
362 @api_ns.route("/coprs/<username>/<coprname>/new_build/", methods=["POST"])
363 @api_login_required
364 @api_req_with_copr
365 -def copr_new_build(copr):
377 return process_creating_new_build(copr, form, create_new_build)
378
379
380 @api_ns.route("/coprs/<username>/<coprname>/new_build_upload/", methods=["POST"])
394 return process_creating_new_build(copr, form, create_new_build)
395
396
397 @api_ns.route("/coprs/<username>/<coprname>/new_build_pypi/", methods=["POST"])
417 return process_creating_new_build(copr, form, create_new_build)
418
419
420 @api_ns.route("/coprs/<username>/<coprname>/new_build_tito/", methods=["POST"])
441 return process_creating_new_build(copr, form, create_new_build)
442
443
444 @api_ns.route("/coprs/<username>/<coprname>/new_build_mock/", methods=["POST"])
465 return process_creating_new_build(copr, form, create_new_build)
466
467
468 @api_ns.route("/coprs/<username>/<coprname>/new_build_rubygems/", methods=["POST"])
482 return process_creating_new_build(copr, form, create_new_build)
483
484
485 @api_ns.route("/coprs/<username>/<coprname>/new_build_scm/", methods=["POST"])
489 form = forms.BuildFormScmFactory(copr.active_chroots)(csrf_enabled=False)
490
491 def create_new_build():
492 return BuildsLogic.create_new_from_scm(
493 flask.g.user,
494 copr,
495 scm_type=form.scm_type.data,
496 clone_url=form.clone_url.data,
497 committish=form.committish.data,
498 subdirectory=form.subdirectory.data,
499 spec=form.spec.data,
500 srpm_build_method=form.srpm_build_method.data,
501 chroot_names=form.selected_chroots,
502 background=form.background.data,
503 )
504 return process_creating_new_build(copr, form, create_new_build)
505
506
507 @api_ns.route("/coprs/<username>/<coprname>/new_build_distgit/", methods=["POST"])
526 return process_creating_new_build(copr, form, create_new_build)
527
530 infos = []
531
532
533 infos.extend(validate_post_keys(form))
534
535 if not form.validate_on_submit():
536 raise LegacyApiError("Invalid request: bad request parameters: {0}".format(form.errors))
537
538 if not flask.g.user.can_build_in(copr):
539 raise LegacyApiError("Invalid request: user {} is not allowed to build in the copr: {}"
540 .format(flask.g.user.username, copr.full_name))
541
542
543 try:
544
545
546 build = create_new_build()
547 db.session.commit()
548 ids = [build.id] if type(build) != list else [b.id for b in build]
549 infos.append("Build was added to {0}:".format(copr.name))
550 for build_id in ids:
551 infos.append(" " + flask.url_for("coprs_ns.copr_build_redirect",
552 build_id=build_id,
553 _external=True))
554
555 except (ActionInProgressException, InsufficientRightsException) as e:
556 raise LegacyApiError("Invalid request: {}".format(e))
557
558 output = {"output": "ok",
559 "ids": ids,
560 "message": "\n".join(infos)}
561
562 return flask.jsonify(output)
563
564
565 @api_ns.route("/coprs/build_status/<int:build_id>/", methods=["GET"])
572
573
574 @api_ns.route("/coprs/build_detail/<int:build_id>/", methods=["GET"])
575 @api_ns.route("/coprs/build/<int:build_id>/", methods=["GET"])
577 build = ComplexLogic.get_build_safe(build_id)
578
579 chroots = {}
580 results_by_chroot = {}
581 for chroot in build.build_chroots:
582 chroots[chroot.name] = chroot.state
583 results_by_chroot[chroot.name] = chroot.result_dir_url
584
585 built_packages = None
586 if build.built_packages:
587 built_packages = build.built_packages.split("\n")
588
589 output = {
590 "output": "ok",
591 "status": build.state,
592 "project": build.copr.name,
593 "owner": build.copr.owner_name,
594 "results": build.results,
595 "built_pkgs": built_packages,
596 "src_version": build.pkg_version,
597 "chroots": chroots,
598 "submitted_on": build.submitted_on,
599 "started_on": build.min_started_on,
600 "ended_on": build.max_ended_on,
601 "src_pkg": build.pkgs,
602 "submitted_by": build.user.name if build.user else None,
603 "results_by_chroot": results_by_chroot
604 }
605 return flask.jsonify(output)
606
607
608 @api_ns.route("/coprs/cancel_build/<int:build_id>/", methods=["POST"])
621
622
623 @api_ns.route("/coprs/delete_build/<int:build_id>/", methods=["POST"])
636
637
638 @api_ns.route('/coprs/<username>/<coprname>/modify/', methods=["POST"])
639 @api_login_required
640 @api_req_with_copr
641 -def copr_modify(copr):
642 form = forms.CoprModifyForm(csrf_enabled=False)
643
644 if not form.validate_on_submit():
645 raise LegacyApiError("Invalid request: {0}".format(form.errors))
646
647
648
649 if form.description.raw_data and len(form.description.raw_data):
650 copr.description = form.description.data
651 if form.instructions.raw_data and len(form.instructions.raw_data):
652 copr.instructions = form.instructions.data
653 if form.repos.raw_data and len(form.repos.raw_data):
654 copr.repos = form.repos.data
655 if form.disable_createrepo.raw_data and len(form.disable_createrepo.raw_data):
656 copr.disable_createrepo = form.disable_createrepo.data
657
658 if "unlisted_on_hp" in flask.request.form:
659 copr.unlisted_on_hp = form.unlisted_on_hp.data
660 if "build_enable_net" in flask.request.form:
661 copr.build_enable_net = form.build_enable_net.data
662 if "auto_prune" in flask.request.form:
663 copr.auto_prune = form.auto_prune.data
664 if "use_bootstrap_container" in flask.request.form:
665 copr.use_bootstrap_container = form.use_bootstrap_container.data
666 if "chroots" in flask.request.form:
667 coprs_logic.CoprChrootsLogic.update_from_names(
668 flask.g.user, copr, form.chroots.data)
669
670 try:
671 CoprsLogic.update(flask.g.user, copr)
672 if copr.group:
673 _ = copr.group.id
674 db.session.commit()
675 except (exceptions.ActionInProgressException,
676 exceptions.InsufficientRightsException,
677 exceptions.NonAdminCannotDisableAutoPrunning) as e:
678 db.session.rollback()
679 raise LegacyApiError("Invalid request: {}".format(e))
680
681 output = {
682 'output': 'ok',
683 'description': copr.description,
684 'instructions': copr.instructions,
685 'repos': copr.repos,
686 'chroots': [c.name for c in copr.mock_chroots],
687 }
688
689 return flask.jsonify(output)
690
691
692 @api_ns.route('/coprs/<username>/<coprname>/modify/<chrootname>/', methods=["POST"])
709
710
711 @api_ns.route('/coprs/<username>/<coprname>/chroot/edit/<chrootname>/', methods=["POST"])
712 @api_login_required
713 @api_req_with_copr
714 -def copr_edit_chroot(copr, chrootname):
715 form = forms.ModifyChrootForm(csrf_enabled=False)
716 chroot = ComplexLogic.get_copr_chroot_safe(copr, chrootname)
717
718 if not form.validate_on_submit():
719 raise LegacyApiError("Invalid request: {0}".format(form.errors))
720 else:
721 buildroot_pkgs = repos = comps_xml = comps_name = None
722 if "buildroot_pkgs" in flask.request.form:
723 buildroot_pkgs = form.buildroot_pkgs.data
724 if "repos" in flask.request.form:
725 repos = form.repos.data
726 if form.upload_comps.has_file():
727 comps_xml = form.upload_comps.data.stream.read()
728 comps_name = form.upload_comps.data.filename
729 if form.delete_comps.data:
730 coprs_logic.CoprChrootsLogic.remove_comps(flask.g.user, chroot)
731 coprs_logic.CoprChrootsLogic.update_chroot(
732 flask.g.user, chroot, buildroot_pkgs, repos, comps=comps_xml, comps_name=comps_name)
733 db.session.commit()
734
735 output = {
736 "output": "ok",
737 "message": "Edit chroot operation was successful.",
738 "chroot": chroot.to_dict(),
739 }
740 return flask.jsonify(output)
741
742
743 @api_ns.route('/coprs/<username>/<coprname>/detail/<chrootname>/', methods=["GET"])
750
751 @api_ns.route('/coprs/<username>/<coprname>/chroot/get/<chrootname>/', methods=["GET"])
757
761 """ Return the list of coprs found in search by the given text.
762 project is taken either from GET params or from the URL itself
763 (in this order).
764
765 :arg project: the text one would like find for coprs.
766
767 """
768 project = flask.request.args.get("project", None) or project
769 if not project:
770 raise LegacyApiError("No project found.")
771
772 try:
773 query = CoprsLogic.get_multiple_fulltext(project)
774
775 repos = query.all()
776 output = {"output": "ok", "repos": []}
777 for repo in repos:
778 output["repos"].append({"username": repo.user.name,
779 "coprname": repo.name,
780 "description": repo.description})
781 except ValueError as e:
782 raise LegacyApiError("Server error: {}".format(e))
783
784 return flask.jsonify(output)
785
789 """ Return list of coprs which are part of playground """
790 query = CoprsLogic.get_playground()
791 repos = query.all()
792 output = {"output": "ok", "repos": []}
793 for repo in repos:
794 output["repos"].append({"username": repo.owner_name,
795 "coprname": repo.name,
796 "chroots": [chroot.name for chroot in repo.active_chroots]})
797
798 jsonout = flask.jsonify(output)
799 jsonout.status_code = 200
800 return jsonout
801
802
803 @api_ns.route("/coprs/<username>/<coprname>/monitor/", methods=["GET"])
804 @api_req_with_copr
805 -def monitor(copr):
809
810
811
812 @api_ns.route("/coprs/<username>/<coprname>/package/add/<source_type_text>/", methods=["POST"])
813 @api_login_required
814 @api_req_with_copr
815 -def copr_add_package(copr, source_type_text):
817
818
819 @api_ns.route("/coprs/<username>/<coprname>/package/<package_name>/edit/<source_type_text>/", methods=["POST"])
820 @api_login_required
821 @api_req_with_copr
822 -def copr_edit_package(copr, package_name, source_type_text):
828
865
868 params = {}
869 if flask.request.args.get('with_latest_build'):
870 params['with_latest_build'] = True
871 if flask.request.args.get('with_latest_succeeded_build'):
872 params['with_latest_succeeded_build'] = True
873 if flask.request.args.get('with_all_builds'):
874 params['with_all_builds'] = True
875 return params
876
879 """
880 A lagging generator to stream JSON so we don't have to hold everything in memory
881 This is a little tricky, as we need to omit the last comma to make valid JSON,
882 thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
883 """
884 packages = query.__iter__()
885 try:
886 prev_package = next(packages)
887 except StopIteration:
888
889 yield '{"packages": []}'
890 raise StopIteration
891
892 yield '{"packages": ['
893
894 for package in packages:
895 yield json.dumps(prev_package.to_dict(**params)) + ', '
896 prev_package = package
897
898 yield json.dumps(prev_package.to_dict(**params)) + ']}'
899
900
901 @api_ns.route("/coprs/<username>/<coprname>/package/list/", methods=["GET"])
907
908
909
910 @api_ns.route("/coprs/<username>/<coprname>/package/get/<package_name>/", methods=["GET"])
920
921
922 @api_ns.route("/coprs/<username>/<coprname>/package/delete/<package_name>/", methods=["POST"])
942
943
944 @api_ns.route("/coprs/<username>/<coprname>/package/reset/<package_name>/", methods=["POST"])
945 @api_login_required
946 @api_req_with_copr
947 -def copr_reset_package(copr, package_name):
964
965
966 @api_ns.route("/coprs/<username>/<coprname>/package/build/<package_name>/", methods=["POST"])
967 @api_login_required
968 @api_req_with_copr
969 -def copr_build_package(copr, package_name):
970 form = forms.BuildFormRebuildFactory.create_form_cls(copr.active_chroots)(csrf_enabled=False)
971
972 try:
973 package = PackagesLogic.get(copr.id, package_name)[0]
974 except IndexError:
975 raise LegacyApiError("No package with name {name} in copr {copr}".format(name=package_name, copr=copr.name))
976
977 if form.validate_on_submit():
978 try:
979 build = PackagesLogic.build_package(flask.g.user, copr, package, form.selected_chroots, **form.data)
980 db.session.commit()
981 except (InsufficientRightsException, ActionInProgressException, NoPackageSourceException) as e:
982 raise LegacyApiError(str(e))
983 else:
984 raise LegacyApiError(form.errors)
985
986 return flask.jsonify({
987 "output": "ok",
988 "ids": [build.id],
989 "message": "Build was added to {0}.".format(copr.name)
990 })
991
992
993 @api_ns.route("/module/build/", methods=["POST"])
996 form = forms.ModuleBuildForm(csrf_enabled=False)
997 if not form.validate_on_submit():
998 raise LegacyApiError(form.errors)
999
1000 try:
1001 common = {"owner": flask.g.user.name,
1002 "copr_owner": form.copr_owner.data,
1003 "copr_project": form.copr_project.data}
1004 if form.scmurl.data:
1005 kwargs = {"json": dict({"scmurl": form.scmurl.data, "branch": form.branch.data}, **common)}
1006 else:
1007 kwargs = {"data": common, "files": {"yaml": (form.modulemd.data.filename, form.modulemd.data)}}
1008
1009 response = requests.post(flask.current_app.config["MBS_URL"], verify=False, **kwargs)
1010 if response.status_code == 500:
1011 raise LegacyApiError("Error from MBS: {} - {}".format(response.status_code, response.reason))
1012
1013 resp = json.loads(response.content)
1014 if response.status_code != 201:
1015 raise LegacyApiError("Error from MBS: {}".format(resp["message"]))
1016
1017 return flask.jsonify({
1018 "output": "ok",
1019 "message": "Created module {}-{}-{}".format(resp["name"], resp["stream"], resp["version"]),
1020 })
1021
1022 except requests.ConnectionError:
1023 raise LegacyApiError("Can't connect to MBS instance")
1024
1025
1026 @api_ns.route("/coprs/<username>/<coprname>/module/make/", methods=["POST"])
1030 form = forms.ModuleFormUploadFactory(csrf_enabled=False)
1031 if not form.validate_on_submit():
1032
1033 raise LegacyApiError(form.errors)
1034
1035 modulemd = form.modulemd.data.read()
1036 module = ModulesLogic.from_modulemd(modulemd)
1037 try:
1038 ModulesLogic.validate(modulemd)
1039 msg = "Nothing happened"
1040 if form.create.data:
1041 module = ModulesLogic.add(flask.g.user, copr, module)
1042 db.session.flush()
1043 msg = "Module was created"
1044
1045 if form.build.data:
1046 if not module.id:
1047 module = ModulesLogic.get_by_nsv(copr, module.name, module.stream, module.version).one()
1048 ActionsLogic.send_build_module(flask.g.user, copr, module)
1049 msg = "Module build was submitted"
1050 db.session.commit()
1051
1052 return flask.jsonify({
1053 "output": "ok",
1054 "message": msg,
1055 "modulemd": modulemd,
1056 })
1057
1058 except sqlalchemy.exc.IntegrityError:
1059 raise LegacyApiError({"nsv": ["Module {} already exists".format(module.nsv)]})
1060
1061 except sqlalchemy.orm.exc.NoResultFound:
1062 raise LegacyApiError({"nsv": ["Module {} doesn't exist. You need to create it first".format(module.nsv)]})
1063
1064 except ValidationError as ex:
1065 raise LegacyApiError({"nsv": [ex.message]})
1066
1067
1068 @api_ns.route("/coprs/<username>/<coprname>/build-config/<chroot>/", methods=["GET"])
1069 @api_ns.route("/g/<group_name>/<coprname>/build-config/<chroot>/", methods=["GET"])
1072 """
1073 Generate build configuration.
1074 """
1075 output = {
1076 "output": "ok",
1077 "build_config": generate_build_config(copr, chroot),
1078 }
1079
1080 if not output['build_config']:
1081 raise LegacyApiError('Chroot not found.')
1082
1083 return flask.jsonify(output)
1084
1085
1086 @api_ns.route("/module/repo/", methods=["POST"])
1102