def get_settings(snap_name): try: snap_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) if "whitelist_country_codes" in snap_details: whitelist_country_codes = ( snap_details["whitelist_country_codes"] if len(snap_details["whitelist_country_codes"]) > 0 else []) else: whitelist_country_codes = [] if "blacklist_country_codes" in snap_details: blacklist_country_codes = ( snap_details["blacklist_country_codes"] if len(snap_details["blacklist_country_codes"]) > 0 else []) else: blacklist_country_codes = [] countries = [] for country in pycountry.countries: countries.append({"key": country.alpha_2, "name": country.name}) is_on_lp = False lp_snap = launchpad.get_snap_by_store_name(snap_name) if lp_snap: is_on_lp = True context = { "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "publisher_name": snap_details["publisher"]["display-name"], "license": license, "private": snap_details["private"], "unlisted": snap_details["unlisted"], "countries": countries, "whitelist_country_codes": whitelist_country_codes, "blacklist_country_codes": blacklist_country_codes, "price": snap_details["price"], "store": snap_details["store"], "keywords": snap_details["keywords"], "status": snap_details["status"], "is_on_lp": is_on_lp, "update_metadata_on_release": snap_details["update_metadata_on_release"], } return flask.render_template("publisher/settings.html", **context)
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 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 get_snap_builds_json(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) context = {"snap_builds": []} start = flask.request.args.get("start", 0, type=int) size = flask.request.args.get("size", 15, type=int) build_slice = slice(start, size) # Get built snap in launchpad with this store name lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) if lp_snap: context.update(get_builds(lp_snap, build_slice)) return flask.jsonify(context)
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 = 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) # Don't allow changes from Admins that are no contributors account_snaps = publisher_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( "The repository doesn't exist or you don't have" " enough permissions", "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: flask.flash( "The specified repository is being used by another snap:" f" {repo_exist['store_name']}", "negative", ) return flask.redirect(redirect_url) macaroon = publisher_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 successfully.", "positive") # Create webhook in the repo, it should also trigger the first build github_hook_url = ( f"{GITHUB_WEBHOOK_HOST_URL}{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 post_settings(snap_name): changes = None changed_fields = flask.request.form.get("changes") if changed_fields: changes = loads(changed_fields) if changes: snap_id = flask.request.form.get("snap_id") error_list = [] body_json = logic.filter_changes_data(changes) if body_json: try: publisher_api.snap_metadata(snap_id, flask.session, body_json) 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: error_list = error_list + api_response_error_list.errors except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) if error_list: try: snap_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: error_list = error_list + api_response_error_list.errors except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) field_errors, other_errors = logic.invalid_field_errors(error_list) countries = [] for country in pycountry.countries: countries.append({ "key": country.alpha_2, "name": country.name }) is_on_lp = False lp_snap = launchpad.get_snap_by_store_name( snap_details["snap_name"]) if lp_snap: is_on_lp = True if "whitelist_country_codes" in snap_details: whitelist_country_codes = ( snap_details["whitelist_country_codes"] if len(snap_details["whitelist_country_codes"]) > 0 else []) else: whitelist_country_codes = [] if "blacklist_country_codes" in snap_details: blacklist_country_codes = ( snap_details["blacklist_country_codes"] if len(snap_details["blacklist_country_codes"]) > 0 else []) else: blacklist_country_codes = [] update_metadata_on_release = True if "update_metadata_on_release" in snap_details: if snap_details["update_metadata_on_release"] == "on": update_metadata_on_release = True else: update_metadata_on_release = False context = { # read-only values from details API "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "publisher_name": snap_details["publisher"]["display-name"], "snap_id": snap_details["snap_id"], "private": snap_details["private"], "unlisted": snap_details["unlisted"], "countries": countries, "whitelist_country_codes": whitelist_country_codes, "blacklist_country_codes": blacklist_country_codes, "price": snap_details["price"], "store": snap_details["store"], "keywords": snap_details["keywords"], "status": snap_details["status"], "is_on_lp": is_on_lp, "update_metadata_on_release": update_metadata_on_release, # errors "error_list": error_list, "field_errors": field_errors, "other_errors": other_errors, } return flask.render_template("publisher/settings.html", **context) flask.flash("Changes applied successfully.", "positive") else: flask.flash("No changes to save.", "information") return flask.redirect(flask.url_for(".get_settings", snap_name=snap_name))