Repository: hetznercloud/hcloud-go
Pull request #26 (2018-01-10 09:20:36)

Implement iso client


Patch:
diff --git a/hcloud/client.go b/hcloud/client.go
index eb8d5a9..1bfc7a3 100644
--- a/hcloud/client.go
+++ b/hcloud/client.go
@@ -55,6 +55,7 @@ type Client struct {
 	Datacenter DatacenterClient
 	FloatingIP FloatingIPClient
 	Image      ImageClient
+	ISO        ISOClient
 	Location   LocationClient
 	Server     ServerClient
 	ServerType ServerTypeClient
@@ -101,6 +102,7 @@ func NewClient(options ...ClientOption) *Client {
 	client.Datacenter = DatacenterClient{client: client}
 	client.FloatingIP = FloatingIPClient{client: client}
 	client.Image = ImageClient{client: client}
+	client.ISO = ISOClient{client: client}
 	client.Location = LocationClient{client: client}
 	client.Server = ServerClient{client: client}
 	client.ServerType = ServerTypeClient{client: client}
diff --git a/hcloud/iso.go b/hcloud/iso.go
index ec6fa9c..73b2387 100644
--- a/hcloud/iso.go
+++ b/hcloud/iso.go
@@ -1,5 +1,14 @@
 package hcloud
 
+import (
+	"context"
+	"fmt"
+	"net/url"
+	"strconv"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
+)
+
 // ISO represents an ISO image in the Hetzner Cloud.
 type ISO struct {
 	ID          int
@@ -18,3 +27,102 @@ const (
 	// ISOTypePrivate is the type of a private ISO image.
 	ISOTypePrivate = "private"
 )
+
+// ISOClient is a client for the ISO API.
+type ISOClient struct {
+	client *Client
+}
+
+// GetByID retrieves an ISO by its ID.
+func (c *ISOClient) GetByID(ctx context.Context, id int) (*ISO, *Response, error) {
+	req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/isos/%d", id), nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var body schema.ISOGetResponse
+	resp, err := c.client.Do(req, &body)
+	if err != nil {
+		if IsError(err, ErrorCodeNotFound) {
+			return nil, resp, nil
+		}
+		return nil, resp, err
+	}
+	return ISOFromSchema(body.ISO), resp, nil
+}
+
+// GetByName retrieves an ISO by its name.
+func (c *ISOClient) GetByName(ctx context.Context, name string) (*ISO, *Response, error) {
+	path := "/isos?name=" + url.QueryEscape(name)
+	req, err := c.client.NewRequest(ctx, "GET", path, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var body schema.ISOListResponse
+	resp, err := c.client.Do(req, &body)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if len(body.ISOs) == 0 {
+		return nil, resp, nil
+	}
+	return ISOFromSchema(body.ISOs[0]), resp, nil
+}
+
+// Get retrieves an ISO by its ID if the input can be parsed as an integer, otherwise it retrieves an ISO by its name.
+func (c *ISOClient) Get(ctx context.Context, idOrName string) (*ISO, *Response, error) {
+	if id, err := strconv.Atoi(idOrName); err == nil {
+		return c.GetByID(ctx, int(id))
+	}
+	return c.GetByName(ctx, idOrName)
+}
+
+// ISOListOpts specifies options for listing isos.
+type ISOListOpts struct {
+	ListOpts
+}
+
+// List returns a list of ISOs for a specific page.
+func (c *ISOClient) List(ctx context.Context, opts ISOListOpts) ([]*ISO, *Response, error) {
+	path := "/isos?" + valuesForListOpts(opts.ListOpts).Encode()
+	req, err := c.client.NewRequest(ctx, "GET", path, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var body schema.ISOListResponse
+	resp, err := c.client.Do(req, &body)
+	if err != nil {
+		return nil, nil, err
+	}
+	isos := make([]*ISO, 0, len(body.ISOs))
+	for _, i := range body.ISOs {
+		isos = append(isos, ISOFromSchema(i))
+	}
+	return isos, resp, nil
+}
+
+// All returns all ISOs.
+func (c *ISOClient) All(ctx context.Context) ([]*ISO, error) {
+	allISOs := []*ISO{}
+
+	opts := ISOListOpts{}
+	opts.PerPage = 50
+
+	_, err := c.client.all(func(page int) (*Response, error) {
+		opts.Page = page
+		isos, resp, err := c.List(ctx, opts)
+		if err != nil {
+			return resp, err
+		}
+		allISOs = append(allISOs, isos...)
+		return resp, nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return allISOs, nil
+}
diff --git a/hcloud/schema/iso.go b/hcloud/schema/iso.go
index e217dcd..cdf8e96 100644
--- a/hcloud/schema/iso.go
+++ b/hcloud/schema/iso.go
@@ -7,3 +7,13 @@ type ISO struct {
 	Description string `json:"description"`
 	Type        string `json:"type"`
 }
+
+// ISOGetResponse defines the schema of the response when retrieving a single ISO.
+type ISOGetResponse struct {
+	ISO ISO `json:"iso"`
+}
+
+// ISOListResponse defines the schema of the response when listing ISOs.
+type ISOListResponse struct {
+	ISOs []ISO `json:"isos"`
+}


Test patch:
diff --git a/hcloud/iso_test.go b/hcloud/iso_test.go
new file mode 100644
index 0000000..d005c6e
--- /dev/null
+++ b/hcloud/iso_test.go
@@ -0,0 +1,212 @@
+package hcloud
+
+import (
+	"context"
+	"encoding/json"
+	"net/http"
+	"testing"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
+)
+
+func TestISOClient(t *testing.T) {
+	t.Run("GetByID", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/isos/1", func(w http.ResponseWriter, r *http.Request) {
+			json.NewEncoder(w).Encode(schema.ISOGetResponse{
+				ISO: schema.ISO{
+					ID: 1,
+				},
+			})
+		})
+
+		ctx := context.Background()
+		iso, _, err := env.Client.ISO.GetByID(ctx, 1)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if iso == nil {
+			t.Fatal("no iso")
+		}
+		if iso.ID != 1 {
+			t.Errorf("unexpected iso ID: %v", iso.ID)
+		}
+
+		t.Run("via Get", func(t *testing.T) {
+			iso, _, err := env.Client.ISO.Get(ctx, "1")
+			if err != nil {
+				t.Fatal(err)
+			}
+			if iso == nil {
+				t.Fatal("no iso")
+			}
+			if iso.ID != 1 {
+				t.Errorf("unexpected iso ID: %v", iso.ID)
+			}
+		})
+	})
+
+	t.Run("GetByID (not found)", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/isos/1", func(w http.ResponseWriter, r *http.Request) {
+			w.Header().Set("Content-Type", "application/json")
+			w.WriteHeader(http.StatusNotFound)
+			json.NewEncoder(w).Encode(schema.ErrorResponse{
+				Error: schema.Error{
+					Code: ErrorCodeNotFound,
+				},
+			})
+		})
+
+		ctx := context.Background()
+		iso, _, err := env.Client.ISO.GetByID(ctx, 1)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if iso != nil {
+			t.Fatal("expected no iso")
+		}
+	})
+
+	t.Run("GetByName", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/isos", func(w http.ResponseWriter, r *http.Request) {
+			if r.URL.RawQuery != "name=debian-9" {
+				t.Fatal("missing name query")
+			}
+			json.NewEncoder(w).Encode(schema.ISOListResponse{
+				ISOs: []schema.ISO{
+					{
+						ID: 1,
+					},
+				},
+			})
+		})
+
+		ctx := context.Background()
+		iso, _, err := env.Client.ISO.GetByName(ctx, "debian-9")
+		if err != nil {
+			t.Fatal(err)
+		}
+		if iso == nil {
+			t.Fatal("no iso")
+		}
+		if iso.ID != 1 {
+			t.Errorf("unexpected iso ID: %v", iso.ID)
+		}
+
+		t.Run("via Get", func(t *testing.T) {
+			iso, _, err := env.Client.ISO.Get(ctx, "debian-9")
+			if err != nil {
+				t.Fatal(err)
+			}
+			if iso == nil {
+				t.Fatal("no iso")
+			}
+			if iso.ID != 1 {
+				t.Errorf("unexpected iso ID: %v", iso.ID)
+			}
+		})
+	})
+
+	t.Run("GetByName (not found)", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/isos", func(w http.ResponseWriter, r *http.Request) {
+			if r.URL.RawQuery != "name=debian-9" {
+				t.Fatal("missing name query")
+			}
+			json.NewEncoder(w).Encode(schema.ISOListResponse{
+				ISOs: []schema.ISO{},
+			})
+		})
+
+		ctx := context.Background()
+		iso, _, err := env.Client.ISO.GetByName(ctx, "debian-9")
+		if err != nil {
+			t.Fatal(err)
+		}
+		if iso != nil {
+			t.Fatal("unexpected iso")
+		}
+	})
+
+	t.Run("List", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/isos", func(w http.ResponseWriter, r *http.Request) {
+			if page := r.URL.Query().Get("page"); page != "2" {
+				t.Errorf("expected page 2; got %q", page)
+			}
+			if perPage := r.URL.Query().Get("per_page"); perPage != "50" {
+				t.Errorf("expected per_page 50; got %q", perPage)
+			}
+			json.NewEncoder(w).Encode(schema.ISOListResponse{
+				ISOs: []schema.ISO{
+					{ID: 1},
+					{ID: 2},
+				},
+			})
+		})
+
+		opts := ISOListOpts{}
+		opts.Page = 2
+		opts.PerPage = 50
+
+		ctx := context.Background()
+		isos, _, err := env.Client.ISO.List(ctx, opts)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(isos) != 2 {
+			t.Fatal("expected 2 isos")
+		}
+	})
+
+	t.Run("All", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/isos", func(w http.ResponseWriter, r *http.Request) {
+			w.Header().Set("Content-Type", "application/json")
+			json.NewEncoder(w).Encode(struct {
+				ISOs []schema.ISO `json:"isos"`
+				Meta schema.Meta  `json:"meta"`
+			}{
+				ISOs: []schema.ISO{
+					{ID: 1},
+					{ID: 2},
+					{ID: 3},
+				},
+				Meta: schema.Meta{
+					Pagination: &schema.MetaPagination{
+						Page:         1,
+						LastPage:     1,
+						PerPage:      3,
+						TotalEntries: 3,
+					},
+				},
+			})
+		})
+
+		ctx := context.Background()
+		isos, err := env.Client.ISO.All(ctx)
+		if err != nil {
+			t.Fatalf("ISO.List failed: %s", err)
+		}
+		if len(isos) != 3 {
+			t.Fatalf("expected 3 isos; got %d", len(isos))
+		}
+		if isos[0].ID != 1 || isos[1].ID != 2 || isos[2].ID != 3 {
+			t.Errorf("unexpected isos")
+		}
+	})
+}