server/app/endpoints/jira_create.py (175 lines of code) (raw):

#!/usr/bin/env python3 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """Selfserve Portal for the Apache Software Foundation""" """Handler for creating a jira project""" if not __debug__: raise RuntimeError("This code requires assert statements to be enabled") from ..lib import middleware, asfuid, email, log, config import quart import re import asyncio import os import json RE_VALID_PROJECT_KEY = re.compile(r"^[A-Z0-9]+$") ACLI_CMD = "/opt/latest-cli/acli.sh" JIRA_SCHEME_FILES = { "workflow": "/x1/acli/site/js/jiraworkflowschemes.json", } async def jira_user_exists(username: str): """Checks if a jira user exists""" proc = await asyncio.create_subprocess_exec( ACLI_CMD, *("jira", "--action", "getUser", "--userId", username, "--quiet"), ) await proc.wait() assert proc.returncode == 0, "Could not find the specified project lead ID in Jira" async def create_jira_project( project_key: str, project_name: str, description: str, project_lead: str, issue_scheme: str, workflow_scheme: str, homepage_url: str, ): """Creates a new jira project""" assert RE_VALID_PROJECT_KEY.match(project_key), "Invalid project key!" proc = await asyncio.create_subprocess_exec( ACLI_CMD, *( "jira", "-v", "--action", "createProject", "--project", project_key, "--name", project_name, "--description", description, "--lead", project_lead, "--issueTypeScheme", issue_scheme, "--workflowScheme", workflow_scheme, "--url", homepage_url, "--notificationScheme", "Empty Scheme", "--permissionScheme", "_Default Permission Scheme_", ), ) await proc.wait() assert proc.returncode == 0, "Could not create new jira project, it may already exist" async def set_project_access(project_key: str, ldap_project: str): """Sets up default permissions for a project""" assert RE_VALID_PROJECT_KEY.match(project_key), "Invalid space name!" assert ( isinstance(ldap_project, str) and ldap_project and ldap_project in config.projects ), "Please specify a valid PMC" # Admin access for PMC proc = await asyncio.create_subprocess_exec( ACLI_CMD, *( "jira", "--action", "addProjectRoleActors", "--project", project_key, "--role", "administrators", "--group", f"{ldap_project}-pmc", ), ) await proc.wait() assert proc.returncode == 0, f"Could not assign administrator access to {ldap_project}-pmc" # Standard access to project committers proc = await asyncio.create_subprocess_exec( ACLI_CMD, *( "jira", "--action", "addProjectRoleActors", "--project", project_key, "--role", "committers", "--group", ldap_project, ), ) await proc.wait() assert proc.returncode == 0, f"Could not assign write access to {ldap_project} committers" @asfuid.session_required async def process(form_data, session): # Create a new jira project project_key = form_data.get("project_key") project_name = form_data.get("project_name") project_lead = form_data.get("project_lead") ldap_project = form_data.get("ldap_project") issue_scheme = form_data.get("issue_scheme") workflow_scheme = form_data.get("workflow_scheme") homepage_url = form_data.get("homepage_url") description = form_data.get("description") try: assert (session.pmcs or session.root), "Only members of a (P)PMC may create jira projects" assert isinstance(project_key, str) and RE_VALID_PROJECT_KEY.match(project_key), "Invalid project key specified" assert isinstance(project_name, str) and project_name, "Please specify a title for the new Jira project" assert isinstance(description, str) and description, "Please write a short description of this new project" assert isinstance(project_lead, str) and project_lead, "Please specify a project lead for this project" assert ( ldap_project in config.projects ), "Please specify a valid, current apache project to assign this Jira project to" if not session.root: assert ldap_project in session.pmcs, "You can only create a Jira project for an Apache project you are on the PMC of" assert isinstance(issue_scheme, str) and issue_scheme, "Please specify a valid issue scheme this project" assert ( isinstance(workflow_scheme, str) and workflow_scheme ), "Please specify a valid workflow scheme for this project" assert isinstance(homepage_url, str) and homepage_url, "Please specify a homepage URL for this project" # Make sure project lead exists in Jira await jira_user_exists(project_lead) # Set up the new project await create_jira_project( project_key=project_key, project_name=project_name, project_lead=project_lead, description=description, issue_scheme=issue_scheme, workflow_scheme=workflow_scheme, homepage_url=homepage_url, ) # Set standard access: admin for PMC, read/write for committers await set_project_access(project_key, ldap_project) except AssertionError as e: return {"success": False, "message": str(e)} # Notify await log.slack(f"A new Jira project, `{project_key}`, has been created as requested by {session.uid}@apache.org.") project_email = email.project_to_private(ldap_project) email.from_template( "jira_project_created.txt", recipient=("private@infra.apache.org", project_email, f"{session.uid}@apache.org"), variables={ "project_key": project_key, "ldap_project": ldap_project, "requester": session.uid, }, ) # All done for now return { "success": True, "message": "Jira project created", } @asfuid.session_required async def list_schemes(form_data, session): """Lists current valid schemes for Jira""" scheme_dict = {} for key, filepath in JIRA_SCHEME_FILES.items(): if os.path.isfile(filepath): try: js = json.load(open(filepath)) scheme_dict[key] = js except json.JSONDecodeError: # Bad JSON file? :/ scheme_dict[key] = {} return quart.jsonify(scheme_dict) quart.current_app.add_url_rule( "/api/jira-project-create", methods=[ "POST", # Create a new jira project ], view_func=middleware.glued(process), ) quart.current_app.add_url_rule( "/api/jira-project-schemes", methods=[ "GET", # List valid schemes ], view_func=middleware.glued(list_schemes), )