Skip to content

_hcloud

sc_crawler.vendors._hcloud #

Inventory of Hetzner Cloud via its API and manual data entry: servers, storages, traffic and IPv4 addresses, with their prices.

Functions:

Name Description
inventory_compliance_frameworks

Manual list of known compliance frameworks at Hetzner.

inventory_regions

List all regions via API call.

inventory_zones

List all regions as availability zones.

inventory_servers

List all server types from API and manual data entry from the Hetzner Cloud homepage.

inventory_server_prices

List server prices per location from the server types API.

inventory_server_prices_spot

There are no spot instances at Hetzner.

inventory_storages

Block storage volume information collected manually.

inventory_storage_prices

Block storage volume pricing information collected manually.

inventory_traffic_prices

Traffic price collected manually.

inventory_ipv4_prices

IPv4 price collected manually.

inventory_databases

Hetzner Cloud does not offer managed PostgreSQL.

inventory_compliance_frameworks #

inventory_compliance_frameworks(vendor)

Manual list of known compliance frameworks at Hetzner.

Data collected from https://www.hetzner.com/unternehmen/zertifizierung.

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

    Data collected from <https://www.hetzner.com/unternehmen/zertifizierung>."""
    return map_compliance_frameworks_to_vendor(vendor.vendor_id, ["iso27001"])

inventory_regions #

inventory_regions(vendor)

List all regions via API call.

Using the unique location name (e.g. fsn1) as the region id instead of the numeric location id, as the numeric ids were renumbered when the datacenters endpoint got deprecated, while the names are stable and are also used to reference locations in the server type prices, and can be passed instead of the id in most hcloud API endpoints via the id_or_name method. Former datacenter region ids are kept in aliases via _LEGACY_DATACENTER_REGION_IDS (not the current location API ids).

All regions are powered by green energy as per https://www.hetzner.com/unternehmen/umweltschutz/.

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

    Using the unique location `name` (e.g. `fsn1`) as the region id
    instead of the numeric location id, as the numeric ids were
    renumbered when the datacenters endpoint got deprecated, while
    the names are stable and are also used to reference locations in
    the server type prices, and can be passed instead of the `id` in
    most `hcloud` API endpoints via the `id_or_name` method.
    Former datacenter region ids are kept in `aliases` via
    `_LEGACY_DATACENTER_REGION_IDS` (not the current location API ids).

    All regions are powered by green energy as per
    <https://www.hetzner.com/unternehmen/umweltschutz/>.
    """
    items = []
    for location in _get_locations():
        with sentry_capture_or_raise(vendor=vendor):
            city = location.city
            state = None
            if ", " in city:
                city_part, maybe_state = city.rsplit(", ", 1)
                if len(maybe_state) == 2 and maybe_state.isalpha():
                    city = city_part
                    state = maybe_state
            legacy_id = _LEGACY_DATACENTER_REGION_IDS.get(location.name)
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "region_id": location.name,
                    "name": location.name,
                    "api_reference": location.name,
                    "display_name": f"{city} ({location.country})",
                    "aliases": [legacy_id] if legacy_id else [],
                    "country_id": location.country,
                    "state": state,
                    "city": city,
                    "address_line": None,
                    "zip_code": None,
                    "lat": location.latitude,
                    "lon": location.longitude,
                    "founding_year": None,
                    "green_energy": True,
                }
            )
    return items

inventory_zones #

inventory_zones(vendor)

List all regions as availability zones.

There is no concept of having multiple availability zones within a location at Hetzner Cloud, so creating 1-1 dummy Zones reusing the Region id and name.

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

    There is no concept of having multiple availability zones within
    a location at Hetzner Cloud, so creating 1-1 dummy Zones reusing
    the Region id and name.

    """
    items = []
    for region in _active_regions(vendor):
        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "region_id": region.region_id,
                "zone_id": region.region_id,
                "name": region.name,
                "api_reference": region.name,
                "display_name": region.name,
            }
        )
    return items

inventory_servers #

inventory_servers(vendor)

List all server types from API and manual data entry from the Hetzner Cloud homepage.

CPU information is recorded from https://www.hetzner.com/cloud/ as not exposed via API.

