From e6c1296b8937733a9d4a5bfebdf94274b29802e4 Mon Sep 17 00:00:00 2001 From: Haardik Dharma <dharmahaardik08@gmail.com> Date: Thu, 13 Apr 2023 10:33:29 +0530 Subject: [PATCH] Kfcluster resource (#144) * Update json flag for lb Signed-off-by: Haardik Dharma <haardik@civo.com> * Fix formatting Signed-off-by: Haardik Dharma <haardik@civo.com> * Add KfCluster Resource --------- Signed-off-by: Haardik Dharma <haardik@civo.com> Co-authored-by: Haardik Dharma <haardik@civo.com> --- kfcluster.go | 146 ++++++++++++++++++++++++++++++++++++++++++++++ kfcluster_test.go | 108 ++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 kfcluster.go create mode 100644 kfcluster_test.go diff --git a/kfcluster.go b/kfcluster.go new file mode 100644 index 0000000..1967160 --- /dev/null +++ b/kfcluster.go @@ -0,0 +1,146 @@ +package civogo + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +// KfCluster represents a cluster with Kubeflow installed. +type KfCluster struct { + ID string `json:"id"` + Name string `json:"name,validate:required"` + NetworkID string `json:"network_id,validate:required"` + FirewallName string `json:"firewall_name"` + Size string `json:"size"` + KubeflowInstalled string `json:"kubeflow_installed"` + DashboardURL string `json:"dashboard_url"` + Namespace string `json:"-"` +} + +// CreateKfClusterReq is the request for creating a KfCluster. +type CreateKfClusterReq struct { + Name string `json:"name" validate:"required"` + NetworkID string `json:"network_id" validate:"required"` + FirewallID string `json:"firewall_id"` + Size string `json:"size"` //what sizes? +} + +// UpdateKfClusterReq is the request for updating a KfCluster. +type UpdateKfClusterReq struct { + Name string `json:"name"` + // Size string `json:"size"` +} + +// PaginatedKfClusters returns a paginated list of KfCluster object +type PaginatedKfClusters struct { + Page int `json:"page"` + PerPage int `json:"per_page"` + Pages int `json:"pages"` + Items []KfCluster `json:"items"` +} + +// ListKfClusters returns all applications in that specific region +func (c *Client) ListKfClusters() (*PaginatedKfClusters, error) { + resp, err := c.SendGetRequest("/v2/kfclusters") + if err != nil { + return nil, decodeError(err) + } + + kfc := &PaginatedKfClusters{} + if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&kfc); err != nil { + return nil, decodeError(err) + } + + return kfc, nil +} + +// GetKfCluster returns a kubeflow cluster by ID +func (c *Client) GetKfCluster(id string) (*KfCluster, error) { + resp, err := c.SendGetRequest(fmt.Sprintf("/v2/kfclusters/%s", id)) + if err != nil { + return nil, decodeError(err) + } + + kfc := &KfCluster{} + if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&kfc); err != nil { + return nil, decodeError(err) + } + + return kfc, nil +} + +// FindKfCluster finds a kubeflow cluster by either part of the ID or part of the name +func (c *Client) FindKfCluster(search string) (*KfCluster, error) { + kfClusters, err := c.ListKfClusters() + if err != nil { + return nil, decodeError(err) + } + + exactMatch := false + partialMatchesCount := 0 + result := KfCluster{} + + for _, value := range kfClusters.Items { + if value.Name == search || value.ID == search { + exactMatch = true + result = value + } else if strings.Contains(value.Name, search) || strings.Contains(value.ID, search) { + if !exactMatch { + result = value + partialMatchesCount++ + } + } + } + + if exactMatch || partialMatchesCount == 1 { + return &result, nil + } else if partialMatchesCount > 1 { + err := fmt.Errorf("unable to find %s because there were multiple matches", search) + return nil, MultipleMatchesError.wrap(err) + } else { + err := fmt.Errorf("unable to find %s, zero matches", search) + return nil, ZeroMatchesError.wrap(err) + } +} + +// CreateKfCluster creates a new kubeflow cluster +func (c *Client) CreateKfCluster(req CreateKfClusterReq) (*KfCluster, error) { + body, err := c.SendPostRequest("/v2/kfclusters", req) + if err != nil { + return nil, decodeError(err) + } + + var kfc KfCluster + if err := json.NewDecoder(bytes.NewReader(body)).Decode(&kfc); err != nil { + return nil, err + } + + return &kfc, nil +} + +// UpdateKfCluster updates a kubeflow cluster +func (c *Client) UpdateKfCluster(id string, kfc *UpdateKfClusterReq) (*KfCluster, error) { + body, err := c.SendPutRequest(fmt.Sprintf("/v2/kfclusters/%s", id), kfc) + if err != nil { + return nil, decodeError(err) + } + + updatedKfCluster := &KfCluster{} + if err := json.NewDecoder(bytes.NewReader(body)).Decode(updatedKfCluster); err != nil { + return nil, err + } + + return updatedKfCluster, nil +} + +// DeleteKfCluster deletes an application +func (c *Client) DeleteKfCluster(id string) (*SimpleResponse, error) { + resp, err := c.SendDeleteRequest(fmt.Sprintf("/v2/kfclusters/%s", id)) + if err != nil { + return nil, decodeError(err) + } + + return c.DecodeSimpleResponse(resp) +} diff --git a/kfcluster_test.go b/kfcluster_test.go new file mode 100644 index 0000000..4e3075c --- /dev/null +++ b/kfcluster_test.go @@ -0,0 +1,108 @@ +package civogo + +import ( + "reflect" + "testing" +) + +func TestListKfClusters(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/kfclusters": `{"page": 1, "per_page": 20, "pages": 2, "items":[{"id": "12345", "name": "test-kfcluster"}]}`, + }) + defer server.Close() + + got, err := client.ListKfClusters() + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := &PaginatedKfClusters{ + Page: 1, + PerPage: 20, + Pages: 2, + Items: []KfCluster{ + { + ID: "12345", + Name: "test-kfcluster", + }, + }, + } + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} + +func TestFindKfCluster(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/kfclusters": `{ + "page": 1, + "per_page": 20, + "pages": 1, + "items": [ + { + "id": "12345", + "name": "test-kfcluster" + } + ] + }`, + }) + defer server.Close() + + got, _ := client.FindKfCluster("test-kfcluster") + if got.ID != "12345" { + t.Errorf("Expected %s, got %s", "12345", got.ID) + } +} + +func TestCreateKfCluster(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/kfclusters": `{ + "id": "12345", + "name": "test-kfcluster", + "size": "g3.kf.small", + "network_id": "09090" + }`, + }) + defer server.Close() + + cfg := CreateKfClusterReq{ + Name: "test-kfcluster", + Size: "g3.kf.small", + NetworkID: "09090", + } + got, err := client.CreateKfCluster(cfg) + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := &KfCluster{ + ID: "12345", + Name: "test-kfcluster", + Size: "g3.kf.small", + NetworkID: "09090", + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} + +func TestDeleteKfCluster(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/kfclusters/12345": `{"result": "success"}`, + }) + defer server.Close() + + got, err := client.DeleteKfCluster("12345") + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := &SimpleResponse{Result: "success"} + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +}