Skip to content

_upcloud

sc_crawler.vendors._upcloud #

Inventory of UpCloud via its API: servers, storages, traffic, IPv4 addresses and managed PostgreSQL, with their prices.

Functions:

Name Description
inventory_compliance_frameworks

Manual list of known compliance frameworks at UpCloud.

inventory_regions

List all regions via API call.

inventory_zones

List all regions as availability zones.

inventory_servers

List all server plans from UpCloud API.

inventory_server_prices

List all on-demand server prices in all zones from the UpCloud price list.

inventory_server_prices_spot

List all spot server prices in all zones from the UpCloud price list.

inventory_storages

Block storage tiers collected manually.

inventory_storage_prices

List block storage prices in all zones from the UpCloud price list.

inventory_traffic_prices

List outbound traffic prices in all zones from the UpCloud price list.

inventory_ipv4_prices

List IPv4 address prices in all zones from the UpCloud price list.

inventory_databases

List UpCloud managed PostgreSQL service plans.

inventory_database_prices

List UpCloud managed PostgreSQL compute prices.

inventory_database_storages

List managed PostgreSQL disk tiers (Standard SSD and MaxIOPS).

inventory_database_storage_prices

List managed PostgreSQL disk tier prices from the UpCloud zone price list.

inventory_compliance_frameworks #

inventory_compliance_frameworks(vendor)

Manual list of known compliance frameworks at UpCloud.

Data collected from their Security and Standards docs at https://upcloud.com/security-privacy.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_compliance_frameworks(vendor):
    """Manual list of known compliance frameworks at UpCloud.

    Data collected from their Security and Standards docs at
    <https://upcloud.com/security-privacy>."""
    return map_compliance_frameworks_to_vendor(
        vendor.vendor_id,
        ["iso27001"],
    )

inventory_regions #

inventory_regions(vendor)

List all regions via API call.

