package hetzner import ( "encoding/json" "fmt" "net" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils" ) // getHCloudServerLabels returns labels for hcloud servers obtained from the given cfg func getHCloudServerLabels(cfg *apiConfig) ([]*promutils.Labels, error) { networks, err := getHCloudNetworks(cfg) if err != nil { return nil, err } servers, err := getHCloudServers(cfg) if err != nil { return nil, err } var ms []*promutils.Labels for i := range servers { ms = appendHCloudTargetLabels(ms, &servers[i], networks, cfg.port) } return ms, nil } func appendHCloudTargetLabels(ms []*promutils.Labels, server *HCloudServer, networks []HCloudNetwork, port int) []*promutils.Labels { m := promutils.NewLabels(24) addr := discoveryutils.JoinHostPort(server.PublicNet.IPv4.IP, port) m.Add("__address__", addr) m.Add("__meta_hetzner_role", "hcloud") m.Add("__meta_hetzner_server_id", fmt.Sprintf("%d", server.ID)) m.Add("__meta_hetzner_server_name", server.Name) m.Add("__meta_hetzner_datacenter", server.Datacenter.Name) m.Add("__meta_hetzner_public_ipv4", server.PublicNet.IPv4.IP) if _, n, _ := net.ParseCIDR(server.PublicNet.IPv6.IP); n != nil { m.Add("__meta_hetzner_public_ipv6_network", n.String()) } m.Add("__meta_hetzner_server_status", server.Status) m.Add("__meta_hetzner_hcloud_datacenter_location", server.Datacenter.Location.Name) m.Add("__meta_hetzner_hcloud_datacenter_location_network_zone", server.Datacenter.Location.NetworkZone) m.Add("__meta_hetzner_hcloud_server_type", server.ServerType.Name) m.Add("__meta_hetzner_hcloud_cpu_cores", fmt.Sprintf("%d", server.ServerType.Cores)) m.Add("__meta_hetzner_hcloud_cpu_type", server.ServerType.CPUType) m.Add("__meta_hetzner_hcloud_memory_size_gb", fmt.Sprintf("%d", int(server.ServerType.Memory))) m.Add("__meta_hetzner_hcloud_disk_size_gb", fmt.Sprintf("%d", server.ServerType.Disk)) if server.Image != nil { m.Add("__meta_hetzner_hcloud_image_name", server.Image.Name) m.Add("__meta_hetzner_hcloud_image_description", server.Image.Description) m.Add("__meta_hetzner_hcloud_image_os_version", server.Image.OsVersion) m.Add("__meta_hetzner_hcloud_image_os_flavor", server.Image.OsFlavor) } for _, privateNet := range server.PrivateNet { networkID := privateNet.ID for _, network := range networks { if networkID == network.ID { labelName := discoveryutils.SanitizeLabelName("__meta_hetzner_hcloud_private_ipv4_" + network.Name) m.Add(labelName, privateNet.IP) } } } for labelKey, labelValue := range server.Labels { labelName := discoveryutils.SanitizeLabelName("__meta_hetzner_hcloud_labelpresent_" + labelKey) m.Add(labelName, "true") labelName = discoveryutils.SanitizeLabelName("__meta_hetzner_hcloud_label_" + labelKey) m.Add(labelName, labelValue) } ms = append(ms, m) return ms } // getHCloudNetworks returns hcloud networks obtained from the given cfg func getHCloudNetworks(cfg *apiConfig) ([]HCloudNetwork, error) { // See https://docs.hetzner.cloud/#networks-get-all-networks var networks []HCloudNetwork page := 1 for { path := fmt.Sprintf("/v1/networks?page=%d", page) data, err := cfg.client.GetAPIResponse(path) if err != nil { return nil, fmt.Errorf("cannot query hcloud api for networks: %w", err) } networksPage, nextPage, err := parseHCloudNetworksList(data) if err != nil { return nil, err } networks = append(networks, networksPage...) if nextPage <= page { break } page = nextPage } return networks, nil } func parseHCloudNetworksList(data []byte) ([]HCloudNetwork, int, error) { var resp HCloudNetworksList if err := json.Unmarshal(data, &resp); err != nil { return nil, 0, fmt.Errorf("cannot unmarshal HCloudNetworksList from %q: %w", data, err) } return resp.Networks, resp.Meta.Pagination.NextPage, nil } // HCloudNetworksList represents the hetzner cloud networks list. // // See https://docs.hetzner.cloud/#networks-get-all-networks type HCloudNetworksList struct { Meta HCloudMeta `json:"meta"` Networks []HCloudNetwork `json:"networks"` } // HCloudNetwork represents the hetzner cloud network information. // // See https://docs.hetzner.cloud/#networks-get-all-networks type HCloudNetwork struct { Name string `json:"name"` ID int `json:"id"` } // getHCloudServers returns hcloud servers obtained from the given cfg func getHCloudServers(cfg *apiConfig) ([]HCloudServer, error) { // See https://docs.hetzner.cloud/#servers-get-all-servers var servers []HCloudServer page := 1 for { path := fmt.Sprintf("/v1/servers?page=%d", page) data, err := cfg.client.GetAPIResponse(path) if err != nil { return nil, fmt.Errorf("cannot query hcloud api for servers: %w", err) } serversPage, nextPage, err := parseHCloudServerList(data) if err != nil { return nil, err } servers = append(servers, serversPage...) if nextPage <= page { break } page = nextPage } return servers, nil } func parseHCloudServerList(data []byte) ([]HCloudServer, int, error) { var resp HCloudServerList if err := json.Unmarshal(data, &resp); err != nil { return nil, 0, fmt.Errorf("cannot unmarshal HCloudServerList from %q: %w", data, err) } return resp.Servers, resp.Meta.Pagination.NextPage, nil } // HCloudServerList represents a list of servers from Hetzner Cloud API. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudServerList struct { Meta HCloudMeta `json:"meta"` Servers []HCloudServer `json:"servers"` } // HCloudServer represents the structure of server data. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudServer struct { ID int `json:"id"` Name string `json:"name"` Status string `json:"status"` PublicNet HCloudPublicNet `json:"public_net"` PrivateNet []HCloudPrivateNet `json:"private_net"` ServerType HCloudServerType `json:"server_type"` Datacenter HCloudDatacenter `json:"datacenter"` Image *HCloudImage `json:"image"` Labels map[string]string `json:"labels"` } // HCloudServerType represents the server type information. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudServerType struct { Name string `json:"name"` Cores int `json:"cores"` CPUType string `json:"cpu_type"` Memory float32 `json:"memory"` Disk int `json:"disk"` } // HCloudDatacenter represents the Hetzner datacenter. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudDatacenter struct { Name string `json:"name"` Location HCloudDatacenterLocation `json:"location"` } // HCloudDatacenterLocation represents the datacenter information. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudDatacenterLocation struct { Name string `json:"name"` NetworkZone string `json:"network_zone"` } // HCloudPublicNet represents the public network information. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudPublicNet struct { IPv4 HCloudIPv4 `json:"ipv4"` IPv6 HCloudIPv6 `json:"ipv6"` } // HCloudIPv4 represents the IPv4 information. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudIPv4 struct { IP string `json:"ip"` } // HCloudIPv6 represents the IPv6 information. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudIPv6 struct { IP string `json:"ip"` } // HCloudPrivateNet represents the private network information. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudPrivateNet struct { ID int `json:"network"` IP string `json:"ip"` } // HCloudImage represents the image information. // // See https://docs.hetzner.cloud/#servers-get-all-servers type HCloudImage struct { Name string `json:"name"` Description string `json:"description"` OsFlavor string `json:"os_flavor"` OsVersion string `json:"os_version"` } // HCloudMeta represents hetzner cloud meta-information. // // See https://docs.hetzner.cloud/#pagination type HCloudMeta struct { Pagination HCloudPagination `json:"pagination"` } // HCloudPagination represents hetzner cloud pagination information. // // See https://docs.hetzner.cloud/#pagination type HCloudPagination struct { NextPage int `json:"next_page"` }