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)
+	}
+}