2026-01-05 07:46:21 -05:00
// Copyright The Prometheus Authors
2020-08-21 09:49:19 -04:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hetzner
import (
"context"
2024-09-09 21:41:53 -04:00
"log/slog"
2020-08-21 09:49:19 -04:00
"net"
"net/http"
"strconv"
"time"
2023-07-17 08:21:18 -04:00
"github.com/hetznercloud/hcloud-go/v2/hcloud"
2020-08-21 09:49:19 -04:00
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
2020-10-22 05:00:08 -04:00
2020-08-21 09:49:19 -04:00
"github.com/prometheus/prometheus/discovery/refresh"
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/util/strutil"
)
const (
hetznerHcloudLabelPrefix = hetznerLabelPrefix + "hcloud_"
hetznerLabelHcloudImageName = hetznerHcloudLabelPrefix + "image_name"
hetznerLabelHcloudImageDescription = hetznerHcloudLabelPrefix + "image_description"
hetznerLabelHcloudImageOSVersion = hetznerHcloudLabelPrefix + "image_os_version"
hetznerLabelHcloudImageOSFlavor = hetznerHcloudLabelPrefix + "image_os_flavor"
hetznerLabelHcloudPrivateIPv4 = hetznerHcloudLabelPrefix + "private_ipv4_"
hetznerLabelHcloudDatacenterLocation = hetznerHcloudLabelPrefix + "datacenter_location"
hetznerLabelHcloudDatacenterLocationNetworkZone = hetznerHcloudLabelPrefix + "datacenter_location_network_zone"
hetznerLabelHcloudCPUCores = hetznerHcloudLabelPrefix + "cpu_cores"
hetznerLabelHcloudCPUType = hetznerHcloudLabelPrefix + "cpu_type"
hetznerLabelHcloudMemoryGB = hetznerHcloudLabelPrefix + "memory_size_gb"
hetznerLabelHcloudDiskGB = hetznerHcloudLabelPrefix + "disk_size_gb"
hetznerLabelHcloudType = hetznerHcloudLabelPrefix + "server_type"
hetznerLabelHcloudLabel = hetznerHcloudLabelPrefix + "label_"
2021-07-02 19:51:50 -04:00
hetznerLabelHcloudLabelPresent = hetznerHcloudLabelPrefix + "labelpresent_"
2020-08-21 09:49:19 -04:00
)
// Discovery periodically performs Hetzner Cloud requests. It implements
// the Discoverer interface.
type hcloudDiscovery struct {
* refresh . Discovery
2025-04-27 07:09:38 -04:00
client * hcloud . Client
port int
labelSelector string
2020-08-21 09:49:19 -04:00
}
// newHcloudDiscovery returns a new hcloudDiscovery which periodically refreshes its targets.
2024-09-09 21:41:53 -04:00
func newHcloudDiscovery ( conf * SDConfig , _ * slog . Logger ) ( * hcloudDiscovery , error ) {
2020-08-21 09:49:19 -04:00
d := & hcloudDiscovery {
2025-04-27 07:09:38 -04:00
port : conf . Port ,
labelSelector : conf . LabelSelector ,
2020-08-21 09:49:19 -04:00
}
2021-09-26 17:16:12 -04:00
rt , err := config . NewRoundTripperFromConfig ( conf . HTTPClientConfig , "hetzner_sd" )
2020-08-21 09:49:19 -04:00
if err != nil {
return nil , err
}
d . client = hcloud . NewClient (
hcloud . WithApplication ( "Prometheus" , version . Version ) ,
hcloud . WithHTTPClient ( & http . Client {
Transport : rt ,
Timeout : time . Duration ( conf . RefreshInterval ) ,
} ) ,
hcloud . WithEndpoint ( conf . hcloudEndpoint ) ,
)
return d , nil
}
2021-10-22 04:06:44 -04:00
2020-08-21 09:49:19 -04:00
func ( d * hcloudDiscovery ) refresh ( ctx context . Context ) ( [ ] * targetgroup . Group , error ) {
2025-04-27 07:09:38 -04:00
servers , err := d . client . Server . AllWithOpts ( ctx , hcloud . ServerListOpts { ListOpts : hcloud . ListOpts {
PerPage : 50 ,
LabelSelector : d . labelSelector ,
} } )
2020-08-21 09:49:19 -04:00
if err != nil {
return nil , err
}
networks , err := d . client . Network . All ( ctx )
if err != nil {
return nil , err
}
targets := make ( [ ] model . LabelSet , len ( servers ) )
for i , server := range servers {
labels := model . LabelSet {
2023-07-31 06:51:41 -04:00
hetznerLabelRole : model . LabelValue ( HetznerRoleHcloud ) ,
2024-05-13 11:36:19 -04:00
hetznerLabelServerID : model . LabelValue ( strconv . FormatInt ( server . ID , 10 ) ) ,
2020-08-21 09:49:19 -04:00
hetznerLabelServerName : model . LabelValue ( server . Name ) ,
2026-01-07 07:21:56 -05:00
hetznerLabelDatacenter : model . LabelValue ( server . Datacenter . Name ) , //nolint:staticcheck // server.Datacenter is deprecated but kept for backwards compatibility until the next minor release
2020-08-21 09:49:19 -04:00
hetznerLabelPublicIPv4 : model . LabelValue ( server . PublicNet . IPv4 . IP . String ( ) ) ,
hetznerLabelPublicIPv6Network : model . LabelValue ( server . PublicNet . IPv6 . Network . String ( ) ) ,
hetznerLabelServerStatus : model . LabelValue ( server . Status ) ,
2026-01-07 07:21:56 -05:00
hetznerLabelHcloudDatacenterLocation : model . LabelValue ( server . Datacenter . Location . Name ) , //nolint:staticcheck // server.Datacenter is deprecated but kept for backwards compatibility until the next minor release
hetznerLabelHcloudDatacenterLocationNetworkZone : model . LabelValue ( server . Datacenter . Location . NetworkZone ) , //nolint:staticcheck // server.Datacenter is deprecated but kept for backwards compatibility until the next minor release
2020-08-21 09:49:19 -04:00
hetznerLabelHcloudType : model . LabelValue ( server . ServerType . Name ) ,
2024-05-13 11:36:19 -04:00
hetznerLabelHcloudCPUCores : model . LabelValue ( strconv . Itoa ( server . ServerType . Cores ) ) ,
2020-08-21 09:49:19 -04:00
hetznerLabelHcloudCPUType : model . LabelValue ( server . ServerType . CPUType ) ,
2024-05-13 11:36:19 -04:00
hetznerLabelHcloudMemoryGB : model . LabelValue ( strconv . Itoa ( int ( server . ServerType . Memory ) ) ) ,
hetznerLabelHcloudDiskGB : model . LabelValue ( strconv . Itoa ( server . ServerType . Disk ) ) ,
2020-08-21 09:49:19 -04:00
model . AddressLabel : model . LabelValue ( net . JoinHostPort ( server . PublicNet . IPv4 . IP . String ( ) , strconv . FormatUint ( uint64 ( d . port ) , 10 ) ) ) ,
}
if server . Image != nil {
labels [ hetznerLabelHcloudImageName ] = model . LabelValue ( server . Image . Name )
labels [ hetznerLabelHcloudImageDescription ] = model . LabelValue ( server . Image . Description )
labels [ hetznerLabelHcloudImageOSVersion ] = model . LabelValue ( server . Image . OSVersion )
labels [ hetznerLabelHcloudImageOSFlavor ] = model . LabelValue ( server . Image . OSFlavor )
}
for _ , privateNet := range server . PrivateNet {
for _ , network := range networks {
if privateNet . Network . ID == network . ID {
networkLabel := model . LabelName ( hetznerLabelHcloudPrivateIPv4 + strutil . SanitizeLabelName ( network . Name ) )
labels [ networkLabel ] = model . LabelValue ( privateNet . IP . String ( ) )
}
}
}
for labelKey , labelValue := range server . Labels {
2021-07-02 19:51:50 -04:00
presentLabel := model . LabelName ( hetznerLabelHcloudLabelPresent + strutil . SanitizeLabelName ( labelKey ) )
labels [ presentLabel ] = model . LabelValue ( "true" )
2020-08-21 09:49:19 -04:00
label := model . LabelName ( hetznerLabelHcloudLabel + strutil . SanitizeLabelName ( labelKey ) )
labels [ label ] = model . LabelValue ( labelValue )
}
targets [ i ] = labels
}
return [ ] * targetgroup . Group { { Source : "hetzner" , Targets : targets } } , nil
}