From 6b285e55b8f86cdf0cfa00ab3004283216bfa37d Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Thu, 12 Sep 2024 15:03:24 +1000 Subject: [PATCH] database insert is working --- db/db.go | 32 ++++++++++++++ db/migrations/20240912012927_init.sql | 12 +++--- db/queries/models.go | 12 +++--- db/queries/query.sql.go | 8 ++-- log/log.go | 6 +-- main.go | 7 ---- server/handler/vmCreate.go | 54 +++++++++++++++++++++++- server/models/models.go | 60 +++++++++++++++++++++++++++ server/server.go | 2 +- 9 files changed, 165 insertions(+), 28 deletions(-) create mode 100644 server/models/models.go diff --git a/db/db.go b/db/db.go index 7fa9c05..0af8bb2 100644 --- a/db/db.go +++ b/db/db.go @@ -1,8 +1,10 @@ package db import ( + "database/sql" "embed" "log/slog" + "reflect" "vctp/db/queries" "github.com/jmoiron/sqlx" @@ -66,3 +68,33 @@ func Migrate(db Database) error { return nil } + +// ConvertToSQLParams is a utility function that generically converts a struct to a corresponding sqlc-generated struct +func ConvertToSQLParams(input interface{}, output interface{}) { + inputVal := reflect.ValueOf(input).Elem() + outputVal := reflect.ValueOf(output).Elem() + + for i := 0; i < outputVal.NumField(); i++ { + outputField := outputVal.Field(i) + inputField := inputVal.FieldByName(outputVal.Type().Field(i).Name) + + if !inputField.IsValid() || !outputField.CanSet() { + continue + } + + switch outputField.Type() { + case reflect.TypeOf(sql.NullString{}): + if inputField.Kind() == reflect.Ptr && inputField.IsNil() { + outputField.Set(reflect.ValueOf(sql.NullString{Valid: false})) + } else { + outputField.Set(reflect.ValueOf(sql.NullString{String: inputField.String(), Valid: true})) + } + case reflect.TypeOf(sql.NullInt64{}): + if inputField.Int() == 0 { + outputField.Set(reflect.ValueOf(sql.NullInt64{Valid: false})) + } else { + outputField.Set(reflect.ValueOf(sql.NullInt64{Int64: inputField.Int(), Valid: true})) + } + } + } +} diff --git a/db/migrations/20240912012927_init.sql b/db/migrations/20240912012927_init.sql index e7f7660..acf5cf8 100644 --- a/db/migrations/20240912012927_init.sql +++ b/db/migrations/20240912012927_init.sql @@ -2,13 +2,13 @@ -- +goose StatementBegin CREATE TABLE IF NOT EXISTS "Inventory" ( "Iid" INTEGER UNIQUE, - "Name" TEXT, - "Vcenter" TEXT, + "Name" TEXT NOT NULL, + "Vcenter" TEXT NOT NULL, "VmId" TEXT, "EventKey" TEXT, "EventId" TEXT, - "CreationTime" TEXT, - "DeletionTime" TEXT, + "CreationTime" INTEGER, + "DeletionTime" INTEGER, "ResourcePool" TEXT, "VmType" TEXT, "Datacenter" TEXT, @@ -23,8 +23,8 @@ CREATE TABLE IF NOT EXISTS "Inventory" ( CREATE TABLE IF NOT EXISTS "Updates" ( "Uid" INTEGER UNIQUE, "InventoryId" INTEGER, - "UpdateTime" TEXT, - "UpdateType" TEXT, + "UpdateTime" INTEGER, + "UpdateType" TEXT NOT NULL, "NewVcpus" INTEGER, "NewRam" INTEGER, "NewResourcePool" TEXT diff --git a/db/queries/models.go b/db/queries/models.go index 45c7552..1866bdb 100644 --- a/db/queries/models.go +++ b/db/queries/models.go @@ -10,13 +10,13 @@ import ( type Inventory struct { Iid sql.NullInt64 - Name sql.NullString - Vcenter sql.NullString + Name string + Vcenter string VmId sql.NullString EventKey sql.NullString EventId sql.NullString - CreationTime sql.NullString - DeletionTime sql.NullString + CreationTime sql.NullInt64 + DeletionTime sql.NullInt64 ResourcePool sql.NullString VmType sql.NullString Datacenter sql.NullString @@ -31,8 +31,8 @@ type Inventory struct { type Updates struct { Uid sql.NullInt64 InventoryId sql.NullInt64 - UpdateTime sql.NullString - UpdateType sql.NullString + UpdateTime sql.NullInt64 + UpdateType string NewVcpus sql.NullInt64 NewRam sql.NullInt64 NewResourcePool sql.NullString diff --git a/db/queries/query.sql.go b/db/queries/query.sql.go index a60f0d9..26e9ca5 100644 --- a/db/queries/query.sql.go +++ b/db/queries/query.sql.go @@ -20,12 +20,12 @@ RETURNING Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTim ` type CreateInventoryParams struct { - Name sql.NullString - Vcenter sql.NullString + Name string + Vcenter string VmId sql.NullString EventKey sql.NullString EventId sql.NullString - CreationTime sql.NullString + CreationTime sql.NullInt64 ResourcePool sql.NullString VmType sql.NullString Datacenter sql.NullString @@ -83,7 +83,7 @@ SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, WHERE "Name" = ? LIMIT 1 ` -func (q *Queries) GetInventoryByName(ctx context.Context, name sql.NullString) (Inventory, error) { +func (q *Queries) GetInventoryByName(ctx context.Context, name string) (Inventory, error) { row := q.db.QueryRowContext(ctx, getInventoryByName, name) var i Inventory err := row.Scan( diff --git a/log/log.go b/log/log.go index c184073..2a17a2f 100644 --- a/log/log.go +++ b/log/log.go @@ -10,11 +10,11 @@ func New(level Level, output Output) *slog.Logger { var h slog.Handler switch output { case OutputJson: - h = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level.ToSlog()}) + h = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level.ToSlog(), AddSource: true}) case OutputText: - h = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level.ToSlog()}) + h = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level.ToSlog(), AddSource: true}) default: - h = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level.ToSlog()}) + h = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level.ToSlog(), AddSource: true}) } return slog.New(h) } diff --git a/main.go b/main.go index a6600b5..45ce151 100644 --- a/main.go +++ b/main.go @@ -40,13 +40,6 @@ func main() { } defer database.Close() - /* - if err = db.Migrate(database); err != nil && !errors.Is(err, migrate.ErrNoChange) { - logger.Error("failed to migrate database", "error", err) - return - } - */ - if err = db.Migrate(database); err != nil { logger.Error("failed to migrate database", "error", err) return diff --git a/server/handler/vmCreate.go b/server/handler/vmCreate.go index 7e5688c..2292324 100644 --- a/server/handler/vmCreate.go +++ b/server/handler/vmCreate.go @@ -1,21 +1,73 @@ package handler import ( + "context" + "database/sql" + "encoding/json" "fmt" "io" "net/http" + "time" + queries "vctp/db/queries" + models "vctp/server/models" ) // VmCreate receives the CloudEvent for a VM creation func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) { + var unixTimestamp int64 + reqBody, err := io.ReadAll(r.Body) if err != nil { fmt.Fprintf(w, "Invalid data received") w.WriteHeader(http.StatusInternalServerError) return + } else { + h.Logger.Debug("received input data", "length", len(reqBody)) + } + + // Decode the JSON body into vmModel struct + var vm models.CloudEventReceived + //if err := json.NewDecoder(r.Body).Decode(&vm); err != nil { + if err := json.Unmarshal(reqBody, &vm); err != nil { + h.Logger.Error("unable to decode json", "error", err) + http.Error(w, "Invalid JSON body", http.StatusBadRequest) + return + } + + // Convert vmModel to CreateInventoryParams using the utility function + //var params queries.CreateInventoryParams + //db.ConvertToSQLParams(&vm, ¶ms) + + // Parse the datetime string to a time.Time object + eventTime, err := time.Parse(time.RFC3339, vm.CloudEvent.Time) + if err != nil { + h.Logger.Warn("unable to convert cloud event time to timestamp", "error", err) + unixTimestamp = time.Now().Unix() + } else { + // Convert to Unix timestamp + unixTimestamp = eventTime.Unix() + } + + // Create an instance of CreateInventoryParams + params := queries.CreateInventoryParams{ + Name: vm.CloudEvent.Data.VM.Name, + Vcenter: vm.CloudEvent.Source, + VmId: sql.NullString{String: "VirtualMachine-" + vm.CloudEvent.Data.VM.VM.Value, Valid: vm.CloudEvent.Data.VM.VM.Value != ""}, + CreationTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0}, + } + + h.Logger.Debug("database params", "vm", vm) + + // Insert the new inventory record into the database + result, err := h.Database.Queries().CreateInventory(context.Background(), params) + if err != nil { + h.Logger.Error("unable to perform database insert", "error", err) + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error : %v\n", err) + return } h.Logger.Debug("received create request", "body", string(reqBody)) w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "Create Request (%d): %v\n", len(reqBody), string(reqBody)) + fmt.Fprintf(w, "Create Request : %v\n", result) } diff --git a/server/models/models.go b/server/models/models.go new file mode 100644 index 0000000..241c363 --- /dev/null +++ b/server/models/models.go @@ -0,0 +1,60 @@ +package models + +import "time" + +type CloudEventReceived struct { + CloudEvent struct { + ID string `json:"id"` + Specversion string `json:"specversion"` + Source string `json:"source"` + Type string `json:"type"` + Time string `json:"time"` + Data struct { + ChainID int `json:"ChainId"` + ChangeTag string `json:"ChangeTag"` + ComputeResource struct { + ComputeResource struct { + Type string `json:"Type"` + Value string `json:"Value"` + } `json:"ComputeResource"` + Name string `json:"Name"` + } `json:"ComputeResource"` + CreatedTime time.Time `json:"CreatedTime"` + Datacenter struct { + Datacenter struct { + Type string `json:"Type"` + Value string `json:"Value"` + } `json:"Datacenter"` + Name string `json:"Name"` + } `json:"Datacenter"` + Ds interface{} `json:"Ds"` + Dvs interface{} `json:"Dvs"` + FullFormattedMessage string `json:"FullFormattedMessage"` + Host struct { + Host struct { + Type string `json:"Type"` + Value string `json:"Value"` + } `json:"Host"` + Name string `json:"Name"` + } `json:"Host"` + Key int `json:"Key"` + Net interface{} `json:"Net"` + SrcTemplate struct { + Name string `json:"Name"` + VM struct { + Type string `json:"Type"` + Value string `json:"Value"` + } `json:"Vm"` + } `json:"SrcTemplate"` + Template bool `json:"Template"` + UserName string `json:"UserName"` + VM struct { + Name string `json:"Name"` + VM struct { + Type string `json:"Type"` + Value string `json:"Value"` + } `json:"Vm"` + } `json:"Vm"` + } `json:"data"` + } `json:"cloudEvent"` +} diff --git a/server/server.go b/server/server.go index d6ca773..d0eb8ab 100644 --- a/server/server.go +++ b/server/server.go @@ -109,7 +109,7 @@ func (s *Server) Start() { } } else { s.logger.Info("starting TLS server", "port", s.srv.Addr, "cert", s.tlsCertFilename, "key", s.tlsKeyFilename) - if err := s.srv.ListenAndServeTLS(s.tlsCertFilename, s.tlsKeyFilename); err != nil { + if err := s.srv.ListenAndServeTLS(s.tlsCertFilename, s.tlsKeyFilename); err != nil && err != http.ErrServerClosed { s.logger.Warn("failed to start server", "error", err) } }