Lifecycle (per-location server type fields): deprecation.unavailable_after in past -> RETIRED, deprecation present -> PLANNED_FOR_RETIREMENT, available is False or empty locations -> INACTIVE, otherwise ACTIVE. See _hcloud_server_status.

Source code in sc_crawler/vendors/_hcloud.py
def inventory_servers(vendor):
    """List all server types from API and manual data entry from the Hetzner Cloud homepage.

    CPU information is recorded from <https://www.hetzner.com/cloud/> as not exposed via API.

    Lifecycle (per-location server type fields): `deprecation.unavailable_after` in
    past -> RETIRED, `deprecation` present -> PLANNED_FOR_RETIREMENT,
    `available is False` or empty locations -> INACTIVE, otherwise ACTIVE.
    See `_hcloud_server_status`.
    """
    now = datetime.now(UTC)
    items = []
    for server in _get_server_types():
        with sentry_capture_or_raise(vendor=vendor):
            # CPU info not available via the API,
            # collected from https://www.hetzner.com/cloud/
            cpu = _server_cpu(server)
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "server_id": str(server.id),
                    "name": server.name,
                    "api_reference": server.name,
                    # https://www.pulumi.com/registry/packages/hcloud/api-docs/server/
                    "api_reference_object": {"server_type": server.name},
                    "display_name": server.name,
                    "description": server.description,
                    "family": _server_family(server),
                    "vcpus": server.cores,
                    "hypervisor": "QEMU",
                    "cpu_allocation": (
                        CpuAllocation.SHARED
                        if server.cpu_type == "shared"
                        else CpuAllocation.DEDICATED
                    ),
                    "cpu_cores": None,
                    "cpu_speed": None,
                    "cpu_architecture": (
                        CpuArchitecture.ARM64
                        if server.architecture == "arm"
                        else CpuArchitecture.X86_64
                    ),
                    "cpu_manufacturer": cpu[0],
                    "cpu_family": cpu[1],
                    "cpu_model": cpu[2],
                    "cpus": [],
                    "memory_amount": server.memory * 1000,
                    "memory_ecc": True,
                    "accelerator_count": 0,
                    "accelerator_memory_min": 0,
                    "accelerator_memory_total": 0,
                    "accelerator_manufacturer": None,
                    "accelerator_model": None,
                    "accelerators": [],
                    "storage_size": server.disk,
                    "storage_type": StorageType.NVME_SSD,
                    "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,
                    # https://docs.hetzner.com/cloud/billing/faq/#how-do-you-bill-for-traffic
                    "inbound_traffic": 0,  # free
                    "outbound_traffic": (
                        max(
                            [region.get("included_traffic") for region in server.prices]
                        )
                        / (1024**3)
                    ),
                    "ipv4": 0,
                    "status": _hcloud_server_status(server, now),
                }
            )
    return items

inventory_server_prices #

inventory_server_prices(vendor)

List server prices per location from the server types API.

prices[].location and locations[].location.name are both the location name, which is also used as our Region id.

Source code in sc_crawler/vendors/_hcloud.py
def inventory_server_prices(vendor):
    """List server prices per location from the server types API.

    `prices[].location` and `locations[].location.name` are both the
    location name, which is also used as our Region id.
    """
    now = datetime.now(UTC)
    items = []
    for server in _get_server_types():
        server_locations = {
            loc.location.name: loc for loc in server.locations or [] if loc.location
        }
        for price in server.prices:
            region_id = price["location"]
            hourly_price = float(price["price_hourly"]["net"])
            # Hetzner provides separate hourly and monthly prices,
            # so to describe the monthly cap with tiered hourly pricing,
            # we need to proxy the number of discounted hours in a month
            # (rounding to full hours is a good-enough approximation)
            monthly_cap = int(float(price["price_monthly"]["net"]) / hourly_price)
            status = _hcloud_location_status(server_locations.get(region_id), now)
            price_status = Status.ACTIVE if status.is_orderable else Status.INACTIVE
            items.append(
                {
                    "vendor_id": vendor.vendor_id,
                    "region_id": region_id,
                    # zone_id is a dummy region_id as there are no zones at Hetzner
                    "zone_id": region_id,
                    "server_id": str(server.id),
                    "operating_system": "Linux",
                    "allocation": Allocation.ONDEMAND,
                    "unit": PriceUnit.HOUR,
                    "price": hourly_price,
                    "price_upfront": 0,
                    "price_tiered": [
                        {"lower": 0, "upper": monthly_cap, "price": hourly_price},
                        {"lower": monthly_cap + 1, "upper": "Infinity", "price": 0},
                    ],
                    "currency": "EUR",
                    "status": price_status,
                }
            )
    return items

