Skip to content

Commit ac5ae2e

Browse files
authored
feat(servertype): implement new Deprecation api field (#268)
* feat(servertype): implement new Deprecation api field We recently added a new Field to the ServerType API response that indicates if and when the server type was deprecated. The same structure will also be used for other resources in the future, replacing the current "deprecated" fields. * refactor(rdns): make sure the all expected resources comply with interface * test: fix linting issue Argument layout of mustParseTime was always set to the same value.
1 parent f396b84 commit ac5ae2e

11 files changed

+283
-71
lines changed

hcloud/deprecation.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package hcloud
2+
3+
import "time"
4+
5+
// Deprecatable is a shared interface implemented by all Resources that have a defined deprecation workflow.
6+
type Deprecatable interface {
7+
// IsDeprecated returns true if the resource is marked as deprecated.
8+
IsDeprecated() bool
9+
10+
// UnavailableAfter returns the time that the deprecated resource will be removed from the API.
11+
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
12+
UnavailableAfter() time.Time
13+
14+
// DeprecationAnnounced returns the time that the deprecation of this resource was announced.
15+
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
16+
DeprecationAnnounced() time.Time
17+
}
18+
19+
// DeprecationInfo contains the information published when a resource is actually deprecated.
20+
type DeprecationInfo struct {
21+
Announced time.Time
22+
UnavailableAfter time.Time
23+
}
24+
25+
// DeprecatableResource implements the [Deprecatable] interface and can be embedded in structs for Resources that can be
26+
// deprecated.
27+
type DeprecatableResource struct {
28+
Deprecation *DeprecationInfo
29+
}
30+
31+
// IsDeprecated returns true if the resource is marked as deprecated.
32+
func (d DeprecatableResource) IsDeprecated() bool {
33+
return d.Deprecation != nil
34+
}
35+
36+
// UnavailableAfter returns the time that the deprecated resource will be removed from the API.
37+
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
38+
func (d DeprecatableResource) UnavailableAfter() time.Time {
39+
if !d.IsDeprecated() {
40+
// Return "null" time if resource is not deprecated
41+
return time.Unix(0, 0)
42+
}
43+
44+
return d.Deprecation.UnavailableAfter
45+
}
46+
47+
// DeprecationAnnounced returns the time that the deprecation of this resource was announced.
48+
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
49+
func (d DeprecatableResource) DeprecationAnnounced() time.Time {
50+
if !d.IsDeprecated() {
51+
// Return "null" time if resource is not deprecated
52+
return time.Unix(0, 0)
53+
}
54+
55+
return d.Deprecation.Announced
56+
}
57+
58+
// Make sure that all expected Resources actually implement the interface.
59+
var _ Deprecatable = ServerType{}

hcloud/deprecation_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package hcloud
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
type TestDeprecatableResource struct {
11+
DeprecatableResource
12+
}
13+
14+
// Interface is implemented.
15+
var _ Deprecatable = TestDeprecatableResource{}
16+
17+
func TestDeprecatableResource_IsDeprecated(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
resource TestDeprecatableResource
21+
want bool
22+
}{
23+
{name: "nil returns false", resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: nil}}, want: false},
24+
{name: "struct returns true", resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{}}}, want: true},
25+
}
26+
for _, tt := range tests {
27+
t.Run(tt.name, func(t *testing.T) {
28+
assert.Equalf(t, tt.want, tt.resource.IsDeprecated(), "IsDeprecated()")
29+
})
30+
}
31+
}
32+
33+
func TestDeprecatableResource_DeprecationAnnounced(t *testing.T) {
34+
tests := []struct {
35+
name string
36+
resource TestDeprecatableResource
37+
want time.Time
38+
}{
39+
{
40+
name: "nil returns default time",
41+
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: nil}},
42+
want: time.Unix(0, 0)},
43+
{
44+
name: "actual value is returned",
45+
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{Announced: mustParseTime(t, "2023-06-01T00:00:00+00:00")}}},
46+
want: mustParseTime(t, "2023-06-01T00:00:00+00:00")},
47+
}
48+
for _, tt := range tests {
49+
t.Run(tt.name, func(t *testing.T) {
50+
assert.Equalf(t, tt.want, tt.resource.DeprecationAnnounced(), "DeprecationAnnounced()")
51+
})
52+
}
53+
}
54+
55+
func TestDeprecatableResource_UnavailableAfter(t *testing.T) {
56+
tests := []struct {
57+
name string
58+
resource TestDeprecatableResource
59+
want time.Time
60+
}{
61+
{
62+
name: "nil returns default time",
63+
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: nil}},
64+
want: time.Unix(0, 0)},
65+
{
66+
name: "actual value is returned",
67+
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{UnavailableAfter: mustParseTime(t, "2023-06-01T00:00:00+00:00")}}},
68+
want: mustParseTime(t, "2023-06-01T00:00:00+00:00")},
69+
}
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
assert.Equalf(t, tt.want, tt.resource.UnavailableAfter(), "UnavailableAfter()")
73+
})
74+
}
75+
}

