예제 #1
0
def apply_overrides(manifest_json, overrides, marketplace):
    """ Overrides fields in the manifest file.

    Args:
        manifest_json (dict): a json dict of the entire manifest
        overrides (dict): a json dict of override parameters. If this dict contains invalid
        marketplace (str): the marketplace name

    Raises:
        UnloggedException: if the marketplace doesn't exist
        ValidationError: if a non-integer is passed as the price or port parameter.

    Returns:
        dict: a json dict of the manifest with fields overridden.
    """
    manifest_json = copy.deepcopy(manifest_json)
    if overrides is None:
        return manifest_json

    if "title" in overrides:
        manifest_json["info"]["title"] = overrides["title"]
    if "description" in overrides:
        manifest_json["info"]["description"] = overrides["description"]
    if "price" in overrides:
        invalid_price_format = "Price should be a non-negative integer."
        try:
            price = int(overrides["price"])
            manifest_json["info"]["x-21-total-price"]["min"] = price
            manifest_json["info"]["x-21-total-price"]["max"] = price
            if price < 0:
                raise exceptions.ValidationError(invalid_price_format)
        except ValueError:
            raise exceptions.ValidationError(invalid_price_format)
    if "name" in overrides:
        manifest_json["info"]["contact"]["name"] = overrides["name"]
    if "email" in overrides:
        manifest_json["info"]["contact"]["email"] = overrides["email"]
    if "host" in overrides:
        manifest_json["host"] = overrides["host"]
    if "port" in overrides:
        host = manifest_json["host"]
        # if the host is in the form of https://x.com/ remove the trailing slash
        host = host.strip("/")
        invalid_port_format = "Port should be an integer between 0 and 65536."
        try:
            port = int(overrides["port"])
            if port <= 0 or port > 65536:
                raise exceptions.ValidationError(invalid_port_format)
        except ValueError:
            raise exceptions.ValidationError(invalid_port_format)
        host += ":{}".format(port)
        manifest_json["host"] = host
    if "basePath" in overrides:
        manifest_json["basePath"] = overrides["basePath"]

    return manifest_json
예제 #2
0
def check_app_manifest(api_docs_path, overrides, marketplace):
    """ Runs validate_manifest and handles any errors that could occur

    Args:
        api_docs_path (str): path to the manifest file
        overrides (dict): Dictionary containing the key/value pairs will be overridden
        in the manifest.
        marketplace (str): the marketplace name

    Raises:
        ValidationError: If manifest is not valid, bad, missing, is a directory, or too large
    """
    if not os.path.exists(api_docs_path):
        raise exceptions.ValidationError(
            uxstring.UxString.manifest_missing.format(api_docs_path))

    if os.path.isdir(api_docs_path):
        raise exceptions.ValidationError(
            uxstring.UxString.manifest_is_directory.format(api_docs_path))

    file_size = os.path.getsize(api_docs_path) / 1e6
    if file_size > 2:
        raise exceptions.ValidationError(
            uxstring.UxString.large_manifest.format(api_docs_path))

    try:
        with open(api_docs_path, "r") as f:
            manifest_dict = yaml.load(f.read())

        # empty yaml files do not raise an error, so do it here
        if not manifest_dict:
            raise YAMLError

        manifest_dict = clean_manifest(manifest_dict)
        if overrides is not None:
            manifest_dict = override_manifest(manifest_dict, overrides,
                                              marketplace)

        # ensure the manifest is valid
        validate_manifest(manifest_dict)

        # write back the manifest in case some clean up or overriding has happend
        if overrides is not None:
            with open(api_docs_path, "w") as f:
                yaml.dump(manifest_dict, f)

        return manifest_dict
    except (YAMLError, ValueError):
        raise exceptions.ValidationError(
            uxstring.UxString.malformed_yaml.format(api_docs_path))
