Repository: hetznercloud/hcloud-go
Pull request #182 (2021-08-17 10:07:29)

Add support for Load Balancer DNS PTRs
Signed-off-by: Lukas Kämmerling <lukas.kaemmerling@hetzner-cloud.de>


Patch:
diff --git a/hcloud/load_balancer.go b/hcloud/load_balancer.go
index d395127..7e76d36 100644
--- a/hcloud/load_balancer.go
+++ b/hcloud/load_balancer.go
@@ -42,12 +42,14 @@ type LoadBalancerPublicNet struct {
 
 // LoadBalancerPublicNetIPv4 represents a Load Balancer's public IPv4 address.
 type LoadBalancerPublicNetIPv4 struct {
-	IP net.IP
+	IP     net.IP
+	DNSPtr string
 }
 
 // LoadBalancerPublicNetIPv6 represents a Load Balancer's public IPv6 address.
 type LoadBalancerPublicNetIPv6 struct {
-	IP net.IP
+	IP     net.IP
+	DNSPtr string
 }
 
 // LoadBalancerPrivateNet represents a Load Balancer's private network.
@@ -1024,3 +1026,29 @@ func (c *LoadBalancerClient) GetMetrics(
 	}
 	return ms, resp, nil
 }
+
+// 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
+	}
+
+	respBody := schema.LoadBalancerActionChangeDNSPtrResponse{}
+	resp, err := c.client.Do(req, &respBody)
+	if err != nil {
+		return nil, resp, err
+	}
+	return ActionFromSchema(respBody.Action), resp, nil
+}
diff --git a/hcloud/schema.go b/hcloud/schema.go
index 1645d1a..e7918cf 100644
--- a/hcloud/schema.go
+++ b/hcloud/schema.go
@@ -433,10 +433,12 @@ func LoadBalancerFromSchema(s schema.LoadBalancer) *LoadBalancer {
 		PublicNet: LoadBalancerPublicNet{
 			Enabled: s.PublicNet.Enabled,
 			IPv4: LoadBalancerPublicNetIPv4{
-				IP: net.ParseIP(s.PublicNet.IPv4.IP),
+				IP:     net.ParseIP(s.PublicNet.IPv4.IP),
+				DNSPtr: s.PublicNet.IPv4.DNSPtr,
 			},
 			IPv6: LoadBalancerPublicNetIPv6{
-				IP: net.ParseIP(s.PublicNet.IPv6.IP),
+				IP:     net.ParseIP(s.PublicNet.IPv6.IP),
+				DNSPtr: s.PublicNet.IPv6.DNSPtr,
 			},
 		},
 		Location:         LocationFromSchema(s.Location),
diff --git a/hcloud/schema/load_balancer.go b/hcloud/schema/load_balancer.go
index b9dbaa9..68adf5e 100644
--- a/hcloud/schema/load_balancer.go
+++ b/hcloud/schema/load_balancer.go
@@ -27,11 +27,13 @@ type LoadBalancerPublicNet struct {
 }
 
 type LoadBalancerPublicNetIPv4 struct {
-	IP string `json:"ip"`
+	IP     string `json:"ip"`
+	DNSPtr string `json:"dns_ptr"`
 }
 
 type LoadBalancerPublicNetIPv6 struct {
-	IP string `json:"ip"`
+	IP     string `json:"ip"`
+	DNSPtr string `json:"dns_ptr"`
 }
 
 type LoadBalancerPrivateNet struct {
@@ -401,3 +403,16 @@ type LoadBalancerGetMetricsResponse struct {
 type LoadBalancerTimeSeriesVals struct {
 	Values []interface{} `json:"values"`
 }
+
+// LoadBalancerActionChangeDNSPtrRequest defines the schema for the request to
+// change a Load Balancer reverse DNS pointer.
+type LoadBalancerActionChangeDNSPtrRequest struct {
+	IP     string  `json:"ip"`
+	DNSPtr *string `json:"dns_ptr"`
+}
+
+// LoadBalancerActionChangeDNSPtrResponse defines the schema of the response when
+// creating a change_dns_ptr Floating IP action.
+type LoadBalancerActionChangeDNSPtrResponse struct {
+	Action Action `json:"action"`
+}


Test patch:
diff --git a/hcloud/load_balancer_test.go b/hcloud/load_balancer_test.go
index e414831..587b778 100644
--- a/hcloud/load_balancer_test.go
+++ b/hcloud/load_balancer_test.go
@@ -1249,3 +1249,72 @@ 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)
+		}
+	})
+}
diff --git a/hcloud/schema_test.go b/hcloud/schema_test.go
index adff95d..4f2fb3b 100644
--- a/hcloud/schema_test.go
+++ b/hcloud/schema_test.go
@@ -1292,10 +1292,12 @@ func TestLoadBalancerFromSchema(t *testing.T) {
 		"name": "Web Frontend",
 		"public_net": {
 			"ipv4": {
-				"ip": "131.232.99.1"
+				"ip": "131.232.99.1",
+				"dns_ptr": "example.org"
 			},
 			"ipv6": {
-				"ip": "2001:db8::1"
+				"ip": "2001:db8::1",
+				"dns_ptr": "example.com"
 			}
 		},
 		"private_net": [
@@ -1424,9 +1426,15 @@ func TestLoadBalancerFromSchema(t *testing.T) {
 	if loadBalancer.PublicNet.IPv4.IP.String() != "131.232.99.1" {
 		t.Errorf("unexpected IPv4: %v", loadBalancer.PublicNet.IPv4.IP)
 	}
+	if loadBalancer.PublicNet.IPv4.DNSPtr != "example.org" {
+		t.Errorf("unexpected IPv4.DNSPtr: %v", loadBalancer.PublicNet.IPv4.DNSPtr)
+	}
 	if loadBalancer.PublicNet.IPv6.IP.String() != "2001:db8::1" {
 		t.Errorf("unexpected IPv6: %v", loadBalancer.PublicNet.IPv6)
 	}
+	if loadBalancer.PublicNet.IPv6.DNSPtr != "example.com" {
+		t.Errorf("unexpected IPv6.DNSPtr: %v", loadBalancer.PublicNet.IPv6.DNSPtr)
+	}
 	if len(loadBalancer.PrivateNet) != 1 {
 		t.Errorf("unexpected length of PrivateNet: %v", len(loadBalancer.PrivateNet))
 	} else {