Repository: hetznercloud/hcloud-go
Pull request #11 (2017-12-04 23:21:15)

Implement creating Floating IPs


Patch:
diff --git a/hcloud/floating_ip.go b/hcloud/floating_ip.go
index 3fa587d..784de82 100644
--- a/hcloud/floating_ip.go
+++ b/hcloud/floating_ip.go
@@ -1,7 +1,10 @@
 package hcloud
 
 import (
+	"bytes"
 	"context"
+	"encoding/json"
+	"errors"
 	"fmt"
 
 	"github.com/hetznercloud/hcloud-go/hcloud/schema"
@@ -121,3 +124,72 @@ func (c *FloatingIPClient) All(ctx context.Context) ([]*FloatingIP, error) {
 
 	return allFloatingIPs, nil
 }
+
+// FloatingIPCreateOpts specifies options for creating a Floating IP.
+type FloatingIPCreateOpts struct {
+	Type         FloatingIPType
+	HomeLocation *Location
+	Server       *Server
+	Description  *string
+}
+
+// Validate checks if options are valid.
+func (o FloatingIPCreateOpts) Validate() error {
+	switch o.Type {
+	case FloatingIPTypeIPv4, FloatingIPTypeIPv6:
+		break
+	default:
+		return errors.New("missing or invalid type")
+	}
+	if o.HomeLocation == nil && o.Server == nil {
+		return errors.New("one of home location or server is required")
+	}
+	return nil
+}
+
+// FloatingIPCreateResult is the result of creating a Floating IP.
+type FloatingIPCreateResult struct {
+	FloatingIP *FloatingIP
+	Action     *Action
+}
+
+// Create creates a Floating IP.
+func (c *FloatingIPClient) Create(ctx context.Context, opts FloatingIPCreateOpts) (FloatingIPCreateResult, *Response, error) {
+	if err := opts.Validate(); err != nil {
+		return FloatingIPCreateResult{}, nil, err
+	}
+
+	reqBody := schema.FloatingIPCreateRequest{
+		Type:        string(opts.Type),
+		Description: opts.Description,
+	}
+	if opts.HomeLocation != nil {
+		reqBody.HomeLocation = String(opts.HomeLocation.Name)
+	}
+	if opts.Server != nil {
+		reqBody.Server = Int(opts.Server.ID)
+	}
+	reqBodyData, err := json.Marshal(reqBody)
+	if err != nil {
+		return FloatingIPCreateResult{}, nil, err
+	}
+
+	req, err := c.client.NewRequest(ctx, "POST", "/floating_ips", bytes.NewReader(reqBodyData))
+	if err != nil {
+		return FloatingIPCreateResult{}, nil, err
+	}
+
+	var respBody schema.FloatingIPCreateResponse
+	resp, err := c.client.Do(req, &respBody)
+	if err != nil {
+		return FloatingIPCreateResult{}, resp, err
+	}
+	var action *Action
+	if respBody.Action != nil {
+		action = ActionFromSchema(*respBody.Action)
+	}
+	return FloatingIPCreateResult{
+		FloatingIP: FloatingIPFromSchema(respBody.FloatingIP),
+		Action:     action,
+	}, resp, nil
+}
diff --git a/hcloud/helper.go b/hcloud/helper.go
new file mode 100644
index 0000000..4219ae8
--- /dev/null
+++ b/hcloud/helper.go
@@ -0,0 +1,7 @@
+package hcloud
+
+// String returns a pointer to the passed string s.
+func String(s string) *string { return &s }
+
+// Int returns a pointer to the passed integer i.
+func Int(i int) *int { return &i }
diff --git a/hcloud/schema/floating_ip.go b/hcloud/schema/floating_ip.go
index 6163e71..b77e24d 100644
--- a/hcloud/schema/floating_ip.go
+++ b/hcloud/schema/floating_ip.go
@@ -69,3 +69,19 @@ type FloatingIPGetResponse struct {
 type FloatingIPListResponse struct {
 	FloatingIPs []FloatingIP `json:"floating_ips"`
 }
+
+// FloatingIPCreateRequest defines the schema of the request to
+// create a Floating IP.
+type FloatingIPCreateRequest struct {
+	Type         string  `json:"type"`
+	HomeLocation *string `json:"home_location,omitempty"`
+	Server       *int    `json:"server,omitempty"`
+	Description  *string `json:"description,omitempty"`
+}
+
+// FloatingIPCreateResponse defines the schema of the response
+// when creating a Floating IP.
+type FloatingIPCreateResponse struct {
+	FloatingIP FloatingIP `json:"floating_ip"`
+	Action     *Action    `json:"action"`
+}


Test patch:
diff --git a/hcloud/floating_ip_test.go b/hcloud/floating_ip_test.go
index 95c5b72..7a2613a 100644
--- a/hcloud/floating_ip_test.go
+++ b/hcloud/floating_ip_test.go
@@ -114,3 +114,42 @@ func TestFloatingIPClientList(t *testing.T) {
 		t.Fatal("expected 2 Floating IPs")
 	}
 }
+
+func TestFloatingIPClientCreate(t *testing.T) {
+	env := newTestEnv()
+	defer env.Teardown()
+
+	env.Mux.HandleFunc("/floating_ips", func(w http.ResponseWriter, r *http.Request) {
+		if r.Method != "POST" {
+			t.Error("expected POST")
+		}
+		json.NewEncoder(w).Encode(schema.FloatingIPCreateResponse{
+			FloatingIP: schema.FloatingIP{
+				ID: 1,
+			},
+			Action: &schema.Action{
+				ID: 1,
+			},
+		})
+	})
+
+	opts := FloatingIPCreateOpts{
+		Type:         FloatingIPTypeIPv4,
+		Description:  String("test"),
+		HomeLocation: &Location{Name: "test"},
+		Server:       &Server{ID: 1},
+	}
+
+	ctx := context.Background()
+	result, _, err := env.Client.FloatingIP.Create(ctx, opts)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if result.FloatingIP.ID != 1 {
+		t.Errorf("unexpected Floating IP ID: %d", result.FloatingIP.ID)
+	}
+	if result.Action.ID != 1 {
+		t.Errorf("unexpected action ID: %d", result.Action.ID)
+	}
+}