Skip to content

Commit

Permalink
Add RDNS client (#183)
Browse files Browse the repository at this point in the history
* Introduce RDNSClient

* Adjust RDNS Tests

* Bump hcloud version
  • Loading branch information
Adi146 committed Aug 20, 2021
1 parent 9218970 commit 1cde0d7
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 269 deletions.
2 changes: 2 additions & 0 deletions hcloud/client.go
Expand Up @@ -75,6 +75,7 @@ type Client struct {
SSHKey SSHKeyClient
Volume VolumeClient
PlacementGroup PlacementGroupClient
RDNS RDNSClient
}

// A ClientOption is used to configure a Client.
Expand Down Expand Up @@ -166,6 +167,7 @@ func NewClient(options ...ClientOption) *Client {
client.Certificate = CertificateClient{client: client}
client.Firewall = FirewallClient{client: client}
client.PlacementGroup = PlacementGroupClient{client: client}
client.RDNS = RDNSClient{client: client}

return client
}
Expand Down
21 changes: 20 additions & 1 deletion hcloud/error.go
@@ -1,6 +1,9 @@
package hcloud

import "fmt"
import (
"fmt"
"net"
)

// ErrorCode represents an error code returned from the API.
type ErrorCode string
Expand Down Expand Up @@ -103,3 +106,19 @@ func IsError(err error, code ErrorCode) bool {
apiErr, ok := err.(Error)
return ok && apiErr.Code == code
}

type InvalidIPError struct {
IP string
}

func (e InvalidIPError) Error() string {
return fmt.Sprintf("could not parse ip address %s", e.IP)
}

type DNSNotFoundError struct {
IP net.IP
}

func (e DNSNotFoundError) Error() string {
return fmt.Sprintf("dns for ip %s not found", e.IP.String())
}
62 changes: 42 additions & 20 deletions hcloud/floating_ip.go
Expand Up @@ -32,6 +32,7 @@ type FloatingIP struct {
}

// DNSPtrForIP returns the reverse DNS pointer of the IP address.
// Deprecated: Use GetDNSPtrForIP instead
func (f *FloatingIP) DNSPtrForIP(ip net.IP) string {
return f.DNSPtr[ip.String()]
}
Expand All @@ -50,6 +51,43 @@ const (
FloatingIPTypeIPv6 FloatingIPType = "ipv6"
)

// 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.
func (f *FloatingIP) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) {
reqBody := schema.FloatingIPActionChangeDNSPtrRequest{
IP: ip.String(),
DNSPtr: ptr,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}

path := fmt.Sprintf("/floating_ips/%d/actions/change_dns_ptr", f.ID)
req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}

respBody := schema.FloatingIPActionChangeDNSPtrResponse{}
resp, err := client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}

// GetDNSPtrForIP searches for the dns assigned to the given IP address.
// It returns an error if there is no dns set for the given IP address.
func (f *FloatingIP) GetDNSPtrForIP(ip net.IP) (string, error) {
dns, ok := f.DNSPtr[ip.String()]
if !ok {
return "", DNSNotFoundError{ip}
}

return dns, nil
}

