Skip to content

Commit ca1c9b0

Browse files
fix conflicts
2 parents d156a23 + 96cc59d commit ca1c9b0

16 files changed

+2153
-550
lines changed

client_monitor.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package linodego
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"os"
9+
"path"
10+
11+
"github.com/go-resty/resty/v2"
12+
)
13+
14+
const (
15+
// MonitorAPIHost is the default monitor-api host
16+
MonitorAPIHost = "monitor-api.linode.com"
17+
// MonitorAPIHostVar is the env var to check for the alternate Monitor API URL
18+
MonitorAPIHostVar = "MONITOR_API_URL"
19+
// MonitorAPIVersion is the default API version to use
20+
MonitorAPIVersion = "v2beta"
21+
// MonitorAPIVersionVar is the env var to check for the alternate Monitor API version
22+
MonitorAPIVersionVar = "MONITOR_API_VERSION"
23+
// MonitorAPIEnvVar is the env var to check for Monitor API token
24+
MonitorAPIEnvVar = "MONITOR_API_TOKEN"
25+
)
26+
27+
// MonitorClient is a wrapper around the Resty client
28+
type MonitorClient struct {
29+
resty *resty.Client
30+
debug bool
31+
apiBaseURL string
32+
apiProtocol string
33+
apiVersion string
34+
userAgent string
35+
}
36+
37+
// NewMonitorClient is the entry point for user to create a new MonitorClient
38+
// It utilizes default values and looks for environment variables to initialize a MonitorClient.
39+
func NewMonitorClient(hc *http.Client) (mClient MonitorClient) {
40+
if hc != nil {
41+
mClient.resty = resty.NewWithClient(hc)
42+
} else {
43+
mClient.resty = resty.New()
44+
}
45+
46+
mClient.SetUserAgent(DefaultUserAgent)
47+
48+
baseURL, baseURLExists := os.LookupEnv(MonitorAPIHostVar)
49+
if baseURLExists {
50+
mClient.SetBaseURL(baseURL)
51+
} else {
52+
mClient.SetBaseURL(MonitorAPIHost)
53+
}
54+
55+
apiVersion, apiVersionExists := os.LookupEnv(MonitorAPIVersionVar)
56+
if apiVersionExists {
57+
mClient.SetAPIVersion(apiVersion)
58+
} else {
59+
mClient.SetAPIVersion(MonitorAPIVersion)
60+
}
61+
62+
token, apiTokenExists := os.LookupEnv(MonitorAPIEnvVar)
63+
if apiTokenExists {
64+
mClient.SetToken(token)
65+
}
66+
67+
mClient.SetDebug(envDebug)
68+
69+
return
70+
}
71+
72+
// SetUserAgent sets a custom user-agent for HTTP requests
73+
func (mc *MonitorClient) SetUserAgent(ua string) *MonitorClient {
74+
mc.userAgent = ua
75+
mc.resty.SetHeader("User-Agent", mc.userAgent)
76+
77+
return mc
78+
}
79+
80+
// R wraps resty's R method
81+
func (mc *MonitorClient) R(ctx context.Context) *resty.Request {
82+
return mc.resty.R().
83+
ExpectContentType("application/json").
84+
SetHeader("Content-Type", "application/json").
85+
SetContext(ctx).
86+
SetError(APIError{})
87+
}
88+
89+
// SetDebug sets the debug on resty's client
90+
func (mc *MonitorClient) SetDebug(debug bool) *MonitorClient {
91+
mc.debug = debug
92+
mc.resty.SetDebug(debug)
93+
94+
return mc
95+
}
96+
97+
// SetLogger allows the user to override the output
98+
// logger for debug logs.
99+
func (mc *MonitorClient) SetLogger(logger Logger) *MonitorClient {
100+
mc.resty.SetLogger(logger)
101+
102+
return mc
103+
}
104+
105+
// SetBaseURL is the helper function to set base url
106+
func (mc *MonitorClient) SetBaseURL(baseURL string) *MonitorClient {
107+
baseURLPath, _ := url.Parse(baseURL)
108+
109+
mc.apiBaseURL = path.Join(baseURLPath.Host, baseURLPath.Path)
110+
mc.apiProtocol = baseURLPath.Scheme
111+
112+
mc.updateMonitorHostURL()
113+
114+
return mc
115+
}
116+
117+
// SetAPIVersion is the helper function to set api version
118+
func (mc *MonitorClient) SetAPIVersion(apiVersion string) *MonitorClient {
119+
mc.apiVersion = apiVersion
120+
121+
mc.updateMonitorHostURL()
122+
123+
return mc
124+
}
125+
126+
// SetRootCertificate adds a root certificate to the underlying TLS client config
127+
func (mc *MonitorClient) SetRootCertificate(path string) *MonitorClient {
128+
mc.resty.SetRootCertificate(path)
129+
return mc
130+
}
131+
132+
// SetToken sets the API token for all requests from this client
133+
func (mc *MonitorClient) SetToken(token string) *MonitorClient {
134+
mc.resty.SetHeader("Authorization", fmt.Sprintf("Bearer %s", token))
135+
return mc
136+
}
137+
138+
// SetHeader sets a custom header to be used in all API requests made with the current client.
139+
// NOTE: Some headers may be overridden by the individual request functions.
140+
func (mc *MonitorClient) SetHeader(name, value string) {
141+
mc.resty.SetHeader(name, value)
142+
}
143+
144+
func (mc *MonitorClient) updateMonitorHostURL() {
145+
apiProto := APIProto
146+
baseURL := MonitorAPIHost
147+
apiVersion := MonitorAPIVersion
148+
149+
if mc.apiBaseURL != "" {
150+
baseURL = mc.apiBaseURL
151+
}
152+
153+
if mc.apiVersion != "" {
154+
apiVersion = mc.apiVersion
155+
}
156+
157+
if mc.apiProtocol != "" {
158+
apiProto = mc.apiProtocol
159+
}
160+
161+
mc.resty.SetBaseURL(
162+
fmt.Sprintf(
163+
"%s://%s/%s",
164+
apiProto,
165+
baseURL,
166+
url.PathEscape(apiVersion),
167+
),
168+
)
169+
}