inventory_server_prices_spot #

inventory_server_prices_spot(vendor)

There are no spot instances at Hetzner.

Source code in sc_crawler/vendors/_hcloud.py
def inventory_server_prices_spot(vendor):
    """There are no spot instances at Hetzner."""
    return []

inventory_storages #

inventory_storages(vendor)

Block storage volume information collected manually.

There is not information shared vie the API, so information was collected manually from:

Source code in sc_crawler/vendors/_hcloud.py
def inventory_storages(vendor):
    """Block storage volume information collected manually.

    There is not information shared vie the API, so information
    was collected manually from:

    - <https://www.hetzner.com/cloud/>
    - <https://docs.hetzner.cloud/#volumes-create-a-volume>
    - <https://docs.hetzner.com/cloud/volumes/overview/>
    """
    items = [
        {
            "storage_id": "block",
            "vendor_id": vendor.vendor_id,
            "name": "Block storage volume",
            "description": None,
            "storage_type": StorageType.SSD,
            "max_iops": 7500,
            "max_throughput": 300,
            "min_size": 10,
            "max_size": 10000,
        }
    ]
    return items

inventory_storage_prices #

inventory_storage_prices(vendor)

Block storage volume pricing information collected manually.

Source: https://www.hetzner.com/cloud/block-storage/

Source code in sc_crawler/vendors/_hcloud.py
def inventory_storage_prices(vendor):
    """Block storage volume pricing information collected manually.

    Source: <https://www.hetzner.com/cloud/block-storage/>
    """
    items = []
    for region in _active_regions(vendor):
        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "region_id": region.region_id,
                "storage_id": "block",
                "unit": PriceUnit.GB_MONTH,
                "price": 0.0440,
                "currency": "EUR",
            }
        )
    return items

inventory_traffic_prices #

inventory_traffic_prices(vendor)

Traffic price collected manually.

Source: https://docs.hetzner.com/robot/general/traffic/

Source code in sc_crawler/vendors/_hcloud.py
def inventory_traffic_prices(vendor):
    """Traffic price collected manually.

    Source: <https://docs.hetzner.com/robot/general/traffic/>
    """
    items = []
    for region in _active_regions(vendor):
        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "region_id": region.region_id,
                "price": 0,
                "price_tiered": [],
                "currency": "EUR",
                "unit": PriceUnit.GB_MONTH,
                "direction": TrafficDirection.IN,
            }
        )
        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "region_id": region.region_id,
                "price": 1 / 1024,
                "price_tiered": [],
                "currency": "EUR",
                "unit": PriceUnit.GB_MONTH,
                "direction": TrafficDirection.OUT,
            }
        )
    return items

inventory_ipv4_prices #

inventory_ipv4_prices(vendor)

IPv4 price collected manually.

Source: https://docs.hetzner.com/cloud/servers/overview#network-options

Source code in sc_crawler/vendors/_hcloud.py
def inventory_ipv4_prices(vendor):
    """IPv4 price collected manually.

    Source: <https://docs.hetzner.com/cloud/servers/overview#network-options>
    """
    items = []
    for region in _active_regions(vendor):
        items.append(
            {
                "vendor_id": vendor.vendor_id,
                "region_id": region.region_id,
                "price": 0.50,
                "currency": "EUR",
                "unit": PriceUnit.MONTH,
            }
        )
    return items

inventory_databases #

inventory_databases(vendor)

Hetzner Cloud does not offer managed PostgreSQL.

Source code in sc_crawler/vendors/_hcloud.py
def inventory_databases(vendor):
    """Hetzner Cloud does not offer managed PostgreSQL."""
    return []