// FloatingIPClient is a client for the Floating IP API.
type FloatingIPClient struct {
client *Client
Expand Down Expand Up @@ -325,27 +363,11 @@ func (c *FloatingIPClient) Unassign(ctx context.Context, floatingIP *FloatingIP)
// ChangeDNSPtr changes or resets the reverse DNS pointer for a Floating IP address.
// Pass a nil ptr to reset the reverse DNS pointer to its default value.
func (c *FloatingIPClient) ChangeDNSPtr(ctx context.Context, floatingIP *FloatingIP, ip string, ptr *string) (*Action, *Response, error) {
reqBody := schema.FloatingIPActionChangeDNSPtrRequest{
IP: ip,
DNSPtr: ptr,
netIP := net.ParseIP(ip)
if netIP == nil {
return nil, nil, InvalidIPError{ip}
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}

path := fmt.Sprintf("/floating_ips/%d/actions/change_dns_ptr", floatingIP.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}

respBody := schema.FloatingIPActionChangeDNSPtrResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
return floatingIP.changeDNSPtr(ctx, c.client, net.ParseIP(ip), ptr)
}

// FloatingIPChangeProtectionOpts specifies options for changing the resource protection level of a Floating IP.
Expand Down
69 changes: 0 additions & 69 deletions hcloud/floating_ip_test.go
Expand Up @@ -535,75 +535,6 @@ func TestFloatingIPClientUnassign(t *testing.T) {
}
}

func TestFloatingIPClientChangeDNSPtr(t *testing.T) {
var (
ctx = context.Background()
floatingIP = &FloatingIP{ID: 1}
)

t.Run("set", func(t *testing.T) {
env := newTestEnv()
defer env.Teardown()

env.Mux.HandleFunc("/floating_ips/1/actions/change_dns_ptr", func(w http.ResponseWriter, r *http.Request) {
var reqBody schema.FloatingIPActionChangeDNSPtrRequest
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
t.Fatal(err)
}
if reqBody.IP != "127.0.0.1" {
t.Errorf("unexpected IP: %v", reqBody.IP)
}
if reqBody.DNSPtr == nil || *reqBody.DNSPtr != "example.com" {
t.Errorf("unexpected DNS ptr: %v", reqBody.DNSPtr)
}
json.NewEncoder(w).Encode(schema.FloatingIPActionChangeDNSPtrResponse{
Action: schema.Action{
ID: 1,
},
})
})

action, _, err := env.Client.FloatingIP.ChangeDNSPtr(ctx, floatingIP, "127.0.0.1", String("example.com"))
if err != nil {
t.Fatal(err)
}
if action.ID != 1 {
t.Errorf("unexpected action ID: %d", action.ID)
}
})

t.Run("reset", func(t *testing.T) {
env := newTestEnv()
defer env.Teardown()

env.Mux.HandleFunc("/floating_ips/1/actions/change_dns_ptr", func(w http.ResponseWriter, r *http.Request) {
var reqBody schema.FloatingIPActionChangeDNSPtrRequest
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
t.Fatal(err)
}
if reqBody.IP != "127.0.0.1" {
t.Errorf("unexpected IP: %v", reqBody.IP)
}
if reqBody.DNSPtr != nil {
t.Errorf("unexpected DNS ptr: %v", reqBody.DNSPtr)
}
json.NewEncoder(w).Encode(schema.FloatingIPActionChangeDNSPtrResponse{
Action: schema.Action{
ID: 1,
},
})
})

action, _, err := env.Client.FloatingIP.ChangeDNSPtr(ctx, floatingIP, "127.0.0.1", nil)
if err != nil {
t.Fatal(err)
}
if action.ID != 1 {
t.Errorf("unexpected action ID: %d", action.ID)
}
})
}

func TestFloatingIPClientChangeProtection(t *testing.T) {
var (
ctx = context.Background()
Expand Down
2 changes: 1 addition & 1 deletion hcloud/hcloud.go
Expand Up @@ -2,4 +2,4 @@
package hcloud

// Version is the library's version following Semantic Versioning.
const Version = "1.31.0"
const Version = "1.31.1"
62 changes: 42 additions & 20 deletions hcloud/load_balancer.go
Expand Up @@ -197,6 +197,44 @@ type LoadBalancerProtection struct {
Delete bool
}

// 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.
func (lb *LoadBalancer) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) {
reqBody := schema.LoadBalancerActionChangeDNSPtrRequest{
IP: ip.String(),
DNSPtr: ptr,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}

path := fmt.Sprintf("/load_balancers/%d/actions/change_dns_ptr", lb.ID)
req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}

respBody := schema.LoadBalancerActionChangeDNSPtrResponse{}
resp, err := client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}

