File: client.go

package info (click to toggle)
golang-github-hetznercloud-hcloud-go 2.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,072 kB
  • sloc: sh: 5; makefile: 2
file content (149 lines) | stat: -rw-r--r-- 4,082 bytes parent folder | download
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")
}