예제 #3
0
def check_app_manifest(api_docs_path, overrides, marketplace):
    """ Runs validate_manifest and handles any errors that could occur

    Args:
        api_docs_path (str): path to the manifest file
        overrides (dict): Dictionary containing the key/value pairs will be overridden
        in the manifest.
        marketplace (str): the marketplace name

    Raises:
        ValidationError: If manifest is not valid, bad, missing, is a directory, or too large
    """
    if not os.path.exists(api_docs_path):
        raise exceptions.ValidationError(
            click.style("Could not find the manifest file at {}.",
                        fg="red").format(api_docs_path))

    if os.path.isdir(api_docs_path):
        raise exceptions.ValidationError(
            click.style(
                "{} is a directory. Please enter the direct path to the manifest file.",
                fg="red").format(api_docs_path))

    file_size = os.path.getsize(api_docs_path) / 1e6
    if file_size > 2:
        raise exceptions.ValidationError(
            click.style(
                "The size of the manifest file at {} exceeds the maximum limit of 2MB.",
                fg="red").format(api_docs_path))

    try:
        with open(api_docs_path, "r") as f:
            original_manifest_dict = yaml.load(f.read())

        manifest_dict = transform_manifest(original_manifest_dict, overrides,
                                           marketplace)

        # write back the manifest in case some clean up or overriding has happend
        with open(api_docs_path, "w") as f:
            yaml.dump(manifest_dict, f)

        return manifest_dict
    except (YAMLError, ValueError):
        raise exceptions.ValidationError(
            click.style("Your manifest file at {} is not valid YAML.",
                        fg="red").format(api_docs_path))
예제 #4
0
def validate_manifest(manifest_json):
    """ Validates the manifest file

        Ensures that the required fields in the manifest are present and valid

    Args:
        manifest_json (dict): a json dict of the entire manifest

    Raises:
        ValueError: if a required field is not valid or present in the manifest
    """
    manifest_json = copy.deepcopy(manifest_json)
    for field in ["schemes", "host", "basePath", "info"]:
        if field not in manifest_json:
            raise exceptions.ValidationError(
                click.style("Field '{}' is missing from the manifest file.", fg="red").format(field),
                json=manifest_json)

    for field in ["contact", "title", "description", "x-21-total-price", "x-21-quick-buy", "x-21-category"]:
        if field not in manifest_json["info"]:
            raise exceptions.ValidationError(
                click.style(
                    "Field '{}' is missing from the manifest file under the 'info' section.",
                    fg="red").format(field),
                json=manifest_json)

    for field in {"name", "email"}:
        if field not in manifest_json["info"]["contact"]:
            raise exceptions.ValidationError(
                click.style(
                    "Field '{}' is missing from the manifest file under the 'contact' section.", fg="red")
                .format(field),
                json=manifest_json)

    for field in ["min", "max"]:
        if field not in manifest_json["info"]["x-21-total-price"]:
            raise exceptions.ValidationError(
                click.style("Field '{}' is missing from the manifest file under the "
                            "'x-21-total-price' section.",
                            fg="red"),
                json=manifest_json)

    if len(manifest_json["schemes"]) == 0:
        raise exceptions.ValidationError(
            click.style(
                "You have to specify either HTTP or HTTPS for your endpoint under the "
                "`schemes` section.",
                fg="red"),
            json=manifest_json)

    valid_app_categories = {'blockchain', 'entertainment', 'social', 'markets', 'utilities', 'iot'}
    if manifest_json["info"]["x-21-category"].lower() not in valid_app_categories:
        valid_categories = ", ".join(valid_app_categories)
        raise exceptions.ValidationError(
            click.style("'{}' is not a valid category for the 21 marketplace. Valid categories are {}.",
                        fg="red").format(
                            manifest_json["info"]["x-21-category"], valid_categories),
            json=manifest_json)
예제 #5
0
def validate_manifest(manifest_json):
    """ Validates the manifest file

        Ensures that the required fields in the manifest are present and valid

    Args:
        manifest_json (dict): a json dict of the entire manifest

    Raises:
        ValueError: if a required field is not valid or present in the manifest
    """
    for field in uxstring.UxString.valid_top_level_manifest_fields:
        if field not in manifest_json:
            raise exceptions.ValidationError(
                uxstring.UxString.top_level_manifest_field_missing.format(
                    field),
                json=manifest_json)

    for field in uxstring.UxString.manifest_info_fields:
        if field not in manifest_json["info"]:
            raise exceptions.ValidationError(
                uxstring.UxString.manifest_info_field_missing.format(field),
                json=manifest_json)

    for field in uxstring.UxString.manifest_contact_fields:
        if field not in manifest_json["info"]["contact"]:
            raise exceptions.ValidationError(
                uxstring.UxString.manifest_contact_field_missing.format(field),
                json=manifest_json)

    for field in uxstring.UxString.price_fields:
        if field not in manifest_json["info"]["x-21-total-price"]:
            raise exceptions.ValidationError(
                uxstring.UxString.price_fields_missing.format(field),
                json=manifest_json)

    if len(manifest_json["schemes"]) == 0:
        raise exceptions.ValidationError(uxstring.UxString.scheme_missing,
                                         json=manifest_json)

    if manifest_json["info"]["x-21-category"].lower(
    ) not in uxstring.UxString.valid_app_categories:
        valid_categories = ", ".join(uxstring.UxString.valid_app_categories)
        raise exceptions.ValidationError(
            uxstring.UxString.invalid_category.format(
                manifest_json["info"]["x-21-category"], valid_categories),
            json=manifest_json)
