update to support postgresql and add godocs
This commit is contained in:
19
README.md
19
README.md
@@ -114,6 +114,25 @@ Create a new up/down migration file with this command
|
|||||||
goose -dir db/migrations sqlite3 ./db.sqlite3 create init sql
|
goose -dir db/migrations sqlite3 ./db.sqlite3 create init sql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Database Configuration
|
||||||
|
By default the app uses SQLite and creates/opens `db.sqlite3`. You can opt into PostgreSQL
|
||||||
|
by setting environment variables:
|
||||||
|
|
||||||
|
- `DB_DRIVER`: `sqlite` (default) or `postgres`
|
||||||
|
- `DB_URL`: SQLite file path/DSN or PostgreSQL DSN
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```shell
|
||||||
|
# SQLite (default)
|
||||||
|
DB_DRIVER=sqlite DB_URL=./db.sqlite3
|
||||||
|
|
||||||
|
# PostgreSQL
|
||||||
|
DB_DRIVER=postgres DB_URL=postgres://user:pass@localhost:5432/vctp?sslmode=disable
|
||||||
|
```
|
||||||
|
|
||||||
|
PostgreSQL migrations live in `db/migrations_postgres`, while SQLite migrations remain in
|
||||||
|
`db/migrations`.
|
||||||
|
|
||||||
### Dist
|
### Dist
|
||||||
|
|
||||||
This is where your assets live. Any Javascript, images, or styling needs to go in the
|
This is where your assets live. Any Javascript, images, or styling needs to go in the
|
||||||
|
|||||||
65
db/db.go
65
db/db.go
@@ -3,26 +3,35 @@ package db
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
"vctp/db/queries"
|
"strings"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/pressly/goose/v3"
|
"github.com/pressly/goose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed migrations/*.sql
|
//go:embed migrations migrations_postgres
|
||||||
var migrations embed.FS
|
var migrations embed.FS
|
||||||
|
|
||||||
type Database interface {
|
type Database interface {
|
||||||
DB() *sqlx.DB
|
DB() *sqlx.DB
|
||||||
Queries() *queries.Queries
|
Queries() Querier
|
||||||
Logger() *slog.Logger
|
Logger() *slog.Logger
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(logger *slog.Logger, url string) (Database, error) {
|
type Config struct {
|
||||||
db, err := newLocalDB(logger, url)
|
Driver string
|
||||||
|
DSN string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(logger *slog.Logger, cfg Config) (Database, error) {
|
||||||
|
driver := normalizeDriver(cfg.Driver)
|
||||||
|
switch driver {
|
||||||
|
case "sqlite":
|
||||||
|
db, err := newLocalDB(logger, cfg.DSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -30,19 +39,43 @@ func New(logger *slog.Logger, url string) (Database, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return db, nil
|
return db, nil
|
||||||
|
case "postgres":
|
||||||
|
db, err := newPostgresDB(logger, cfg.DSN)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = db.db.Ping(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return db, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported database driver: %s", cfg.Driver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate runs the migrations on the database. Assumes the database is SQLite.
|
// Migrate runs the migrations on the database.
|
||||||
func Migrate(db Database) error {
|
func Migrate(db Database, driver string) error {
|
||||||
|
driver = normalizeDriver(driver)
|
||||||
|
|
||||||
goose.SetBaseFS(migrations)
|
goose.SetBaseFS(migrations)
|
||||||
|
|
||||||
|
switch driver {
|
||||||
|
case "sqlite":
|
||||||
if err := goose.SetDialect("sqlite3"); err != nil {
|
if err := goose.SetDialect("sqlite3"); err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("failed to set sqlite dialect: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := goose.Up(db.DB().DB, "migrations"); err != nil {
|
if err := goose.Up(db.DB().DB, "migrations"); err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("failed to run sqlite migrations: %w", err)
|
||||||
|
}
|
||||||
|
case "postgres":
|
||||||
|
if err := goose.SetDialect("postgres"); err != nil {
|
||||||
|
return fmt.Errorf("failed to set postgres dialect: %w", err)
|
||||||
|
}
|
||||||
|
if err := goose.Up(db.DB().DB, "migrations_postgres"); err != nil {
|
||||||
|
return fmt.Errorf("failed to run postgres migrations: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported database driver: %s", driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - replace with goose
|
// TODO - replace with goose
|
||||||
@@ -69,6 +102,18 @@ func Migrate(db Database) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeDriver(driver string) string {
|
||||||
|
normalized := strings.ToLower(strings.TrimSpace(driver))
|
||||||
|
switch normalized {
|
||||||
|
case "", "sqlite3":
|
||||||
|
return "sqlite"
|
||||||
|
case "postgresql":
|
||||||
|
return "postgres"
|
||||||
|
default:
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ConvertToSQLParams is a utility function that generically converts a struct to a corresponding sqlc-generated struct
|
// ConvertToSQLParams is a utility function that generically converts a struct to a corresponding sqlc-generated struct
|
||||||
func ConvertToSQLParams(input interface{}, output interface{}) {
|
func ConvertToSQLParams(input interface{}, output interface{}) {
|
||||||
inputVal := reflect.ValueOf(input).Elem()
|
inputVal := reflect.ValueOf(input).Elem()
|
||||||
|
|||||||
22
db/local.go
22
db/local.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strings"
|
||||||
"vctp/db/queries"
|
"vctp/db/queries"
|
||||||
|
|
||||||
//_ "github.com/tursodatabase/libsql-client-go/libsql"
|
//_ "github.com/tursodatabase/libsql-client-go/libsql"
|
||||||
@@ -28,7 +29,7 @@ func (d *LocalDB) DB() *sqlx.DB {
|
|||||||
return d.db
|
return d.db
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDB) Queries() *queries.Queries {
|
func (d *LocalDB) Queries() Querier {
|
||||||
return d.queries
|
return d.queries
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ func (d *LocalDB) Close() error {
|
|||||||
return d.db.Close()
|
return d.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) {
|
func newLocalDB(logger *slog.Logger, dsn string) (*LocalDB, error) {
|
||||||
|
|
||||||
// TODO - work out if https://kerkour.com/sqlite-for-servers is possible without using sqlx
|
// TODO - work out if https://kerkour.com/sqlite-for-servers is possible without using sqlx
|
||||||
/*
|
/*
|
||||||
@@ -62,8 +63,9 @@ func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) {
|
|||||||
readDB.SetMaxOpenConns(max(4, runtime.NumCPU()))
|
readDB.SetMaxOpenConns(max(4, runtime.NumCPU()))
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//db, err := sql.Open("libsql", "file:"+path)
|
normalizedDSN := normalizeSqliteDSN(dsn)
|
||||||
db, err := sqlx.Open("sqlite", "file:"+path)
|
//db, err := sql.Open("libsql", normalizedDSN)
|
||||||
|
db, err := sqlx.Open("sqlite", normalizedDSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("can't open database connection", "error", err)
|
logger.Error("can't open database connection", "error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -92,3 +94,15 @@ func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) {
|
|||||||
|
|
||||||
return &LocalDB{logger: logger, db: db, queries: queries.New(db)}, nil
|
return &LocalDB{logger: logger, db: db, queries: queries.New(db)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeSqliteDSN(dsn string) string {
|
||||||
|
trimmed := strings.TrimSpace(dsn)
|
||||||
|
if trimmed == "" {
|
||||||
|
return "file:db.sqlite3"
|
||||||
|
}
|
||||||
|
lower := strings.ToLower(trimmed)
|
||||||
|
if strings.HasPrefix(lower, "file:") || strings.HasPrefix(lower, "file::memory:") || trimmed == ":memory:" {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
return "file:" + trimmed
|
||||||
|
}
|
||||||
|
|||||||
38
db/migrations_postgres/20240912012927_init.sql
Normal file
38
db/migrations_postgres/20240912012927_init.sql
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE TABLE IF NOT EXISTS "Inventory" (
|
||||||
|
"Iid" BIGSERIAL PRIMARY KEY,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"Vcenter" TEXT NOT NULL,
|
||||||
|
"VmId" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"EventId" TEXT,
|
||||||
|
"CreationTime" BIGINT,
|
||||||
|
"DeletionTime" BIGINT,
|
||||||
|
"ResourcePool" TEXT,
|
||||||
|
"VmType" TEXT,
|
||||||
|
"Datacenter" TEXT,
|
||||||
|
"Cluster" TEXT,
|
||||||
|
"Folder" TEXT,
|
||||||
|
"ProvisionedDisk" REAL,
|
||||||
|
"InitialVcpus" INTEGER,
|
||||||
|
"InitialRam" INTEGER,
|
||||||
|
"SrmPlaceholder" INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "Updates" (
|
||||||
|
"Uid" BIGSERIAL PRIMARY KEY,
|
||||||
|
"InventoryId" INTEGER,
|
||||||
|
"UpdateTime" BIGINT,
|
||||||
|
"UpdateType" TEXT NOT NULL,
|
||||||
|
"NewVcpus" INTEGER,
|
||||||
|
"NewRam" INTEGER,
|
||||||
|
"NewResourcePool" TEXT
|
||||||
|
);
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP TABLE "Inventory";
|
||||||
|
DROP TABLE "Updates";
|
||||||
|
-- +goose StatementEnd
|
||||||
11
db/migrations_postgres/20240912231739_extend_updates.sql
Normal file
11
db/migrations_postgres/20240912231739_extend_updates.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" ADD COLUMN "EventKey" TEXT;
|
||||||
|
ALTER TABLE "Updates" ADD COLUMN "EventId" TEXT;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "EventKey";
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "EventId";
|
||||||
|
-- +goose StatementEnd
|
||||||
21
db/migrations_postgres/20240913021038_events.sql
Normal file
21
db/migrations_postgres/20240913021038_events.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE TABLE IF NOT EXISTS "Events" (
|
||||||
|
"Eid" BIGSERIAL PRIMARY KEY,
|
||||||
|
"CloudId" TEXT NOT NULL,
|
||||||
|
"Source" TEXT NOT NULL,
|
||||||
|
"EventTime" BIGINT,
|
||||||
|
"ChainId" TEXT NOT NULL,
|
||||||
|
"VmId" TEXT,
|
||||||
|
"EventKey" TEXT,
|
||||||
|
"Datacenter" TEXT,
|
||||||
|
"ComputeResource" TEXT,
|
||||||
|
"UserName" TEXT,
|
||||||
|
"Processed" INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP TABLE "Events";
|
||||||
|
-- +goose StatementEnd
|
||||||
17
db/migrations_postgres/20240913043145_extend_events.sql
Normal file
17
db/migrations_postgres/20240913043145_extend_events.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Events" RENAME COLUMN "Datacenter" TO "DatacenterName";
|
||||||
|
ALTER TABLE "Events" RENAME COLUMN "ComputeResource" TO "ComputeResourceName";
|
||||||
|
ALTER TABLE "Events" ADD COLUMN "DatacenterId" TEXT;
|
||||||
|
ALTER TABLE "Events" ADD COLUMN "ComputeResourceId" TEXT;
|
||||||
|
ALTER TABLE "Events" ADD COLUMN "VmName" TEXT;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Events" DROP COLUMN "VmName";
|
||||||
|
ALTER TABLE "Events" DROP COLUMN "ComputeResourceId";
|
||||||
|
ALTER TABLE "Events" DROP COLUMN "DatacenterId";
|
||||||
|
ALTER TABLE "Events" RENAME COLUMN "ComputeResourceName" TO "ComputeResource";
|
||||||
|
ALTER TABLE "Events" RENAME COLUMN "DatacenterName" TO "Datacenter";
|
||||||
|
-- +goose StatementEnd
|
||||||
9
db/migrations_postgres/20240915015710_events_type.sql
Normal file
9
db/migrations_postgres/20240915015710_events_type.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Events" ADD COLUMN "EventType" TEXT;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "EventType";
|
||||||
|
-- +goose StatementEnd
|
||||||
11
db/migrations_postgres/20240915232747_extend_inventory.sql
Normal file
11
db/migrations_postgres/20240915232747_extend_inventory.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "IsTemplate" INTEGER;
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "PowerState" INTEGER;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "PowerState";
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "IsTemplate";
|
||||||
|
-- +goose StatementEnd
|
||||||
9
db/migrations_postgres/20240916041639_rename_eventid.sql
Normal file
9
db/migrations_postgres/20240916041639_rename_eventid.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "EventId" TO "CloudId";
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "CloudId" TO "EventId";
|
||||||
|
-- +goose StatementEnd
|
||||||
9
db/migrations_postgres/20240916045259_updates_disk.sql
Normal file
9
db/migrations_postgres/20240916045259_updates_disk.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" ADD COLUMN "NewProvisionedDisk" REAL;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "NewProvisionedDisk";
|
||||||
|
-- +goose StatementEnd
|
||||||
9
db/migrations_postgres/20240926022714_updates_user.sql
Normal file
9
db/migrations_postgres/20240926022714_updates_user.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" ADD COLUMN "UserName" TEXT;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "UserName";
|
||||||
|
-- +goose StatementEnd
|
||||||
55
db/migrations_postgres/20240927002029_change_inventory.sql
Normal file
55
db/migrations_postgres/20240927002029_change_inventory.sql
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "IsTemplate" TO "IsTemplate_old";
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "PowerState" TO "PowerState_old";
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "SrmPlaceholder" TO "SrmPlaceholder_old";
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "IsTemplate" TEXT NOT NULL DEFAULT 'FALSE';
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "PoweredOn" TEXT NOT NULL DEFAULT 'FALSE';
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "SrmPlaceholder" TEXT NOT NULL DEFAULT 'FALSE';
|
||||||
|
UPDATE "Inventory"
|
||||||
|
SET "IsTemplate" = CASE
|
||||||
|
WHEN "IsTemplate_old" = 1 THEN 'TRUE'
|
||||||
|
ELSE 'FALSE'
|
||||||
|
END;
|
||||||
|
UPDATE "Inventory"
|
||||||
|
SET "PoweredOn" = CASE
|
||||||
|
WHEN "PowerState_old" = 1 THEN 'TRUE'
|
||||||
|
ELSE 'FALSE'
|
||||||
|
END;
|
||||||
|
UPDATE "Inventory"
|
||||||
|
SET "SrmPlaceholder" = CASE
|
||||||
|
WHEN "SrmPlaceholder_old" = 1 THEN 'TRUE'
|
||||||
|
ELSE 'FALSE'
|
||||||
|
END;
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "IsTemplate_old";
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "PowerState_old";
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "SrmPlaceholder_old";
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "IsTemplate" TO "IsTemplate_old";
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "PoweredOn" TO "PoweredOn_old";
|
||||||
|
ALTER TABLE "Inventory" RENAME COLUMN "SrmPlaceholder" TO "SrmPlaceholder_old";
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "IsTemplate" INTEGER;
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "PowerState" INTEGER;
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "SrmPlaceholder" INTEGER;
|
||||||
|
UPDATE "Inventory"
|
||||||
|
SET "IsTemplate" = CASE
|
||||||
|
WHEN "IsTemplate_old" = 'TRUE' THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END;
|
||||||
|
UPDATE "Inventory"
|
||||||
|
SET "PowerState" = CASE
|
||||||
|
WHEN "PoweredOn_old" = 'TRUE' THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END;
|
||||||
|
UPDATE "Inventory"
|
||||||
|
SET "SrmPlaceholder" = CASE
|
||||||
|
WHEN "SrmPlaceholder_old" = 'TRUE' THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END;
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "IsTemplate_old";
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "PoweredOn_old";
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "SrmPlaceholder_old";
|
||||||
|
-- +goose StatementEnd
|
||||||
9
db/migrations_postgres/20240930012450_add_uuid.sql
Normal file
9
db/migrations_postgres/20240930012450_add_uuid.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" ADD COLUMN "VmUuid" TEXT;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Inventory" DROP COLUMN "VmUuid";
|
||||||
|
-- +goose StatementEnd
|
||||||
18
db/migrations_postgres/20240930031506_add_table.sql
Normal file
18
db/migrations_postgres/20240930031506_add_table.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE TABLE IF NOT EXISTS "InventoryHistory" (
|
||||||
|
"Hid" BIGSERIAL PRIMARY KEY,
|
||||||
|
"InventoryId" INTEGER,
|
||||||
|
"ReportDate" BIGINT,
|
||||||
|
"UpdateTime" BIGINT,
|
||||||
|
"PreviousVcpus" INTEGER,
|
||||||
|
"PreviousRam" INTEGER,
|
||||||
|
"PreviousResourcePool" TEXT,
|
||||||
|
"PreviousProvisionedDisk" REAL
|
||||||
|
);
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP TABLE "InventoryHistory";
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" ADD COLUMN "PlaceholderChange" TEXT;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "PlaceholderChange";
|
||||||
|
-- +goose StatementEnd
|
||||||
9
db/migrations_postgres/20241002032439_add_name.sql
Normal file
9
db/migrations_postgres/20241002032439_add_name.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" ADD COLUMN "Name" TEXT;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "Name";
|
||||||
|
-- +goose StatementEnd
|
||||||
9
db/migrations_postgres/20241002033323_add_change.sql
Normal file
9
db/migrations_postgres/20241002033323_add_change.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" ADD COLUMN "RawChangeString" BYTEA;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE "Updates" DROP COLUMN "RawChangeString";
|
||||||
|
-- +goose StatementEnd
|
||||||
79
db/postgres.go
Normal file
79
db/postgres.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"vctp/db/queries"
|
||||||
|
|
||||||
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostgresDB struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
db *sqlx.DB
|
||||||
|
queries *queries.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Database = (*PostgresDB)(nil)
|
||||||
|
|
||||||
|
func (d *PostgresDB) DB() *sqlx.DB {
|
||||||
|
return d.db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresDB) Queries() Querier {
|
||||||
|
return d.queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresDB) Logger() *slog.Logger {
|
||||||
|
return d.logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresDB) Close() error {
|
||||||
|
return d.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPostgresDB(logger *slog.Logger, dsn string) (*PostgresDB, error) {
|
||||||
|
if strings.TrimSpace(dsn) == "" {
|
||||||
|
return nil, fmt.Errorf("postgres DSN is required")
|
||||||
|
}
|
||||||
|
db, err := sqlx.Open("pgx", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db.SetMaxOpenConns(10)
|
||||||
|
|
||||||
|
rebindDB := rebindDBTX{db: db}
|
||||||
|
return &PostgresDB{logger: logger, db: db, queries: queries.New(rebindDB)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rebindDBTX struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rebindDBTX) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return r.db.ExecContext(ctx, rebindQuery(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rebindDBTX) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||||
|
return r.db.PrepareContext(ctx, rebindQuery(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rebindDBTX) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return r.db.QueryContext(ctx, rebindQuery(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rebindDBTX) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||||
|
return r.db.QueryRowContext(ctx, rebindQuery(query), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var numberedPlaceholderRe = regexp.MustCompile(`\?\d+`)
|
||||||
|
|
||||||
|
func rebindQuery(query string) string {
|
||||||
|
unindexed := numberedPlaceholderRe.ReplaceAllString(query, "?")
|
||||||
|
return sqlx.Rebind(sqlx.DOLLAR, unindexed)
|
||||||
|
}
|
||||||
35
db/querier.go
Normal file
35
db/querier.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"vctp/db/queries"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Querier abstracts sqlc-generated queries so multiple database backends can share call sites.
|
||||||
|
type Querier interface {
|
||||||
|
CleanupUpdates(ctx context.Context, arg queries.CleanupUpdatesParams) error
|
||||||
|
CleanupUpdatesNullVm(ctx context.Context) error
|
||||||
|
CreateEvent(ctx context.Context, arg queries.CreateEventParams) (queries.Events, error)
|
||||||
|
CreateInventory(ctx context.Context, arg queries.CreateInventoryParams) (queries.Inventory, error)
|
||||||
|
CreateInventoryHistory(ctx context.Context, arg queries.CreateInventoryHistoryParams) (queries.InventoryHistory, error)
|
||||||
|
CreateUpdate(ctx context.Context, arg queries.CreateUpdateParams) (queries.Updates, error)
|
||||||
|
GetInventoryByName(ctx context.Context, name string) ([]queries.Inventory, error)
|
||||||
|
GetInventoryByVcenter(ctx context.Context, vcenter string) ([]queries.Inventory, error)
|
||||||
|
GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (queries.Inventory, error)
|
||||||
|
GetInventoryVcUrl(ctx context.Context, vc string) ([]queries.Inventory, error)
|
||||||
|
GetInventoryVmId(ctx context.Context, arg queries.GetInventoryVmIdParams) (queries.Inventory, error)
|
||||||
|
GetInventoryVmUuid(ctx context.Context, arg queries.GetInventoryVmUuidParams) (queries.Inventory, error)
|
||||||
|
GetReportInventory(ctx context.Context) ([]queries.Inventory, error)
|
||||||
|
GetReportUpdates(ctx context.Context) ([]queries.Updates, error)
|
||||||
|
GetVmUpdates(ctx context.Context, arg queries.GetVmUpdatesParams) ([]queries.Updates, error)
|
||||||
|
InventoryCleanup(ctx context.Context, arg queries.InventoryCleanupParams) error
|
||||||
|
InventoryCleanupTemplates(ctx context.Context) error
|
||||||
|
InventoryCleanupVcenter(ctx context.Context, vc string) error
|
||||||
|
InventoryMarkDeleted(ctx context.Context, arg queries.InventoryMarkDeletedParams) error
|
||||||
|
InventoryUpdate(ctx context.Context, arg queries.InventoryUpdateParams) error
|
||||||
|
ListEvents(ctx context.Context) ([]queries.Events, error)
|
||||||
|
ListInventory(ctx context.Context) ([]queries.Inventory, error)
|
||||||
|
ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]queries.Events, error)
|
||||||
|
UpdateEventsProcessed(ctx context.Context, eid int64) error
|
||||||
|
}
|
||||||
@@ -60,7 +60,7 @@ RETURNING *;
|
|||||||
|
|
||||||
-- name: InventoryCleanupTemplates :exec
|
-- name: InventoryCleanupTemplates :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM "Inventory"
|
||||||
WHERE "IsTemplate" = "TRUE"
|
WHERE "IsTemplate" = 'TRUE'
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: CreateUpdate :one
|
-- name: CreateUpdate :one
|
||||||
|
|||||||
@@ -694,7 +694,7 @@ func (q *Queries) InventoryCleanup(ctx context.Context, arg InventoryCleanupPara
|
|||||||
|
|
||||||
const inventoryCleanupTemplates = `-- name: InventoryCleanupTemplates :exec
|
const inventoryCleanupTemplates = `-- name: InventoryCleanupTemplates :exec
|
||||||
DELETE FROM "Inventory"
|
DELETE FROM "Inventory"
|
||||||
WHERE "IsTemplate" = "TRUE"
|
WHERE "IsTemplate" = 'TRUE'
|
||||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -5,6 +5,7 @@ go 1.25.0
|
|||||||
require (
|
require (
|
||||||
github.com/a-h/templ v0.3.977
|
github.com/a-h/templ v0.3.977
|
||||||
github.com/go-co-op/gocron/v2 v2.19.0
|
github.com/go-co-op/gocron/v2 v2.19.0
|
||||||
|
github.com/jackc/pgx/v5 v5.8.0
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/pressly/goose/v3 v3.26.0
|
github.com/pressly/goose/v3 v3.26.0
|
||||||
@@ -17,6 +18,9 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -2,6 +2,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg=
|
github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg=
|
||||||
github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
|
github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@@ -19,6 +20,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||||
|
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
@@ -55,6 +64,9 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
|||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
|
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
|
||||||
@@ -95,6 +107,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||||
|
|||||||
18
main.go
18
main.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"vctp/db"
|
"vctp/db"
|
||||||
"vctp/internal/secrets"
|
"vctp/internal/secrets"
|
||||||
@@ -46,7 +47,20 @@ func main() {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// Configure database
|
// Configure database
|
||||||
database, err := db.New(logger, utils.GetFilePath("db.sqlite3"))
|
dbDriver := os.Getenv("DB_DRIVER")
|
||||||
|
if dbDriver == "" {
|
||||||
|
dbDriver = "sqlite"
|
||||||
|
}
|
||||||
|
normalizedDriver := strings.ToLower(strings.TrimSpace(dbDriver))
|
||||||
|
if normalizedDriver == "" || normalizedDriver == "sqlite3" {
|
||||||
|
normalizedDriver = "sqlite"
|
||||||
|
}
|
||||||
|
dbURL := os.Getenv("DB_URL")
|
||||||
|
if dbURL == "" && normalizedDriver == "sqlite" {
|
||||||
|
dbURL = utils.GetFilePath("db.sqlite3")
|
||||||
|
}
|
||||||
|
|
||||||
|
database, err := db.New(logger, db.Config{Driver: dbDriver, DSN: dbURL})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to create database", "error", err)
|
logger.Error("Failed to create database", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -54,7 +68,7 @@ func main() {
|
|||||||
defer database.Close()
|
defer database.Close()
|
||||||
//defer database.DB().Close()
|
//defer database.DB().Close()
|
||||||
|
|
||||||
if err = db.Migrate(database); err != nil {
|
if err = db.Migrate(database, dbDriver); err != nil {
|
||||||
logger.Error("failed to migrate database", "error", err)
|
logger.Error("failed to migrate database", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// EncryptData encrypts a plaintext value and returns the ciphertext.
|
||||||
|
// @Summary Encrypt data
|
||||||
|
// @Description Encrypts a plaintext value and returns the ciphertext.
|
||||||
|
// @Tags crypto
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param payload body map[string]string true "Plaintext payload"
|
||||||
|
// @Success 200 {object} map[string]string "Ciphertext response"
|
||||||
|
// @Failure 500 {object} map[string]string "Server error"
|
||||||
|
// @Router /api/encrypt [post]
|
||||||
func (h *Handler) EncryptData(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) EncryptData(w http.ResponseWriter, r *http.Request) {
|
||||||
//ctx := context.Background()
|
//ctx := context.Background()
|
||||||
var cipherText string
|
var cipherText string
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ import (
|
|||||||
"vctp/components/views"
|
"vctp/components/views"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Home handles the home page.
|
// Home renders the web UI home page.
|
||||||
|
// @Summary Home page
|
||||||
|
// @Description Renders the main UI page.
|
||||||
|
// @Tags ui
|
||||||
|
// @Produce text/html
|
||||||
|
// @Success 200 {string} string "HTML page"
|
||||||
|
// @Failure 500 {string} string "Render failed"
|
||||||
|
// @Router / [get]
|
||||||
func (h *Handler) Home(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Home(w http.ResponseWriter, r *http.Request) {
|
||||||
//h.html(r.Context(), w, http.StatusOK, core.HTML("Example Site", home.Home()))
|
//h.html(r.Context(), w, http.StatusOK, core.HTML("Example Site", home.Home()))
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ import (
|
|||||||
"vctp/internal/report"
|
"vctp/internal/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// InventoryReportDownload returns the inventory report as an XLSX download.
|
||||||
|
// @Summary Download inventory report
|
||||||
|
// @Description Generates an inventory XLSX report and returns it as a file download.
|
||||||
|
// @Tags reports
|
||||||
|
// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
// @Success 200 {file} file "Inventory XLSX report"
|
||||||
|
// @Failure 500 {object} map[string]string "Report generation failed"
|
||||||
|
// @Router /api/report/inventory [get]
|
||||||
func (h *Handler) InventoryReportDownload(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) InventoryReportDownload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -34,6 +42,14 @@ func (h *Handler) InventoryReportDownload(w http.ResponseWriter, r *http.Request
|
|||||||
w.Write(reportData)
|
w.Write(reportData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateReportDownload returns the updates report as an XLSX download.
|
||||||
|
// @Summary Download updates report
|
||||||
|
// @Description Generates an updates XLSX report and returns it as a file download.
|
||||||
|
// @Tags reports
|
||||||
|
// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
// @Success 200 {file} file "Updates XLSX report"
|
||||||
|
// @Failure 500 {object} map[string]string "Report generation failed"
|
||||||
|
// @Router /api/report/updates [get]
|
||||||
func (h *Handler) UpdateReportDownload(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) UpdateReportDownload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
// UpdateCleanup removes orphaned update records.
|
||||||
|
// @Summary Cleanup updates
|
||||||
|
// @Description Removes update records that are no longer associated with a VM.
|
||||||
|
// @Tags maintenance
|
||||||
|
// @Produce text/plain
|
||||||
|
// @Success 200 {string} string "Cleanup completed"
|
||||||
|
// @Failure 500 {string} string "Server error"
|
||||||
|
// @Router /api/cleanup/updates [delete]
|
||||||
func (h *Handler) UpdateCleanup(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) UpdateCleanup(w http.ResponseWriter, r *http.Request) {
|
||||||
/*
|
/*
|
||||||
// Get the current time
|
// Get the current time
|
||||||
|
|||||||
@@ -9,7 +9,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Remove a specified VM from the inventory
|
// VcCleanup removes inventory entries for a vCenter instance.
|
||||||
|
// @Summary Cleanup vCenter inventory
|
||||||
|
// @Description Removes all inventory entries associated with a vCenter URL.
|
||||||
|
// @Tags maintenance
|
||||||
|
// @Produce json
|
||||||
|
// @Param vc_url query string true "vCenter URL"
|
||||||
|
// @Success 200 {object} map[string]string "Cleanup completed"
|
||||||
|
// @Failure 400 {object} map[string]string "Invalid request"
|
||||||
|
// @Router /api/cleanup/vcenter [delete]
|
||||||
func (h *Handler) VcCleanup(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VcCleanup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,16 @@ import (
|
|||||||
"vctp/db/queries"
|
"vctp/db/queries"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Remove a specified VM from the inventory
|
// VmCleanup removes a VM from inventory by ID and datacenter.
|
||||||
|
// @Summary Cleanup VM inventory entry
|
||||||
|
// @Description Removes a VM inventory entry by VM ID and datacenter name.
|
||||||
|
// @Tags inventory
|
||||||
|
// @Produce json
|
||||||
|
// @Param vm_id query string true "VM ID"
|
||||||
|
// @Param datacenter_name query string true "Datacenter name"
|
||||||
|
// @Success 200 {object} map[string]string "Cleanup completed"
|
||||||
|
// @Failure 400 {object} map[string]string "Invalid request"
|
||||||
|
// @Router /api/inventory/vm/delete [delete]
|
||||||
func (h *Handler) VmCleanup(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmCleanup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,17 @@ import (
|
|||||||
models "vctp/server/models"
|
models "vctp/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VmCreateEvent receives the CloudEvent for a VM creation
|
// VmCreateEvent records a VM creation CloudEvent.
|
||||||
|
// @Summary Record VM create event
|
||||||
|
// @Description Parses a VM create CloudEvent and stores the event data.
|
||||||
|
// @Tags events
|
||||||
|
// @Accept json
|
||||||
|
// @Produce text/plain
|
||||||
|
// @Param event body models.CloudEventReceived true "CloudEvent payload"
|
||||||
|
// @Success 200 {string} string "Create event processed"
|
||||||
|
// @Failure 400 {string} string "Invalid request"
|
||||||
|
// @Failure 500 {string} string "Server error"
|
||||||
|
// @Router /api/event/vm/create [post]
|
||||||
func (h *Handler) VmCreateEvent(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmCreateEvent(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
unixTimestamp int64
|
unixTimestamp int64
|
||||||
|
|||||||
@@ -12,7 +12,17 @@ import (
|
|||||||
models "vctp/server/models"
|
models "vctp/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
// VmDeleteEvent records a VM deletion CloudEvent in the inventory.
|
||||||
|
// @Summary Record VM delete event
|
||||||
|
// @Description Parses a VM delete CloudEvent and marks the VM as deleted in inventory.
|
||||||
|
// @Tags events
|
||||||
|
// @Accept json
|
||||||
|
// @Produce text/plain
|
||||||
|
// @Param event body models.CloudEventReceived true "CloudEvent payload"
|
||||||
|
// @Success 200 {string} string "Delete event processed"
|
||||||
|
// @Failure 400 {string} string "Invalid request"
|
||||||
|
// @Failure 500 {string} string "Server error"
|
||||||
|
// @Router /api/event/vm/delete [post]
|
||||||
func (h *Handler) VmDeleteEvent(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmDeleteEvent(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
deletedTimestamp int64
|
deletedTimestamp int64
|
||||||
|
|||||||
@@ -13,7 +13,16 @@ import (
|
|||||||
models "vctp/server/models"
|
models "vctp/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VmImport is used for bulk import of existing VMs
|
// VmImport ingests a bulk VM import payload.
|
||||||
|
// @Summary Import VMs
|
||||||
|
// @Description Imports existing VM inventory data in bulk.
|
||||||
|
// @Tags inventory
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param import body models.ImportReceived true "Bulk import payload"
|
||||||
|
// @Success 200 {object} map[string]string "Import processed"
|
||||||
|
// @Failure 500 {object} map[string]string "Server error"
|
||||||
|
// @Router /api/import/vm [post]
|
||||||
func (h *Handler) VmImport(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmImport(w http.ResponseWriter, r *http.Request) {
|
||||||
// Read request body
|
// Read request body
|
||||||
reqBody, err := io.ReadAll(r.Body)
|
reqBody, err := io.ReadAll(r.Body)
|
||||||
|
|||||||
@@ -19,7 +19,17 @@ import (
|
|||||||
"github.com/vmware/govmomi/vim25/types"
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VmModifyEvent receives the CloudEvent for a VM modification or move
|
// VmModifyEvent records a VM modification CloudEvent.
|
||||||
|
// @Summary Record VM modify event
|
||||||
|
// @Description Parses a VM modify CloudEvent and creates an update record when relevant changes are detected.
|
||||||
|
// @Tags events
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param event body models.CloudEventReceived true "CloudEvent payload"
|
||||||
|
// @Success 200 {object} map[string]string "Modify event processed"
|
||||||
|
// @Success 202 {object} map[string]string "No relevant changes"
|
||||||
|
// @Failure 500 {object} map[string]string "Server error"
|
||||||
|
// @Router /api/event/vm/modify [post]
|
||||||
func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
|
||||||
var configChanges []map[string]string
|
var configChanges []map[string]string
|
||||||
params := queries.CreateUpdateParams{}
|
params := queries.CreateUpdateParams{}
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ import (
|
|||||||
models "vctp/server/models"
|
models "vctp/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// VmMoveEvent records a VM move CloudEvent as an update.
|
||||||
|
// @Summary Record VM move event
|
||||||
|
// @Description Parses a VM move CloudEvent and creates an update record.
|
||||||
|
// @Tags events
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param event body models.CloudEventReceived true "CloudEvent payload"
|
||||||
|
// @Success 200 {object} map[string]string "Move event processed"
|
||||||
|
// @Failure 400 {object} map[string]string "Invalid request"
|
||||||
|
// @Failure 500 {object} map[string]string "Server error"
|
||||||
|
// @Router /api/event/vm/move [post]
|
||||||
func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) {
|
||||||
params := queries.CreateUpdateParams{}
|
params := queries.CreateUpdateParams{}
|
||||||
var unixTimestamp int64
|
var unixTimestamp int64
|
||||||
|
|||||||
@@ -9,7 +9,14 @@ import (
|
|||||||
"vctp/internal/vcenter"
|
"vctp/internal/vcenter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
// VmUpdateDetails refreshes inventory metadata from vCenter.
|
||||||
|
// @Summary Refresh VM details
|
||||||
|
// @Description Queries vCenter and updates inventory records with missing details.
|
||||||
|
// @Tags inventory
|
||||||
|
// @Produce text/plain
|
||||||
|
// @Success 200 {string} string "Update completed"
|
||||||
|
// @Failure 500 {string} string "Server error"
|
||||||
|
// @Router /api/inventory/vm/update [post]
|
||||||
func (h *Handler) VmUpdateDetails(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmUpdateDetails(w http.ResponseWriter, r *http.Request) {
|
||||||
var matchFound bool
|
var matchFound bool
|
||||||
var inventoryId int64
|
var inventoryId int64
|
||||||
|
|||||||
Reference in New Issue
Block a user