Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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))
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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)
Exemplo n.º 6
0
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))
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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))