예제 #6
0
def _publish(client, manifest_path, marketplace, skip, overrides):
    """ Publishes application by uploading the manifest to the given marketplace

    Args:
        client (two1.server.rest_client.TwentyOneRestClient) an object for
            sending authenticated requests to the TwentyOne backend.
        manifest_path (str): the path to the manifest file.
        marketplace (str): the zerotier marketplace name.
        skip (bool): skips strict checking of manifest file.
        overrides (dict): Dictionary containing the key/value pairs will be overridden
        in the manifest.

    Raises:
        ValidationError: if an error occurs while parsing the manifest file
    """
    try:
        manifest_json = check_app_manifest(manifest_path, overrides,
                                           marketplace)
        app_url = "{}://{}".format(manifest_json["schemes"][0],
                                   manifest_json["host"])
        app_ip = urlparse(app_url).hostname

        if not skip:
            address = get_zerotier_address(marketplace)

            if address != app_ip:
                wrong_ip = click.style("It seems that the IP address that you put in your manifest file (") +\
                           click.style("{}", bold=True) +\
                           click.style(") is different than your current 21market IP (") +\
                           click.style("{}", bold=True) +\
                           click.style(")\nAre you sure you want to continue publishing with ") +\
                           click.style("{}", bold=True) +\
                           click.style("?")
                if not click.confirm(wrong_ip.format(app_ip, address, app_ip)):
                    switch_host = click.style("Please edit ") +\
                                  click.style("{}", bold=True) +\
                                  click.style(" and replace ") +\
                                  click.style("{}", bold=True) +\
                                  click.style(" with ") +\
                                  click.style("[{}].", bold=True)
                    logger.info(
                        switch_host.format(manifest_path, app_ip, address))
                    return

    except exceptions.ValidationError as ex:
        # catches and re-raises the same exception to enhance the error message
        publish_docs_url = click.style("https://21.co/learn/21-publish/",
                                       bold=True)
        publish_instructions = "For instructions on publishing your app, please refer to {}".format(
            publish_docs_url)
        raise exceptions.ValidationError(
            "The following error occurred while reading your manifest file at {}:\n{}\n\n{}"
            .format(manifest_path, ex.args[0], publish_instructions),
            json=ex.json)

    app_name = manifest_json["info"]["title"]
    app_endpoint = "{}://{}{}".format(manifest_json["schemes"][0],
                                      manifest_json["host"],
                                      manifest_json["basePath"])

    logger.info(
        (click.style("Publishing {} at ") + click.style("{}", bold=True) +
         click.style(" to {}.")).format(app_name, app_endpoint, marketplace))
    payload = {"manifest": manifest_json, "marketplace": marketplace}
    try:
        response = client.publish(payload)
    except ServerRequestError as e:
        if e.status_code == 403 and e.data.get("error") == "TO600":
            logger.info(
                "The endpoint {} specified in your manifest has already been registered in "
                "the marketplace by another user.\nPlease check your manifest file and make "
                "sure your 'host' field is correct.\nIf the problem persists please contact "
                "[email protected].".format(app_endpoint),
                fg="red")
            return
        else:
            raise e

    if response.status_code == 201:
        response_data = response.json()
        mkt_url = response_data['mkt_url']
        permalink = response_data['permalink']
        logger.info(
            click.style(
                "\n"
                "You have successfully published {} to {}. "
                "You should be able to view the listing within a few minutes at {}\n\n"
                "Users will be able to purchase it, using 21 buy, at {} ",
                fg="magenta").format(app_name, marketplace, permalink,
                                     mkt_url))