client_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,3 +650,56 @@ func TestClient_CustomRootCAWithoutCustomRoundTripper(t *testing.T) {
650650
})
651651
}
652652
}
653+
654+
func TestMonitorClient_SetAPIBasics(t *testing.T) {
655+
defaultURL := "https://monitor-api.linode.com/v2beta"
656+
657+
baseURL := "api.very.cool.com"
658+
apiVersion := "v4beta"
659+
expectedHost := fmt.Sprintf("https://%s/%s", baseURL, apiVersion)
660+
661+
updatedBaseURL := "api.more.cool.com"
662+
updatedAPIVersion := "v4beta_changed"
663+
updatedExpectedHost := fmt.Sprintf("https://%s/%s", updatedBaseURL, updatedAPIVersion)
664+
665+
protocolBaseURL := "http://api.more.cool.com"
666+
protocolAPIVersion := "v4_http"
667+
protocolExpectedHost := fmt.Sprintf("%s/%s", protocolBaseURL, protocolAPIVersion)
668+
669+
client := NewMonitorClient(nil)
670+
671+
if client.resty.BaseURL != defaultURL {
672+
t.Fatal(cmp.Diff(client.resty.BaseURL, defaultURL))
673+
}
674+
675+
client.SetBaseURL(baseURL)
676+
client.SetAPIVersion(apiVersion)
677+
678+
if client.resty.BaseURL != expectedHost {
679+
t.Fatal(cmp.Diff(client.resty.BaseURL, expectedHost))
680+
}
681+
682+
// Ensure setting twice does not cause conflicts
683+
client.SetBaseURL(updatedBaseURL)
684+
client.SetAPIVersion(updatedAPIVersion)
685+
686+
if client.resty.BaseURL != updatedExpectedHost {
687+
t.Fatal(cmp.Diff(client.resty.BaseURL, updatedExpectedHost))
688+
}
689+
690+
// Revert
691+
client.SetBaseURL(baseURL)
692+
client.SetAPIVersion(apiVersion)
693+
694+
if client.resty.BaseURL != expectedHost {
695+
t.Fatal(cmp.Diff(client.resty.BaseURL, expectedHost))
696+
}
697+
698+
// Custom protocol
699+
client.SetBaseURL(protocolBaseURL)
700+
client.SetAPIVersion(protocolAPIVersion)
701+
702+
if client.resty.BaseURL != protocolExpectedHost {
703+
t.Fatal(cmp.Diff(client.resty.BaseURL, expectedHost))
704+
}
705+
}

