Skip to content

Commit

Permalink
feat(servertype): implement new Deprecation api field
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apricote committed Jun 12, 2023
1 parent 2a7249e commit 0c6daec
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 3 deletions.
59 changes: 59 additions & 0 deletions hcloud/deprecation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package hcloud

import "time"

// Deprecatable is a shared interface implemented by all Resources that have a defined deprecation workflow.
type Deprecatable interface {
// IsDeprecated returns true if the resource is marked as deprecated.
IsDeprecated() bool

// UnavailableAfter returns the time that the deprecated resource will be removed from the API.
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
UnavailableAfter() time.Time

// DeprecationAnnounced returns the time that the deprecation of this resource was announced.
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
DeprecationAnnounced() time.Time
}

// DeprecationInfo contains the information published when a resource is actually deprecated.
type DeprecationInfo struct {
Announced time.Time
UnavailableAfter time.Time
}

// DeprecatableResource implements the [Deprecatable] interface and can be embedded in structs for Resources that can be
// deprecated.
type DeprecatableResource struct {
Deprecation *DeprecationInfo
}

// IsDeprecated returns true if the resource is marked as deprecated.
func (d DeprecatableResource) IsDeprecated() bool {
return d.Deprecation != nil
}

// UnavailableAfter returns the time that the deprecated resource will be removed from the API.
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
func (d DeprecatableResource) UnavailableAfter() time.Time {
if !d.IsDeprecated() {
// Return "null" time if resource is not deprecated
return time.Unix(0, 0)
}

return d.Deprecation.UnavailableAfter
}

// DeprecationAnnounced returns the time that the deprecation of this resource was announced.
// This only returns a valid value if [Deprecatable.IsDeprecated] returned true.
func (d DeprecatableResource) DeprecationAnnounced() time.Time {
if !d.IsDeprecated() {
// Return "null" time if resource is not deprecated
return time.Unix(0, 0)
}

return d.Deprecation.Announced
}

// Make sure that all expected Resources actually implement the interface.
var _ Deprecatable = ServerType{}
75 changes: 75 additions & 0 deletions hcloud/deprecation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package hcloud

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

type TestDeprecatableResource struct {
DeprecatableResource
}

// Interface is implemented
var _ Deprecatable = TestDeprecatableResource{}

func TestDeprecatableResource_IsDeprecated(t *testing.T) {
tests := []struct {
name string
resource TestDeprecatableResource
want bool
}{
{name: "nil returns false", resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: nil}}, want: false},
{name: "struct returns true", resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{}}}, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, tt.resource.IsDeprecated(), "IsDeprecated()")
})
}
}

func TestDeprecatableResource_DeprecationAnnounced(t *testing.T) {
tests := []struct {
name string
resource TestDeprecatableResource
want time.Time
}{
{
name: "nil returns default time",
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: nil}},
want: time.Unix(0, 0)},
{
name: "actual value is returned",
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{Announced: mustParseTime(t, apiTimestampFormat, "2023-06-01T00:00:00+00:00")}}},
want: mustParseTime(t, apiTimestampFormat, "2023-06-01T00:00:00+00:00")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, tt.resource.DeprecationAnnounced(), "DeprecationAnnounced()")
})
}
}

func TestDeprecatableResource_UnavailableAfter(t *testing.T) {
tests := []struct {
name string
resource TestDeprecatableResource
want time.Time
}{
{
name: "nil returns default time",
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: nil}},
want: time.Unix(0, 0)},
{
name: "actual value is returned",
resource: TestDeprecatableResource{DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{UnavailableAfter: mustParseTime(t, apiTimestampFormat, "2023-06-01T00:00:00+00:00")}}},
want: mustParseTime(t, apiTimestampFormat, "2023-06-01T00:00:00+00:00")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, tt.resource.UnavailableAfter(), "UnavailableAfter()")
})
}
}
4 changes: 2 additions & 2 deletions hcloud/rdns.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

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

// RDNSClient simplifys the handling objects which support reverse dns entries.
// RDNSClient simplifies the handling objects which support reverse dns entries.
type RDNSClient struct {
client *Client
}
Expand Down
16 changes: 16 additions & 0 deletions hcloud/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ func ServerTypeFromSchema(s schema.ServerType) *ServerType {
CPUType: CPUType(s.CPUType),
Architecture: Architecture(s.Architecture),
IncludedTraffic: s.IncludedTraffic,
DeprecatableResource: DeprecatableResource{
DeprecationFromSchema(s.Deprecation),
},
}
for _, price := range s.Prices {
st.Pricings = append(st.Pricings, ServerTypeLocationPricing{
Expand All @@ -302,6 +305,7 @@ func ServerTypeFromSchema(s schema.ServerType) *ServerType {
},
})
}