Data manually enriched from https://upcloud.com/data-centres.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_regions(vendor):
    """List all regions via API call.

    Data manually enriched from <https://upcloud.com/data-centres>."""
    manual_data = {
        "au-syd1": {
            "country_id": "AU",
            "state": "New South Wales",
            "city": "Sydney",
            "founding_year": 2021,
            "green_energy": False,
            "lon": 151.189377,
            "lat": -33.918251,
        },
        "de-fra1": {
            "country_id": "DE",
            "state": "Hesse",
            "city": "Frankfurt",
            "founding_year": 2015,
            "green_energy": True,
            "lon": 8.735120,
            "lat": 50.119190,
        },
        "dk-cph1": {
            "country_id": "DK",
            "city": "Copenhagen",
            "founding_year": 2026,
            "green_energy": True,
            # approximation based on city as the datacenter is not listed on homepage yet
            "lon": 12.57,
            "lat": 55.68,
        },
        "fi-hel1": {
            "country_id": "FI",
            "state": "Uusimaa",
            "city": "Helsinki",
            "founding_year": 2011,
            "green_energy": True,
            "lon": 24.778570,
            "lat": 60.20323,
        },
        "fi-hel2": {
            "country_id": "FI",
            "state": "Uusimaa",
            "city": "Helsinki",
            "founding_year": 2018,
            "green_energy": True,
            "lon": 24.876350,
            "lat": 60.216209,
        },
        "es-mad1": {
            "country_id": "ES",
            "state": "Madrid",
            "city": "Madrid",
            "founding_year": 2020,
            "green_energy": True,
            "lon": -3.6239873,
            "lat": 40.4395019,
        },
        "nl-ams1": {
            "country_id": "NL",
            "state": "Noord Holland",
            "city": "Amsterdam",
            "founding_year": 2017,
            "green_energy": True,
            "lon": 4.8400019,
            "lat": 52.3998291,
        },
        "no-svg1": {
            "country_id": "NO",
            "state": "Rogaland",
            "city": "Stavanger",
            "founding_year": 2025,
            # TODO update when data shared on homepage
            "green_energy": False,
            # approximation based on city - TODO update when info becomes available on the homepage
            "lon": 5.5979374,
            "lat": 58.9487157,
        },
        "pl-waw1": {
            "country_id": "PL",
            "state": "Mazowieckie",
            "city": "Warsaw",
            "founding_year": 2020,
            "green_energy": True,
            "lon": 20.9192823,
            "lat": 52.1905901,
        },
        "se-sto1": {
            "country_id": "SE",
            "state": "Stockholm",
            "city": "Stockholm",
            "founding_year": 2015,
            "green_energy": True,
            "lon": 18.102788,
            "lat": 59.2636708,
        },
        "sg-sin1": {
            "country_id": "SG",
            "state": "Singapore",
            "city": "Singapore",
            "founding_year": 2017,
            "green_energy": True,
            "lon": 103.7022636,
            "lat": 1.3172304,
        },
        "uk-lon1": {
            "country_id": "GB",
            "state": "London",
            "city": "London",
            "founding_year": 2012,
            "green_energy": True,
            # approximate .. probably business address
            "lon": -0.1037341,
            "lat": 51.5232232,
        },
        "us-chi1": {
            "country_id": "US",
            "state": "Illinois",
            "city": "Chicago",
            "founding_year": 2014,
            "green_energy": False,
            "lon": -87.6342056,
            "lat": 41.8761287,
        },
        "us-nyc1": {
            "country_id": "US",
            "state": "New York",
            "city": "New York",
            "founding_year": 2020,
            "green_energy": False,
            "lon": -74.0645536,
            "lat": 40.7834325,
        },
        "us-sjo1": {
            "country_id": "US",
            "state": "California",
            "city": "San Jose",
            "founding_year": 2018,
            "green_energy": False,
            "lon": -121.9754458,
            "lat": 37.3764769,
        },
    }
    items = []
    regions = _get_zones()["zones"]["zone"]
    for region in regions:
        with sentry_capture_or_raise(vendor=vendor):
            if region["public"] == "yes":
                if region["id"] not in manual_data:
                    raise ValueError(f"Missing manual data for {region['id']}")
                region_data = manual_data[region["id"]]
                items.append(
                    {
                        "vendor_id": vendor.vendor_id,
                        "region_id": region["id"],
                        "name": region["description"],
                        "api_reference": region["id"],
                        "display_name": (
                            region["description"] + f" ({region_data['country_id']})"
                        ),
                        "aliases": [],
                        "country_id": region_data["country_id"],
                        "state": region_data.get("state"),
                        "city": region_data["city"],
                        "address_line": None,
                        "zip_code": None,
                        "lon": region_data["lon"],
                        "lat": region_data["lat"],
                        "founding_year": region_data["founding_year"],
                        "green_energy": region_data["green_energy"],
                    }
                )
    return items

inventory_zones #

inventory_zones(vendor)

List all regions as availability zones.

There is no concept of having multiple availability zones withing a region (virtual datacenter) at UpCloud, so creating 1-1 dummy Zones reusing the Region id and name.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_zones(vendor):
    """List all regions as availability zones.

    There is no concept of having multiple availability zones withing
    a region (virtual datacenter) at UpCloud, so creating 1-1
    dummy Zones reusing the Region id and name.
    """
    items = []
    for region in vendor.regions:
        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "region_id": region.region_id,
                "zone_id": region.region_id,
                "name": region.name,
                "api_reference": region.region_id,
                "display_name": region.name,
            }
        )
    return items

inventory_servers #

inventory_servers(vendor)

List all server plans from UpCloud API.

