1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236 |
- # -*- coding: utf-8 -*-
- """
- (c) 2015-2019 - Copyright Red Hat Inc
- Authors:
- Pierre-Yves Chibon <pingou@pingoured.fr>
- """
- from __future__ import unicode_literals, absolute_import
- import flask
- import logging
- from sqlalchemy.exc import SQLAlchemyError
- from six import string_types
- from pygit2 import GitError, Repository
- import pagure
- import pagure.forms
- import pagure.exceptions
- import pagure.lib.git
- import pagure.lib.query
- import pagure.utils
- from pagure.api import (
- API,
- api_method,
- APIERROR,
- api_login_required,
- get_authorized_api_project,
- api_login_optional,
- get_request_data,
- get_page,
- get_per_page,
- )
- from pagure.api.utils import _get_repo, _check_token
- from pagure.config import config as pagure_config
- _log = logging.getLogger(__name__)
- @API.route("/<repo>/git/tags")
- @API.route("/<namespace>/<repo>/git/tags")
- @API.route("/fork/<username>/<repo>/git/tags")
- @API.route("/fork/<username>/<namespace>/<repo>/git/tags")
- @api_method
- def api_git_tags(repo, username=None, namespace=None):
- """
- Project git tags
- ----------------
- List the tags made on the project Git repository.
- ::
- GET /api/0/<repo>/git/tags
- GET /api/0/<namespace>/<repo>/git/tags
- ::
- GET /api/0/fork/<username>/<repo>/git/tags
- GET /api/0/fork/<username>/<namespace>/<repo>/git/tags
- Parameters
- ^^^^^^^^^^
- +-----------------+----------+---------------+--------------------------+
- | Key | Type | Optionality | Description |
- +=================+==========+===============+==========================+
- | ``with_commits``| string | Optional | | Include the commit hash|
- | | | | corresponding to the |
- | | | | tags found in the repo |
- +-----------------+----------+---------------+--------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "total_tags": 2,
- "tags": ["0.0.1", "0.0.2"]
- }
- {
- "total_tags": 2,
- "tags": {
- "0.0.1": "bb8fa2aa199da08d6085e1c9badc3d83d188d38c",
- "0.0.2": "d16fe107eca31a1bdd66fb32c6a5c568e45b627e"
- }
- }
- """
- with_commits = pagure.utils.is_true(
- flask.request.values.get("with_commits", False)
- )
- repo = _get_repo(repo, username, namespace)
- tags = pagure.lib.git.get_git_tags(repo, with_commits=with_commits)
- jsonout = flask.jsonify({"total_tags": len(tags), "tags": tags})
- return jsonout
- @API.route("/<repo>/watchers")
- @API.route("/<namespace>/<repo>/watchers")
- @API.route("/fork/<username>/<repo>/watchers")
- @API.route("/fork/<username>/<namespace>/<repo>/watchers")
- @api_method
- def api_project_watchers(repo, username=None, namespace=None):
- """
- Project watchers
- ----------------
- List the watchers on the project.
- ::
- GET /api/0/<repo>/watchers
- GET /api/0/<namespace>/<repo>/watchers
- ::
- GET /api/0/fork/<username>/<repo>/watchers
- GET /api/0/fork/<username>/<namespace>/<repo>/watchers
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "total_watchers": 1,
- "watchers": {
- "mprahl": [
- "issues",
- "commits"
- ]
- }
- }
- """
- repo = _get_repo(repo, username, namespace)
- implicit_watch_users = set([repo.user.username])
- for access_type in repo.access_users:
- implicit_watch_users = implicit_watch_users.union(
- set([user.username for user in repo.access_users[access_type]])
- )
- watching_users_to_watch_level = {}
- for implicit_watch_user in implicit_watch_users:
- user_watch_level = pagure.lib.query.get_watch_level_on_repo(
- flask.g.session, implicit_watch_user, repo
- )
- watching_users_to_watch_level[implicit_watch_user] = user_watch_level
- for access_type in repo.access_groups.keys():
- group_names = [
- "@" + group.group_name for group in repo.access_groups[access_type]
- ]
- for group_name in group_names:
- if group_name not in watching_users_to_watch_level:
- watching_users_to_watch_level[group_name] = set()
- # By the logic in pagure.lib.query.get_watch_level_on_repo, group
- # members only by default watch issues. If they want to watch
- # commits they have to explicitly subscribe.
- watching_users_to_watch_level[group_name].add("issues")
- for key in watching_users_to_watch_level:
- watching_users_to_watch_level[key] = list(
- watching_users_to_watch_level[key]
- )
- # Get the explicit watch statuses
- for watcher in repo.watchers:
- if watcher.watch_issues or watcher.watch_commits:
- watching_users_to_watch_level[
- watcher.user.username
- ] = pagure.lib.query.get_watch_level_on_repo(
- flask.g.session, watcher.user.username, repo
- )
- else:
- if watcher.user.username in watching_users_to_watch_level:
- watching_users_to_watch_level.pop(watcher.user.username, None)
- return flask.jsonify(
- {
- "total_watchers": len(watching_users_to_watch_level),
- "watchers": watching_users_to_watch_level,
- }
- )
- @API.route("/<repo>/git/urls")
- @API.route("/<namespace>/<repo>/git/urls")
- @API.route("/fork/<username>/<repo>/git/urls")
- @API.route("/fork/<username>/<namespace>/<repo>/git/urls")
- @api_login_optional()
- @api_method
- def api_project_git_urls(repo, username=None, namespace=None):
- """
- Project Git URLs
- ----------------
- List the Git URLS on the project.
- ::
- GET /api/0/<repo>/git/urls
- GET /api/0/<namespace>/<repo>/git/urls
- ::
- GET /api/0/fork/<username>/<repo>/git/urls
- GET /api/0/fork/<username>/<namespace>/<repo>/git/urls
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "total_urls": 2,
- "urls": {
- "ssh": "ssh://git@pagure.io/mprahl-test123.git",
- "git": "https://pagure.io/mprahl-test123.git"
- }
- }
- """
- repo = _get_repo(repo, username, namespace)
- git_urls = {}
- git_url_ssh = pagure_config.get("GIT_URL_SSH")
- if pagure.utils.api_authenticated() and git_url_ssh:
- try:
- git_url_ssh = git_url_ssh.format(
- username=flask.g.fas_user.username
- )
- except (KeyError, IndexError):
- pass
- if git_url_ssh:
- git_urls["ssh"] = "{0}{1}.git".format(git_url_ssh, repo.fullname)
- if pagure_config.get("GIT_URL_GIT"):
- git_urls["git"] = "{0}{1}.git".format(
- pagure_config["GIT_URL_GIT"], repo.fullname
- )
- return flask.jsonify({"total_urls": len(git_urls), "urls": git_urls})
- @API.route("/<repo>/git/branches")
- @API.route("/<namespace>/<repo>/git/branches")
- @API.route("/fork/<username>/<repo>/git/branches")
- @API.route("/fork/<username>/<namespace>/<repo>/git/branches")
- @api_method
- def api_git_branches(repo, username=None, namespace=None):
- """
- List project branches
- ---------------------
- List the branches associated with a Pagure git repository
- ::
- GET /api/0/<repo>/git/branches
- GET /api/0/<namespace>/<repo>/git/branches
- ::
- GET /api/0/fork/<username>/<repo>/git/branches
- GET /api/0/fork/<username>/<namespace>/<repo>/git/branches
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "total_branches": 2,
- "branches": ["master", "dev"]
- }
- """
- repo = _get_repo(repo, username, namespace)
- branches = pagure.lib.git.get_git_branches(repo)
- return flask.jsonify(
- {"total_branches": len(branches), "branches": branches}
- )
- @API.route("/projects")
- @api_method
- def api_projects():
- """
- List projects
- --------------
- Search projects given the specified criterias.
- ::
- GET /api/0/projects
- ::
- GET /api/0/projects?tags=fedora-infra
- ::
- GET /api/0/projects?page=1&per_page=50
- Parameters
- ^^^^^^^^^^
- +---------------+----------+---------------+--------------------------+
- | Key | Type | Optionality | Description |
- +===============+==========+===============+==========================+
- | ``tags`` | string | Optional | | Filters the projects |
- | | | | returned by their tags |
- +---------------+----------+---------------+--------------------------+
- | ``pattern`` | string | Optional | | Filters the projects |
- | | | | by the pattern string |
- +---------------+----------+---------------+--------------------------+
- | ``username`` | string | Optional | | Filters the projects |
- | | | | returned by the users |
- | | | | having commit rights |
- | | | | to it |
- +---------------+----------+---------------+--------------------------+
- | ``owner`` | string | Optional | | Filters the projects |
- | | | | by ownership. |
- | | | | If the argument is of |
- | | | | the form <!owner> then |
- | | | | the project returned |
- | | | | are the ones *not* |
- | | | | owned by this user. |
- +---------------+----------+---------------+--------------------------+
- | ``namespace`` | string | Optional | | Filters the projects |
- | | | | by namespace |
- +---------------+----------+---------------+--------------------------+
- | ``fork`` | boolean | Optional | | Filters the projects |
- | | | | returned depending if |
- | | | | they are forks or not |
- +---------------+----------+---------------+--------------------------+
- | ``short`` | boolean | Optional | | Whether to return the |
- | | | | entrie project JSON |
- | | | | or just a sub-set |
- +---------------+----------+---------------+--------------------------+
- | ``page`` | int | Optional | | Specifies which |
- | | | | page to return |
- | | | | (defaults to: 1) |
- +---------------+----------+---------------+--------------------------+
- | ``per_page`` | int | Optional | | The number of projects |
- | | | | to return per page. |
- | | | | The maximum is 100. |
- +---------------+----------+---------------+--------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "args": {
- "fork": null,
- "namespace": null,
- "owner": null,
- "page": 1,
- "pattern": null,
- "per_page": 2,
- "short": false,
- "tags": [],
- "username": null
- },
- "pagination": {
- "first": "http://127.0.0.1:5000/api/0/projects?per_page=2&page=1",
- "last": "http://127.0.0.1:5000/api/0/projects?per_page=2&page=500",
- "next": "http://127.0.0.1:5000/api/0/projects?per_page=2&page=2",
- "page": 1,
- "pages": 500,
- "per_page": 2,
- "prev": null
- },
- "projects": [
- {
- "access_groups": {
- "admin": [],
- "commit": [],
- "ticket": []
- },
- "access_users": {
- "admin": [],
- "commit": [],
- "owner": [
- "mprahl"
- ],
- "ticket": []
- },
- "close_status": [],
- "custom_keys": [],
- "date_created": "1498841289",
- "description": "test1",
- "fullname": "test1",
- "id": 1,
- "milestones": {},
- "name": "test1",
- "namespace": null,
- "parent": null,
- "priorities": {},
- "tags": [],
- "url_path": "test1",
- "user": {
- "fullname": "Matt Prahl",
- "name": "mprahl"
- }
- },
- {
- "access_groups": {
- "admin": [],
- "commit": [],
- "ticket": []
- },
- "access_users": {
- "admin": [],
- "commit": [],
- "owner": [
- "mprahl"
- ],
- "ticket": []
- },
- "close_status": [],
- "custom_keys": [],
- "date_created": "1499795310",
- "description": "test2",
- "fullname": "test2",
- "id": 2,
- "milestones": {},
- "name": "test2",
- "namespace": null,
- "parent": null,
- "priorities": {},
- "tags": [],
- "url_path": "test2",
- "user": {
- "fullname": "Matt Prahl",
- "name": "mprahl"
- }
- }
- ],
- "total_projects": 1000
- }
- """
- tags = flask.request.values.getlist("tags")
- username = flask.request.values.get("username", None)
- fork = flask.request.values.get("fork", None)
- namespace = flask.request.values.get("namespace", None)
- owner = flask.request.values.get("owner", None)
- pattern = flask.request.values.get("pattern", None)
- short = pagure.utils.is_true(flask.request.values.get("short", False))
- if fork is not None:
- fork = pagure.utils.is_true(fork)
- private = False
- if pagure.utils.authenticated() and username == flask.g.fas_user.username:
- private = flask.g.fas_user.username
- project_count = pagure.lib.query.search_projects(
- flask.g.session,
- username=username,
- fork=fork,
- tags=tags,
- pattern=pattern,
- private=private,
- namespace=namespace,
- owner=owner,
- count=True,
- )
- # Pagination code inspired by Flask-SQLAlchemy
- page = get_page()
- per_page = get_per_page()
- pagination_metadata = pagure.lib.query.get_pagination_metadata(
- flask.request, page, per_page, project_count
- )
- query_start = (page - 1) * per_page
- query_limit = per_page
- projects = pagure.lib.query.search_projects(
- flask.g.session,
- username=username,
- fork=fork,
- tags=tags,
- pattern=pattern,
- private=private,
- namespace=namespace,
- owner=owner,
- limit=query_limit,
- start=query_start,
- )
- # prepare the output json
- jsonout = {
- "total_projects": project_count,
- "projects": projects,
- "args": {
- "tags": tags,
- "username": username,
- "fork": fork,
- "pattern": pattern,
- "namespace": namespace,
- "owner": owner,
- "short": short,
- },
- }
- if not short:
- projects = [p.to_json(api=True, public=True) for p in projects]
- else:
- projects = [
- {
- "name": p.name,
- "namespace": p.namespace,
- "fullname": p.fullname.replace("forks/", "fork/", 1)
- if p.fullname.startswith("forks/")
- else p.fullname,
- "description": p.description,
- }
- for p in projects
- ]
- jsonout["projects"] = projects
- if pagination_metadata:
- jsonout["args"]["page"] = page
- jsonout["args"]["per_page"] = per_page
- jsonout["pagination"] = pagination_metadata
- return flask.jsonify(jsonout)
- @API.route("/<repo>")
- @API.route("/<namespace>/<repo>")
- @API.route("/fork/<username>/<repo>")
- @API.route("/fork/<username>/<namespace>/<repo>")
- @api_method
- def api_project(repo, username=None, namespace=None):
- """
- Project information
- -------------------
- Return information about a specific project
- ::
- GET /api/0/<repo>
- GET /api/0/<namespace>/<repo>
- ::
- GET /api/0/fork/<username>/<repo>
- GET /api/0/fork/<username>/<namespace>/<repo>
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "access_groups": {
- "admin": [],
- "commit": [],
- "ticket": []
- },
- "access_users": {
- "admin": [
- "ryanlerch"
- ],
- "commit": [
- "puiterwijk"
- ],
- "owner": [
- "pingou"
- ],
- "ticket": [
- "vivekanand1101",
- "mprahl",
- "jcline",
- "lslebodn",
- "cverna",
- "farhaan"
- ]
- },
- "close_status": [
- "Invalid",
- "Insufficient data",
- "Fixed",
- "Duplicate"
- ],
- "custom_keys": [],
- "date_created": "1431549490",
- "date_modified": "1431549490",
- "description": "A git centered forge",
- "fullname": "pagure",
- "id": 10,
- "milestones": {},
- "name": "pagure",
- "namespace": null,
- "parent": null,
- "priorities": {},
- "tags": [
- "pagure",
- "fedmsg"
- ],
- "user": {
- "fullname": "Pierre-YvesChibon",
- "name": "pingou"
- }
- }
- """
- repo = _get_repo(repo, username, namespace)
- expand_group = pagure.utils.is_true(
- flask.request.values.get("expand_group", False)
- )
- output = repo.to_json(api=True, public=True)
- if expand_group:
- group_details = {}
- for grp in repo.projects_groups:
- group_details[grp.group.group_name] = [
- user.username for user in grp.group.users
- ]
- output["group_details"] = group_details
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/new/", methods=["POST"])
- @API.route("/new", methods=["POST"])
- @api_login_required(acls=["create_project"])
- @api_method
- def api_new_project():
- """
- Create a new project
- --------------------
- Create a new project on this pagure instance.
- This is an asynchronous call.
- ::
- POST /api/0/new
- Input
- ^^^^^
- +----------------------------+---------+--------------+---------------------------+
- | Key | Type | Optionality | Description |
- +============================+=========+==============+===========================+
- | ``name`` | string | Mandatory | | The name of the new |
- | | | | project. |
- +----------------------------+---------+--------------+---------------------------+
- | ``description`` | string | Mandatory | | A short description of |
- | | | | the new project. |
- +----------------------------+---------+--------------+---------------------------+
- | ``namespace`` | string | Optional | | The namespace of the |
- | | | | project to fork. |
- +----------------------------+---------+--------------+---------------------------+
- | ``url`` | string | Optional | | An url providing more |
- | | | | information about the |
- | | | | project. |
- +----------------------------+---------+--------------+---------------------------+
- | ``avatar_email`` | string | Optional | | An email address for the|
- | | | | avatar of the project. |
- +----------------------------+---------+--------------+---------------------------+
- | ``create_readme`` | boolean | Optional | | A boolean to specify if |
- | | | | there should be a readme|
- | | | | added to the project on |
- | | | | creation. |
- +----------------------------+---------+--------------+---------------------------+
- | ``private`` | boolean | Optional | | A boolean to specify if |
- | | | | the project to create |
- | | | | is private. |
- | | | | Note: not all pagure |
- | | | | instance support private|
- | | | | projects, confirm this |
- | | | | with your administrators|
- +----------------------------+---------+--------------+---------------------------+
- | ``ignore_existing_repos`` | boolean | Optional | | Only available to admins|
- | | | | this option allows them |
- | | | | to make project creation|
- | | | | pass even if there is |
- | | | | already a coresopnding |
- | | | | git repository on disk |
- +----------------------------+---------+--------------+---------------------------+
- | ``repospanner_region`` | boolean | Optional | | Only available to admins|
- | | | | this option allows them |
- | | | | to override the default |
- | | | | respoSpanner region |
- | | | | configured |
- +----------------------------+---------+--------------+---------------------------+
- | ``wait`` | boolean | Optional | | A boolean to specify if |
- | | | | this API call should |
- | | | | return a taskid or if it|
- | | | | should wait for the task|
- | | | | to finish. |
- +----------------------------+---------+--------------+---------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- wait=False:
- {
- 'message': 'Project creation queued',
- 'taskid': '123-abcd'
- }
- wait=True:
- {
- 'message': 'Project creation queued'
- }
- """ # noqa
- user = pagure.lib.query.search_user(
- flask.g.session, username=flask.g.fas_user.username
- )
- output = {}
- if not pagure_config.get("ENABLE_NEW_PROJECTS", True):
- raise pagure.exceptions.APIError(
- 404, error_code=APIERROR.ENEWPROJECTDISABLED
- )
- namespaces = pagure_config["ALLOWED_PREFIX"][:]
- if user:
- namespaces.extend([grp for grp in user.groups])
- form = pagure.forms.ProjectForm(namespaces=namespaces, csrf_enabled=False)
- if form.validate_on_submit():
- name = form.name.data
- description = form.description.data
- namespace = form.namespace.data
- url = form.url.data
- avatar_email = form.avatar_email.data
- create_readme = form.create_readme.data
- if namespace:
- namespace = namespace.strip()
- private = False
- if pagure_config.get("PRIVATE_PROJECTS", False):
- private = form.private.data
- if form.repospanner_region:
- repospanner_region = form.repospanner_region.data
- else:
- repospanner_region = None
- if form.ignore_existing_repos:
- ignore_existing_repos = form.ignore_existing_repos.data
- else:
- ignore_existing_repos = False
- try:
- task = pagure.lib.query.new_project(
- flask.g.session,
- name=name,
- namespace=namespace,
- repospanner_region=repospanner_region,
- ignore_existing_repo=ignore_existing_repos,
- description=description,
- private=private,
- url=url,
- avatar_email=avatar_email,
- user=flask.g.fas_user.username,
- blacklist=pagure_config["BLACKLISTED_PROJECTS"],
- allowed_prefix=pagure_config["ALLOWED_PREFIX"],
- add_readme=create_readme,
- userobj=user,
- prevent_40_chars=pagure_config.get(
- "OLD_VIEW_COMMIT_ENABLED", False
- ),
- user_ns=pagure_config.get("USER_NAMESPACE", False),
- )
- flask.g.session.commit()
- output = {"message": "Project creation queued", "taskid": task.id}
- if get_request_data().get("wait", True):
- result = task.get()
- project = pagure.lib.query._get_project(
- flask.g.session,
- name=result["repo"],
- namespace=result["namespace"],
- )
- output = {"message": 'Project "%s" created' % project.fullname}
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(err)
- )
- except SQLAlchemyError as err: # pragma: no cover
- _log.exception(err)
- flask.g.session.rollback()
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- else:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors
- )
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/<repo>", methods=["PATCH"])
- @API.route("/<namespace>/<repo>", methods=["PATCH"])
- @api_login_required(acls=["modify_project"])
- @api_method
- def api_modify_project(repo, namespace=None):
- """
- Modify a project
- ----------------
- Modify an existing project on this Pagure instance.
- ::
- PATCH /api/0/<repo>
- Input
- ^^^^^
- +------------------+---------+--------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+==============+===========================+
- | ``main_admin`` | string | Mandatory | | The new main admin of |
- | | | | the project. |
- +------------------+---------+--------------+---------------------------+
- | ``retain_access``| string | Optional | | The old main admin |
- | | | | retains access on the |
- | | | | project when giving the |
- | | | | project. Defaults to |
- | | | | ``False``. |
- +------------------+---------+--------------+---------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "access_groups": {
- "admin": [],
- "commit": [],
- "ticket": []
- },
- "access_users": {
- "admin": [],
- "commit": [],
- "owner": [
- "testuser1"
- ],
- "ticket": []
- },
- "close_status": [],
- "custom_keys": [],
- "date_created": "1496326387",
- "description": "Test",
- "fullname": "test-project2",
- "id": 2,
- "milestones": {},
- "name": "test-project2",
- "namespace": null,
- "parent": null,
- "priorities": {},
- "tags": [],
- "user": {
- "default_email": "testuser1@domain.local",
- "emails": [],
- "fullname": "Test User1",
- "name": "testuser1"
- }
- }
- """
- project = _get_repo(repo, namespace=namespace)
- _check_token(project, project_token=False)
- is_site_admin = pagure.utils.is_admin()
- admins = [u.username for u in project.get_project_users("admin")]
- # Only allow the main admin, the admins of the project, and Pagure site
- # admins to modify projects, even if the user has the right ACLs on their
- # token
- if (
- flask.g.fas_user.username not in admins
- and flask.g.fas_user.username != project.user.username
- and not is_site_admin
- ):
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.EMODIFYPROJECTNOTALLOWED
- )
- valid_keys = ["main_admin", "retain_access"]
- # Check if it's JSON or form data
- if flask.request.headers.get("Content-Type") == "application/json":
- # Set force to True to ignore the mimetype. Set silent so that None is
- # returned if it's invalid JSON.
- args = flask.request.get_json(force=True, silent=True) or {}
- retain_access = args.get("retain_access", False)
- else:
- args = get_request_data()
- retain_access = args.get("retain_access", "").lower() in ["true", "1"]
- if not args:
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ)
- # Check to make sure there aren't parameters we don't support
- for key in args.keys():
- if key not in valid_keys:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.EINVALIDREQ
- )
- if "main_admin" in args:
- if (
- flask.g.fas_user.username != project.user.username
- and not is_site_admin
- ):
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.ENOTMAINADMIN
- )
- # If the main_admin is already set correctly, don't do anything
- if flask.g.fas_user.username == project.user:
- return flask.jsonify(project.to_json(public=False, api=True))
- try:
- new_main_admin = pagure.lib.query.get_user(
- flask.g.session, args["main_admin"]
- )
- except pagure.exceptions.PagureException:
- raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOUSER)
- old_main_admin = project.user.user
- pagure.lib.query.set_project_owner(
- flask.g.session, project, new_main_admin
- )
- if retain_access and flask.g.fas_user.username == old_main_admin:
- pagure.lib.query.add_user_to_project(
- flask.g.session,
- project,
- new_user=flask.g.fas_user.username,
- user=flask.g.fas_user.username,
- )
- try:
- flask.g.session.commit()
- except SQLAlchemyError: # pragma: no cover
- flask.g.session.rollback()
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- pagure.lib.git.generate_gitolite_acls(project=project)
- return flask.jsonify(project.to_json(public=False, api=True))
- @API.route("/fork/", methods=["POST"])
- @API.route("/fork", methods=["POST"])
- @api_login_required(acls=["fork_project"])
- @api_method
- def api_fork_project():
- """
- Fork a project
- --------------------
- Fork a project on this pagure instance.
- This is an asynchronous call.
- ::
- POST /api/0/fork
- Input
- ^^^^^
- +------------------+---------+--------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+==============+===========================+
- | ``repo`` | string | Mandatory | | The name of the project |
- | | | | to fork. |
- +------------------+---------+--------------+---------------------------+
- | ``namespace`` | string | Optional | | The namespace of the |
- | | | | project to fork. |
- +------------------+---------+--------------+---------------------------+
- | ``username`` | string | Optional | | The username of the user|
- | | | | of the fork. |
- +------------------+---------+--------------+---------------------------+
- | ``wait`` | boolean | Optional | | A boolean to specify if |
- | | | | this API call should |
- | | | | return a taskid or if it|
- | | | | should wait for the task|
- | | | | to finish. |
- +------------------+---------+--------------+---------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- wait=False:
- {
- "message": "Project forking queued",
- "taskid": "123-abcd"
- }
- wait=True:
- {
- "message": 'Repo "test" cloned to "pingou/test"
- }
- """
- output = {}
- form = pagure.forms.ForkRepoForm(csrf_enabled=False)
- if form.validate_on_submit():
- repo = form.repo.data
- username = form.username.data or None
- namespace = form.namespace.data.strip() or None
- repo = get_authorized_api_project(
- flask.g.session, repo, user=username, namespace=namespace
- )
- if repo is None:
- raise pagure.exceptions.APIError(
- 404, error_code=APIERROR.ENOPROJECT
- )
- try:
- task = pagure.lib.query.fork_project(
- flask.g.session, user=flask.g.fas_user.username, repo=repo
- )
- flask.g.session.commit()
- output = {"message": "Project forking queued", "taskid": task.id}
- if get_request_data().get("wait", True):
- task.get()
- output = {
- "message": 'Repo "%s" cloned to "%s/%s"'
- % (repo.fullname, flask.g.fas_user.username, repo.fullname)
- }
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(err)
- )
- except SQLAlchemyError as err: # pragma: no cover
- _log.exception(err)
- flask.g.session.rollback()
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- else:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors
- )
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/<repo>/git/generateacls", methods=["POST"])
- @API.route("/<namespace>/<repo>/git/generateacls", methods=["POST"])
- @API.route("/fork/<username>/<repo>/git/generateacls", methods=["POST"])
- @API.route(
- "/fork/<username>/<namespace>/<repo>/git/generateacls", methods=["POST"]
- )
- @api_login_required(acls=["generate_acls_project"])
- @api_method
- def api_generate_acls(repo, username=None, namespace=None):
- """
- Generate Gitolite ACLs on a project
- -----------------------------------
- Generate Gitolite ACLs on a project. This is restricted to Pagure admins.
- This is an asynchronous call.
- ::
- POST /api/0/rpms/python-requests/git/generateacls
- Input
- ^^^^^
- +------------------+---------+--------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+==============+===========================+
- | ``wait`` | boolean | Optional | | A boolean to specify if |
- | | | | this API call should |
- | | | | return a taskid or if it|
- | | | | should wait for the task|
- | | | | to finish. |
- +------------------+---------+--------------+---------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- wait=False:
- {
- 'message': 'Project ACL generation queued',
- 'taskid': '123-abcd'
- }
- wait=True:
- {
- 'message': 'Project ACLs generated'
- }
- """
- project = _get_repo(repo, username, namespace)
- _check_token(project, project_token=False)
- # Check if it's JSON or form data
- if flask.request.headers.get("Content-Type") == "application/json":
- # Set force to True to ignore the mimetype. Set silent so that None is
- # returned if it's invalid JSON.
- json = flask.request.get_json(force=True, silent=True) or {}
- wait = json.get("wait", False)
- else:
- wait = pagure.utils.is_true(get_request_data().get("wait"))
- try:
- task = pagure.lib.git.generate_gitolite_acls(project=project)
- if wait:
- task.get()
- output = {"message": "Project ACLs generated"}
- else:
- output = {
- "message": "Project ACL generation queued",
- "taskid": task.id,
- }
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(err)
- )
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/<repo>/git/branch", methods=["POST"])
- @API.route("/<namespace>/<repo>/git/branch", methods=["POST"])
- @API.route("/fork/<username>/<repo>/git/branch", methods=["POST"])
- @API.route("/fork/<username>/<namespace>/<repo>/git/branch", methods=["POST"])
- @api_login_required(acls=["create_branch"])
- @api_method
- def api_new_branch(repo, username=None, namespace=None):
- """
- Create a new git branch on a project
- ------------------------------------
- Create a new git branch on a project
- ::
- POST /api/0/rpms/python-requests/git/branch
- Input
- ^^^^^
- +------------------+---------+--------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+==============+===========================+
- | ``branch`` | string | Mandatory | | A string of the branch |
- | | | | to create. |
- +------------------+---------+--------------+---------------------------+
- | ``from_branch`` | string | Optional | | A string of the branch |
- | | | | to branch off of. This |
- | | | | defaults to "master". |
- | | | | if ``from_commit`` |
- | | | | isn't set. |
- +------------------+---------+--------------+---------------------------+
- | ``from_commit`` | string | Optional | | A string of the commit |
- | | | | to branch off of. |
- +------------------+---------+--------------+---------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- 'message': 'Project branch was created'
- }
- """
- project = _get_repo(repo, username, namespace)
- _check_token(project, project_token=False)
- # Check if it's JSON or form data
- if flask.request.headers.get("Content-Type") == "application/json":
- # Set force to True to ignore the mimetype. Set silent so that None is
- # returned if it's invalid JSON.
- args = flask.request.get_json(force=True, silent=True) or {}
- else:
- args = get_request_data()
- branch = args.get("branch")
- from_branch = args.get("from_branch")
- from_commit = args.get("from_commit")
- if from_branch and from_commit:
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ)
- if (
- not branch
- or not isinstance(branch, string_types)
- or (from_branch and not isinstance(from_branch, string_types))
- or (from_commit and not isinstance(from_commit, string_types))
- ):
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ)
- try:
- pagure.lib.git.new_git_branch(
- flask.g.fas_user.username,
- project,
- branch,
- from_branch=from_branch,
- from_commit=from_commit,
- )
- except GitError: # pragma: no cover
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EGITERROR)
- except pagure.exceptions.PagureException as error:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(error)
- )
- output = {"message": "Project branch was created"}
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/<repo>/c/<commit_hash>/flag")
- @API.route("/<namespace>/<repo>/c/<commit_hash>/flag")
- @API.route("/fork/<username>/<repo>/c/<commit_hash>/flag")
- @API.route("/fork/<username>/<namespace>/<repo>/c/<commit_hash>/flag")
- @api_method
- def api_commit_flags(repo, commit_hash, username=None, namespace=None):
- """
- Flags for a commit
- ------------------
- Return all flags for given commit of given project
- ::
- GET /api/0/<repo>/c/<commit_hash>/flag
- GET /api/0/<namespace>/<repo>/c/<commit_hash>/flag
- ::
- GET /api/0/fork/<username>/<repo>/c/<commit_hash>/flag
- GET /api/0/fork/<username>/<namespace>/<repo>/c/<commit_hash>/flag
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "flags": [
- {
- "comment": "flag-comment",
- "commit_hash": "28f1f7fe844301f0e5f7aecacae0a1e5ec50a090",
- "date_created": "1520341983",
- "percent": null,
- "status": "success",
- "url": "https://some.url.com",
- "user": {
- "fullname": "Full name",
- "name": "fname"
- },
- "username": "somename"
- },
- {
- "comment": "different-comment",
- "commit_hash": "28f1f7fe844301f0e5f7aecacae0a1e5ec50a090",
- "date_created": "1520512543",
- "percent": null,
- "status": "pending",
- "url": "https://other.url.com",
- "user": {
- "fullname": "Other Name",
- "name": "oname"
- },
- "username": "differentname"
- }
- ],
- "total_flags": 2
- }
- """
- repo = _get_repo(repo, username, namespace)
- reponame = pagure.utils.get_repo_path(repo)
- repo_obj = Repository(reponame)
- try:
- repo_obj.get(commit_hash)
- except ValueError:
- raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOCOMMIT)
- flags = pagure.lib.query.get_commit_flag(
- flask.g.session, repo, commit_hash
- )
- flags = [f.to_json(public=True) for f in flags]
- return flask.jsonify({"total_flags": len(flags), "flags": flags})
- @API.route("/<repo>/c/<commit_hash>/flag", methods=["POST"])
- @API.route("/<namespace>/<repo>/c/<commit_hash>/flag", methods=["POST"])
- @API.route("/fork/<username>/<repo>/c/<commit_hash>/flag", methods=["POST"])
- @API.route(
- "/fork/<username>/<namespace>/<repo>/c/<commit_hash>/flag",
- methods=["POST"],
- )
- @api_login_required(acls=["commit_flag"])
- @api_method
- def api_commit_add_flag(repo, commit_hash, username=None, namespace=None):
- """
- Flag a commit
- -------------------
- Add or edit flags on a commit.
- ::
- POST /api/0/<repo>/c/<commit_hash>/flag
- POST /api/0/<namespace>/<repo>/c/<commit_hash>/flag
- ::
- POST /api/0/fork/<username>/<repo>/c/<commit_hash>/flag
- POST /api/0/fork/<username>/<namespace>/<repo>/c/<commit_hash>/flag
- Input
- ^^^^^
- +---------------+---------+--------------+-----------------------------+
- | Key | Type | Optionality | Description |
- +===============+=========+==============+=============================+
- | ``username`` | string | Mandatory | | The name of the |
- | | | | application to be |
- | | | | presented to users |
- | | | | on the commit pages |
- +---------------+---------+--------------+-----------------------------+
- | ``comment`` | string | Mandatory | | A short message |
- | | | | summarizing the |
- | | | | presented results |
- +---------------+---------+--------------+-----------------------------+
- | ``url`` | string | Mandatory | | A URL to the result |
- | | | | of this flag |
- +---------------+---------+--------------+-----------------------------+
- | ``status`` | string | Mandatory | | The status of the task, |
- | | | | can be any of: |
- | | | | $$FLAG_STATUSES_COMMAS$$ |
- +---------------+---------+--------------+-----------------------------+
- | ``percent`` | int | Optional | | A percentage of |
- | | | | completion compared to |
- | | | | the goal. The percentage |
- | | | | also determine the |
- | | | | background color of the |
- | | | | flag on the pages |
- +---------------+---------+--------------+-----------------------------+
- | ``uid`` | string | Optional | | A unique identifier used |
- | | | | to identify a flag across |
- | | | | all projects. If the |
- | | | | provided UID matches an |
- | | | | existing one, then the |
- | | | | API call will update the |
- | | | | existing one rather than |
- | | | | create a new one. |
- | | | | Maximum Length: 32 |
- | | | | characters. Default: an |
- | | | | auto generated UID |
- +---------------+---------+--------------+-----------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "flag": {
- "comment": "Tests passed",
- "commit_hash": "62b49f00d489452994de5010565fab81",
- "date_created": "1510742565",
- "percent": 100,
- "status": "success",
- "url": "http://jenkins.cloud.fedoraproject.org/",
- "user": {
- "default_email": "bar@pingou.com",
- "emails": ["bar@pingou.com", "foo@pingou.com"],
- "fullname": "PY C",
- "name": "pingou"},
- "username": "Jenkins"
- },
- "message": "Flag added",
- "uid": "b1de8f80defd4a81afe2e09f39678087"
- }
- ::
- {
- "flag": {
- "comment": "Tests passed",
- "commit_hash": "62b49f00d489452994de5010565fab81",
- "date_created": "1510742565",
- "percent": 100,
- "status": "success",
- "url": "http://jenkins.cloud.fedoraproject.org/",
- "user": {
- "default_email": "bar@pingou.com",
- "emails": ["bar@pingou.com", "foo@pingou.com"],
- "fullname": "PY C",
- "name": "pingou"},
- "username": "Jenkins"
- },
- "message": "Flag updated",
- "uid": "b1de8f80defd4a81afe2e09f39678087"
- }
- """ # noqa
- repo = _get_repo(repo, username, namespace)
- _check_token(repo, project_token=False)
- output = {}
- reponame = pagure.utils.get_repo_path(repo)
- repo_obj = Repository(reponame)
- try:
- repo_obj.get(commit_hash)
- except ValueError:
- raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOCOMMIT)
- form = pagure.forms.AddPullRequestFlagForm(csrf_enabled=False)
- if form.validate_on_submit():
- username = form.username.data
- percent = form.percent.data.strip() or None
- comment = form.comment.data.strip()
- url = form.url.data.strip()
- uid = form.uid.data.strip() if form.uid.data else None
- status = form.status.data.strip()
- try:
- # New Flag
- message, uid = pagure.lib.query.add_commit_flag(
- session=flask.g.session,
- repo=repo,
- commit_hash=commit_hash,
- username=username,
- percent=percent,
- comment=comment,
- status=status,
- url=url,
- uid=uid,
- user=flask.g.fas_user.username,
- token=flask.g.token.id,
- )
- flask.g.session.commit()
- c_flag = pagure.lib.query.get_commit_flag_by_uid(
- flask.g.session, commit_hash, uid
- )
- output["message"] = message
- output["uid"] = uid
- output["flag"] = c_flag.to_json()
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(err)
- )
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- else:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors
- )
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/<repo>/watchers/update", methods=["POST"])
- @API.route("/<namespace>/<repo>/watchers/update", methods=["POST"])
- @API.route("/fork/<username>/<repo>/watchers/update", methods=["POST"])
- @API.route(
- "/fork/<username>/<namespace>/<repo>/watchers/update", methods=["POST"]
- )
- @api_login_required(acls=["update_watch_status"])
- @api_method
- def api_update_project_watchers(repo, username=None, namespace=None):
- """
- Update project watchers
- -----------------------
- Allows anyone to update their own watch status on the project.
- ::
- POST /api/0/<repo>/watchers/update
- POST /api/0/<namespace>/<repo>/watchers/update
- ::
- POST /api/0/fork/<username>/<repo>/watchers/update
- POST /api/0/fork/<username>/<namespace>/<repo>/watchers/update
- Input
- ^^^^^
- +------------------+---------+--------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+==============+===========================+
- | ``repo`` | string | Mandatory | | The name of the project |
- | | | | to fork. |
- +------------------+---------+--------------+---------------------------+
- | ``status`` | string | Mandatory | | The new watch status to |
- | | | | set on that project. |
- | | | | (See options below) |
- +------------------+---------+--------------+---------------------------+
- | ``watcher`` | string | Mandatory | | The name of the user |
- | | | | changing their watch |
- | | | | status. |
- +------------------+---------+--------------+---------------------------+
- | ``namespace`` | string | Optional | | The namespace of the |
- | | | | project to fork. |
- +------------------+---------+--------------+---------------------------+
- | ``username`` | string | Optional | | The username of the user|
- | | | | of the fork. |
- +------------------+---------+--------------+---------------------------+
- Watch Status
- ^^^^^^^^^^^^
- +------------+----------------------------------------------+
- | Key | Description |
- +============+==============================================+
- | -1 | Reset the watch status to default |
- +------------+----------------------------------------------+
- | 0 | Unwatch, don't notify the user of anything |
- +------------+----------------------------------------------+
- | 1 | Watch issues and pull-requests |
- +------------+----------------------------------------------+
- | 2 | Watch commits |
- +------------+----------------------------------------------+
- | 3 | Watch commits, issues and pull-requests |
- +------------+----------------------------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "message": "You are now watching issues and PRs on this project",
- "status": "ok"
- }
- """
- project = _get_repo(repo, username, namespace)
- _check_token(project)
- # Get the input submitted
- data = get_request_data()
- watcher = data.get("watcher")
- if not watcher:
- _log.debug("api_update_project_watchers: Invalid watcher: %s", watcher)
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ)
- is_site_admin = pagure.utils.is_admin()
- # Only allow the main admin, and the user themselves to update their
- # status
- if not is_site_admin and flask.g.fas_user.username != watcher:
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.EMODIFYPROJECTNOTALLOWED
- )
- try:
- pagure.lib.query.get_user(flask.g.session, watcher)
- except pagure.exceptions.PagureException:
- _log.debug(
- "api_update_project_watchers: Invalid user watching: %s", watcher
- )
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ)
- watch_status = data.get("status")
- try:
- msg = pagure.lib.query.update_watch_status(
- session=flask.g.session,
- project=project,
- user=watcher,
- watch=watch_status,
- )
- flask.g.session.commit()
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(err)
- )
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- return flask.jsonify({"message": msg, "status": "ok"})
- @API.route("/<repo>/git/modifyacls", methods=["POST"])
- @API.route("/<namespace>/<repo>/git/modifyacls", methods=["POST"])
- @API.route("/fork/<username>/<repo>/git/modifyacls", methods=["POST"])
- @API.route(
- "/fork/<username>/<namespace>/<repo>/git/modifyacls", methods=["POST"]
- )
- @api_login_required(acls=["modify_project"])
- @api_method
- def api_modify_acls(repo, namespace=None, username=None):
- """
- Modify ACLs on a project
- ------------------------
- Add, remove or update ACLs on a project for a particular user or group.
- This is restricted to project admins.
- ::
- POST /api/0/<repo>/git/modifyacls
- POST /api/0/<namespace>/<repo>/git/modifyacls
- ::
- POST /api/0/fork/<username>/<repo>/git/modifyacls
- POST /api/0/fork/<username>/<namespace>/<repo>/git/modifyacls
- Input
- ^^^^^
- +------------------+---------+---------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+===============+===========================+
- | ``user_type`` | String | Mandatory | A string to specify if |
- | | | | the ACL should be changed |
- | | | | for a user or a group. |
- | | | | Specifying one of either |
- | | | | 'user' or 'group' is |
- | | | | mandatory |
- | | | | |
- +------------------+---------+---------------+---------------------------+
- | ``name`` | String | Mandatory | The name of the user or |
- | | | | group whose ACL |
- | | | | should be changed. |
- | | | | |
- +------------------+---------+---------------+---------------------------+
- | ``acl`` | String | Optional | Can be either unspecified,|
- | | | | 'ticket', 'commit', |
- | | | | 'admin'. If unspecified, |
- | | | | the access will be removed|
- | | | | |
- +------------------+---------+---------------+---------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "access_groups": {
- "admin": [],
- "commit": [],
- "ticket": []
- },
- "access_users": {
- "admin": [],
- "commit": [
- "ta2"
- ],
- "owner": [
- "karsten"
- ],
- "ticket": [
- "ta1"
- ]
- },
- "close_status": [],
- "custom_keys": [],
- "date_created": "1531131619",
- "date_modified": "1531302337",
- "description": "pagure local instance",
- "fullname": "pagure",
- "id": 1,
- "milestones": {},
- "name": "pagure",
- "namespace": null,
- "parent": null,
- "priorities": {},
- "tags": [],
- "url_path": "pagure",
- "user": {
- "fullname": "KH",
- "name": "karsten"
- }
- }
- """
- output = {}
- project = _get_repo(repo, username, namespace)
- _check_token(project, project_token=False)
- form = pagure.forms.ModifyACLForm(csrf_enabled=False)
- if form.validate_on_submit():
- acl = form.acl.data
- group = None
- user = None
- if form.user_type.data == "user":
- user = form.name.data
- else:
- group = form.name.data
- is_site_admin = pagure.utils.is_admin()
- admins = [u.username for u in project.get_project_users("admin")]
- if not acl:
- if (
- user
- and flask.g.fas_user.username != user
- and flask.g.fas_user.username not in admins
- and flask.g.fas_user.username != project.user.username
- and not is_site_admin
- ):
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.EMODIFYPROJECTNOTALLOWED
- )
- elif (
- flask.g.fas_user.username not in admins
- and flask.g.fas_user.username != project.user.username
- and not is_site_admin
- ):
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.EMODIFYPROJECTNOTALLOWED
- )
- if user:
- user_obj = pagure.lib.query.search_user(
- flask.g.session, username=user
- )
- if not user_obj:
- raise pagure.exceptions.APIError(
- 404, error_code=APIERROR.ENOUSER
- )
- elif group:
- group_obj = pagure.lib.query.search_groups(
- flask.g.session, group_name=group
- )
- if not group_obj:
- raise pagure.exceptions.APIError(
- 404, error_code=APIERROR.ENOGROUP
- )
- if acl:
- if (
- user
- and user_obj not in project.access_users[acl]
- and user_obj.user != project.user.user
- ):
- _log.info(
- "Adding user %s to project: %s", user, project.fullname
- )
- pagure.lib.query.add_user_to_project(
- session=flask.g.session,
- project=project,
- new_user=user,
- user=flask.g.fas_user.username,
- access=acl,
- )
- elif group and group_obj not in project.access_groups[acl]:
- _log.info(
- "Adding group %s to project: %s", group, project.fullname
- )
- pagure.lib.query.add_group_to_project(
- session=flask.g.session,
- project=project,
- new_group=group,
- user=flask.g.fas_user.username,
- access=acl,
- create=pagure_config.get("ENABLE_GROUP_MNGT", False),
- is_admin=pagure.utils.is_admin(),
- )
- else:
- if user:
- _log.info(
- "Looking at removing user %s from project %s",
- user,
- project.fullname,
- )
- try:
- pagure.lib.query.remove_user_of_project(
- flask.g.session,
- user_obj,
- project,
- flask.g.fas_user.username,
- )
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.EINVALIDREQ, errors="%s" % err
- )
- elif group:
- pass
- try:
- flask.g.session.commit()
- except pagure.exceptions.PagureException as msg:
- flask.g.session.rollback()
- _log.debug(msg)
- flask.flash(str(msg), "error")
- except SQLAlchemyError as err:
- _log.exception(err)
- flask.g.session.rollback()
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- pagure.lib.git.generate_gitolite_acls(project=project)
- output = project.to_json(api=True, public=True)
- else:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors
- )
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/<repo>/options", methods=["GET"])
- @API.route("/<namespace>/<repo>/options", methods=["GET"])
- @API.route("/fork/<username>/<repo>/options", methods=["GET"])
- @API.route("/fork/<username>/<namespace>/<repo>/options", methods=["GET"])
- @api_login_required(acls=["modify_project"])
- @api_method
- def api_get_project_options(repo, username=None, namespace=None):
- """
- Get project options
- ----------------------
- Allow project admins to retrieve the current options of a project.
- ::
- GET /api/0/<repo>/options
- GET /api/0/<namespace>/<repo>/options
- ::
- GET /api/0/fork/<username>/<repo>/options
- GET /api/0/fork/<username>/<namespace>/<repo>/options
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "settings": {
- "Enforce_signed-off_commits_in_pull-request": false,
- "Minimum_score_to_merge_pull-request": -1,
- "Only_assignee_can_merge_pull-request": false,
- "Web-hooks": null,
- "always_merge": false,
- "disable_non_fast-forward_merges": false,
- "fedmsg_notifications": true,
- "issue_tracker": true,
- "issue_tracker_read_only": false,
- "issues_default_to_private": false,
- "notify_on_commit_flag": false,
- "notify_on_pull-request_flag": false,
- "open_metadata_access_to_all": false,
- "project_documentation": false,
- "pull_request_access_only": false,
- "pull_requests": true,
- "stomp_notifications": true
- },
- "status": "ok"
- }
- """
- project = _get_repo(repo, username, namespace)
- _check_token(project, project_token=False)
- return flask.jsonify({"settings": project.settings, "status": "ok"})
- @API.route("/<repo>/connector", methods=["GET"])
- @API.route("/<namespace>/<repo>/connector", methods=["GET"])
- @API.route("/fork/<username>/<repo>/connector", methods=["GET"])
- @API.route("/fork/<username>/<namespace>/<repo>/connector", methods=["GET"])
- @api_login_required(acls=["modify_project"])
- @api_method
- def api_get_project_connector(repo, username=None, namespace=None):
- """
- Get project connector
- ---------------------
- Allow project owners and admins to retrieve their own connector tokens.
- Connector tokens are the API tokens and the Web Hook token
- of the project. Connector tokens make possible for an external
- application to listen and verify project notifications and act
- on project via the REST API.
- ::
- GET /api/0/<repo>/connector
- GET /api/0/<namespace>/<repo>/connector
- ::
- GET /api/0/fork/<username>/<repo>/connector
- GET /api/0/fork/<username>/<namespace>/<repo>/connector
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "connector": {
- "hook_token": "aaabbbccc",
- "api_token": [
- {'name': 'foo token',
- 'id': "abcdefoo",
- 'expired': True}
- {'name': 'bar token',
- 'id': "abcdebar",
- 'expired': False}
- ]
- },
- "status": "ok"
- }
- """
- project = _get_repo(repo, username, namespace)
- _check_token(project, project_token=False)
- authorized_users = [project.user.username]
- authorized_users.extend(
- [user.user for user in project.access_users["admin"]]
- )
- if flask.g.fas_user.user not in authorized_users:
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.ENOTHIGHENOUGH
- )
- user_obj = pagure.lib.query.search_user(
- flask.g.session, username=flask.g.fas_user.user
- )
- user_project_tokens = [
- token for token in user_obj.tokens if token.project_id == project.id
- ]
- connector = {
- "hook_token": project.hook_token,
- "api_tokens": [
- {"description": t.description, "id": t.id, "expired": t.expired}
- for t in user_project_tokens
- ],
- }
- return flask.jsonify({"connector": connector, "status": "ok"})
- def _check_value(value):
- """ Convert the provided value into a boolean, an int or leave it as it.
- """
- if str(value).lower() in ["true"]:
- value = True
- elif str(value).lower() in ["false"]:
- value = True
- elif str(value).isnumeric():
- value = int(value)
- return value
- @API.route("/<repo>/options/update", methods=["POST"])
- @API.route("/<namespace>/<repo>/options/update", methods=["POST"])
- @API.route("/fork/<username>/<repo>/options/update", methods=["POST"])
- @API.route(
- "/fork/<username>/<namespace>/<repo>/options/update", methods=["POST"]
- )
- @api_login_required(acls=["modify_project"])
- @api_method
- def api_modify_project_options(repo, username=None, namespace=None):
- """
- Update project options
- ----------------------
- Allow project admins to modify the options of a project.
- ::
- POST /api/0/<repo>/options/update
- POST /api/0/<namespace>/<repo>/options/update
- ::
- POST /api/0/fork/<username>/<repo>/options/update
- POST /api/0/fork/<username>/<namespace>/<repo>/options/update
- Input
- ^^^^^
- Simply specify the key/values you would like to set. Beware that if you
- do not specify in the request values that have been changed before they
- will go back to their default value.
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- 'message': 'Edited successfully settings of repo: test',
- 'status': 'ok'
- }
- """
- project = _get_repo(repo, username, namespace)
- _check_token(project, project_token=False)
- settings = {}
- for key in flask.request.form:
- settings[key] = _check_value(flask.request.form[key])
- try:
- message = pagure.lib.query.update_project_settings(
- flask.g.session,
- repo=project,
- settings=settings,
- user=flask.g.fas_user.username,
- from_api=True,
- )
- flask.g.session.commit()
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(err)
- )
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- return flask.jsonify({"message": message, "status": "ok"})
- @API.route("/<repo>/token/new", methods=["POST"])
- @API.route("/<namespace>/<repo>/token/new", methods=["POST"])
- @API.route("/fork/<username>/<repo>/token/new", methods=["POST"])
- @API.route("/fork/<username>/<namespace>/<repo>/token/new", methods=["POST"])
- @api_login_required(acls=["modify_project"])
- @api_method
- def api_project_create_api_token(repo, namespace=None, username=None):
- """
- Create API project Token
- ------------------------
- Create a project token API for the caller user
- This is restricted to project admins.
- ::
- POST /api/0/<repo>/token/new
- POST /api/0/<namespace>/<repo>/token/new
- ::
- POST /api/0/fork/<username>/<repo>/token/new
- POST /api/0/fork/<username>/<namespace>/<repo>/token/new
- Input
- ^^^^^
- +------------------+---------+---------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+===============+===========================+
- | ``description`` | String | optional | A string to specify the |
- | | | | description of the token |
- | | | | |
- +------------------+---------+---------------+---------------------------+
- | ``acls`` | List | Mandatory | The ACLs |
- | | | | |
- +------------------+---------+---------------+---------------------------+
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {
- "token": {
- "description": "My foo token",
- "id": "aaabbbcccfootoken",
- },
- }
- """
- output = {}
- project = _get_repo(repo, username, namespace)
- _check_token(project, project_token=False)
- authorized_users = [project.user.username]
- authorized_users.extend(
- [user.user for user in project.access_users["admin"]]
- )
- if flask.g.fas_user.user not in authorized_users:
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.ENOTHIGHENOUGH
- )
- authorized_acls = pagure_config.get("USER_ACLS", [])
- form = pagure.forms.NewTokenForm(csrf_enabled=False, sacls=authorized_acls)
- if form.validate_on_submit():
- acls = form.acls.data
- description = form.description.data
- else:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors
- )
- token = pagure.lib.query.add_token_to_user(
- flask.g.session, project, acls, flask.g.fas_user.user, description
- )
- output = {"token": {"description": token.description, "id": token.id}}
- jsonout = flask.jsonify(output)
- return jsonout
- @API.route("/<repo>/blockuser", methods=["POST"])
- @API.route("/<namespace>/<repo>/blockuser", methods=["POST"])
- @API.route("/fork/<username>/<repo>/blockuser", methods=["POST"])
- @API.route("/fork/<username>/<namespace>/<repo>/blockuser", methods=["POST"])
- @api_login_required(acls=["modify_project"])
- @api_method
- def api_project_block_user(repo, namespace=None, username=None):
- """
- Block an user from a project
- ----------------------------
- Block an user from interacting with the project
- This is restricted to project admins.
- ::
- POST /api/0/<repo>/blockuser
- POST /api/0/<namespace>/<repo>/blockuser
- ::
- POST /api/0/fork/<username>/<repo>/blockuser
- POST /api/0/fork/<username>/<namespace>/<repo>/blockuser
- Input
- ^^^^^
- +------------------+---------+---------------+---------------------------+
- | Key | Type | Optionality | Description |
- +==================+=========+===============+===========================+
- | ``username`` | String | optional | The username of the user |
- | | | | to block on this project |
- +------------------+---------+---------------+---------------------------+
- Beware that this API endpoint updates **all** the users blocked in the
- project, so if you are updating this list, do not submit just one username,
- submit the updated list.
- Sample response
- ^^^^^^^^^^^^^^^
- ::
- {"message": "User(s) blocked"}
- """
- output = {}
- project = _get_repo(repo, username, namespace)
- _check_token(project)
- authorized_users = [project.user.username]
- authorized_users.extend(
- [user.user for user in project.access_users["admin"]]
- )
- if flask.g.fas_user.username not in authorized_users:
- raise pagure.exceptions.APIError(
- 401, error_code=APIERROR.ENOTHIGHENOUGH
- )
- usernames = flask.request.form.getlist("username")
- try:
- users = set()
- for user in usernames:
- user = user.strip()
- if user:
- pagure.lib.query.get_user(flask.g.session, user)
- users.add(user)
- project.block_users = list(users)
- flask.g.session.add(project)
- flask.g.session.commit()
- output = {"message": "User(s) blocked"}
- except pagure.exceptions.PagureException as err:
- raise pagure.exceptions.APIError(
- 400, error_code=APIERROR.ENOCODE, error=str(err)
- )
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
- jsonout = flask.jsonify(output)
- return jsonout
|