monitor_api_services.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Functions in the file are making calls to the monitor-api instead of linode domain.
2+
// Please initialize a MonitorClient for using the endpoints below.
3+
4+
package linodego
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"time"
10+
)
11+
12+
type MetricFilterOperator string
13+
14+
const (
15+
MetricFilterOperatorEq MetricFilterOperator = "eq"
16+
MetricFilterOperatorNeq MetricFilterOperator = "neq"
17+
MetricFilterOperatorStartsWith MetricFilterOperator = "startswith"
18+
MetricFilterOperatorEndsWith MetricFilterOperator = "endswith"
19+
MetricFilterOperatorIn MetricFilterOperator = "in"
20+
)
21+
22+
type MetricTimeUnit string
23+
24+
const (
25+
MetricTimeUnitSec MetricTimeUnit = "sec"
26+
MetricTimeUnitMin MetricTimeUnit = "min"
27+
MetricTimeUnitHr MetricTimeUnit = "hr"
28+
MetricTimeUnitDays MetricTimeUnit = "days"
29+
)
30+
31+
// EntityMetrics is the response body of the metrics for the entities requested
32+
type EntityMetrics struct {
33+
Data EntityMetricsData `json:"data"`
34+
IsPartial bool `json:"is_partial"`
35+
Stats EntityMetricsStats `json:"stats"`
36+
Status string `json:"status"`
37+
}
38+
39+
// EntityMetricsData describes the result and type for the entity metrics
40+
type EntityMetricsData struct {
41+
Result []EntityMetricsDataResult `json:"result"`
42+
ResultType string `json:"result_type"`
43+
}
44+
45+
// EntityMetricsDataResult contains the information of a metric and values
46+
type EntityMetricsDataResult struct {
47+
Metric map[string]any `json:"metric"`
48+
Values [][]any `json:"values"`
49+
}
50+
51+
// EntityMetricsStats shows statistics info of the metrics fetched
52+
type EntityMetricsStats struct {
53+
ExecutionTimeMsec int `json:"executionTimeMsec"`
54+
SeriesFetched string `json:"seriesFetched"`
55+
}
56+
57+
// EntityMetricsFetchOptions are the options used to fetch metrics with the entity ids provided
58+
type EntityMetricsFetchOptions struct {
59+
// EntityIDs are expected to be type "any" as different service_types have different variable type for their entity_ids. For example, Linode has "int" entity_ids whereas object storage has "string" as entity_ids.
60+
EntityIDs []any `json:"entity_ids"`
61+
62+
Filters []MetricFilter `json:"filters,omitempty"`
63+
Metrics []EntityMetric `json:"metrics"`
64+
TimeGranularity []MetricTimeGranularity `json:"time_granularity,omitempty"`
65+
RelativeTimeDuration *MetricRelativeTimeDuration `json:"relative_time_duration,omitempty"`
66+
AbsoluteTimeDuration *MetricAbsoluteTimeDuration `json:"absolute_time_duration,omitempty"`
67+
}
68+
69+
// MetricFilter describes individual objects that define dimension filters for the query.
70+
type MetricFilter struct {
71+
DimensionLabel string `json:"dimension_label"`
72+
Operator MetricFilterOperator `json:"operator"`
73+
Value string `json:"value"`
74+
}
75+
76+
// EntityMetric specifies a metric name and its corresponding aggregation function.
77+
type EntityMetric struct {
78+
Name string `json:"name"`
79+
AggregateFunction AggregateFunction `json:"aggregate_function"`
80+
}
81+
82+
// MetricTimeGranularity allows for an optional time granularity setting for metric data.
83+
type MetricTimeGranularity struct {
84+
Unit MetricTimeUnit `json:"unit"`
85+
Value int `json:"value"`
86+
}
87+
88+
// MetricRelativeTimeDuration specifies a relative time duration for data queries
89+
type MetricRelativeTimeDuration struct {
90+
Unit MetricTimeUnit `json:"unit"`
91+
Value int `json:"value"`
92+
}
93+
94+
// MetricAbsoluteTimeDuration specifies an absolute time range for data queries
95+
type MetricAbsoluteTimeDuration struct {
96+
Start time.Time `json:"start,omitempty"`
97+
End time.Time `json:"end,omitempty"`
98+
}
99+
100+
// FetchEntityMetrics returns metrics information for the individual entities within a specific service type
101+
func (mc *MonitorClient) FetchEntityMetrics(ctx context.Context, serviceType string, opts *EntityMetricsFetchOptions) (*EntityMetrics, error) {
102+
endpoint := formatAPIPath("monitor/services/%s/metrics", serviceType)
103+
104+
req := mc.R(ctx).SetResult(&EntityMetrics{})
105+
106+
if opts != nil {
107+
body, err := json.Marshal(opts)
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
req.SetBody(string(body))
113+
}
114+
115+
r, err := coupleAPIErrors(req.Post(endpoint))
116+
if err != nil {
117+
return nil, err
118+
}
119+
120+
return r.Result().(*EntityMetrics), nil
121+
}

nodebalancer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type NodeBalancerCreateOptions struct {
7272
FirewallID int `json:"firewall_id,omitempty"`
7373
Type NodeBalancerPlanType `json:"type,omitempty"`
7474
VPCs []NodeBalancerVPCOptions `json:"vpcs,omitempty"`
75+
IPv4 *string `json:"ipv4,omitempty"`
7576
}
7677

7778
// NodeBalancerUpdateOptions are the options permitted for UpdateNodeBalancer

0 commit comments

Comments
 (0)