Lifecycle: current_offering == "no" -> RETIRED; GPU plans with zero stock in /device/availability across all regions -> INACTIVE; otherwise ACTIVE. See _upcloud_server_status.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_servers(vendor):
    """List all server plans from UpCloud API.

    Lifecycle: `current_offering == "no"` -> RETIRED; GPU plans with zero stock
    in `/device/availability` across all regions -> INACTIVE; otherwise ACTIVE.
    See `_upcloud_server_status`.
    """
    servers = _get_server_plans()["plans"]["plan"]
    items = []
    for server in servers:
        with sentry_capture_or_raise(vendor=vendor):
            server_data = _parse_server_name(server["name"])
            if server_data.get("spot"):
                continue
            gpu_count = server.get("gpu_amount", 0)
            gpu_fields = _parse_gpu_model(server.get("gpu_model"), gpu_count)
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "server_id": server["name"],
                    "name": server["name"],
                    "api_reference": server["name"],
                    # https://www.pulumi.com/registry/packages/upcloud/api-docs/server/
                    "api_reference_object": {"plan": server["name"]},
                    "display_name": server["name"],
                    "description": server_data["description"],
                    "family": server_data["family"],
                    "vcpus": server["core_number"],
                    # https://upcloud.com/docs/products/cloud-servers/features/cloud-server-system/#virtualisation
                    "hypervisor": "KVM",
                    # no dedicated vCPUs in the public cloud offerings
                    "cpu_allocation": CpuAllocation.SHARED,
                    "cpu_cores": None,
                    "cpu_speed": None,
                    # no known ARM options
                    "cpu_architecture": CpuArchitecture.X86_64,
                    "cpu_manufacturer": None,
                    "cpu_family": None,
                    "cpu_model": None,
                    "cpu_flags": [],
                    "cpus": [],
                    "memory_amount": server["memory_amount"],
                    "memory_generation": None,
                    "memory_speed": None,
                    "memory_ecc": None,
                    "accelerator_count": gpu_count,
                    "accelerator_type": AcceleratorType.GPU if gpu_count else None,
                    **gpu_fields,
                    "accelerators": [],  # TODO fill this array
                    "storage_size": server["storage_size"],
                    "storage_type": (
                        StorageType.SSD if server["storage_tier"] else None
                    ),
                    "storages": [],
                    # TODO: have to implement manual mapping for network_speed related fields
                    "network_speed_baseline": None,
                    "network_speed_max": None,
                    "network_storage_speed_baseline": None,
                    "network_storage_speed_max": None,
                    "inbound_traffic": 0,
                    "outbound_traffic": server["public_traffic_out"],
                    "ipv4": 0 if server_data["family"] == "CLOUDNATIVE" else 1,
                    "status": _upcloud_server_status(vendor, server),
                }
            )
    return items

inventory_server_prices #

inventory_server_prices(vendor)

List all on-demand server prices in all zones from the UpCloud price list.

GPU plans are listed only in the zones where they are available. Servers are billed hourly up to a monthly cap of 672 hours, described as tiered prices.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_server_prices(vendor):
    """List all on-demand server prices in all zones from the UpCloud price list.

    GPU plans are listed only in the zones where they are available. Servers
    are billed hourly up to a monthly cap of 672 hours, described as tiered
    prices."""
    items = []
    prices = _get_prices()
    for zone_prices in prices["prices"]["zone"]:
        region_id = zone_prices["name"]
        gpu_region_availability = _get_gpu_region_availability(region_id)
        for k, v in zone_prices.items():
            if not k.startswith("server_plan"):
                continue
            server_plan = k[len("server_plan_") :]
            if "SPOT" in server_plan:
                continue
            if server_plan.startswith("GPU"):
                amount = gpu_region_availability.get(server_plan, {}).get("amount", 0)
                if amount == 0:
                    continue
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "region_id": region_id,
                    "zone_id": region_id,
                    "server_id": server_plan,
                    "operating_system": "Linux",
                    "allocation": Allocation.ONDEMAND,
                    "unit": PriceUnit.HOUR,
                    "price": v["price"] / 100,
                    "price_upfront": 0,
                    # as per UpCloud FAQ at <https://upcloud.com/docs/getting-started/faq/>:
                    # > All Cloud Server plans on your account are billed hourly up to the monthly rate cap
                    # > and the hourly rate is determined by dividing the monthly rate by 672 hours (28 days).
                    # > However, if your server is online for more than 672 hours in a calendar month,
                    # > we will bill you on the monthly rate.
                    "price_tiered": [
                        {"lower": 0, "upper": 672, "price": v["price"] / 100},
                        {"lower": 673, "upper": "Infinity", "price": 0},
                    ],
                    "currency": "EUR",
                }
            )
    return items

inventory_server_prices_spot #

inventory_server_prices_spot(vendor)

List all spot server prices in all zones from the UpCloud price list.