return st
}

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

return &ms, nil
}

// DeprecationFromSchema converts a [schema.DeprecationInfo] to a [DeprecationInfo].
func DeprecationFromSchema(s *schema.DeprecationInfo) *DeprecationInfo {
if s == nil {
return nil
}

return &DeprecationInfo{
Announced: s.Announced,
UnavailableAfter: s.UnavailableAfter,
}
}
12 changes: 12 additions & 0 deletions hcloud/schema/deprecation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package schema

import "time"

type DeprecationInfo struct {
Announced time.Time `json:"announced"`
UnavailableAfter time.Time `json:"unavailable_after"`
}

type DeprecatableResource struct {
Deprecation *DeprecationInfo `json:"deprecation"`
}
1 change: 1 addition & 0 deletions hcloud/schema/server_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type ServerType struct {
Architecture string `json:"architecture"`
IncludedTraffic int64 `json:"included_traffic"`
Prices []PricingServerTypePrice `json:"prices"`
DeprecatableResource
}

// ServerTypeListResponse defines the schema of the response when
Expand Down
44 changes: 44 additions & 0 deletions hcloud/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,7 @@ func TestServerTypeFromSchema(t *testing.T) {
"storage_type": "local",
"cpu_type": "shared",
"architecture": "x86",
"deprecation": null,
"prices": [
{
"location": "fsn1",
Expand Down Expand Up @@ -969,6 +970,9 @@ func TestServerTypeFromSchema(t *testing.T) {
if serverType.Architecture != ArchitectureX86 {
t.Errorf("unexpected cpu architecture: %q", serverType.Architecture)
}
if serverType.Deprecation != nil {
t.Errorf("unexpected deprecation: %v", serverType.Deprecation)
}
if len(serverType.Pricings) != 1 {
t.Errorf("unexpected number of pricings: %d", len(serverType.Pricings))
} else {
Expand Down Expand Up @@ -3169,3 +3173,43 @@ func TestPlacementGroupFromSchema(t *testing.T) {
t.Errorf("unexpected Type %s", placementGroup.Type)
}
}

func TestDeprecationFromSchema(t *testing.T) {
t.Run("Deprecated Resource", func(t *testing.T) {
data := []byte(`{
"deprecation": {
"announced": "2023-06-01T00:00:00+00:00",
"unavailable_after": "2023-09-01T00:00:00+00:00"
}
}`)

var d schema.DeprecatableResource
if err := json.Unmarshal(data, &d); err != nil {
t.Fatal(err)
}
deprecationInfo := DeprecationFromSchema(d.Deprecation)
if deprecationInfo == nil {
t.Fatal("unexpected nil DeprecationInfo")
}
if deprecationInfo.Announced != mustParseTime(t, apiTimestampFormat, "2023-06-01T00:00:00+00:00") {
t.Errorf("unexpected anounce time %s", deprecationInfo.Announced)
}
if deprecationInfo.UnavailableAfter != mustParseTime(t, apiTimestampFormat, "2023-09-01T00:00:00+00:00") {
t.Errorf("unexpected unavailable after time %s", deprecationInfo.UnavailableAfter)
}
})

t.Run("Not-deprecated Resource", func(t *testing.T) {
data := []byte(`{
"deprecation": null
}`)
var d schema.DeprecatableResource
if err := json.Unmarshal(data, &d); err != nil {
t.Fatal(err)
}
deprecationInfo := DeprecationFromSchema(d.Deprecation)
if deprecationInfo != nil {
t.Fatal("unexpected non-nil DeprecationInfo", deprecationInfo)
}
})
}
1 change: 1 addition & 0 deletions hcloud/server_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type ServerType struct {
// IncludedTraffic is the free traffic per month in bytes
IncludedTraffic int64
Pricings []ServerTypeLocationPricing
DeprecatableResource
}

// StorageType specifies the type of storage.
Expand Down
2 changes: 1 addition & 1 deletion hcloud/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"time"
)

const apiTimestampFormat = "2006-01-02T15:04:05-07:00"
const apiTimestampFormat = time.RFC3339

func mustParseTime(t *testing.T, layout, value string) time.Time {
t.Helper()
Expand Down

0 comments on commit 0c6daec

Please sign in to comment.