Skip to content

Commit

Permalink
Introduce UseCloudscaleDefaults to allow setting default cloudscale d…
Browse files Browse the repository at this point in the history
…ns servers.
  • Loading branch information
alakae committed Feb 22, 2024
1 parent b4d0be5 commit a5a51c2
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 9 deletions.
70 changes: 66 additions & 4 deletions subnets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package cloudscale

import (
"context"
"encoding/json"
"fmt"
"net/http"
"reflect"
)

const subnetBasePath = "v1/subnets"

var UseCloudscaleDefaults = []string{"CLOUDSCALE_DEFAULTS"}

type Subnet struct {
TaggedResource
// Just use omitempty everywhere. This makes it easy to use restful. Errors
Expand All @@ -28,10 +32,10 @@ type SubnetStub struct {

type SubnetCreateRequest struct {
TaggedResourceRequest
CIDR string `json:"cidr,omitempty"`
Network string `json:"network,omitempty"`
GatewayAddress string `json:"gateway_address,omitempty"`
DNSServers []string `json:"dns_servers,omitempty"`
CIDR string `json:"cidr,omitempty"`
Network string `json:"network,omitempty"`
GatewayAddress string `json:"gateway_address,omitempty"`
DNSServers *[]string `json:"dns_servers,omitempty"`
}

type SubnetUpdateRequest struct {
Expand All @@ -40,6 +44,64 @@ type SubnetUpdateRequest struct {
DNSServers *[]string `json:"dns_servers"`
}

func (request SubnetUpdateRequest) MarshalJSON() ([]byte, error) {
type Alias SubnetUpdateRequest // Create an alias to avoid recursion

if request.DNSServers == nil {
return json.Marshal(&struct {
Alias
DNSServers []string `json:"dns_servers,omitempty"`
}{
Alias: (Alias)(request),
})
}

if reflect.DeepEqual(*request.DNSServers, UseCloudscaleDefaults) {
return json.Marshal(&struct {
Alias
DNSServers []string `json:"dns_servers"` // important: no omitempty
}{
Alias: (Alias)(request),
DNSServers: nil,
})
}

return json.Marshal(&struct {
Alias
}{
Alias: (Alias)(request),
})
}

func (request SubnetCreateRequest) MarshalJSON() ([]byte, error) {
type Alias SubnetCreateRequest // Create an alias to avoid recursion

if request.DNSServers == nil {
return json.Marshal(&struct {
Alias
DNSServers []string `json:"dns_servers,omitempty"`
}{
Alias: (Alias)(request),
})
}

if reflect.DeepEqual(*request.DNSServers, UseCloudscaleDefaults) {
return json.Marshal(&struct {
Alias
DNSServers []string `json:"dns_servers"` // important: no omitempty
}{
Alias: (Alias)(request),
DNSServers: nil,
})
}

return json.Marshal(&struct {
Alias
}{
Alias: (Alias)(request),
})
}

type SubnetService interface {
Create(ctx context.Context, createRequest *SubnetCreateRequest) (*Subnet, error)
Get(ctx context.Context, subnetID string) (*Subnet, error)
Expand Down
113 changes: 113 additions & 0 deletions subnets_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cloudscale

import (
"encoding/json"
"fmt"
"net/http"
"reflect"
Expand Down Expand Up @@ -47,3 +48,115 @@ func TestSubnets_List(t *testing.T) {
}

}

func TestMarshalingOfDNSServersInSubnetUpdateRequest(t *testing.T) {
testCases := []struct {
name string
request SubnetUpdateRequest
expected string
}{
{
name: "one dns server",
request: SubnetUpdateRequest{
DNSServers: &[]string{"8.8.8.8"},
},
expected: "{\"dns_servers\":[\"8.8.8.8\"]}",
},
{
name: "two dns servers",
request: SubnetUpdateRequest{
DNSServers: &[]string{"8.8.8.8", "8.8.4.4"},
},
expected: "{\"dns_servers\":[\"8.8.8.8\",\"8.8.4.4\"]}",
},
{
name: "no dns servers",
request: SubnetUpdateRequest{
DNSServers: &[]string{},
},
expected: "{\"dns_servers\":[]}",
},
{
name: "defaults",
request: SubnetUpdateRequest{
DNSServers: &UseCloudscaleDefaults,
},
expected: "{\"dns_servers\":null}",
},
{
name: "gateway",
request: SubnetUpdateRequest{
GatewayAddress: "192.168.1.1",
},
expected: "{\"gateway_address\":\"192.168.1.1\"}",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b, err := json.Marshal(tc.request)
if err != nil {
t.Errorf("Error marshaling JSON: %v", err)
}
if actualOutput := string(b); actualOutput != tc.expected {
t.Errorf("Unexpected JSON output:\nExpected: %s\nActual: %s", tc.expected, actualOutput)
}
})
}
}

func TestMarshalingOfDNSServersInSubnetSubnetCreateRequest(t *testing.T) {
testCases := []struct {
name string
request SubnetCreateRequest
expected string
}{
{
name: "one dns server",
request: SubnetCreateRequest{
DNSServers: &[]string{"8.8.8.8"},
},
expected: "{\"dns_servers\":[\"8.8.8.8\"]}",
},
{
name: "two dns servers",
request: SubnetCreateRequest{
DNSServers: &[]string{"8.8.8.8", "8.8.4.4"},
},
expected: "{\"dns_servers\":[\"8.8.8.8\",\"8.8.4.4\"]}",
},
{
name: "no dns servers",
request: SubnetCreateRequest{
DNSServers: &[]string{},
},
expected: "{\"dns_servers\":[]}",
},
{
name: "defaults",
request: SubnetCreateRequest{
DNSServers: &UseCloudscaleDefaults,
},
expected: "{\"dns_servers\":null}",
},
{
name: "gateway",
request: SubnetCreateRequest{
GatewayAddress: "192.168.1.1",
},
expected: "{\"gateway_address\":\"192.168.1.1\"}",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b, err := json.Marshal(tc.request)
if err != nil {
t.Errorf("Error marshaling JSON: %v", err)
}
if actualOutput := string(b); actualOutput != tc.expected {
t.Errorf("Unexpected JSON output:\nExpected: %s\nActual: %s", tc.expected, actualOutput)
}
})
}
}
78 changes: 73 additions & 5 deletions test/integration/subnets_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"testing"
)

const numberOfDefaultEntries = 2

func TestIntegrationSubnet_GetAndList(t *testing.T) {
integrationTest(t)

Expand Down Expand Up @@ -75,7 +77,7 @@ func TestIntegrationSubnet_CRUD(t *testing.T) {
createSubnetRequest := &cloudscale.SubnetCreateRequest{
CIDR: "192.168.192.0/22",
GatewayAddress: "192.168.192.2",
DNSServers: []string{"77.109.128.2", "213.144.129.20"},
DNSServers: &[]string{"77.109.128.2", "213.144.129.20"},
Network: network.UUID,
}
expected, err := client.Subnets.Create(context.TODO(), createSubnetRequest)
Expand Down Expand Up @@ -134,6 +136,11 @@ func TestIntegrationSubnet_Update(t *testing.T) {
t.Fatalf("Subnets.Create returned error %s\n", err)
}

// assert initial DNSServers, no option was passed, hence defaults should be used
if actualDNSServers := subnet.DNSServers; !(len(actualDNSServers) == 2) {
t.Errorf("Subnet DNSServers length\ngot=%#v\nwant=%#v", len(actualDNSServers), 2)
}

// update gateway
expectedGateway := "10.255.255.254"
updateRequest := &cloudscale.SubnetUpdateRequest{
Expand Down Expand Up @@ -193,9 +200,9 @@ func TestIntegrationSubnet_Update(t *testing.T) {
t.Errorf("Subnet DNSServers\ngot=%#v\nwant=%#v", actualDNSServers, []string{})
}

// update to Default DNSServer by submitting nil
// update to Default DNSServer
updateRequest = &cloudscale.SubnetUpdateRequest{
DNSServers: nil,
DNSServers: &cloudscale.UseCloudscaleDefaults,
}

err = client.Subnets.Update(context.Background(), subnet.UUID, updateRequest)
Expand All @@ -208,8 +215,69 @@ func TestIntegrationSubnet_Update(t *testing.T) {
t.Fatalf("Subnets.Get returned error %s\n", err)
}

if actualDNSServers := updatedSubnet.DNSServers; !reflect.DeepEqual(actualDNSServers, []string{}) {
t.Errorf("Subnet DNSServers\ngot=%#v\nwant=%#v", actualDNSServers, []string{})
// assert default servers
if actualNumberOfEntries := len(updatedSubnet.DNSServers); !(actualNumberOfEntries == numberOfDefaultEntries) {
t.Errorf("Subnet DNSServers length\ngot=%#v\nwant=%#v", actualNumberOfEntries, numberOfDefaultEntries)
}

err = client.Subnets.Delete(context.Background(), subnet.UUID)
if err != nil {
t.Fatalf("Networks.Delete returned error %s\n", err)
}

err = client.Networks.Delete(context.Background(), network.UUID)
if err != nil {
t.Fatalf("Networks.Delete returned error %s\n", err)
}
}

func TestIntegrationSubnet_InitialEmptyDNSServers(t *testing.T) {
integrationTest(t)

autoCreateSubnet := false
createNetworkRequest := &cloudscale.NetworkCreateRequest{
Name: testRunPrefix,
AutoCreateIPV4Subnet: &autoCreateSubnet,
}
network, err := client.Networks.Create(context.TODO(), createNetworkRequest)
if err != nil {
t.Fatalf("Networks.Create returned error %s\n", err)
}

createSubnetRequest := &cloudscale.SubnetCreateRequest{
Network: network.UUID,
CIDR: "10.0.0.0/8",
DNSServers: &[]string{},
}

subnet, err := client.Subnets.Create(context.TODO(), createSubnetRequest)
if err != nil {
t.Fatalf("Subnets.Create returned error %s\n", err)
}

// assert initial DNSServers
if actualDNSServers := subnet.DNSServers; !reflect.DeepEqual(actualDNSServers, []string{}) {
t.Errorf("Subnet DNSServers\ngot=%#v\nwant=%#v", subnet.DNSServers, []string{})
}

// update DNSServers
updateRequest := &cloudscale.SubnetUpdateRequest{
DNSServers: &cloudscale.UseCloudscaleDefaults,
}

err = client.Subnets.Update(context.Background(), subnet.UUID, updateRequest)
if err != nil {
t.Fatalf("Subnets.Update returned error %s\n", err)
}

updatedSubnet, err := client.Subnets.Get(context.Background(), subnet.UUID)
if err != nil {
t.Fatalf("Subnets.Get returned error %s\n", err)
}

// assert default servers
if actualNumberOfEntries := len(updatedSubnet.DNSServers); !(actualNumberOfEntries == numberOfDefaultEntries) {
t.Errorf("Subnet DNSServers length\ngot=%#v\nwant=%#v", actualNumberOfEntries, numberOfDefaultEntries)
}

err = client.Subnets.Delete(context.Background(), subnet.UUID)
Expand Down

0 comments on commit a5a51c2

Please sign in to comment.