GPU plans are listed only in the zones where they are available.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_server_prices_spot(vendor):
    """List all spot server prices in all zones from the UpCloud price list.

    GPU plans are listed only in the zones where they are available."""
    items = []
    prices = _get_prices()
    for zone_prices in prices["prices"]["zone"]:
        region_id = zone_prices["name"]
        gpu_region_availability = _get_gpu_region_availability(region_id)
        for k, v in zone_prices.items():
            if not k.startswith("server_plan"):
                continue
            server_plan = k[len("server_plan_") :]
            if "SPOT" not in server_plan:
                continue
            if server_plan.startswith("GPU"):
                amount = gpu_region_availability.get(server_plan, {}).get("amount", 0)
                if amount == 0:
                    continue
            server_plan = server_plan.replace("SPOT-", "")
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "region_id": region_id,
                    "zone_id": region_id,
                    "server_id": server_plan,
                    "operating_system": "Linux",
                    "allocation": Allocation.SPOT,
                    "unit": PriceUnit.HOUR,
                    "price": v["price"] / 100,
                    "price_upfront": 0,
                    "currency": "EUR",
                }
            )
    return items

inventory_storages #

inventory_storages(vendor)

Block storage tiers collected manually.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_storages(vendor):
    """Block storage tiers collected manually."""
    items = []
    for storage in UPCLOUD_STORAGES:
        items.append(
            {
                "storage_id": storage["id"],
                "vendor_id": vendor.vendor_id,
                "name": storage["name"],
                "description": storage["description"],
                "storage_type": storage["storage_type"],
                "max_iops": storage["max_iops"],
                "max_throughput": storage["max_throughput"],
                "min_size": storage["min_size"],
                "max_size": storage["max_size"],
            }
        )
    return items

inventory_storage_prices #

inventory_storage_prices(vendor)

List block storage prices in all zones from the UpCloud price list.

UpCloud charges storage per hour, converted to monthly prices as at other vendors.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_storage_prices(vendor):
    """List block storage prices in all zones from the UpCloud price list.

    UpCloud charges storage per hour, converted to monthly prices as at other
    vendors."""
    items = []
    prices = _get_prices()
    for zone_prices in prices["prices"]["zone"]:
        for k, v in zone_prices.items():
            if k in ["storage_" + s["id"] for s in UPCLOUD_STORAGES]:
                items.append(
                    {
                        "vendor_id": vendor.vendor_id,
                        "region_id": zone_prices["name"],
                        "storage_id": k[len("storage_") :],
                        "unit": PriceUnit.GB_MONTH,
                        # UpCloud pricing is per hour, but other providers are per month
                        "price": v["price"] / 100 * 24 * 30,
                        "currency": "EUR",
                    }
                )
    return items

inventory_traffic_prices #

inventory_traffic_prices(vendor)

List outbound traffic prices in all zones from the UpCloud price list.

Inbound traffic is free.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_traffic_prices(vendor):
    """List outbound traffic prices in all zones from the UpCloud price list.

    Inbound traffic is free."""
    items = []
    prices = _get_prices()
    for zone_prices in prices["prices"]["zone"]:
        for k, v in zone_prices.items():
            if k == "public_ipv4_bandwidth_out":
                for direction in [d for d in TrafficDirection]:
                    items.append(
                        {
                            "vendor_id": vendor.vendor_id,
                            "region_id": zone_prices["name"],
                            "price": (
                                v["price"] / 100
                                if direction == TrafficDirection.OUT
                                else 0
                            ),
                            "price_tiered": [],
                            "currency": "EUR",
                            "unit": PriceUnit.GB_MONTH,
                            "direction": direction,
                        }
                    )
    return items

inventory_ipv4_prices #

inventory_ipv4_prices(vendor)

List IPv4 address prices in all zones from the UpCloud price list.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_ipv4_prices(vendor):
    """List IPv4 address prices in all zones from the UpCloud price list."""
    items = []
    prices = _get_prices()
    for zone_prices in prices["prices"]["zone"]:
        for k, v in zone_prices.items():
            if k == "ipv4_address":
                items.append(
                    {
                        "vendor_id": vendor.vendor_id,
                        "region_id": zone_prices["name"],
                        "price": v["price"] / 100,
                        "currency": "EUR",
                        "unit": PriceUnit.HOUR,
                    }
                )
    return items

