def get_manage_members(store_id): try: members = admin_api.get_store_members(flask.session, store_id) for item in members: if item["email"] == flask.session["publisher"]["email"]: item["current_user"] = True except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return jsonify(members)
def get_models(store_id): try: stores = admin_api.get_stores(flask.session) store = admin_api.get_store(flask.session, store_id) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return flask.render_template( "admin/models.html", stores=stores, store=store, models=[], )
def get_release_history_json(snap_name): page = flask.request.args.get("page", default=1, type=int) try: release_history = api.snap_release_history(flask.session, snap_name, page) 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 flask.jsonify(api_response_error_list.errors), 400 except ApiError as api_error: return _handle_error(api_error) return flask.jsonify(release_history)
def post_settings(store_id): settings = {} settings["private"] = json.loads(flask.request.form.get("private")) settings["manual-review-policy"] = flask.request.form.get( "manual-review-policy") res = {} try: admin_api.change_store_settings(flask.session, store_id, settings) res["msg"] = "Changes saved" except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return jsonify({"success": True})
def get_snap_revision_json(snap_name, revision): """ Return JSON object from the publisher API """ try: revision = publisher_api.get_snap_revision(flask.session, snap_name, revision) 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 flask.jsonify(api_response_error_list.errors), 400 except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return flask.jsonify(revision)
def get_settings(snap_name): try: snap_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) 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}) 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"], } return flask.render_template("publisher/settings.html", **context)
def get_stores(): """ In this view we get all the stores the user is an admin or we show a 403 """ try: stores = admin_api.get_stores(flask.session) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) if not stores: flask.abort(403) # We redirect to the first store snap list return flask.redirect( flask.url_for(".get_settings", store_id=stores[0]["id"]))
def post_release(snap_name): data = flask.request.json if not data: return flask.jsonify({}), 400 try: response = api.post_snap_release(flask.session, snap_name, data) 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 flask.jsonify(api_response_error_list.errors), 400 except ApiError as api_error: return _handle_error(api_error) return flask.jsonify(response)
def get_manage_members(store_id): try: stores = admin_api.get_stores(flask.session) store = admin_api.get_store(flask.session, store_id) members = admin_api.get_store_members(flask.session, store_id) invites = admin_api.get_store_invites(flask.session, store_id) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return flask.render_template( "admin/manage_members.html", stores=stores, store=store, members=members, invites=invites, )
def post_update_invites(store_id): invites = json.loads(flask.request.form.get("invites")) try: admin_api.update_store_invites(flask.session, store_id, invites) flask.flash("Changes saved", "positive") except StoreApiResponseErrorList as api_response_error_list: msgs = [ f"{error.get('message', 'An error occurred')}" for error in api_response_error_list.errors ] for msg in msgs: flask.flash(msg, "negative") except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return flask.redirect(flask.url_for(".get_invites", store_id=store_id))
def get_invites(store_id): try: stores = admin_api.get_stores(flask.session) store = admin_api.get_store(flask.session, store_id) invites = admin_api.get_store_invites(flask.session, store_id) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) pending_invites = [] expired_invites = [] revoked_invites = [] for invite in invites: if invite["status"] == "Pending": pending_invites.append(invite) if invite["status"] == "Expired": expired_invites.append(invite) if invite["status"] == "Revoked": revoked_invites.append(invite) sorted_pending_invites = sorted( pending_invites, key=lambda item: item["expiration-date"] ) sorted_expired_invites = sorted( expired_invites, key=lambda item: item["expiration-date"] ) sorted_revoked_invites = sorted( revoked_invites, key=lambda item: item["expiration-date"] ) return flask.render_template( "admin/invites.html", stores=stores, store=store, pending_invites=sorted_pending_invites, expired_invites=sorted_expired_invites, revoked_invites=sorted_revoked_invites, )
def get_account_snaps(): try: account_info = publisher_api.get_account(flask.session) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) user_snaps, registered_snaps = logic.get_snaps_account_info(account_info) flask_user = flask.session["publisher"] context = { "snaps": user_snaps, "current_user": flask_user["nickname"], "registered_snaps": registered_snaps, } return flask.render_template("publisher/account-snaps.html", **context)
def get_snap_build(snap_name, build_id): 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_id": details["snap_id"], "snap_name": details["snap_name"], "snap_title": details["title"], "snap_build": {}, } # Get build by snap name and build_id lp_build = launchpad.get_snap_build(details["snap_name"], build_id) if lp_build: status = map_build_and_upload_states( lp_build["buildstate"], lp_build["store_upload_status"] ) context["snap_build"] = { "id": lp_build["self_link"].split("/")[-1], "arch_tag": lp_build["arch_tag"], "datebuilt": lp_build["datebuilt"], "duration": lp_build["duration"], "logs": lp_build["build_log_url"], "revision_id": lp_build["revision_id"], "status": status, "title": lp_build["title"], } if context["snap_build"]["logs"]: context["raw_logs"] = launchpad.get_snap_build_log( details["snap_name"], build_id ) return flask.render_template("publisher/build.html", **context)
def get_validate_repo(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) owner, repo = flask.request.args.get("repo").split("/") return flask.jsonify( validate_repo( flask.session.get("github_auth_secret"), details["snap_name"], owner, repo, ))
def post_build(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) if launchpad.is_snap_building(details["snap_name"]): launchpad.cancel_snap_builds(details["snap_name"]) launchpad.build_snap(details["snap_name"]) flask.flash("Build triggered", "positive") return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name))
def post_register_name_dispute(): try: snap_name = flask.request.form.get("snap-name", "") claim_comment = flask.request.form.get("claim-comment", "") api.post_register_name_dispute(flask.session, bleach.clean(snap_name), bleach.clean(claim_comment)) except ApiResponseErrorList as api_response_error_list: if api_response_error_list.status_code in [400, 409]: return flask.render_template( "publisher/register-name-dispute.html", snap_name=snap_name, errors=api_response_error_list.errors, ) else: return _handle_error_list(api_response_error_list.errors) except ApiError as api_error: return _handle_error(api_error) return flask.render_template( "publisher/register-name-dispute-success.html", snap_name=snap_name)
def get_register_name(): try: user = publisher_api.get_account(flask.session) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) available_stores = logic.filter_available_stores(user["stores"]) snap_name = flask.request.args.get("snap_name", default="", type=str) store = flask.request.args.get("store", default="", type=str) conflict_str = flask.request.args.get("conflict", default="False", type=str) conflict = conflict_str == "True" already_owned_str = flask.request.args.get("already_owned", default="False", type=str) already_owned = already_owned_str == "True" reserved_str = flask.request.args.get("reserved", default="False", type=str) reserved = reserved_str == "True" is_private_str = flask.request.args.get("is_private", default="False", type=str) is_private = is_private_str == "True" context = { "snap_name": snap_name, "is_private": is_private, "conflict": conflict, "already_owned": already_owned, "reserved": reserved, "store": store, "available_stores": available_stores, } return flask.render_template("publisher/register-snap.html", **context)
def post_settings(store_id): settings = {} settings["private"] = not flask.request.form.get("is_public") settings["manual-review-policy"] = flask.request.form.get( "manual-review-policy") try: admin_api.change_store_settings(flask.session, store_id, settings) flask.flash("Changes saved", "positive") except StoreApiResponseErrorList as api_response_error_list: msgs = [ f"{error.get('message', 'An error occurred')}" for error in api_response_error_list.errors ] for msg in msgs: flask.flash(msg, "negative") except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return flask.redirect(flask.url_for(".get_settings", store_id=store_id))
def login_callback(): code = flask.request.args["code"] state = flask.request.args["state"] flask.session.pop("macaroon_root", None) flask.session.pop("macaroon_discharge", None) # Avoid CSRF attacks validate_csrf(state) discharged_token = candid.discharge_token(code) candid_macaroon = candid.discharge_macaroon( flask.session["publisher-macaroon"], discharged_token) # Store bakery authentication flask.session["macaroons"] = candid.get_serialized_bakery_macaroon( flask.session["publisher-macaroon"], candid_macaroon) try: publisher = publisher_api.whoami(flask.session) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) flask.session["publisher"] = { "account_id": publisher["account"]["id"], "nickname": publisher["account"]["username"], "fullname": publisher["account"]["name"], "image": None, "email": publisher["account"]["email"], } return flask.redirect( flask.session.pop( "next_url", flask.url_for("publisher_snaps.get_account_snaps"), ), 302, )
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 update_invite_status(store_id): invites = json.loads(flask.request.form.get("invites")) res = {} try: admin_api.update_store_invites(flask.session, store_id, invites) res["msg"] = "Changes saved" except StoreApiResponseErrorList as api_response_error_list: msgs = [ f"{error.get('message', 'An error occurred')}" for error in api_response_error_list.errors ] msgs = list(dict.fromkeys(msgs)) for msg in msgs: flask.flash(msg, "negative") except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return jsonify(res)
def post_manage_members(store_id): members = json.loads(flask.request.form.get("members")) res = {} try: admin_api.update_store_members(flask.session, store_id, members) res["msg"] = "Changes saved" except StoreApiResponseErrorList as api_response_error_list: codes = [error.get("code") for error in api_response_error_list.errors] msgs = [ f"{error.get('message', 'An error occurred')}" for error in api_response_error_list.errors ] for code in codes: account_id = "" if code == "store-users-no-match": if account_id: res["msg"] = code else: res["msg"] = "invite" elif code == "store-users-multiple-matches": res["msg"] = code else: for msg in msgs: flask.flash(msg, "negative") except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return jsonify(res)
def get_store_snaps(store_id): try: stores = admin_api.get_stores(flask.session) store = admin_api.get_store(flask.session, store_id) snaps = admin_api.get_store_snaps(flask.session, store_id) # list of all deduped store IDs that are not current store other_store_ids = list(dict.fromkeys([d["store"] for d in snaps])) other_stores = list( filter(lambda id: id != store["id"], other_store_ids)) # store data for each store ID other_stores_data = [] for other_store_id in other_stores: if other_store_id == "ubuntu": other_stores_data.append({ "id": "ubuntu", "name": "Global store" }) else: store_data = admin_api.get_store(flask.session, other_store_id) other_stores_data.append(store_data) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) return flask.render_template( "admin/snaps.html", stores=stores, store=store, store_json=json.dumps(store), snaps=json.dumps(snaps), other_stores_data=json.dumps(other_stores_data), )
def get_snap_builds_json(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) 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, 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 publisher_snap_metrics(snap_name): """ A view to display the snap metrics page for specific snaps. This queries the snapcraft API (api.snapcraft.io) and passes some of the data through to the publisher/metrics.html template, with appropriate sanitation. """ 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) metric_requested = logic.extract_metrics_period( flask.request.args.get("period", default="30d", type=str)) installed_base_metric = logic.verify_base_metrics( flask.request.args.get("active-devices", default="version", type=str)) installed_base = logic.get_installed_based_metric(installed_base_metric) metrics_query_json = metrics_helper.build_metrics_json( snap_id=details["snap_id"], installed_base=installed_base, metric_period=metric_requested["int"], metric_bucket=metric_requested["bucket"], ) try: metrics_response = publisher_api.get_publisher_metrics( flask.session, json=metrics_query_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: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) try: latest_day_period = logic.extract_metrics_period("1d") latest_installed_base = logic.get_installed_based_metric("version") latest_day_query_json = metrics_helper.build_metrics_json( snap_id=details["snap_id"], installed_base=latest_installed_base, metric_period=latest_day_period["int"], metric_bucket=latest_day_period["bucket"], ) latest_day_response = publisher_api.get_publisher_metrics( flask.session, json=latest_day_query_json) except StoreApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) active_metrics = metrics_helper.find_metric(metrics_response["metrics"], installed_base) series = active_metrics["series"] if installed_base_metric == "os": capitalized_series = active_metrics["series"] for item in capitalized_series: item["name"] = metrics._capitalize_os_name(item["name"]) series = capitalized_series active_devices = metrics.ActiveDevices( name=active_metrics["metric_name"], series=series, buckets=active_metrics["buckets"], status=active_metrics["status"], ) latest_active = 0 if active_devices: latest_active = active_devices.get_number_latest_active_devices() if latest_day_response: latest_active_metrics = metrics_helper.find_metric( latest_day_response["metrics"], latest_installed_base) if latest_active_metrics: latest_active_devices = metrics.ActiveDevices( name=latest_active_metrics["metric_name"], series=latest_active_metrics["series"], buckets=latest_active_metrics["buckets"], status=latest_active_metrics["status"], ) latest_active = ( latest_active_devices.get_number_latest_active_devices()) country_metric = metrics_helper.find_metric( metrics_response["metrics"], "weekly_installed_base_by_country") country_devices = metrics.CountryDevices( name=country_metric["metric_name"], series=country_metric["series"], buckets=country_metric["buckets"], status=country_metric["status"], private=True, ) territories_total = 0 if country_devices: territories_total = country_devices.get_number_territories() nodata = not any([country_devices, active_devices]) annotations = {"name": "annotations", "series": [], "buckets": []} default_track = (details.get("default-track") if details.get("default-track") else "latest") for category in details["categories"]["items"]: date = category["since"].split("T")[0] new_date = logic.convert_date(category["since"]) if date not in annotations["buckets"]: annotations["buckets"].append(date) index_of_date = annotations["buckets"].index(date) single_series = { "values": [0] * (len(annotations)), "name": category["name"], "display_name": category["name"].capitalize().replace("-", " "), "display_date": new_date, "date": date, } single_series["values"][index_of_date] = 1 annotations["series"].append(single_series) annotations["series"] = sorted(annotations["series"], key=lambda k: k["date"]) context = { # Data direct from details API "snap_name": snap_name, "snap_title": details["title"], "publisher_name": details["publisher"]["display-name"], "metric_period": metric_requested["period"], "active_device_metric": installed_base_metric, "default_track": default_track, "private": details["private"], # Metrics data "nodata": nodata, "latest_active_devices": latest_active, "active_devices": dict(active_devices), "territories_total": territories_total, "territories": country_devices.country_data, "active_devices_annotations": annotations, # Context info "is_linux": "Linux" in flask.request.headers["User-Agent"], } return flask.render_template("publisher/metrics.html", **context)
def get_listing_snap(snap_name): try: snap_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) details_metrics_enabled = snap_details["public_metrics_enabled"] details_blacklist = snap_details["public_metrics_blacklist"] is_on_stable = logic.is_snap_on_stable(snap_details["channel_maps_list"]) # Filter icon & screenshot urls from the media set. icon_urls, screenshot_urls, banner_urls = logic.categorise_media( snap_details["media"]) licenses = [] for license in helpers.get_licenses(): licenses.append({"key": license["licenseId"], "name": license["name"]}) license = snap_details["license"] license_type = "custom" if " AND " not in license.upper() and " WITH " not in license.upper(): license_type = "simple" referrer = None if flask.request.args.get("from"): referrer = flask.request.args.get("from") try: categories_results = store_api.get_categories() except StoreApiError: categories_results = [] categories = sorted( get_categories(categories_results), key=lambda category: category["slug"], ) snap_categories = logic.replace_reserved_categories_key( snap_details["categories"]) snap_categories = logic.filter_categories(snap_categories) snap_categories["categories"] = [ category["name"] for category in snap_categories["categories"] ] filename = "publisher/content/listing_tour.yaml" tour_steps = helpers.get_yaml(filename, typ="rt") context = { "snap_id": snap_details["snap_id"], "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_categories": snap_categories, "summary": snap_details["summary"], "description": snap_details["description"], "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "screenshot_urls": screenshot_urls, "banner_urls": banner_urls, "contact": snap_details["contact"], "private": snap_details["private"], "website": snap_details["website"] or "", "public_metrics_enabled": details_metrics_enabled, "public_metrics_blacklist": details_blacklist, "license": license, "license_type": license_type, "licenses": licenses, "video_urls": snap_details["video_urls"], "is_on_stable": is_on_stable, "from": referrer, "categories": categories, "tour_steps": tour_steps, "status": snap_details["status"], } return flask.render_template("publisher/listing.html", **context)
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 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["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.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 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_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_register_name(): snap_name = flask.request.form.get("snap-name") if not snap_name: return flask.redirect(flask.url_for(".get_register_name")) is_private = flask.request.form.get("is_private") == "private" store = flask.request.form.get("store") registrant_comment = flask.request.form.get("registrant_comment") try: publisher_api.post_register_name( session=flask.session, snap_name=snap_name, is_private=is_private, store=store, registrant_comment=registrant_comment, ) except StoreApiResponseErrorList as api_response_error_list: try: user = publisher_api.get_account(flask.session) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) available_stores = logic.filter_available_stores(user["stores"]) if api_response_error_list.status_code == 409: for error in api_response_error_list.errors: if error["code"] == "already_claimed": return flask.redirect( flask.url_for("account.get_account_details")) elif error["code"] == "already_registered": return flask.redirect( flask.url_for( ".get_register_name", snap_name=snap_name, is_private=is_private, store=store, conflict=True, )) elif error["code"] == "already_owned": return flask.redirect( flask.url_for( ".get_register_name", snap_name=snap_name, is_private=is_private, store=store, already_owned=True, )) elif error["code"] == "reserved_name": return flask.redirect( flask.url_for( ".get_register_name", snap_name=snap_name, is_private=is_private, store=store, reserved=True, )) context = { "snap_name": snap_name, "is_private": is_private, "available_stores": available_stores, "errors": api_response_error_list.errors, } return flask.render_template("publisher/register-snap.html", **context) except (StoreApiError, ApiError) as api_error: return _handle_error(api_error) flask.flash("".join([ snap_name, " registered.", ' <a href="https://docs.snapcraft.io/build-snaps/upload"', " ", ' target="blank">How to upload a Snap</a>', ])) return flask.redirect(flask.url_for("account.get_account"))