| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | // Copyright 2020 The Prometheus Authors
 | 
					
						
							|  |  |  | // 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" | 
					
						
							| 
									
										
										
										
											2022-06-03 19:47:14 +08:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2024-09-10 09:41:53 +08:00
										 |  |  | 	"log/slog" | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 20:21:18 +08:00
										 |  |  | 	"github.com/hetznercloud/hcloud-go/v2/hcloud" | 
					
						
							| 
									
										
										
										
											2023-10-23 21:55:36 +08:00
										 |  |  | 	"github.com/prometheus/client_golang/prometheus" | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	"github.com/prometheus/common/config" | 
					
						
							|  |  |  | 	"github.com/prometheus/common/model" | 
					
						
							| 
									
										
										
										
											2020-10-22 17:00:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	"github.com/prometheus/prometheus/discovery" | 
					
						
							|  |  |  | 	"github.com/prometheus/prometheus/discovery/refresh" | 
					
						
							|  |  |  | 	"github.com/prometheus/prometheus/discovery/targetgroup" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	hetznerLabelPrefix            = model.MetaLabelPrefix + "hetzner_" | 
					
						
							|  |  |  | 	hetznerLabelRole              = hetznerLabelPrefix + "role" | 
					
						
							|  |  |  | 	hetznerLabelServerID          = hetznerLabelPrefix + "server_id" | 
					
						
							|  |  |  | 	hetznerLabelServerName        = hetznerLabelPrefix + "server_name" | 
					
						
							|  |  |  | 	hetznerLabelServerStatus      = hetznerLabelPrefix + "server_status" | 
					
						
							|  |  |  | 	hetznerLabelDatacenter        = hetznerLabelPrefix + "datacenter" | 
					
						
							|  |  |  | 	hetznerLabelPublicIPv4        = hetznerLabelPrefix + "public_ipv4" | 
					
						
							|  |  |  | 	hetznerLabelPublicIPv6Network = hetznerLabelPrefix + "public_ipv6_network" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DefaultSDConfig is the default Hetzner SD configuration.
 | 
					
						
							|  |  |  | var DefaultSDConfig = SDConfig{ | 
					
						
							| 
									
										
										
										
											2021-02-27 05:48:06 +08:00
										 |  |  | 	Port:             80, | 
					
						
							|  |  |  | 	RefreshInterval:  model.Duration(60 * time.Second), | 
					
						
							|  |  |  | 	HTTPClientConfig: config.DefaultHTTPClientConfig, | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							|  |  |  | 	discovery.RegisterConfig(&SDConfig{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SDConfig is the configuration for Hetzner based service discovery.
 | 
					
						
							|  |  |  | type SDConfig struct { | 
					
						
							|  |  |  | 	HTTPClientConfig config.HTTPClientConfig `yaml:",inline"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	RefreshInterval model.Duration `yaml:"refresh_interval"` | 
					
						
							|  |  |  | 	Port            int            `yaml:"port"` | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | 	Role            Role           `yaml:"role"` | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	hcloudEndpoint  string         // For tests only.
 | 
					
						
							|  |  |  | 	robotEndpoint   string         // For tests only.
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-23 23:53:55 +08:00
										 |  |  | // NewDiscovererMetrics implements discovery.Config.
 | 
					
						
							| 
									
										
										
										
											2025-02-10 15:06:58 +08:00
										 |  |  | func (*SDConfig) NewDiscovererMetrics(_ prometheus.Registerer, rmi discovery.RefreshMetricsInstantiator) discovery.DiscovererMetrics { | 
					
						
							| 
									
										
										
										
											2024-01-23 23:53:55 +08:00
										 |  |  | 	return &hetznerMetrics{ | 
					
						
							|  |  |  | 		refreshMetrics: rmi, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | // Name returns the name of the Config.
 | 
					
						
							|  |  |  | func (*SDConfig) Name() string { return "hetzner" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewDiscoverer returns a Discoverer for the Config.
 | 
					
						
							|  |  |  | func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) { | 
					
						
							| 
									
										
										
										
											2024-01-23 23:53:55 +08:00
										 |  |  | 	return NewDiscovery(c, opts.Logger, opts.Metrics) | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type refresher interface { | 
					
						
							|  |  |  | 	refresh(context.Context) ([]*targetgroup.Group, error) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | // Role is the Role of the target within the Hetzner Ecosystem.
 | 
					
						
							|  |  |  | type Role string | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // The valid options for role.
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	// Hetzner Robot Role (Dedicated Server)
 | 
					
						
							|  |  |  | 	// https://robot.hetzner.com
 | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | 	HetznerRoleRobot Role = "robot" | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	// Hetzner Cloud Role
 | 
					
						
							|  |  |  | 	// https://console.hetzner.cloud
 | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | 	HetznerRoleHcloud Role = "hcloud" | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UnmarshalYAML implements the yaml.Unmarshaler interface.
 | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error { | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	if err := unmarshal((*string)(c)); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	switch *c { | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | 	case HetznerRoleRobot, HetznerRoleHcloud: | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2022-06-03 19:47:14 +08:00
										 |  |  | 		return fmt.Errorf("unknown role %q", *c) | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UnmarshalYAML implements the yaml.Unmarshaler interface.
 | 
					
						
							|  |  |  | func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { | 
					
						
							|  |  |  | 	*c = DefaultSDConfig | 
					
						
							|  |  |  | 	type plain SDConfig | 
					
						
							|  |  |  | 	err := unmarshal((*plain)(c)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if c.Role == "" { | 
					
						
							|  |  |  | 		return errors.New("role missing (one of: robot, hcloud)") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-19 06:14:49 +08:00
										 |  |  | 	return c.HTTPClientConfig.Validate() | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 13:54:43 +08:00
										 |  |  | // SetDirectory joins any relative file paths with dir.
 | 
					
						
							|  |  |  | func (c *SDConfig) SetDirectory(dir string) { | 
					
						
							|  |  |  | 	c.HTTPClientConfig.SetDirectory(dir) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | // Discovery periodically performs Hetzner requests. It implements
 | 
					
						
							|  |  |  | // the Discoverer interface.
 | 
					
						
							|  |  |  | type Discovery struct { | 
					
						
							|  |  |  | 	*refresh.Discovery | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewDiscovery returns a new Discovery which periodically refreshes its targets.
 | 
					
						
							| 
									
										
										
										
											2024-09-10 09:41:53 +08:00
										 |  |  | func NewDiscovery(conf *SDConfig, logger *slog.Logger, metrics discovery.DiscovererMetrics) (*refresh.Discovery, error) { | 
					
						
							| 
									
										
										
										
											2024-01-23 23:53:55 +08:00
										 |  |  | 	m, ok := metrics.(*hetznerMetrics) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2024-11-03 20:15:51 +08:00
										 |  |  | 		return nil, errors.New("invalid discovery metrics type") | 
					
						
							| 
									
										
										
										
											2024-01-23 23:53:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	r, err := newRefresher(conf, logger) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return refresh.NewDiscovery( | 
					
						
							| 
									
										
										
										
											2023-10-23 21:55:36 +08:00
										 |  |  | 		refresh.Options{ | 
					
						
							| 
									
										
										
										
											2024-01-23 23:53:55 +08:00
										 |  |  | 			Logger:              logger, | 
					
						
							|  |  |  | 			Mech:                "hetzner", | 
					
						
							|  |  |  | 			Interval:            time.Duration(conf.RefreshInterval), | 
					
						
							|  |  |  | 			RefreshF:            r.refresh, | 
					
						
							|  |  |  | 			MetricsInstantiator: m.refreshMetrics, | 
					
						
							| 
									
										
										
										
											2023-10-23 21:55:36 +08:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 09:41:53 +08:00
										 |  |  | func newRefresher(conf *SDConfig, l *slog.Logger) (refresher, error) { | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 	switch conf.Role { | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | 	case HetznerRoleHcloud: | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 		if conf.hcloudEndpoint == "" { | 
					
						
							|  |  |  | 			conf.hcloudEndpoint = hcloud.Endpoint | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return newHcloudDiscovery(conf, l) | 
					
						
							| 
									
										
										
										
											2023-07-31 18:51:41 +08:00
										 |  |  | 	case HetznerRoleRobot: | 
					
						
							| 
									
										
										
										
											2020-08-21 21:49:19 +08:00
										 |  |  | 		if conf.robotEndpoint == "" { | 
					
						
							|  |  |  | 			conf.robotEndpoint = "https://robot-ws.your-server.de" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return newRobotDiscovery(conf, l) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil, errors.New("unknown Hetzner discovery role") | 
					
						
							|  |  |  | } |