inventory_databases #

inventory_databases(vendor)

List UpCloud managed PostgreSQL service plans.

  • Flexible Developer/Standard/High Memory shapes from GET /1.3/database/plans.
  • Legacy bundled plans from GET /1.3/database/service-types/pg.
  • Supported versions come from service-types/pg properties.version.enum. https://developers.upcloud.com/1.3/16-managed-database/ https://upcloud.com/docs/products/managed-postgresql/configurations/ https://upcloud.com/global/pricing/
Source code in sc_crawler/vendors/_upcloud.py
def inventory_databases(vendor):
    """List UpCloud managed PostgreSQL service plans.

    - Flexible Developer/Standard/High Memory shapes from GET /1.3/database/plans.
    - Legacy bundled plans from GET /1.3/database/service-types/pg.
    - Supported versions come from service-types/pg `properties.version.enum`.
    https://developers.upcloud.com/1.3/16-managed-database/
    https://upcloud.com/docs/products/managed-postgresql/configurations/
    https://upcloud.com/global/pricing/
    """
    payload = _get_pg_service_type()
    properties = payload.get("properties", {})
    versions = properties.get("version", {}).get("enum", [])
    shared = _database_shared_capabilities(properties)
    server_ids = {server.server_id for server in vendor.servers}
    items = []

    # Flexible plans: compute is independent of node count (1–3 for Standard /
    # High Memory; Developer is single-node only).
    plans_payload = _get_database_plans()
    for service_type in plans_payload.get("service_types", []):
        if service_type.get("type") not in ("pg", "postgresql"):
            continue
        zones = service_type.get("zones") or []
        status = Status.ACTIVE if zones else Status.INACTIVE
        for shape in service_type.get("compute_shapes", []):
            database_id = shape["compute"]
            family_key = _shape_family(shape)
            family = _DATABASE_PLAN_FAMILIES.get(family_key, family_key)
            vcpus = shape.get("cpu")
            memory_gb = shape.get("memory_gb")
            memory_amount = memory_gb * _MIB_PER_GIB if memory_gb is not None else None
            node_counts = shape.get("node_counts") or [1]
            ha, ha_strategy = _database_ha_from_node_counts(node_counts)
            storage = shape.get("storage") or {}
            options = storage.get("options") or []
            if options:
                min_base_gib = min(option["base_gib"] for option in options)
                max_gib = max(option["max_gib"] for option in options)
            else:
                min_base_gib = 0
                max_gib = storage.get("total_cap_gib") or 0
            # Storage is billed separately (not bundled into compute).
            # https://upcloud.com/global/pricing/
            can_extend = shape.get("dynamic_storage_supported") or min_base_gib
            storage_extra_min = round(min_base_gib * _GIB_TO_GB)
            storage_extra_max = round(max_gib * _GIB_TO_GB) if can_extend else 0
            display_name = (
                f"{family}: {vcpus} vCPU, {memory_gb} GiB RAM"
                if vcpus is not None and memory_gb is not None
                else database_id
            )
            description_parts = [
                f"{vcpus} vCPUs" if vcpus else None,
                f"{memory_gb} GiB RAM" if memory_gb else None,
            ]
            description = (
                f"UpCloud PostgreSQL {family} "
                f"({', '.join(filter(None, description_parts))})"
            )
            backup_days = [
                _DATABASE_BACKUP_RETENTION_DAYS[name]
                for name in shape.get("backups") or []
                if name in _DATABASE_BACKUP_RETENTION_DAYS
            ]
            continuous_backups = min(backup_days) if backup_days else None
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "database_id": database_id,
                    "name": database_id,
                    "display_name": display_name,
                    "description": description,
                    "api_reference": database_id,
                    # Flexible plans use plan_compute / plan_node_count /
                    # plan_storage_gib / plan_backups instead of a fixed plan name.
                    # https://developers.upcloud.com/1.3/16-managed-database/
                    "api_reference_object": {
                        "service_type": "pg",
                        "plan_compute": database_id,
                    },
                    "server_id": _match_database_server_id(
                        server_ids, vcpus, memory_gb, family_key
                    ),
                    "engine": DatabaseEngine.POSTGRESQL,
                    "wire_protocol": DatabaseWireProtocol.POSTGRESQL,
                    "engine_versions": versions,
                    "family": family,
                    "vcpus": vcpus,
                    "memory_amount": memory_amount,
                    "storage_size": None,
                    "storage_extra_min": storage_extra_min,
                    "storage_extra_max": storage_extra_max,
                    "storage_extra_autosize": False,
                    "ha": ha,
                    "ha_strategy": ha_strategy,
                    "max_read_replicas": max(max(node_counts) - 1, 0),
                    "scheduled_backups": bool(backup_days),
                    "continuous_backups": continuous_backups,
                    "status": status,
                    **shared,
                }
            )

    # Legacy bundled plans (fixed compute + storage + node count).
    for plan in payload.get("service_plans", []):
        database_id = plan["plan"]
        node_count = plan.get("node_count")
        vcpus = plan.get("core_number")
        memory_amount = plan.get("memory_amount")
        components = plan.get("components", {})
        storage_component = components.get("storage", {})
        # UI/plan names say GB, but API values are GiB
        # https://upcloud.com/docs/products/managed-postgresql/configurations/
        nodes = node_count or 1
        included_gib = storage_component.get("included_gib")
        bundled_gib = (
            int(included_gib)
            if included_gib is not None
            else plan["storage_size"] // _MIB_PER_GIB
        )
        storage_size_gb = round((bundled_gib // nodes) * _GIB_TO_GB)
        storage_step_gb = round(
            (plan["storage_step_size"] // _MIB_PER_GIB) * _GIB_TO_GB
        )
        storage_extra_max_gb = round(
            ((plan["storage_cap_size"] - plan["storage_size"]) // _MIB_PER_GIB // nodes)
            * _GIB_TO_GB
        )
        dynamic_storage_supported = storage_component.get("dynamic_storage_supported")
        if dynamic_storage_supported:
            storage_extra_min = storage_step_gb
            storage_extra_max = storage_extra_max_gb
        else:
            storage_extra_min = 0
            storage_extra_max = 0
        family = "Legacy"
        compute = components.get("compute", {})
        cpu = compute.get("cpu")
        memory_gb = compute.get("memory_gb")
        memory_gib = memory_amount / _MIB_PER_GIB
        description_parts = [
            f"{vcpus} vCPUs" if vcpus else None,
            f"{int(memory_gib)} GiB RAM" if memory_gib else None,
            f"{int(storage_size_gb)} GB storage" if storage_size_gb else None,
        ]
        display_name = (
            f"{family}: {', '.join(filter(None, description_parts))}, "
            f"{nodes} node{'s' if nodes > 1 else ''}"
        )
        description_parts.append(f"{nodes} node{'s' if nodes > 1 else ''}")
        description = (
            f"UpCloud PostgreSQL {family} "
            f"({', '.join(filter(None, description_parts))})"
        )
        backup_cfg = plan.get("backup_config_pg", {})
        if backup_cfg.get("recovery_mode") == "pitr":
            interval = backup_cfg.get("interval")
            max_count = backup_cfg.get("max_count")
            if interval is not None and max_count is not None:
                continuous_backups = (max_count * interval) // 24
            else:
                continuous_backups = None
        else:
            continuous_backups = None
        zones = plan.get("zones", {}).get("zone", [])
        status = Status.ACTIVE if zones else Status.INACTIVE
        ha, ha_strategy = _database_ha_from_node_counts([node_count or 1])

        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "database_id": database_id,
                "name": database_id,
                "display_name": display_name,
                "description": description,
                "api_reference": database_id,
                # Terraform/API provisioning uses `plan` on managed DB resources.
                # https://registry.terraform.io/providers/UpCloudLtd/upcloud/latest/docs/resources/managed_database_postgresql
                "api_reference_object": {
                    "service_type": "pg",
                    "service_plan": database_id,
                },
                # Per-node sizing from service-types/pg `components.compute`.
                "server_id": _match_database_server_id(server_ids, cpu, memory_gb),
                "engine": DatabaseEngine.POSTGRESQL,
                "wire_protocol": DatabaseWireProtocol.POSTGRESQL,
                "engine_versions": versions,
                # Node topology groups from configurations docs (1/2/3 nodes).
                # https://upcloud.com/docs/products/managed-postgresql/configurations/
                "family": family,
                "vcpus": vcpus,
                "memory_amount": memory_amount,
                "storage_size": storage_size_gb,
                # Extra disk is added manually via control panel/API, not when usage grows,
                # `storage_extra_min/max` apply only when `dynamic_storage_supported` is true.
                # https://upcloud.com/docs/changelog/2025-05-26-additional-disk-space-managed-databases/
                "storage_extra_min": storage_extra_min,
                "storage_extra_max": storage_extra_max,
                "storage_extra_autosize": False,
                "ha": ha,
                "ha_strategy": ha_strategy,
                "max_read_replicas": max((node_count or 1) - 1, 0),
                # Plans include daily full backups (`backup_config.interval`).
                # https://upcloud.com/docs/products/managed-postgresql/backups/
                "scheduled_backups": bool(backup_cfg.get("interval")),
                # PITR retention days from backup_config_pg interval * max_count.
                # https://upcloud.com/docs/products/managed-postgresql/backups/
                "continuous_backups": continuous_backups,
                # Plans list orderable zones under `zones.zone`.
                "status": status,
                **shared,
            }
        )
    return items

inventory_database_prices #

inventory_database_prices(vendor)

List UpCloud managed PostgreSQL compute prices.

Legacy plans bill the full cluster under managed_database_{plan}. Flexible plans bill per-node compute under managed_database_compute_{plan_compute}. Node topology maps to: 1 node NONE/NONE, 2-node package SINGLE_ZONE/PASSIVE_STANDBY (1.9x), 3-node package SINGLE_ZONE/READABLE_CLUSTER (2.6x) with published multi-node discounts (PASSIVE is only a price key when both 2 and 3 are orderable on the same compute shape). https://upcloud.com/global/pricing/

Source code in sc_crawler/vendors/_upcloud.py
def inventory_database_prices(vendor):
    """List UpCloud managed PostgreSQL compute prices.

    Legacy plans bill the full cluster under ``managed_database_{plan}``.
    Flexible plans bill per-node compute under
    ``managed_database_compute_{plan_compute}``. Node topology maps to:
    1 node NONE/NONE, 2-node package SINGLE_ZONE/PASSIVE_STANDBY (1.9x),
    3-node package SINGLE_ZONE/READABLE_CLUSTER (2.6x) with published
    multi-node discounts (PASSIVE is only a price key when both 2 and 3
    are orderable on the same compute shape).
    https://upcloud.com/global/pricing/
    """
    items = []
    prices = _get_prices()
    databases = {database.database_id: database for database in vendor.databases}
    legacy_prefix = "managed_database_"
    compute_prefix = "managed_database_compute_"
    currency = prices["prices"].get("currency", "EUR")
    for zone_prices in prices["prices"]["zone"]:
        region_id = zone_prices["name"]
        for k, v in zone_prices.items():
            if k.startswith(compute_prefix):
                database_id = k[len(compute_prefix) :]
                per_node = True
            elif k.startswith(legacy_prefix):
                database_id = k[len(legacy_prefix) :]
                # Skip non-plan meters (storage, backups, …).
                if database_id.startswith(("tiered_storage", "backup_", "storage_")):
                    continue
                per_node = False
            else:
                continue
            database = databases.get(database_id)
            if database is None:
                continue
            base_price = v["price"] / 100
            if per_node:
                strategies = {
                    DatabaseHaStrategy(strategy)
                    for strategy in database.ha_strategy or []
                }
                price_rows = []
                if DatabaseHaStrategy.NONE in strategies:
                    price_rows.append(
                        (
                            DatabaseHaLevel.NONE,
                            DatabaseHaStrategy.NONE,
                            base_price * _DATABASE_HA_NODE_PRICE_FACTOR[1],
                        )
                    )
                if DatabaseHaStrategy.PASSIVE_STANDBY in strategies:
                    price_rows.append(
                        (
                            DatabaseHaLevel.SINGLE_ZONE,
                            DatabaseHaStrategy.PASSIVE_STANDBY,
                            base_price * _DATABASE_HA_NODE_PRICE_FACTOR[2],
                        )
                    )
                if DatabaseHaStrategy.READABLE_CLUSTER in strategies:
                    price_rows.append(
                        (
                            DatabaseHaLevel.SINGLE_ZONE,
                            DatabaseHaStrategy.READABLE_CLUSTER,
                            base_price * _DATABASE_HA_NODE_PRICE_FACTOR[3],
                        )
                    )
            else:
                price_rows = [
                    (database.ha[0], database.ha_strategy[0], base_price),
                ]
            for ha, ha_strategy, price in price_rows:
                items.append(
                    {
                        "vendor_id": vendor.vendor_id,
                        "region_id": region_id,
                        "database_id": database_id,
                        "allocation": Allocation.ONDEMAND,
                        "ha": ha,
                        "ha_strategy": ha_strategy,
                        "unit": PriceUnit.HOUR,
                        "price": price,
                        "currency": currency,
                    }
                )
    return items

inventory_database_storages #

inventory_database_storages(vendor)

List managed PostgreSQL disk tiers (Standard SSD and MaxIOPS).

Flexible plans bill all disk via these meters; legacy plans also use Standard tiered storage for additional disk above the bundled size. Size bounds are per tier (Developer/legacy → Standard; Std/HM → MaxIOPS). https://upcloud.com/global/pricing/ https://upcloud.com/docs/products/block-storage/tiers/ https://developers.upcloud.com/1.3/16-managed-database/

Source code in sc_crawler/vendors/_upcloud.py
def inventory_database_storages(vendor):
    """List managed PostgreSQL disk tiers (Standard SSD and MaxIOPS).

    Flexible plans bill all disk via these meters; legacy plans also use
    Standard tiered storage for additional disk above the bundled size.
    Size bounds are per tier (Developer/legacy → Standard; Std/HM → MaxIOPS).
    https://upcloud.com/global/pricing/
    https://upcloud.com/docs/products/block-storage/tiers/
    https://developers.upcloud.com/1.3/16-managed-database/
    """
    items = []
    for tier in _DATABASE_STORAGE_TIERS:
        bounds = _database_storage_size_bounds(tier["database_storage_id"])
        if bounds is None:
            continue
        min_size, max_size = bounds
        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "database_storage_id": tier["database_storage_id"],
                "name": tier["name"],
                "description": tier["description"],
                "scope": DatabaseStorageScope.DATA,
                "min_size": min_size,
                "max_size": max_size,
                "max_iops": tier["max_iops"],
                "max_throughput": tier["max_throughput"],
            }
        )
    return items