예제 #7
0
def override_manifest(manifest_json, overrides, marketplace):
    """ Overrides fields in the manifest file.

    Args:
        manifest_json (dict): a json dict of the entire manifest
        overrides (dict): a json dict of override parameters. If this dict contains invalid
        marketplace (str): the marketplace name

    Raises:
        UnloggedException: if the marketplace doesn't exist
        ValidationError: if a non-integer is passed as the price or port parameter.

    Returns:
        dict: a json dict of the manifest with fields overridden.
    """

    old_host = manifest_json["host"].strip("/")

    if "title" in overrides:
        manifest_json["info"]["title"] = overrides["title"]
    if "description" in overrides:
        manifest_json["info"]["description"] = overrides["description"]
    if "price" in overrides:
        try:
            price = int(overrides["price"])
            manifest_json["info"]["x-21-total-price"]["min"] = price
            manifest_json["info"]["x-21-total-price"]["max"] = price
            if price < 0:
                raise exceptions.ValidationError(
                    uxstring.UxString.invalid_price_format)
        except ValueError:
            raise exceptions.ValidationError(
                uxstring.UxString.invalid_price_format)
    if "name" in overrides:
        manifest_json["info"]["contact"]["name"] = overrides["name"]
    if "email" in overrides:
        manifest_json["info"]["contact"]["email"] = overrides["email"]
    if "host" in overrides:
        host = overrides["host"]
        if host == "AUTO":
            host = get_zerotier_address(marketplace)
        manifest_json["host"] = host
    if "port" in overrides:
        host = manifest_json["host"]
        # if the host is in the form of https://x.com/ remove the trailing slash
        host = host.strip("/")
        try:
            port = int(overrides["port"])
            if port <= 0 or port > 65536:
                raise exceptions.ValidationError(
                    uxstring.UxString.invalid_port_format)
        except ValueError:
            raise exceptions.ValidationError(
                uxstring.UxString.invalid_port_format)
        host += ":{}".format(port)
        manifest_json["host"] = host
    if "basePath" in overrides:
        manifest_json["basePath"] = overrides["basePath"]

    new_host = manifest_json["host"]
    if new_host != old_host:
        manifest_json = replace_host_in_docs(manifest_json, new_host, old_host)
    return manifest_json
예제 #8
0
def _publish(client, manifest_path, marketplace, skip, overrides):
    """ Publishes application by uploading the manifest to the given marketplace

    Args:
        client (two1.server.rest_client.TwentyOneRestClient) an object for
            sending authenticated requests to the TwentyOne backend.
        manifest_path (str): the path to the manifest file.
        marketplace (str): the zerotier marketplace name.
        skip (bool): skips strict checking of manifest file.
        overrides (dict): Dictionary containing the key/value pairs will be overridden
        in the manifest.

    Raises:
        ValidationError: if an error occurs while parsing the manifest file
    """
    try:
        manifest_json = check_app_manifest(manifest_path, overrides,
                                           marketplace)
        app_url = urlparse(manifest_json["host"])
        app_ip = app_url.path.split(":")[0]

        if not skip:
            address = get_zerotier_address(marketplace)

            if address != app_ip:
                if not click.confirm(
                        uxstring.UxString.wrong_ip.format(
                            app_ip, address, app_ip)):
                    logger.info(
                        uxstring.UxString.switch_host.format(
                            manifest_path, app_ip, address))
                    return

    except exceptions.ValidationError as ex:
        # catches and re-raises the same exception to enhance the error message
        raise exceptions.ValidationError(uxstring.UxString.bad_manifest.format(
            manifest_path, ex.args[0]),
                                         json=ex._json)

    app_name = manifest_json["info"]["title"]
    app_endpoint = "{}://{}{}".format(manifest_json["schemes"][0],
                                      manifest_json["host"],
                                      manifest_json["basePath"])

    logger.info(
        uxstring.UxString.publish_start.format(app_name, app_endpoint,
                                               marketplace))
    payload = {"manifest": manifest_json, "marketplace": marketplace}
    try:
        response = client.publish(payload)
    except ServerRequestError as e:
        if e.status_code == 403 and e.data.get("error") == "TO600":
            logger.info(uxstring.UxString.app_url_claimed.format(app_endpoint),
                        fg="red")
            return
        else:
            raise e

    if response.status_code == 201:
        logger.info(
            uxstring.UxString.publish_success.format(app_name, marketplace))