hcloud/load_balancer_test.go

+14-14
Original file line numberDiff line numberDiff line change
@@ -1036,14 +1036,14 @@ func TestLoadBalancerGetMetrics(t *testing.T) {
10361036
LoadBalancerMetricRequestsPerSecond,
10371037
LoadBalancerMetricBandwidth,
10381038
},
1039-
Start: mustParseTime(t, time.RFC3339, "2017-01-01T00:00:00Z"),
1040-
End: mustParseTime(t, time.RFC3339, "2017-01-01T23:00:00Z"),
1039+
Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
1040+
End: mustParseTime(t, "2017-01-01T23:00:00Z"),
10411041
},
10421042
respFn: func() schema.LoadBalancerGetMetricsResponse {
10431043
var resp schema.LoadBalancerGetMetricsResponse
10441044

1045-
resp.Metrics.Start = mustParseTime(t, time.RFC3339, "2017-01-01T00:00:00Z")
1046-
resp.Metrics.End = mustParseTime(t, time.RFC3339, "2017-01-01T23:00:00Z")
1045+
resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
1046+
resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
10471047
resp.Metrics.TimeSeries = map[string]schema.LoadBalancerTimeSeriesVals{
10481048
"open_connections": {
10491049
Values: []interface{}{
@@ -1080,8 +1080,8 @@ func TestLoadBalancerGetMetrics(t *testing.T) {
10801080
return resp
10811081
},
10821082
expected: LoadBalancerMetrics{
1083-
Start: mustParseTime(t, time.RFC3339, "2017-01-01T00:00:00Z"),
1084-
End: mustParseTime(t, time.RFC3339, "2017-01-01T23:00:00Z"),
1083+
Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
1084+
End: mustParseTime(t, "2017-01-01T23:00:00Z"),
10851085
TimeSeries: map[string][]LoadBalancerMetricsValue{
10861086
"open_connections": {
10871087
{Timestamp: 1435781470.622, Value: "42"},
@@ -1110,8 +1110,8 @@ func TestLoadBalancerGetMetrics(t *testing.T) {
11101110
name: "missing metrics types",
11111111
lb: &LoadBalancer{ID: 3},
11121112
opts: LoadBalancerGetMetricsOpts{
1113-
Start: mustParseTime(t, time.RFC3339, "2017-01-01T00:00:00Z"),
1114-
End: mustParseTime(t, time.RFC3339, "2017-01-01T23:00:00Z"),
1113+
Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
1114+
End: mustParseTime(t, "2017-01-01T23:00:00Z"),
11151115
},
11161116
expectedErr: "add query params: no metric types specified",
11171117
},
@@ -1120,7 +1120,7 @@ func TestLoadBalancerGetMetrics(t *testing.T) {
11201120
lb: &LoadBalancer{ID: 4},
11211121
opts: LoadBalancerGetMetricsOpts{
11221122
Types: []LoadBalancerMetricType{LoadBalancerMetricBandwidth},
1123-
End: mustParseTime(t, time.RFC3339, "2017-01-01T23:00:00Z"),
1123+
End: mustParseTime(t, "2017-01-01T23:00:00Z"),
11241124
},
11251125
expectedErr: "add query params: no start time specified",
11261126
},
@@ -1129,7 +1129,7 @@ func TestLoadBalancerGetMetrics(t *testing.T) {
11291129
lb: &LoadBalancer{ID: 5},
11301130
opts: LoadBalancerGetMetricsOpts{
11311131
Types: []LoadBalancerMetricType{LoadBalancerMetricBandwidth},
1132-
Start: mustParseTime(t, time.RFC3339, "2017-01-01T00:00:00Z"),
1132+
Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
11331133
},
11341134
expectedErr: "add query params: no end time specified",
11351135
},
@@ -1138,8 +1138,8 @@ func TestLoadBalancerGetMetrics(t *testing.T) {
11381138
lb: &LoadBalancer{ID: 6},
11391139
opts: LoadBalancerGetMetricsOpts{
11401140
Types: []LoadBalancerMetricType{LoadBalancerMetricBandwidth},
1141-
Start: mustParseTime(t, time.RFC3339, "2017-01-01T00:00:00Z"),
1142-
End: mustParseTime(t, time.RFC3339, "2017-01-01T23:00:00Z"),
1141+
Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
1142+
End: mustParseTime(t, "2017-01-01T23:00:00Z"),
11431143
},
11441144
respStatus: http.StatusInternalServerError,
11451145
expectedErr: "get metrics: hcloud: server responded with status code 500",
@@ -1148,8 +1148,8 @@ func TestLoadBalancerGetMetrics(t *testing.T) {
11481148
name: "no load balancer passed",
11491149
opts: LoadBalancerGetMetricsOpts{
11501150
Types: []LoadBalancerMetricType{LoadBalancerMetricBandwidth},
1151-
Start: mustParseTime(t, time.RFC3339, "2017-01-01T00:00:00Z"),
1152-
End: mustParseTime(t, time.RFC3339, "2017-01-01T23:00:00Z"),
1151+
Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
1152+
End: mustParseTime(t, "2017-01-01T23:00:00Z"),
11531153
},
11541154
expectedErr: "illegal argument: load balancer is nil",
11551155
},

hcloud/rdns.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
// RDNSSupporter defines functions to change and lookup reverse dns entries.
10-
// currently implemented by Server, FloatingIP and LoadBalancer.
10+
// currently implemented by Server, FloatingIP, PrimaryIP and LoadBalancer.
1111
type RDNSSupporter interface {
1212
// changeDNSPtr changes or resets the reverse DNS pointer for a IP address.
1313
// Pass a nil ptr to reset the reverse DNS pointer to its default value.
@@ -17,7 +17,7 @@ type RDNSSupporter interface {
1717
GetDNSPtrForIP(ip net.IP) (string, error)
1818
}
1919

20-
// RDNSClient simplifys the handling objects which support reverse dns entries.
20+
// RDNSClient simplifies the handling objects which support reverse dns entries.
2121
type RDNSClient struct {
2222
client *Client
2323
}
@@ -44,3 +44,9 @@ func RDNSLookup(i interface{}, ip net.IP) (string, error) {
4444

4545
return rdns.GetDNSPtrForIP(ip)
4646
}
47+
48+
// Make sure that all expected Resources actually implement the interface.
49+
var _ RDNSSupporter = &FloatingIP{}
50+
var _ RDNSSupporter = &PrimaryIP{}
51+
var _ RDNSSupporter = &Server{}
52+
var _ RDNSSupporter = &LoadBalancer{}

hcloud/schema.go

+16
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ func ServerTypeFromSchema(s schema.ServerType) *ServerType {
288288
CPUType: CPUType(s.CPUType),
289289
Architecture: Architecture(s.Architecture),
290290
IncludedTraffic: s.IncludedTraffic,
291+
DeprecatableResource: DeprecatableResource{
292+
DeprecationFromSchema(s.Deprecation),
293+
},
291294
}
292295
for _, price := range s.Prices {
293296
st.Pricings = append(st.Pricings, ServerTypeLocationPricing{
@@ -302,6 +305,7 @@ func ServerTypeFromSchema(s schema.ServerType) *ServerType {
302305
},
303306
})
304307
}
308+
305309
return st
306310
}
307311

@@ -1249,3 +1253,15 @@ func loadBalancerMetricsFromSchema(s *schema.LoadBalancerGetMetricsResponse) (*L
12491253

12501254
return &ms, nil
12511255
}
1256+
1257+
// DeprecationFromSchema converts a [schema.DeprecationInfo] to a [DeprecationInfo].
1258+
func DeprecationFromSchema(s *schema.DeprecationInfo) *DeprecationInfo {
1259+
if s == nil {
1260+
return nil
1261+
}
1262+
1263+
return &DeprecationInfo{
1264+
Announced: s.Announced,
1265+
UnavailableAfter: s.UnavailableAfter,
1266+
}
1267+
}

hcloud/schema/deprecation.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package schema
2+
3+
import "time"
4+
5+
type DeprecationInfo struct {
6+
Announced time.Time `json:"announced"`
7+
UnavailableAfter time.Time `json:"unavailable_after"`
8+
}
9+
10+
type DeprecatableResource struct {
11+
Deprecation *DeprecationInfo `json:"deprecation"`
12+
}

hcloud/schema/server_type.go

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type ServerType struct {
1313
Architecture string `json:"architecture"`
1414
IncludedTraffic int64 `json:"included_traffic"`
1515
Prices []PricingServerTypePrice `json:"prices"`
16+
DeprecatableResource
1617
}
1718

1819
// ServerTypeListResponse defines the schema of the response when

0 commit comments

Comments
 (0)