inventory_database_storage_prices #

inventory_database_storage_prices(vendor)

List managed PostgreSQL disk tier prices from the UpCloud zone price list.

Source code in sc_crawler/vendors/_upcloud.py
def inventory_database_storage_prices(vendor):
    """List managed PostgreSQL disk tier prices from the UpCloud zone price list."""
    if not vendor.database_storages:
        return []
    price_key_by_id = {
        tier["database_storage_id"]: tier["price_key"]
        for tier in _DATABASE_STORAGE_TIERS
    }
    prices = _get_prices()
    currency = prices["prices"].get("currency", "EUR")
    items = []
    for zone_prices in prices["prices"]["zone"]:
        region_id = zone_prices["name"]
        for storage in vendor.database_storages:
            storage_id = storage.database_storage_id
            price_key = price_key_by_id.get(storage_id)
            if price_key is None:
                continue
            value = zone_prices.get(price_key)
            if value is None:
                continue
            raw_price = value.get("price") if isinstance(value, dict) else value
            if raw_price is None:
                continue
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "region_id": region_id,
                    "database_storage_id": storage_id,
                    "unit": PriceUnit.GB_MONTH,
                    # UpCloud list prices are hourly; normalize to GB/month.
                    "price": (float(raw_price) / 100) * _HOURS_PER_MONTH,
                    "currency": currency,
                }
            )
    return items