def post_github_webhook(snap_name=None, github_owner=None, github_repo=None): repo_url = flask.request.json["repository"]["html_url"] gh_owner = flask.request.json["repository"]["owner"]["login"] gh_repo = flask.request.json["repository"]["name"] if snap_name: lp_snap = launchpad.get_snap_by_store_name(snap_name) else: lp_snap = launchpad.get_snap(md5(repo_url.encode("UTF-8")).hexdigest()) # Check that this is the repo for this snap if lp_snap["git_repository_url"] != repo_url: return ("The repository does not match the one used by this Snap", 403) github = GitHub() if not github.validate_webhook_signature( flask.request.data, flask.request.headers.get("X-Hub-Signature")): return ("Invalid secret", 403) validation = validate_repo(GITHUB_SNAPCRAFT_USER_TOKEN, lp_snap["store_name"], gh_owner, gh_repo) if not validation["success"]: return (validation["error"]["message"], 400) if launchpad.is_snap_building(lp_snap["store_name"]): launchpad.cancel_snap_builds(lp_snap["store_name"]) launchpad.build_snap(lp_snap["store_name"]) return ("", 204)
def post_update_gh_webhooks(snap_name): try: details = api.get_snap_info(snap_name, flask.session) except ApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code == 404: return flask.abort(404, "No snap named {}".format(snap_name)) else: return _handle_error_list(api_response_error_list.errors) except ApiError as api_error: return _handle_error(api_error) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) gh_link = lp_snap["git_repository_url"][19:] gh_owner, gh_repo = gh_link.split("/") github = GitHub(flask.session.get("github_auth_secret")) old_url = f"https://build.snapcraft.io/{gh_owner}/{gh_repo}/webhook/notify" old_hook = github.get_hook_by_url(gh_owner, gh_repo, old_url) if old_hook: github.update_hook_url( gh_owner, gh_repo, old_hook["id"], f"https://snapcraft.io/{snap_name}/webhook/notify", ) return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name))
def post_github_webhook(snap_name=None, github_owner=None, github_repo=None): payload = flask.request.json repo_url = payload["repository"]["html_url"] gh_owner = payload["repository"]["owner"]["login"] gh_repo = payload["repository"]["name"] gh_default_branch = payload["repository"]["default_branch"] # The first payload after the webhook creation # doesn't contain a "ref" key if "ref" in payload: gh_event_branch = payload["ref"][11:] else: gh_event_branch = gh_default_branch # Check the push event is in the default branch if gh_default_branch != gh_event_branch: return ("The push event is not for the default branch", 200) if snap_name: lp_snap = launchpad.get_snap_by_store_name(snap_name) else: lp_snap = launchpad.get_snap(md5(repo_url.encode("UTF-8")).hexdigest()) if not lp_snap: return ("This repository is not linked with any Snap", 403) # Check that this is the repo for this snap if lp_snap["git_repository_url"] != repo_url: return ("The repository does not match the one used by this Snap", 403) github = GitHub() signature = flask.request.headers.get("X-Hub-Signature") if not github.validate_webhook_signature(flask.request.data, signature): if not github.validate_bsi_webhook_secret( gh_owner, gh_repo, flask.request.data, signature ): return ("Invalid secret", 403) validation = validate_repo( GITHUB_SNAPCRAFT_USER_TOKEN, lp_snap["store_name"], gh_owner, gh_repo ) if not validation["success"]: return (validation["error"]["message"], 400) if launchpad.is_snap_building(lp_snap["store_name"]): launchpad.cancel_snap_builds(lp_snap["store_name"]) launchpad.build_snap(lp_snap["store_name"]) return ("", 204)
def get_snap_builds(snap_name): try: details = api.get_snap_info(snap_name, flask.session) # API call to make users without needed permissions refresh the session # Users needs package_upload_request permission to use this feature api.get_package_upload_macaroon(session=flask.session, snap_name=snap_name, channels=["edge"]) except ApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code == 404: return flask.abort(404, "No snap named {}".format(snap_name)) else: return _handle_error_list(api_response_error_list.errors) except ApiError as api_error: return _handle_error(api_error) context = { "snap_id": details["snap_id"], "snap_name": details["snap_name"], "snap_title": details["title"], "snap_builds_enabled": False, "snap_builds": [], "total_builds": 0, } # Get built snap in launchpad with this store name github = GitHub(flask.session.get("github_auth_secret")) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) if lp_snap: # Git repository without GitHub hostname context["github_repository"] = lp_snap["git_repository_url"][19:] github_owner, github_repo = context["github_repository"].split("/") context["yaml_file_exists"] = github.get_snapcraft_yaml_location( github_owner, github_repo) if not context["yaml_file_exists"]: flask.flash("This repository doesn't contain a snapcraft.yaml", "negative") context = get_builds(lp_snap, context, slice(0, BUILDS_PER_PAGE)) else: try: context["github_user"] = github.get_user() except Unauthorized: context["github_user"] = None if context["github_user"]: context["github_orgs"] = github.get_orgs() return flask.render_template("publisher/builds.html", **context)
def get_repos(): github = GitHub(flask.session.get("github_auth_secret")) org = flask.request.args.get("org") try: if org: repos = github.get_org_repositories(org) else: repos = github.get_user_repositories() except Unauthorized: return ( flask.jsonify({"error": "You need to be authenticated on GitHub"}), 401, ) return flask.jsonify(repos)
def validate_repo(github_token, snap_name, gh_owner, gh_repo): github = GitHub(github_token) result = {"success": True} yaml_location = github.get_snapcraft_yaml_location(gh_owner, gh_repo) # The snapcraft.yaml is not present if not yaml_location: result["success"] = False result["error"] = { "type": "MISSING_YAML_FILE", "message": ("Missing snapcraft.yaml: this repo needs a snapcraft.yaml " "file, so that Snapcraft can make it buildable, installable " "and runnable."), } # The property name inside the yaml file doesn't match the snap else: try: gh_snap_name = github.get_snapcraft_yaml_name(gh_owner, gh_repo) if gh_snap_name != snap_name: result["success"] = False result["error"] = { "type": "SNAP_NAME_DOES_NOT_MATCH", "message": ("Name mismatch: the snapcraft.yaml uses the snap " f'name "{gh_snap_name}", but you\'ve registered' f' the name "{snap_name}". Update your ' "snapcraft.yaml to continue."), "yaml_location": yaml_location, "gh_snap_name": gh_snap_name, } except InvalidYAML: result["success"] = False result["error"] = { "type": "INVALID_YAML_FILE", "message": ("Invalid snapcraft.yaml: there was an issue parsing the " f"snapcraft.yaml for {snap_name}."), } return result
def post_disconnect_repo(snap_name): try: details = publisher_api.get_snap_info(snap_name, flask.session) except StoreApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code == 404: return flask.abort(404, "No snap named {}".format(snap_name)) else: return _handle_error_list(api_response_error_list.errors) except StoreApiError as api_error: return _handle_error(api_error) lp_snap = launchpad.get_snap_by_store_name(snap_name) launchpad.delete_snap(details["snap_name"]) # Try to remove the GitHub webhook if possible if flask.session.get("github_auth_secret"): github = GitHub(flask.session.get("github_auth_secret")) try: gh_owner, gh_repo = lp_snap["git_repository_url"][19:].split("/") old_hook = github.get_hook_by_url( gh_owner, gh_repo, f"{GITHUB_WEBHOOK_HOST_URL}{snap_name}/webhook/notify", ) if old_hook: github.remove_hook( gh_owner, gh_repo, old_hook["id"], ) except HTTPError: pass return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name) )
def post_snap_builds(snap_name): try: details = api.get_snap_info(snap_name, flask.session) except ApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code == 404: return flask.abort(404, "No snap named {}".format(snap_name)) else: return _handle_error_list(api_response_error_list.errors) except ApiError as api_error: return _handle_error(api_error) # Don't allow changes from Admins that are no contributors account_snaps = api.get_account_snaps(flask.session) if snap_name not in account_snaps: flask.flash("You do not have permissions to modify this Snap", "negative") return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name)) redirect_url = flask.url_for(".get_snap_builds", snap_name=snap_name) # Get built snap in launchpad with this store name github = GitHub(flask.session.get("github_auth_secret")) owner, repo = flask.request.form.get("github_repository").split("/") if not github.check_permissions_over_repo(owner, repo): flask.flash( "Your GitHub account doesn't have permissions in the repository", "negative", ) return flask.redirect(redirect_url) repo_validation = validate_repo(flask.session.get("github_auth_secret"), snap_name, owner, repo) if not repo_validation["success"]: flask.flash(repo_validation["error"]["message"], "negative") return flask.redirect(redirect_url) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) git_url = f"https://github.com/{owner}/{repo}" if not lp_snap: lp_snap_name = md5(git_url.encode("UTF-8")).hexdigest() try: repo_exist = launchpad.get_snap(lp_snap_name) except HTTPError as e: if e.response.status_code == 404: repo_exist = False else: raise e if repo_exist: # The user registered the repo in BSI but didn't register a name # We can remove it and continue with the normal process if not repo_exist["store_name"]: # This conditional should be removed when issue 2657 is solved launchpad._request(path=repo_exist["self_link"][32:], method="DELETE") else: flask.flash( "The specified repository is being used by another snap:" f" {repo_exist['store_name']}", "negative", ) return flask.redirect(redirect_url) macaroon = api.get_package_upload_macaroon(session=flask.session, snap_name=snap_name, channels=["edge" ])["macaroon"] launchpad.create_snap(snap_name, git_url, macaroon) flask.flash("The GitHub repository was linked correctly.", "positive") # Create webhook in the repo, it should also trigger the first build github_hook_url = f"https://snapcraft.io/{snap_name}/webhook/notify" try: hook = github.get_hook_by_url(owner, repo, github_hook_url) # We create the webhook if doesn't exist already in this repo if not hook: github.create_hook(owner, repo, github_hook_url) except HTTPError: flask.flash( "The GitHub Webhook could not be created. " "Please trigger a new build manually.", "caution", ) elif lp_snap["git_repository_url"] != git_url: # In the future, create a new record, delete the old one raise AttributeError( f"Snap {snap_name} already has a build repository associated") return flask.redirect(redirect_url)
def get_snap_builds(snap_name): try: details = publisher_api.get_snap_info(snap_name, flask.session) # API call to make users without needed permissions refresh the session # Users needs package_upload_request permission to use this feature publisher_api.get_package_upload_macaroon(session=flask.session, snap_name=snap_name, channels=["edge"]) except StoreApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code == 404: return flask.abort(404, "No snap named {}".format(snap_name)) else: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) context = { "snap_id": details["snap_id"], "snap_name": details["snap_name"], "snap_title": details["title"], "snap_builds_enabled": False, "snap_builds": [], "total_builds": 0, } # Get built snap in launchpad with this store name lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) if lp_snap: # In this case we can use the GitHub user account or # the Snapcraft GitHub user to check the snapcraft.yaml github = GitHub( flask.session.get("github_auth_secret", GITHUB_SNAPCRAFT_USER_TOKEN)) # Git repository without GitHub hostname context["github_repository"] = lp_snap["git_repository_url"][19:] github_owner, github_repo = context["github_repository"].split("/") context["github_repository_exists"] = github.check_if_repo_exists( github_owner, github_repo) context["yaml_file_exists"] = github.get_snapcraft_yaml_location( github_owner, github_repo) context.update(get_builds(lp_snap, slice(0, BUILDS_PER_PAGE))) context["snap_builds_enabled"] = bool(context["snap_builds"]) else: github = GitHub(flask.session.get("github_auth_secret")) try: context["github_user"] = github.get_user() except Unauthorized: context["github_user"] = None if context["github_user"]: context["github_orgs"] = github.get_orgs() return flask.render_template("publisher/builds.html", **context)
def get_update_gh_webhooks(snap_name): try: details = publisher_api.get_snap_info(snap_name, flask.session) except StoreApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code == 404: return flask.abort(404, "No snap named {}".format(snap_name)) else: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) if not lp_snap: flask.flash("This snap is not linked with a GitHub repository", "negative") return flask.redirect( flask.url_for(".get_settings", snap_name=snap_name)) github = GitHub(flask.session.get("github_auth_secret")) try: github.get_user() except Unauthorized: return flask.redirect(f"/github/auth?back={flask.request.path}") gh_link = lp_snap["git_repository_url"][19:] gh_owner, gh_repo = gh_link.split("/") try: # Remove old BSI webhook if present old_url = ( f"https://build.snapcraft.io/{gh_owner}/{gh_repo}/webhook/notify") old_hook = github.get_hook_by_url(gh_owner, gh_repo, old_url) if old_hook: github.remove_hook( gh_owner, gh_repo, old_hook["id"], ) # Remove current hook github_hook_url = ( f"{GITHUB_WEBHOOK_HOST_URL}{snap_name}/webhook/notify") snapcraft_hook = github.get_hook_by_url(gh_owner, gh_repo, github_hook_url) if snapcraft_hook: github.remove_hook( gh_owner, gh_repo, snapcraft_hook["id"], ) # Create webhook in the repo github.create_hook(gh_owner, gh_repo, github_hook_url) except HTTPError: flask.flash( "The GitHub Webhook could not be created. " "Please try again or check your permissions over the repository.", "caution", ) else: flask.flash("The webhook has been created successfully", "positive") return flask.redirect(flask.url_for(".get_settings", snap_name=snap_name))
def post_snap_builds(snap_name): try: details = api.get_snap_info(snap_name, flask.session) except ApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code == 404: return flask.abort(404, "No snap named {}".format(snap_name)) else: return _handle_error_list(api_response_error_list.errors) except ApiError as api_error: return _handle_error(api_error) redirect_url = flask.url_for(".get_snap_builds", snap_name=snap_name) # Get built snap in launchpad with this store name github = GitHub(flask.session.get("github_auth_secret")) owner, repo = flask.request.form.get("github_repository").split("/") if not github.check_permissions_over_repo(owner, repo): flask.flash( "Your GitHub account doesn't have permissions in the repository", "negative", ) return flask.redirect(redirect_url) repo_validation = validate_repo(flask.session.get("github_auth_secret"), snap_name, owner, repo) if not repo_validation["success"]: flask.flash(repo_validation["error"]["message"], "negative") return flask.redirect(redirect_url) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) git_url = f"https://github.com/{owner}/{repo}" if not lp_snap: lp_snap_name = md5(git_url.encode("UTF-8")).hexdigest() try: repo_exist = bool(launchpad.get_snap(lp_snap_name)) except HTTPError as e: if e.response.status_code == 404: repo_exist = False else: raise e if repo_exist: error_msg = ( "The specified GitHub repository is being used by another snap" ) flask.flash(error_msg, "negative") return flask.redirect(redirect_url) macaroon = api.get_package_upload_macaroon(session=flask.session, snap_name=snap_name, channels=["edge" ])["macaroon"] launchpad.create_snap(snap_name, git_url, macaroon) # Create webhook in the repo, it should also trigger the first build github.create_hook(owner, repo, f"https://snapcraft.io/{snap_name}/webhook/notify") flask.flash("The GitHub repository was linked correctly.", "positive") elif lp_snap["git_repository_url"] != git_url: # In the future, create a new record, delete the old one raise AttributeError( f"Snap {snap_name} already has a build repository associated") return flask.redirect(redirect_url)