From 441dcc0391cab9ff53124b96099b6e4acbd9d751 Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Mon, 2 Dec 2024 21:23:09 -0500 Subject: [PATCH 01/10] add submit button update --- frontend/package.json | 6 ++- frontend/src/components/cluster-create.tsx | 46 +++++++++++----------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 5692da3a..34e951e2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,7 +43,8 @@ "tss-react": "^4.8.4", "typescript": "5.0.3", "url-join": "^4.0.1", - "web-vitals": "^0.2.4" + "web-vitals": "^0.2.4", + "uuid": "^9.0.0" }, "overrides": { "svgo": "3.0.2", @@ -105,7 +106,8 @@ "react-inject-env": "^2.1.0", "react-test-renderer": "^18.2.0", "redux-mock-store": "^1.5.4", - "markdownlint-cli2": "^0.7.1" + "markdownlint-cli2": "^0.7.1", + "@types/uuid": "^9.0.0" }, "jest": { "moduleNameMapper": { diff --git a/frontend/src/components/cluster-create.tsx b/frontend/src/components/cluster-create.tsx index dc29c184..4d48f521 100644 --- a/frontend/src/components/cluster-create.tsx +++ b/frontend/src/components/cluster-create.tsx @@ -8,6 +8,7 @@ import TornjakApi from './tornjak-api-helpers'; import { clusterType } from '../data/data'; import { ToastContainer } from 'react-toastify'; import './style.css'; +import { v4 as uuidv4 } from 'uuid'; import { clusterTypeInfoFunc, serverSelectedFunc, @@ -232,13 +233,14 @@ class ClusterCreate extends Component { var cjtData = { cluster: { - name: this.state.clusterName, - platformType: this.state.clusterType, - domainName: this.state.clusterDomainName, - managedBy: this.state.clusterManagedBy, - agentsList: this.state.clusterAgentsList ? this.state.clusterAgentsList : [] + uid: uniqueID, + name: this.state.clusterName, + platformType: this.state.clusterType, + domainName: this.state.clusterDomainName, + managedBy: this.state.clusterManagedBy, + agentsList: this.state.clusterAgentsList ? this.state.clusterAgentsList : [] } - } + } let endpoint = this.getApiEntryCreateEndpoint() @@ -247,20 +249,20 @@ class ClusterCreate extends Component { } axios.post(endpoint, cjtData) - .then( - res => { - this.setState({ - message: "Request:" + JSON.stringify(cjtData, null, ' ') + "\n\nSuccess:" + JSON.stringify(res.data, null, ' '), - statusOK: "OK", - }) - } - ) - .catch(err => showResponseToast(err)) + .then( + res => { + this.setState({ + message: "Request:" + JSON.stringify(cjtData, null, ' ') + "\n\nSuccess:" + JSON.stringify(res.data, null, ' '), + statusOK: "OK", + }) + } + ) + .catch(err => showResponseToast(err)) //scroll to bottom of page after submission setTimeout(() => { - window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); + window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); }, 100); - } +} render() { const ClusterType = this.props.clusterTypeList; @@ -375,14 +377,14 @@ class ClusterCreate extends Component { hideCloseButton title="CLUSTER SUCCESSFULLY CREATED" subtitle={ -
-
-                          {this.state.message}
-                        
+
+
+                              {this.state.message}
+                          
+

Unique ID: {cjtData.cluster.uid}

} /> - } {(this.state.statusOK === "ERROR") && Date: Mon, 2 Dec 2024 22:18:36 -0500 Subject: [PATCH 02/10] add new sqlite.go on the agent side to fix this new features issue from the backend --- pkg/agent/db/sqlite.go | 65 ++++++++++++++++++---------------- pkg/agent/types/clusterinfo.go | 13 +++---- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/pkg/agent/db/sqlite.go b/pkg/agent/db/sqlite.go index 8b0cb6af..1917b907 100644 --- a/pkg/agent/db/sqlite.go +++ b/pkg/agent/db/sqlite.go @@ -7,6 +7,7 @@ import ( "strings" backoff "github.com/cenkalti/backoff/v4" + "github.com/google/uuid" _ "github.com/mattn/go-sqlite3" "github.com/pkg/errors" @@ -19,8 +20,8 @@ const ( (id INTEGER PRIMARY KEY AUTOINCREMENT, spiffeid TEXT, plugin TEXT, UNIQUE (spiffeid))` // cluster table with fields name, domainName, platformtype, managedby initClustersTable = `CREATE TABLE IF NOT EXISTS clusters - (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, created_at TEXT, - domain_name TEXT, platform_type TEXT, managed_by TEXT, UNIQUE (name))` + (id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT UNIQUE, name TEXT, created_at TEXT, + domain_name TEXT, platform_type TEXT, managed_by TEXT, UNIQUE (name))` // cluster - agent relation table specifying by clusterid and spiffeid // enforces uniqueness of spiffeid initClusterMemberTable = `CREATE TABLE IF NOT EXISTS cluster_memberships @@ -254,12 +255,12 @@ func (db *LocalSqliteDb) GetAgentsMetadata(req types.AgentMetadataRequest) (type // GetClusters outputs a list of ClusterInfo structs with information on currently registered clusters func (db *LocalSqliteDb) GetClusters() (types.ClusterInfoList, error) { // BEGIN transaction - cmd := `SELECT clusters.name, clusters.created_at, clusters.domain_name, clusters.managed_by, - clusters.platform_type, GROUP_CONCAT(agents.spiffeid) - FROM clusters - LEFT JOIN cluster_memberships ON clusters.id=cluster_memberships.cluster_id - LEFT JOIN agents ON cluster_memberships.agent_id=agents.id - GROUP BY clusters.name` + cmd := `SELECT clusters.uid, clusters.name, clusters.created_at, clusters.domain_name, clusters.managed_by, + clusters.platform_type, GROUP_CONCAT(agents.spiffeid) + FROM clusters + LEFT JOIN cluster_memberships ON clusters.id=cluster_memberships.cluster_id + LEFT JOIN agents ON cluster_memberships.agent_id=agents.id + GROUP BY clusters.name` rows, err := db.database.Query(cmd) if err != nil { @@ -268,6 +269,7 @@ func (db *LocalSqliteDb) GetClusters() (types.ClusterInfoList, error) { sinfos := []types.ClusterInfo{} var ( + uid string name string createdAt string domainName string @@ -277,16 +279,17 @@ func (db *LocalSqliteDb) GetClusters() (types.ClusterInfoList, error) { agentsList []string ) for rows.Next() { - if err = rows.Scan(&name, &createdAt, &domainName, &managedBy, &platformType, &agentsListConcatted); err != nil { + if err = rows.Scan(&uid, &name, &createdAt, &domainName, &managedBy, &platformType, &agentsListConcatted); err != nil { return types.ClusterInfoList{}, SQLError{cmd, err} } - if agentsListConcatted.Valid { // handle clusters with no assigned agents + if agentsListConcatted.Valid { agentsList = strings.Split(agentsListConcatted.String, ",") } else { agentsList = []string{} } sinfos = append(sinfos, types.ClusterInfo{ + UID: uid, Name: name, CreationTime: createdAt, DomainName: domainName, @@ -303,26 +306,28 @@ func (db *LocalSqliteDb) GetClusters() (types.ClusterInfoList, error) { // CreateClusterEntry takes in struct cinfo of type ClusterInfo. If a cluster with cinfo.Name already registered, returns error. func (db *LocalSqliteDb) createClusterEntryOp(cinfo types.ClusterInfo) error { - // BEGIN transaction - ctx := context.Background() - tx, err := db.database.BeginTx(ctx, nil) - if err != nil { - return errors.Errorf("Error initializing context: %v", err) - } - txHelper := getTornjakTxHelper(ctx, tx) - - // INSERT cluster metadata - err = txHelper.insertClusterMetadata(cinfo) - if err != nil { - return backoff.Permanent(txHelper.rollbackHandler(err)) - } - - // ADD agents to cluster - err = txHelper.addAgentBatchToCluster(cinfo.Name, cinfo.AgentsList) - if err != nil { - return backoff.Permanent(txHelper.rollbackHandler(err)) - } - return tx.Commit() + ctx := context.Background() + tx, err := db.database.BeginTx(ctx, nil) + if err != nil { + return errors.Errorf("Error initializing context: %v", err) + } + txHelper := getTornjakTxHelper(ctx, tx) + + // Generate UID + cinfo.UID = uuid.New().String() + + // INSERT cluster metadata with UID + err = txHelper.insertClusterMetadata(cinfo) + if err != nil { + return backoff.Permanent(txHelper.rollbackHandler(err)) + } + + // ADD agents to cluster + err = txHelper.addAgentBatchToCluster(cinfo.Name, cinfo.AgentsList) + if err != nil { + return backoff.Permanent(txHelper.rollbackHandler(err)) + } + return tx.Commit() } // EditClusterEntry takes in struct cinfo of type ClusterInfo. If cluster with cinfo.Name does not exist, throws error. diff --git a/pkg/agent/types/clusterinfo.go b/pkg/agent/types/clusterinfo.go index 07e5174f..8dd3a7c0 100644 --- a/pkg/agent/types/clusterinfo.go +++ b/pkg/agent/types/clusterinfo.go @@ -3,13 +3,14 @@ package types // ClusterInfo contains the meta-information about clusters // TODO include details field for extra info/tags in json format (probably a byte array) type ClusterInfo struct { + UID string `json:"uid"` Name string `json:"name"` - EditedName string `json:"editedName"` - CreationTime string `json:"creationTime"` - DomainName string `json:"domainName"` - ManagedBy string `json:"managedBy"` - PlatformType string `json:"platformType"` - AgentsList []string `json:"agentsList"` + EditedName string `json:"edited_name"` // Add this field for the edited cluster name + CreationTime string `json:"created_time"` + DomainName string `json:"domain_name"` + ManagedBy string `json:"managed_by"` + PlatformType string `json:"platform_type"` + AgentsList []string `json:"agents_list"` } type ClusterInput struct { From e93219b835311fc61bf46c53934dd0ae040a3a2e Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Mon, 2 Dec 2024 23:47:11 -0500 Subject: [PATCH 03/10] fixing manager sqlite part of backend. Created functions to pull data from backend to safely create uid creation for cluster --- pkg/manager/db/sqlite.go | 109 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/pkg/manager/db/sqlite.go b/pkg/manager/db/sqlite.go index f6c85e51..7f25f616 100644 --- a/pkg/manager/db/sqlite.go +++ b/pkg/manager/db/sqlite.go @@ -3,34 +3,69 @@ package db import ( "database/sql" + "github.com/google/uuid" _ "github.com/mattn/go-sqlite3" "github.com/pkg/errors" - "github.com/spiffe/tornjak/pkg/manager/types" ) // TO DO: Add DELETE servers option from the data base const ( - initServersTable = "CREATE TABLE IF NOT EXISTS servers (servername TEXT PRIMARY KEY, address TEXT, tls bool, mtls bool, ca varBinary, cert varBinary, key varBinary)" + initServersTable = "CREATE TABLE IF NOT EXISTS servers (servername TEXT PRIMARY KEY, address TEXT, tls bool, mtls bool, ca varBinary, cert varBinary, key varBinary)" + initClustersTable = `CREATE TABLE IF NOT EXISTS clusters + (id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT UNIQUE, name TEXT, domain_name TEXT, platform_type TEXT, managed_by TEXT, UNIQUE(name))` ) type LocalSqliteDb struct { database *sql.DB } +func BackfillClusterUIDs(db *sql.DB) error { + rows, err := db.Query("SELECT id FROM clusters WHERE uid IS NULL") + if err != nil { + return err + } + defer rows.Close() + + updateCmd := `UPDATE clusters SET uid = ? WHERE id = ?` + for rows.Next() { + var id int + if err := rows.Scan(&id); err != nil { + return err + } + uid := uuid.New().String() + _, err := db.Exec(updateCmd, uid, id) + if err != nil { + return err + } + } + return nil +} + func NewLocalSqliteDB(dbpath string) (ManagerDB, error) { database, err := sql.Open("sqlite3", dbpath) if err != nil { return nil, errors.New("Unable to open connection to DB") } - // Table for servers - statement, err := database.Prepare(initServersTable) - if err != nil { - return nil, errors.Errorf("Unable to execute SQL query :%v", initServersTable) + + // Initialize tables + initTableList := []string{initServersTable, initClustersTable} + + for _, cmd := range initTableList { + statement, err := database.Prepare(cmd) + if err != nil { + return nil, errors.Errorf("Unable to execute SQL query: %v", cmd) + } + _, err = statement.Exec() + if err != nil { + return nil, errors.Errorf("Unable to execute SQL query: %v", cmd) + } } - _, err = statement.Exec() + + // Backfill UID for existing clusters + err = BackfillClusterUIDs(database) if err != nil { - return nil, errors.Errorf("Unable to execute SQL query :%v", initServersTable) + return nil, errors.Errorf("Failed to backfill cluster UIDs: %v", err) } return &LocalSqliteDb{ @@ -38,6 +73,64 @@ func NewLocalSqliteDB(dbpath string) (ManagerDB, error) { }, nil } +func (db *LocalSqliteDb) CreateClusterEntry(cinfo types.ClusterInfo) error { + statement, err := db.database.Prepare("INSERT INTO clusters (uid, name, domain_name, platform_type, managed_by) VALUES (?, ?, ?, ?, ?)") + if err != nil { + return errors.Errorf("Unable to prepare SQL query: %v", err) + } + + _, err = statement.Exec(cinfo.UID, cinfo.Name, cinfo.DomainName, cinfo.PlatformType, cinfo.ManagedBy) + if err != nil { + return errors.Errorf("Unable to execute SQL query: %v", err) + } + return nil +} + +func (db *LocalSqliteDb) GetClusters() ([]types.ClusterInfo, error) { + rows, err := db.database.Query("SELECT uid, name, domain_name, platform_type, managed_by FROM clusters") + if err != nil { + return nil, errors.New("Unable to execute SQL query") + } + + clusters := []types.ClusterInfo{} + var ( + uid string + name string + domainName string + platformType string + managedBy string + ) + for rows.Next() { + if err = rows.Scan(&uid, &name, &domainName, &platformType, &managedBy); err != nil { + return nil, err + } + + clusters = append(clusters, types.ClusterInfo{ + UID: uid, + Name: name, + DomainName: domainName, + PlatformType: platformType, + ManagedBy: managedBy, + }) + } + + return clusters, nil +} + +func (db *LocalSqliteDb) GetClusterByUID(uid string) (types.ClusterInfo, error) { + row := db.database.QueryRow("SELECT uid, name, domain_name, platform_type, managed_by FROM clusters WHERE uid=?", uid) + + cinfo := types.ClusterInfo{} + err := row.Scan(&cinfo.UID, &cinfo.Name, &cinfo.DomainName, &cinfo.PlatformType, &cinfo.ManagedBy) + if err == sql.ErrNoRows { + return types.ClusterInfo{}, errors.New("Cluster not found") + } else if err != nil { + return types.ClusterInfo{}, err + } + + return cinfo, nil +} + func (db *LocalSqliteDb) CreateServerEntry(sinfo types.ServerInfo) error { statement, err := db.database.Prepare("INSERT INTO servers (servername, address, tls, mtls, ca, cert, key) VALUES (?,?,?,?,?,?,?)") if err != nil { From a1f947514bcefb7553969853202a49d627af0907 Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Tue, 3 Dec 2024 10:11:29 -0500 Subject: [PATCH 04/10] un-needed functions and reorder of imputs --- pkg/agent/db/sqlite_test.go | 5 +- pkg/manager/db/sqlite.go | 114 ++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 59 deletions(-) diff --git a/pkg/agent/db/sqlite_test.go b/pkg/agent/db/sqlite_test.go index 400d9fcc..c1bd15f3 100644 --- a/pkg/agent/db/sqlite_test.go +++ b/pkg/agent/db/sqlite_test.go @@ -2,11 +2,12 @@ package db import ( "fmt" - "github.com/pkg/errors" "os" "testing" "time" + "github.com/pkg/errors" + backoff "github.com/cenkalti/backoff/v4" "github.com/spiffe/tornjak/pkg/agent/types" @@ -282,7 +283,7 @@ func TestClusterCreate(t *testing.T) { } // ATTEMPT Create with no conflicting agent assignment [CreateClusterEntry, GetClusters] - err = db.CreateClusterEntry(cinfo3) + err = db.(cinfo3) if err != nil { t.Fatal(err) } diff --git a/pkg/manager/db/sqlite.go b/pkg/manager/db/sqlite.go index 7f25f616..111cdfa6 100644 --- a/pkg/manager/db/sqlite.go +++ b/pkg/manager/db/sqlite.go @@ -73,63 +73,63 @@ func NewLocalSqliteDB(dbpath string) (ManagerDB, error) { }, nil } -func (db *LocalSqliteDb) CreateClusterEntry(cinfo types.ClusterInfo) error { - statement, err := db.database.Prepare("INSERT INTO clusters (uid, name, domain_name, platform_type, managed_by) VALUES (?, ?, ?, ?, ?)") - if err != nil { - return errors.Errorf("Unable to prepare SQL query: %v", err) - } - - _, err = statement.Exec(cinfo.UID, cinfo.Name, cinfo.DomainName, cinfo.PlatformType, cinfo.ManagedBy) - if err != nil { - return errors.Errorf("Unable to execute SQL query: %v", err) - } - return nil -} - -func (db *LocalSqliteDb) GetClusters() ([]types.ClusterInfo, error) { - rows, err := db.database.Query("SELECT uid, name, domain_name, platform_type, managed_by FROM clusters") - if err != nil { - return nil, errors.New("Unable to execute SQL query") - } - - clusters := []types.ClusterInfo{} - var ( - uid string - name string - domainName string - platformType string - managedBy string - ) - for rows.Next() { - if err = rows.Scan(&uid, &name, &domainName, &platformType, &managedBy); err != nil { - return nil, err - } - - clusters = append(clusters, types.ClusterInfo{ - UID: uid, - Name: name, - DomainName: domainName, - PlatformType: platformType, - ManagedBy: managedBy, - }) - } - - return clusters, nil -} - -func (db *LocalSqliteDb) GetClusterByUID(uid string) (types.ClusterInfo, error) { - row := db.database.QueryRow("SELECT uid, name, domain_name, platform_type, managed_by FROM clusters WHERE uid=?", uid) - - cinfo := types.ClusterInfo{} - err := row.Scan(&cinfo.UID, &cinfo.Name, &cinfo.DomainName, &cinfo.PlatformType, &cinfo.ManagedBy) - if err == sql.ErrNoRows { - return types.ClusterInfo{}, errors.New("Cluster not found") - } else if err != nil { - return types.ClusterInfo{}, err - } - - return cinfo, nil -} +// func (db *LocalSqliteDb) CreateClusterEntry(cinfo types.ClusterInfo) error { +// statement, err := db.database.Prepare("INSERT INTO clusters (uid, name, domain_name, platform_type, managed_by) VALUES (?, ?, ?, ?, ?)") +// if err != nil { +// return errors.Errorf("Unable to prepare SQL query: %v", err) +// } + +// _, err = statement.Exec(cinfo.UID, cinfo.Name, cinfo.DomainName, cinfo.PlatformType, cinfo.ManagedBy) +// if err != nil { +// return errors.Errorf("Unable to execute SQL query: %v", err) +// } +// return nil +// } + +// func (db *LocalSqliteDb) GetClusters() ([]types.ClusterInfo, error) { +// rows, err := db.database.Query("SELECT uid, name, domain_name, platform_type, managed_by FROM clusters") +// if err != nil { +// return nil, errors.New("Unable to execute SQL query") +// } + +// clusters := []types.ClusterInfo{} +// var ( +// uid string +// name string +// domainName string +// platformType string +// managedBy string +// ) +// for rows.Next() { +// if err = rows.Scan(&uid, &name, &domainName, &platformType, &managedBy); err != nil { +// return nil, err +// } + +// clusters = append(clusters, types.ClusterInfo{ +// UID: uid, +// Name: name, +// DomainName: domainName, +// PlatformType: platformType, +// ManagedBy: managedBy, +// }) +// } + +// return clusters, nil +// } + +// func (db *LocalSqliteDb) GetClusterByUID(uid string) (types.ClusterInfo, error) { +// row := db.database.QueryRow("SELECT uid, name, domain_name, platform_type, managed_by FROM clusters WHERE uid=?", uid) + +// cinfo := types.ClusterInfo{} +// err := row.Scan(&cinfo.UID, &cinfo.Name, &cinfo.DomainName, &cinfo.PlatformType, &cinfo.ManagedBy) +// if err == sql.ErrNoRows { +// return types.ClusterInfo{}, errors.New("Cluster not found") +// } else if err != nil { +// return types.ClusterInfo{}, err +// } + +// return cinfo, nil +// } func (db *LocalSqliteDb) CreateServerEntry(sinfo types.ServerInfo) error { statement, err := db.database.Prepare("INSERT INTO servers (servername, address, tls, mtls, ca, cert, key) VALUES (?,?,?,?,?,?,?)") From ba707d5f5dbd5218655d5dae854ce29f84d461d7 Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Tue, 3 Dec 2024 13:13:08 -0500 Subject: [PATCH 05/10] fix inputs to handle this uid update --- api/agent/handlers.go | 2 ++ api/agent/server.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/agent/handlers.go b/api/agent/handlers.go index d086abd7..641faec0 100644 --- a/api/agent/handlers.go +++ b/api/agent/handlers.go @@ -7,7 +7,9 @@ import ( "net/http" "strings" + "github.com/google/uuid" trustdomain "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" + "github.com/spiffe/tornjak/pkg/agent/types" "google.golang.org/protobuf/encoding/protojson" ) diff --git a/api/agent/server.go b/api/agent/server.go index 27de0e54..402128d9 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -1,10 +1,10 @@ package api - import ( "crypto/tls" "encoding/json" "fmt" "io" + "github.com/google/uuid" "log" "net" "net/http" From afb638c4c520fde57dc805c4069b79e762687ff2 Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Wed, 4 Dec 2024 12:31:35 -0500 Subject: [PATCH 06/10] update structure --- pkg/agent/db/sqlite.go | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/pkg/agent/db/sqlite.go b/pkg/agent/db/sqlite.go index 1917b907..d8e7b3ed 100644 --- a/pkg/agent/db/sqlite.go +++ b/pkg/agent/db/sqlite.go @@ -306,28 +306,28 @@ func (db *LocalSqliteDb) GetClusters() (types.ClusterInfoList, error) { // CreateClusterEntry takes in struct cinfo of type ClusterInfo. If a cluster with cinfo.Name already registered, returns error. func (db *LocalSqliteDb) createClusterEntryOp(cinfo types.ClusterInfo) error { - ctx := context.Background() - tx, err := db.database.BeginTx(ctx, nil) - if err != nil { - return errors.Errorf("Error initializing context: %v", err) - } - txHelper := getTornjakTxHelper(ctx, tx) - - // Generate UID - cinfo.UID = uuid.New().String() - - // INSERT cluster metadata with UID - err = txHelper.insertClusterMetadata(cinfo) - if err != nil { - return backoff.Permanent(txHelper.rollbackHandler(err)) - } - - // ADD agents to cluster - err = txHelper.addAgentBatchToCluster(cinfo.Name, cinfo.AgentsList) - if err != nil { - return backoff.Permanent(txHelper.rollbackHandler(err)) - } - return tx.Commit() + ctx := context.Background() + tx, err := db.database.BeginTx(ctx, nil) + if err != nil { + return errors.Errorf("Error initializing context: %v", err) + } + txHelper := getTornjakTxHelper(ctx, tx) + + // Generate UID + cinfo.UID = uuid.New().String() + + // INSERT cluster metadata with UID + err = txHelper.insertClusterMetadata(cinfo) + if err != nil { + return backoff.Permanent(txHelper.rollbackHandler(err)) + } + + // ADD agents to cluster + err = txHelper.addAgentBatchToCluster(cinfo.Name, cinfo.AgentsList) + if err != nil { + return backoff.Permanent(txHelper.rollbackHandler(err)) + } + return tx.Commit() } // EditClusterEntry takes in struct cinfo of type ClusterInfo. If cluster with cinfo.Name does not exist, throws error. From 262cc3e183b69c54ad0a69a0336f36491650d6c0 Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Fri, 6 Dec 2024 19:35:02 -0500 Subject: [PATCH 07/10] add a recive for uid decryption. and fixed funcs --- api/agent/handlers.go | 66 +++++++++++++++++---------------------- api/agent/server.go | 7 +++-- api/agent/tornjak_apis.go | 27 +++++++++++++++- api/manager/server.go | 4 +-- 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/api/agent/handlers.go b/api/agent/handlers.go index 641faec0..09b45e65 100644 --- a/api/agent/handlers.go +++ b/api/agent/handlers.go @@ -9,7 +9,6 @@ import ( "github.com/google/uuid" trustdomain "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" - "github.com/spiffe/tornjak/pkg/agent/types" "google.golang.org/protobuf/encoding/protojson" ) @@ -915,106 +914,97 @@ func (s *Server) clusterCreate(w http.ResponseWriter, r *http.Request) { buf := new(strings.Builder) n, err := io.Copy(buf, r.Body) if err != nil { - emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error parsing data: %v", err.Error()), http.StatusBadRequest) return } data := buf.String() + var input RegisterClusterRequest if n == 0 { input = RegisterClusterRequest{} } else { err := json.Unmarshal([]byte(data), &input) if err != nil { - emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error parsing data: %v", err.Error()), http.StatusBadRequest) return } } + + input.UID = uuid.New().String() + err = s.DefineCluster(input) if err != nil { - emsg := fmt.Sprintf("Error: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error: %v", err.Error()), http.StatusBadRequest) return } cors(w, r) - _, err = w.Write([]byte("SUCCESS")) - if err != nil { - emsg := fmt.Sprintf("Error: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) - return - } + json.NewEncoder(w).Encode(map[string]string{"uid": input.UID, "message": "Cluster created successfully"}) } func (s *Server) clusterEdit(w http.ResponseWriter, r *http.Request) { buf := new(strings.Builder) n, err := io.Copy(buf, r.Body) if err != nil { - emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error parsing data: %v", err.Error()), http.StatusBadRequest) return } data := buf.String() + var input EditClusterRequest if n == 0 { input = EditClusterRequest{} } else { err := json.Unmarshal([]byte(data), &input) if err != nil { - emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error parsing data: %v", err.Error()), http.StatusBadRequest) return } } - err = s.EditCluster(input) + + // Check that the UID exists in the database + _, err = s.Db.GetClusterByUID(input.UID) if err != nil { - emsg := fmt.Sprintf("Error: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error: Cluster not found: %v", err.Error()), http.StatusBadRequest) return } - cors(w, r) - _, err = w.Write([]byte("SUCCESS")) + + err = s.EditCluster(input) if err != nil { - emsg := fmt.Sprintf("Error: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error: %v", err.Error()), http.StatusBadRequest) return } + cors(w, r) + w.Write([]byte("SUCCESS")) } func (s *Server) clusterDelete(w http.ResponseWriter, r *http.Request) { buf := new(strings.Builder) n, err := io.Copy(buf, r.Body) if err != nil { - emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error parsing data: %v", err.Error()), http.StatusBadRequest) return } data := buf.String() + var input DeleteClusterRequest if n == 0 { input = DeleteClusterRequest{} } else { err := json.Unmarshal([]byte(data), &input) if err != nil { - emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error parsing data: %v", err.Error()), http.StatusBadRequest) return } } - err = s.DeleteCluster(input) + + // Delete by UID + err = s.Db.DeleteClusterEntry(input.UID) if err != nil { - emsg := fmt.Sprintf("Error: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) + retError(w, fmt.Sprintf("Error: %v", err.Error()), http.StatusBadRequest) return } cors(w, r) - _, err = w.Write([]byte("SUCCESS")) - if err != nil { - emsg := fmt.Sprintf("Error: %v", err.Error()) - retError(w, emsg, http.StatusBadRequest) - return - } - + w.Write([]byte("SUCCESS")) } /********* END CLUSTER *********/ diff --git a/api/agent/server.go b/api/agent/server.go index 402128d9..ca1c5a8e 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -1,10 +1,10 @@ package api + import ( "crypto/tls" "encoding/json" "fmt" "io" - "github.com/google/uuid" "log" "net" "net/http" @@ -19,6 +19,7 @@ import ( "github.com/spiffe/tornjak/pkg/agent/authorization" agentdb "github.com/spiffe/tornjak/pkg/agent/db" "github.com/spiffe/tornjak/pkg/agent/spirecrd" + "github.com/spiffe/tornjak/pkg/agent/types" ) type Server struct { @@ -198,14 +199,14 @@ func (s *Server) GetRouter() http.Handler { apiRtr.HandleFunc("/api/v1/spire/entries", s.entryList).Methods(http.MethodGet, http.MethodOptions) apiRtr.HandleFunc("/api/v1/spire/entries", s.entryCreate).Methods(http.MethodPost) apiRtr.HandleFunc("/api/v1/spire/entries", s.entryDelete).Methods(http.MethodDelete) - + // SPIRE server bundles apiRtr.HandleFunc("/api/v1/spire/bundle", s.bundleGet).Methods(http.MethodGet, http.MethodOptions) apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleList).Methods(http.MethodGet, http.MethodOptions) apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleCreate).Methods(http.MethodPost) apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleUpdate).Methods(http.MethodPatch) apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleDelete).Methods(http.MethodDelete) - + // SPIRE server federations apiRtr.HandleFunc("/api/v1/spire/federations", s.federationList).Methods(http.MethodGet, http.MethodOptions) apiRtr.HandleFunc("/api/v1/spire/federations", s.federationCreate).Methods(http.MethodPost) diff --git a/api/agent/tornjak_apis.go b/api/agent/tornjak_apis.go index 16392ec8..dd827308 100644 --- a/api/agent/tornjak_apis.go +++ b/api/agent/tornjak_apis.go @@ -3,6 +3,7 @@ package api import ( "errors" + "github.com/google/uuid" tornjakTypes "github.com/spiffe/tornjak/pkg/agent/types" ) @@ -86,8 +87,11 @@ func (s *Server) ListClusters(inp ListClustersRequest) (*ListClustersResponse, e type RegisterClusterRequest tornjakTypes.ClusterInput // DefineCluster registers cluster to local DB +// DefineCluster registers a cluster to the local DB func (s *Server) DefineCluster(inp RegisterClusterRequest) error { cinfo := tornjakTypes.ClusterInfo(inp.ClusterInstance) + + // Validation for mandatory fields if len(cinfo.Name) == 0 { return errors.New("cluster definition missing mandatory field - Name") } else if len(cinfo.PlatformType) == 0 { @@ -95,14 +99,21 @@ func (s *Server) DefineCluster(inp RegisterClusterRequest) error { } else if len(cinfo.EditedName) > 0 { return errors.New("cluster definition attempts renaming on create cluster - EditedName") } + + // Generate UID for the cluster + cinfo.UID = uuid.New().String() + return s.Db.CreateClusterEntry(cinfo) } type EditClusterRequest tornjakTypes.ClusterInput // EditCluster registers cluster to local DB +// EditCluster registers updates to a cluster in the local DB func (s *Server) EditCluster(inp EditClusterRequest) error { cinfo := tornjakTypes.ClusterInfo(inp.ClusterInstance) + + // Validation for mandatory fields if len(cinfo.Name) == 0 { return errors.New("cluster definition missing mandatory field - Name") } else if len(cinfo.PlatformType) == 0 { @@ -110,7 +121,21 @@ func (s *Server) EditCluster(inp EditClusterRequest) error { } else if len(cinfo.EditedName) == 0 { return errors.New("cluster definition missing mandatory field - EditedName") } - return s.Db.EditClusterEntry(cinfo) + + // Retrieve existing cluster by UID to ensure it exists + existingCluster, err := s.Db.GetClusterByUID(cinfo.UID) + if err != nil { + return errors.New("cluster not found in database") + } + + // Update the cluster fields + existingCluster.Name = cinfo.Name + existingCluster.PlatformType = cinfo.PlatformType + existingCluster.ManagedBy = cinfo.ManagedBy + existingCluster.DomainName = cinfo.DomainName + existingCluster.AgentsList = cinfo.AgentsList + + return s.Db.EditClusterEntry(existingCluster) } type DeleteClusterRequest tornjakTypes.ClusterInput diff --git a/api/manager/server.go b/api/manager/server.go index 8d22fdd1..cf853a16 100644 --- a/api/manager/server.go +++ b/api/manager/server.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/gorilla/mux" + "github.com/spiffe/tornjak/pkg/agent/types" managerdb "github.com/spiffe/tornjak/pkg/manager/db" ) @@ -108,14 +109,13 @@ func (s *Server) apiServerProxyFunc(apiPath string, apiMethod string) func(w htt return } - req, err := http.NewRequest(apiMethod, strings.TrimSuffix(sinfo.Address, "/") + apiPath, r.Body) + req, err := http.NewRequest(apiMethod, strings.TrimSuffix(sinfo.Address, "/")+apiPath, r.Body) if err != nil { emsg := fmt.Sprintf("Error creating http request: %v", err.Error()) retError(w, emsg, http.StatusBadRequest) return } - resp, err := client.Do(req) if err != nil { emsg := fmt.Sprintf("Error making api call to server: %v", err.Error()) From 0e8a449b834f4c763fdc1dbf7acecca78e3bfdbf Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Fri, 6 Dec 2024 19:45:53 -0500 Subject: [PATCH 08/10] add get cluster from uid function to decrypt --- api/agent/server.go | 1 - api/manager/server.go | 1 - pkg/agent/db/db.go | 1 + pkg/agent/db/sqlite.go | 15 +++++++++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index ca1c5a8e..80e53664 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -19,7 +19,6 @@ import ( "github.com/spiffe/tornjak/pkg/agent/authorization" agentdb "github.com/spiffe/tornjak/pkg/agent/db" "github.com/spiffe/tornjak/pkg/agent/spirecrd" - "github.com/spiffe/tornjak/pkg/agent/types" ) type Server struct { diff --git a/api/manager/server.go b/api/manager/server.go index cf853a16..209350f6 100644 --- a/api/manager/server.go +++ b/api/manager/server.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/gorilla/mux" - "github.com/spiffe/tornjak/pkg/agent/types" managerdb "github.com/spiffe/tornjak/pkg/manager/db" ) diff --git a/pkg/agent/db/db.go b/pkg/agent/db/db.go index 8186e6be..5770651b 100644 --- a/pkg/agent/db/db.go +++ b/pkg/agent/db/db.go @@ -13,6 +13,7 @@ type AgentDB interface { // CLUSTER interface GetClusters() (types.ClusterInfoList, error) CreateClusterEntry(cinfo types.ClusterInfo) error + GetClusterByUID(uid string) (types.ClusterInfo, error) EditClusterEntry(cinfo types.ClusterInfo) error DeleteClusterEntry(name string) error diff --git a/pkg/agent/db/sqlite.go b/pkg/agent/db/sqlite.go index d8e7b3ed..b5aac48b 100644 --- a/pkg/agent/db/sqlite.go +++ b/pkg/agent/db/sqlite.go @@ -170,6 +170,21 @@ func (db *LocalSqliteDb) GetClusterAgents(name string) ([]string, error) { } +// GetClusterByUID takes in a UID string and outputs the cluster +func (db *LocalSqliteDb) GetClusterByUID(uid string) (types.ClusterInfo, error) { + row := db.database.QueryRow("SELECT uid, name, domain_name, platform_type, managed_by FROM clusters WHERE uid=?", uid) + + cinfo := types.ClusterInfo{} + err := row.Scan(&cinfo.UID, &cinfo.Name, &cinfo.DomainName, &cinfo.PlatformType, &cinfo.ManagedBy) + if err == sql.ErrNoRows { + return types.ClusterInfo{}, errors.New("Cluster not found") + } else if err != nil { + return types.ClusterInfo{}, err + } + + return cinfo, nil +} + // GetAgentClusterName takes in string of spiffeid of agent and outputs the name of the cluster func (db *LocalSqliteDb) GetAgentClusterName(spiffeid string) (string, error) { var clusterName sql.NullString From 183bf10c55cff81c29d7b1fb73bf22151698340f Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Fri, 6 Dec 2024 19:59:27 -0500 Subject: [PATCH 09/10] adding and debugging for backfillClusterUID --- pkg/agent/db/sqlite.go | 43 ++++++++++++++++++++++++++++++++++++++-- pkg/manager/db/sqlite.go | 23 ++++++++++++++++----- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/pkg/agent/db/sqlite.go b/pkg/agent/db/sqlite.go index b5aac48b..0da94257 100644 --- a/pkg/agent/db/sqlite.go +++ b/pkg/agent/db/sqlite.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "log" "strings" backoff "github.com/cenkalti/backoff/v4" @@ -62,12 +63,52 @@ func NewLocalSqliteDB(driverName string, dbpath string, backOffParams backoff.Ba } } + // Backfill UID for existing clusters + err = BackfillClusterUIDs(database) + if err != nil { + return nil, errors.Errorf("Failed to backfill cluster UIDs: %v", err) + } + return &LocalSqliteDb{ database: database, expBackoff: &backOffParams, }, nil } +func BackfillClusterUIDs(db *sql.DB) error { + rows, err := db.Query("SELECT id FROM clusters WHERE uid IS NULL OR uid = ''") + if err != nil { + log.Printf("Error querying clusters with NULL UID: %v", err) + return fmt.Errorf("failed to query clusters with NULL UID: %w", err) + } + defer rows.Close() + + updateCmd := `UPDATE clusters SET uid = ? WHERE id = ?` + for rows.Next() { + var id int + if err := rows.Scan(&id); err != nil { + log.Printf("Error scanning cluster ID: %v", err) + return fmt.Errorf("failed to scan cluster ID: %w", err) + } + + uid := uuid.New().String() + _, err := db.Exec(updateCmd, uid, id) + if err != nil { + log.Printf("Error updating cluster UID for ID %d: %v", id, err) + return fmt.Errorf("failed to update cluster UID for id %d: %w", id, err) + } + log.Printf("Successfully updated cluster ID %d with UID %s", id, uid) + } + + if err := rows.Err(); err != nil { + log.Printf("Error iterating over rows: %v", err) + return fmt.Errorf("error iterating over rows: %w", err) + } + + log.Printf("BackfillClusterUIDs completed successfully") + return nil +} + // AGENT - SELECTOR/PLUGIN HANDLERS func (db *LocalSqliteDb) CreateAgentEntry(sinfo types.AgentInfo) error { @@ -328,10 +369,8 @@ func (db *LocalSqliteDb) createClusterEntryOp(cinfo types.ClusterInfo) error { } txHelper := getTornjakTxHelper(ctx, tx) - // Generate UID cinfo.UID = uuid.New().String() - // INSERT cluster metadata with UID err = txHelper.insertClusterMetadata(cinfo) if err != nil { return backoff.Permanent(txHelper.rollbackHandler(err)) diff --git a/pkg/manager/db/sqlite.go b/pkg/manager/db/sqlite.go index 111cdfa6..7fe36077 100644 --- a/pkg/manager/db/sqlite.go +++ b/pkg/manager/db/sqlite.go @@ -2,6 +2,8 @@ package db import ( "database/sql" + "fmt" + "log" "github.com/google/uuid" _ "github.com/mattn/go-sqlite3" @@ -21,9 +23,10 @@ type LocalSqliteDb struct { } func BackfillClusterUIDs(db *sql.DB) error { - rows, err := db.Query("SELECT id FROM clusters WHERE uid IS NULL") + rows, err := db.Query("SELECT id FROM clusters WHERE uid IS NULL OR uid = ''") if err != nil { - return err + log.Printf("Error querying clusters with NULL UID: %v", err) + return fmt.Errorf("failed to query clusters with NULL UID: %w", err) } defer rows.Close() @@ -31,17 +34,27 @@ func BackfillClusterUIDs(db *sql.DB) error { for rows.Next() { var id int if err := rows.Scan(&id); err != nil { - return err + log.Printf("Error scanning cluster ID: %v", err) + return fmt.Errorf("failed to scan cluster ID: %w", err) } + uid := uuid.New().String() _, err := db.Exec(updateCmd, uid, id) if err != nil { - return err + log.Printf("Error updating cluster UID for ID %d: %v", id, err) + return fmt.Errorf("failed to update cluster UID for id %d: %w", id, err) } + log.Printf("Successfully updated cluster ID %d with UID %s", id, uid) + } + + if err := rows.Err(); err != nil { + log.Printf("Error iterating over rows: %v", err) + return fmt.Errorf("error iterating over rows: %w", err) } + + log.Printf("BackfillClusterUIDs completed successfully") return nil } - func NewLocalSqliteDB(dbpath string) (ManagerDB, error) { database, err := sql.Open("sqlite3", dbpath) if err != nil { From 3443c9f41d952ac3c6e4bd5841f0c324bfa69b31 Mon Sep 17 00:00:00 2001 From: Arnav Pawar Date: Fri, 6 Dec 2024 20:41:02 -0500 Subject: [PATCH 10/10] fix genuid and fix editing functions --- api/agent/tornjak_apis.go | 6 ------ api/agent/types.go | 27 ++++++++++++++++++++++----- pkg/agent/db/sqlite_test.go | 20 +++++++++++++++++++- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/api/agent/tornjak_apis.go b/api/agent/tornjak_apis.go index dd827308..c8a82047 100644 --- a/api/agent/tornjak_apis.go +++ b/api/agent/tornjak_apis.go @@ -84,8 +84,6 @@ func (s *Server) ListClusters(inp ListClustersRequest) (*ListClustersResponse, e return (*ListClustersResponse)(&retVal), nil } -type RegisterClusterRequest tornjakTypes.ClusterInput - // DefineCluster registers cluster to local DB // DefineCluster registers a cluster to the local DB func (s *Server) DefineCluster(inp RegisterClusterRequest) error { @@ -106,8 +104,6 @@ func (s *Server) DefineCluster(inp RegisterClusterRequest) error { return s.Db.CreateClusterEntry(cinfo) } -type EditClusterRequest tornjakTypes.ClusterInput - // EditCluster registers cluster to local DB // EditCluster registers updates to a cluster in the local DB func (s *Server) EditCluster(inp EditClusterRequest) error { @@ -138,8 +134,6 @@ func (s *Server) EditCluster(inp EditClusterRequest) error { return s.Db.EditClusterEntry(existingCluster) } -type DeleteClusterRequest tornjakTypes.ClusterInput - // DeleteCluster deletes cluster with name cinfo.Name and assignment to agents func (s *Server) DeleteCluster(inp DeleteClusterRequest) error { cinfo := tornjakTypes.ClusterInfo(inp.ClusterInstance) diff --git a/api/agent/types.go b/api/agent/types.go index 3ded588e..e050666b 100644 --- a/api/agent/types.go +++ b/api/agent/types.go @@ -31,6 +31,23 @@ type SPIREConfig struct { Plugins ast.Node `hcl:"plugins"` } +// RegisterClusterRequest represents the request for creating a cluster +type RegisterClusterRequest struct { + // ClusterInstance ClusterInfo `json:"cluster_instance"` + UID string `json:"uid"` +} + +// EditClusterRequest represents the request for editing a cluster +type EditClusterRequest struct { + // ClusterInstance ClusterInfo `json:"cluster_instance"` + UID string `json:"uid"` +} + +// DeleteClusterRequest represents the request for deleting a cluster +type DeleteClusterRequest struct { + UID string `json:"uid"` +} + type TornjakConfig struct { Server *serverConfig `hcl:"server"` Plugins *ast.Node `hcl:"plugins"` @@ -123,14 +140,14 @@ type AuthRole struct { } type APIv1RoleMapping struct { - Name string `hcl:",key"` - Method string `hcl:"-"` - Path string `hcl:"-"` + Name string `hcl:",key"` + Method string `hcl:"-"` + Path string `hcl:"-"` AllowedRoles []string `hcl:"allowed_roles"` } type pluginAuthorizerRBAC struct { - Name string `hcl:"name"` - RoleList []*AuthRole `hcl:"role,block"` + Name string `hcl:"name"` + RoleList []*AuthRole `hcl:"role,block"` APIv1RoleMappings []*APIv1RoleMapping `hcl:"APIv1,block"` } diff --git a/pkg/agent/db/sqlite_test.go b/pkg/agent/db/sqlite_test.go index c1bd15f3..fe7e069b 100644 --- a/pkg/agent/db/sqlite_test.go +++ b/pkg/agent/db/sqlite_test.go @@ -191,6 +191,8 @@ func TestSelectorDB(t *testing.T) { // Uses functions NewLocalSqliteDB, db.GetClusters, db.CreateClusterEntry, // // db.GetAgentClusterName, db.GetClusterAgents +var generatedUID string // Test-level variable +// Test variable func TestClusterCreate(t *testing.T) { cleanup() defer cleanup() @@ -210,6 +212,13 @@ func TestClusterCreate(t *testing.T) { if len(cList) > 0 { t.Fatal("Clusters list should initially be empty") } + // CHECK that UID is generated for new cluster + if cList[0].UID == "" { + t.Fatal("UID not generated for new cluster") + } + + // Save the generated UID for further tests + // generatedUID := cList[0].UID cluster1 := "cluster1" cluster2 := "cluster2" @@ -283,7 +292,7 @@ func TestClusterCreate(t *testing.T) { } // ATTEMPT Create with no conflicting agent assignment [CreateClusterEntry, GetClusters] - err = db.(cinfo3) + err = db.CreateClusterEntry(cinfo3) if err != nil { t.Fatal(err) } @@ -407,6 +416,10 @@ func TestClusterEdit(t *testing.T) { if len(cList) > 0 { t.Fatal("Clusters list should initially be empty") } + // CHECK that the UID remains unchanged after editing + if cList[0].UID != generatedUID { + t.Fatalf("UID changed after editing: expected %v, got %v", generatedUID, cList[0].UID) + } cluster1 := "cluster1" cluster2 := "cluster2" @@ -634,6 +647,11 @@ func TestClusterDelete(t *testing.T) { if len(cList) > 0 { t.Fatal("Clusters list should initially be empty") } + // CHECK that clusters can be deleted using UID + err = db.DeleteClusterEntry(generatedUID) + if err != nil { + t.Fatalf("Failed to delete cluster by UID: %v", err) + } cluster1 := "cluster1" cluster2 := "cluster2"