1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
package metadata
import (
"fmt"
"io"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/hetznercloud/hcloud-go/v2/hcloud/internal/instrumentation"
)
const Endpoint = "http://169.254.169.254/hetzner/v1/metadata"
// Client is a client for the Hetzner Cloud Server Metadata Endpoints.
type Client struct {
endpoint string
timeout time.Duration
httpClient *http.Client
instrumentationRegistry *prometheus.Registry
}
// A ClientOption is used to configure a [Client].
type ClientOption func(*Client)
// WithEndpoint configures a [Client] to use the specified Metadata API endpoint.
func WithEndpoint(endpoint string) ClientOption {
return func(client *Client) {
client.endpoint = strings.TrimRight(endpoint, "/")
}
}
// WithHTTPClient configures a [Client] to perform HTTP requests with httpClient.
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(client *Client) {
client.httpClient = httpClient
}
}
// WithInstrumentation configures a [Client] to collect metrics about the performed HTTP requests.
func WithInstrumentation(registry *prometheus.Registry) ClientOption {
return func(client *Client) {
client.instrumentationRegistry = registry
}
}
// WithTimeout specifies a time limit for requests made by this [Client]. Defaults to 5 seconds.
func WithTimeout(timeout time.Duration) ClientOption {
return func(client *Client) {
client.timeout = timeout
}
}
// NewClient creates a new [Client] with the options applied.
func NewClient(options ...ClientOption) *Client {
client := &Client{
endpoint: Endpoint,
httpClient: &http.Client{},
timeout: 5 * time.Second,
}
for _, option := range options {
option(client)
}
client.httpClient.Timeout = client.timeout
if client.instrumentationRegistry != nil {
i := instrumentation.New("metadata", client.instrumentationRegistry)
client.httpClient.Transport = i.InstrumentedRoundTripper()
}
return client
}
// get executes an HTTP request against the API.
func (c *Client) get(path string) (string, error) {
url := c.endpoint + path
resp, err := c.httpClient.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
body := string(bodyBytes)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return body, fmt.Errorf("response status was %d", resp.StatusCode)
}
return body, nil
}
// IsHcloudServer checks if the currently called server is a hcloud server by calling a metadata endpoint
// if the endpoint answers with a non-empty value this method returns true, otherwise false.
func (c *Client) IsHcloudServer() bool {
hostname, err := c.Hostname()
if err != nil {
return false
}
if len(hostname) > 0 {
return true
}
return false
}
// Hostname returns the hostname of the server that did the request to the Metadata server.
func (c *Client) Hostname() (string, error) {
return c.get("/hostname")
}
// InstanceID returns the ID of the server that did the request to the Metadata server.
func (c *Client) InstanceID() (int64, error) {
resp, err := c.get("/instance-id")
if err != nil {
return 0, err
}
return strconv.ParseInt(resp, 10, 64)
}
// PublicIPv4 returns the Public IPv4 of the server that did the request to the Metadata server.
func (c *Client) PublicIPv4() (net.IP, error) {
resp, err := c.get("/public-ipv4")
if err != nil {
return nil, err
}
return net.ParseIP(resp), nil
}
// Region returns the Network Zone of the server that did the request to the Metadata server.
func (c *Client) Region() (string, error) {
return c.get("/region")
}
// AvailabilityZone returns the datacenter of the server that did the request to the Metadata server.
func (c *Client) AvailabilityZone() (string, error) {
return c.get("/availability-zone")
}
// PrivateNetworks returns details about the private networks the server is attached to.
// Returns YAML (unparsed).
func (c *Client) PrivateNetworks() (string, error) {
return c.get("/private-networks")
}
|