// GetDNSPtrForIP searches for the dns assigned to the given IP address.
// It returns an error if there is no dns set for the given IP address.
func (lb *LoadBalancer) GetDNSPtrForIP(ip net.IP) (string, error) {
if net.IP.Equal(lb.PublicNet.IPv4.IP, ip) {
return lb.PublicNet.IPv4.DNSPtr, nil
} else if net.IP.Equal(lb.PublicNet.IPv6.IP, ip) {
return lb.PublicNet.IPv6.DNSPtr, nil
}

return "", DNSNotFoundError{ip}
}

// LoadBalancerClient is a client for the Load Balancers API.
type LoadBalancerClient struct {
client *Client
Expand Down Expand Up @@ -1030,25 +1068,9 @@ func (c *LoadBalancerClient) GetMetrics(
// ChangeDNSPtr changes or resets the reverse DNS pointer for a Load Balancer.
// Pass a nil ptr to reset the reverse DNS pointer to its default value.
func (c *LoadBalancerClient) ChangeDNSPtr(ctx context.Context, lb *LoadBalancer, ip string, ptr *string) (*Action, *Response, error) {
reqBody := schema.LoadBalancerActionChangeDNSPtrRequest{
IP: ip,
DNSPtr: ptr,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}

path := fmt.Sprintf("/load_balancers/%d/actions/change_dns_ptr", lb.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
netIP := net.ParseIP(ip)
if netIP == nil {
return nil, nil, InvalidIPError{ip}
}

respBody := schema.LoadBalancerActionChangeDNSPtrResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
return lb.changeDNSPtr(ctx, c.client, net.ParseIP(ip), ptr)
}
69 changes: 0 additions & 69 deletions hcloud/load_balancer_test.go
Expand Up @@ -1249,72 +1249,3 @@ func loadBalancerMetricsOptsFromURL(t *testing.T, u *url.URL) LoadBalancerGetMet

return opts
}

func TestLoadBalancerClientChangeDNSPtr(t *testing.T) {
var (
ctx = context.Background()
loadBalancer = &LoadBalancer{ID: 1}
)

t.Run("set", func(t *testing.T) {
env := newTestEnv()
defer env.Teardown()

env.Mux.HandleFunc("/load_balancers/1/actions/change_dns_ptr", func(w http.ResponseWriter, r *http.Request) {
var reqBody schema.LoadBalancerActionChangeDNSPtrRequest
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
t.Fatal(err)
}
if reqBody.IP != "127.0.0.1" {
t.Errorf("unexpected IP: %v", reqBody.IP)
}
if reqBody.DNSPtr == nil || *reqBody.DNSPtr != "example.com" {
t.Errorf("unexpected DNS ptr: %v", reqBody.DNSPtr)
}
json.NewEncoder(w).Encode(schema.LoadBalancerActionChangeDNSPtrResponse{
Action: schema.Action{
ID: 1,
},
})
})

action, _, err := env.Client.LoadBalancer.ChangeDNSPtr(ctx, loadBalancer, "127.0.0.1", String("example.com"))
if err != nil {
t.Fatal(err)
}
if action.ID != 1 {
t.Errorf("unexpected action ID: %d", action.ID)
}
})

t.Run("reset", func(t *testing.T) {
env := newTestEnv()
defer env.Teardown()

env.Mux.HandleFunc("/load_balancers/1/actions/change_dns_ptr", func(w http.ResponseWriter, r *http.Request) {
var reqBody schema.LoadBalancerActionChangeDNSPtrRequest
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
t.Fatal(err)
}
if reqBody.IP != "127.0.0.1" {
t.Errorf("unexpected IP: %v", reqBody.IP)
}
if reqBody.DNSPtr != nil {
t.Errorf("unexpected DNS ptr: %v", reqBody.DNSPtr)
}
json.NewEncoder(w).Encode(schema.LoadBalancerActionChangeDNSPtrResponse{
Action: schema.Action{
ID: 1,
},
})
})

action, _, err := env.Client.LoadBalancer.ChangeDNSPtr(ctx, loadBalancer, "127.0.0.1", nil)
if err != nil {
t.Fatal(err)
}
if action.ID != 1 {
t.Errorf("unexpected action ID: %d", action.ID)
}
})
}

0 comments on commit 1cde0d7

Please sign in to comment.