Compare commits
85 Commits
40fb860385
...
main
Author | SHA1 | Date | |
---|---|---|---|
dc120f2bdc | |||
c8ae94fb43 | |||
b46369811b | |||
fb40abfd48 | |||
b07ed9ee09 | |||
2a9489619d | |||
f86ec3d615 | |||
309db2f1a6 | |||
37d921f635 | |||
c9375f3099 | |||
81271873f3 | |||
665750548f | |||
ce1f28d9c3 | |||
1ecdb10cf7 | |||
cc6601146a | |||
9cdde0b278 | |||
f9b8e25c2f | |||
f94339446d | |||
77c1928436 | |||
f80dfe9027 | |||
b9c1f65971 | |||
3bc7f922d3 | |||
f28fed831a | |||
8f43603613 | |||
380707cf23 | |||
6a41528f41 | |||
5875550802 | |||
7665227ac6 | |||
9802419713 | |||
ea63ffa178 | |||
6f5d21fa71 | |||
3d86092816 | |||
5afbe9bb30 | |||
c4eedb55b7 | |||
a91642b450 | |||
fb47006809 | |||
3501967c9e | |||
5a00f4a8c7 | |||
a7dc838c83 | |||
d76bcf5ca5 | |||
78e1da3149 | |||
a18cca1f0e | |||
c691763430 | |||
b371e28469 | |||
54ff68590c | |||
f88b812fa9 | |||
dcbbff830d | |||
44c4bb2d66 | |||
3b0206b1e9 | |||
bc93fa4bad | |||
0c2aecd989 | |||
e1703e401b | |||
8931cb4891 | |||
00d474b937 | |||
f712c7254f | |||
dd13fd6759 | |||
3b53455343 | |||
2354d85a37 | |||
b8abc7e6fd | |||
8e399de31e | |||
fd64990e8e | |||
3c5aa418df | |||
7cc16819f7 | |||
c7c890f6bb | |||
47bc8acace | |||
2bae3e7541 | |||
08568e3600 | |||
c122e775a3 | |||
fb4a7a790d | |||
c0e6eec89d | |||
6d86a93539 | |||
a84c403a69 | |||
e47718cd7f | |||
4efdf50433 | |||
d2aac0c6d4 | |||
afb85ff34a | |||
659347ad87 | |||
cfa9c45e56 | |||
32e3bc6e66 | |||
ab24b5f6b9 | |||
56cf2e8366 | |||
282459ccf8 | |||
57980a860a | |||
85bb431de1 | |||
19d5b2406e |
@@ -23,14 +23,16 @@ do
|
||||
output_name+='.exe'
|
||||
fi
|
||||
|
||||
echo "build commences"
|
||||
starttime=$(TZ=Australia/Sydney date +%Y-%m-%dT%T%z)
|
||||
echo "build commences at $starttime"
|
||||
env GOOS=$GOOS GOARCH=$GOARCH go build -trimpath -ldflags="-X main.sha1ver=$commit -X main.buildTime=$buildtime" -o build/$output_name $package
|
||||
if [ $? -ne 0 ]; then
|
||||
echo 'An error has occurred! Aborting the script execution...'
|
||||
exit 1
|
||||
fi
|
||||
gzip build/$output_name
|
||||
echo "build complete at $buildtime : $output_name"
|
||||
sha256sum build/$output_name > build/${output_name}_checksum.txt
|
||||
sha256sum build/${output_name}.gz > build/${output_name}_checksum.txt
|
||||
done
|
||||
|
||||
ls -lah build
|
||||
|
@@ -3,7 +3,7 @@ package core
|
||||
templ Footer() {
|
||||
<footer class="fixed p-1 bottom-0 bg-gray-100 w-full border-t">
|
||||
<div class="rounded-lg p-4 text-xs italic text-gray-700 text-center">
|
||||
© Go Fullstack
|
||||
© Nathan Coad (nathan.coad@dell.com)
|
||||
</div>
|
||||
</footer>
|
||||
}
|
@@ -29,7 +29,7 @@ func Footer() templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"fixed p-1 bottom-0 bg-gray-100 w-full border-t\"><div class=\"rounded-lg p-4 text-xs italic text-gray-700 text-center\">© Go Fullstack</div></footer>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"fixed p-1 bottom-0 bg-gray-100 w-full border-t\"><div class=\"rounded-lg p-4 text-xs italic text-gray-700 text-center\">© Nathan Coad (nathan.coad@dell.com)</div></footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@@ -6,8 +6,8 @@ templ Header() {
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="description" content="Hello world"/>
|
||||
<title>Test Page</title>
|
||||
<meta name="description" content="vCTP API endpoint"/>
|
||||
<title>vCTP API</title>
|
||||
<script src="/assets/js/htmx@v2.0.2.min.js"></script>
|
||||
<link href={ "/assets/css/output@" + version.Value + ".css" } rel="stylesheet"/>
|
||||
</head>
|
||||
|
@@ -31,7 +31,7 @@ func Header() templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"Hello world\"><title>Test Page</title><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><link href=\"")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"vCTP API endpoint\"><title>vCTP API</title><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><link href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
29
db/db.go
29
db/db.go
@@ -82,19 +82,48 @@ func ConvertToSQLParams(input interface{}, output interface{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle fields of type sql.NullString, sql.NullInt64, and normal string/int64 fields
|
||||
switch outputField.Type() {
|
||||
case reflect.TypeOf(sql.NullString{}):
|
||||
// Handle 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{}):
|
||||
// Handle 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}))
|
||||
}
|
||||
|
||||
case reflect.TypeOf(sql.NullFloat64{}):
|
||||
// Handle sql.NullFloat64
|
||||
if inputField.Float() == 0 {
|
||||
outputField.Set(reflect.ValueOf(sql.NullFloat64{Valid: false}))
|
||||
} else {
|
||||
outputField.Set(reflect.ValueOf(sql.NullFloat64{Float64: inputField.Float(), Valid: true}))
|
||||
}
|
||||
|
||||
case reflect.TypeOf(""):
|
||||
// Handle normal string fields
|
||||
if inputField.Kind() == reflect.Ptr && inputField.IsNil() {
|
||||
outputField.SetString("") // Set to empty string if input is nil
|
||||
} else {
|
||||
outputField.SetString(inputField.String())
|
||||
}
|
||||
|
||||
case reflect.TypeOf(int64(0)):
|
||||
// Handle normal int64 fields
|
||||
outputField.SetInt(inputField.Int())
|
||||
|
||||
case reflect.TypeOf(float64(0)):
|
||||
// Handle normal float64 fields
|
||||
outputField.SetFloat(inputField.Float())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,8 +10,8 @@ ALTER TABLE "Events" ADD COLUMN VmName TEXT;
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE "Events" DROP COLUMN VmName;
|
||||
ALTER TABLE "Updates" DROP COLUMN ComputeResourceId;
|
||||
ALTER TABLE "Updates" DROP COLUMN DatacenterId;
|
||||
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/20240915015710_events_type.sql
Normal file
9
db/migrations/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/20240915232747_extend_inventory.sql
Normal file
11
db/migrations/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/20240916041639_rename_eventid.sql
Normal file
9
db/migrations/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/20240916045259_updates_disk.sql
Normal file
9
db/migrations/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/20240926022714_updates_user.sql
Normal file
9
db/migrations/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
|
56
db/migrations/20240927002029_change_inventory.sql
Normal file
56
db/migrations/20240927002029_change_inventory.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- +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/20240930012450_add_uuid.sql
Normal file
9
db/migrations/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/20240930031506_add_table.sql
Normal file
18
db/migrations/20240930031506_add_table.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS "InventoryHistory" (
|
||||
"Hid" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"InventoryId" INTEGER,
|
||||
"ReportDate" INTEGER,
|
||||
"UpdateTime" INTEGER,
|
||||
"PreviousVcpus" INTEGER,
|
||||
"PreviousRam" INTEGER,
|
||||
"PreviousResourcePool" TEXT,
|
||||
"PreviousProvisionedDisk" REAL
|
||||
)
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE "InventoryHistory";
|
||||
-- +goose StatementEnd
|
9
db/migrations/20241001222729_add_placeholder.sql
Normal file
9
db/migrations/20241001222729_add_placeholder.sql
Normal file
@@ -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/20241002032439_add_name.sql
Normal file
9
db/migrations/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/20241002033323_add_change.sql
Normal file
9
db/migrations/20241002033323_add_change.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE "Updates" ADD COLUMN RawChangeString BLOB;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE "Updates" DROP COLUMN RawChangeString;
|
||||
-- +goose StatementEnd
|
@@ -23,6 +23,7 @@ type Events struct {
|
||||
DatacenterId sql.NullString
|
||||
ComputeResourceId sql.NullString
|
||||
VmName sql.NullString
|
||||
EventType sql.NullString
|
||||
}
|
||||
|
||||
type Inventory struct {
|
||||
@@ -31,7 +32,7 @@ type Inventory struct {
|
||||
Vcenter string
|
||||
VmId sql.NullString
|
||||
EventKey sql.NullString
|
||||
EventId sql.NullString
|
||||
CloudId sql.NullString
|
||||
CreationTime sql.NullInt64
|
||||
DeletionTime sql.NullInt64
|
||||
ResourcePool sql.NullString
|
||||
@@ -42,17 +43,36 @@ type Inventory struct {
|
||||
ProvisionedDisk sql.NullFloat64
|
||||
InitialVcpus sql.NullInt64
|
||||
InitialRam sql.NullInt64
|
||||
SrmPlaceholder sql.NullInt64
|
||||
IsTemplate interface{}
|
||||
PoweredOn interface{}
|
||||
SrmPlaceholder interface{}
|
||||
VmUuid sql.NullString
|
||||
}
|
||||
|
||||
type InventoryHistory struct {
|
||||
Hid int64
|
||||
InventoryId sql.NullInt64
|
||||
ReportDate sql.NullInt64
|
||||
UpdateTime sql.NullInt64
|
||||
PreviousVcpus sql.NullInt64
|
||||
PreviousRam sql.NullInt64
|
||||
PreviousResourcePool sql.NullString
|
||||
PreviousProvisionedDisk sql.NullFloat64
|
||||
}
|
||||
|
||||
type Updates struct {
|
||||
Uid int64
|
||||
InventoryId sql.NullInt64
|
||||
UpdateTime sql.NullInt64
|
||||
UpdateType string
|
||||
NewVcpus sql.NullInt64
|
||||
NewRam sql.NullInt64
|
||||
NewResourcePool sql.NullString
|
||||
EventKey sql.NullString
|
||||
EventId sql.NullString
|
||||
Uid int64
|
||||
InventoryId sql.NullInt64
|
||||
UpdateTime sql.NullInt64
|
||||
UpdateType string
|
||||
NewVcpus sql.NullInt64
|
||||
NewRam sql.NullInt64
|
||||
NewResourcePool sql.NullString
|
||||
EventKey sql.NullString
|
||||
EventId sql.NullString
|
||||
NewProvisionedDisk sql.NullFloat64
|
||||
UserName sql.NullString
|
||||
PlaceholderChange sql.NullString
|
||||
Name sql.NullString
|
||||
RawChangeString []byte
|
||||
}
|
||||
|
@@ -2,39 +2,98 @@
|
||||
SELECT * FROM "Inventory"
|
||||
ORDER BY "Name";
|
||||
|
||||
-- name: GetReportInventory :many
|
||||
SELECT * FROM "Inventory"
|
||||
ORDER BY "CreationTime";
|
||||
|
||||
-- name: GetInventoryByName :many
|
||||
SELECT * FROM "Inventory"
|
||||
WHERE "Name" = ?;
|
||||
|
||||
-- name: GetInventoryByVcenter :many
|
||||
SELECT * FROM "Inventory"
|
||||
WHERE "Vcenter" = ?;
|
||||
|
||||
-- name: GetInventoryVmId :one
|
||||
SELECT * FROM "Inventory"
|
||||
WHERE "VmId" = ? LIMIT 1;
|
||||
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
|
||||
|
||||
-- name: GetInventoryVmUuid :one
|
||||
SELECT * FROM "Inventory"
|
||||
WHERE "VmUuid" = sqlc.arg('vmUuid') AND "Datacenter" = sqlc.arg('datacenterName');
|
||||
|
||||
-- name: GetInventoryVcUrl :many
|
||||
SELECT * FROM "Inventory"
|
||||
WHERE "Vcenter" = sqlc.arg('vc');
|
||||
|
||||
-- name: GetInventoryEventId :one
|
||||
SELECT * FROM "Inventory"
|
||||
WHERE "EventId" = ? LIMIT 1;
|
||||
WHERE "CloudId" = ? LIMIT 1;
|
||||
|
||||
-- name: CreateInventory :one
|
||||
INSERT INTO "Inventory" (
|
||||
"Name", "Vcenter", "VmId", "EventKey", "EventId", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
|
||||
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: InventoryUpdate :exec
|
||||
UPDATE "Inventory"
|
||||
SET "VmUuid" = sqlc.arg('uuid'), "SrmPlaceholder" = sqlc.arg('srmPlaceholder')
|
||||
WHERE "Iid" = sqlc.arg('iid');
|
||||
|
||||
-- name: InventoryMarkDeleted :exec
|
||||
UPDATE "Inventory"
|
||||
SET "DeletionTime" = sqlc.arg('deletionTime')
|
||||
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
|
||||
|
||||
-- name: InventoryCleanup :exec
|
||||
DELETE FROM "Inventory"
|
||||
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName')
|
||||
RETURNING *;
|
||||
|
||||
-- name: InventoryCleanupVcenter :exec
|
||||
DELETE FROM "Inventory"
|
||||
WHERE "Vcenter" = sqlc.arg('vc')
|
||||
RETURNING *;
|
||||
|
||||
-- name: InventoryCleanupTemplates :exec
|
||||
DELETE FROM "Inventory"
|
||||
WHERE "IsTemplate" = "TRUE"
|
||||
RETURNING *;
|
||||
|
||||
-- name: CreateUpdate :one
|
||||
INSERT INTO "Updates" (
|
||||
"InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool"
|
||||
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetReportUpdates :many
|
||||
SELECT * FROM "Updates"
|
||||
ORDER BY "UpdateTime";
|
||||
|
||||
-- name: GetVmUpdates :many
|
||||
SELECT * FROM "Updates"
|
||||
WHERE "UpdateType" = sqlc.arg('updateType') AND "InventoryId" = sqlc.arg('InventoryId');
|
||||
|
||||
-- name: CleanupUpdates :exec
|
||||
DELETE FROM "Updates"
|
||||
WHERE "UpdateType" = sqlc.arg('updateType') AND "UpdateTime" <= sqlc.arg('updateTime')
|
||||
RETURNING *;
|
||||
|
||||
-- name: CleanupUpdatesNullVm :exec
|
||||
DELETE FROM "Updates"
|
||||
WHERE "InventoryId" IS NULL
|
||||
RETURNING *;
|
||||
|
||||
-- name: CreateEvent :one
|
||||
INSERT INTO "Events" (
|
||||
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
||||
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
@@ -45,9 +104,18 @@ ORDER BY "EventTime";
|
||||
-- name: ListUnprocessedEvents :many
|
||||
SELECT * FROM "Events"
|
||||
WHERE "Processed" = 0
|
||||
AND "EventTime" > sqlc.arg('eventTime')
|
||||
ORDER BY "EventTime";
|
||||
|
||||
-- name: UpdateEventsProcessed :exec
|
||||
UPDATE "Events"
|
||||
SET "Processed" = 1
|
||||
WHERE "Eid" = sqlc.arg('eid');
|
||||
WHERE "Eid" = sqlc.arg('eid');
|
||||
|
||||
-- name: CreateInventoryHistory :one
|
||||
INSERT INTO "InventoryHistory" (
|
||||
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING *;
|
@@ -10,13 +10,40 @@ import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
const cleanupUpdates = `-- name: CleanupUpdates :exec
|
||||
DELETE FROM "Updates"
|
||||
WHERE "UpdateType" = ?1 AND "UpdateTime" <= ?2
|
||||
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
|
||||
`
|
||||
|
||||
type CleanupUpdatesParams struct {
|
||||
UpdateType string
|
||||
UpdateTime sql.NullInt64
|
||||
}
|
||||
|
||||
func (q *Queries) CleanupUpdates(ctx context.Context, arg CleanupUpdatesParams) error {
|
||||
_, err := q.db.ExecContext(ctx, cleanupUpdates, arg.UpdateType, arg.UpdateTime)
|
||||
return err
|
||||
}
|
||||
|
||||
const cleanupUpdatesNullVm = `-- name: CleanupUpdatesNullVm :exec
|
||||
DELETE FROM "Updates"
|
||||
WHERE "InventoryId" IS NULL
|
||||
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
|
||||
`
|
||||
|
||||
func (q *Queries) CleanupUpdatesNullVm(ctx context.Context) error {
|
||||
_, err := q.db.ExecContext(ctx, cleanupUpdatesNullVm)
|
||||
return err
|
||||
}
|
||||
|
||||
const createEvent = `-- name: CreateEvent :one
|
||||
INSERT INTO "Events" (
|
||||
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
||||
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName
|
||||
RETURNING Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType
|
||||
`
|
||||
|
||||
type CreateEventParams struct {
|
||||
@@ -26,6 +53,7 @@ type CreateEventParams struct {
|
||||
ChainId string
|
||||
VmId sql.NullString
|
||||
VmName sql.NullString
|
||||
EventType sql.NullString
|
||||
EventKey sql.NullString
|
||||
DatacenterId sql.NullString
|
||||
DatacenterName sql.NullString
|
||||
@@ -42,6 +70,7 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
|
||||
arg.ChainId,
|
||||
arg.VmId,
|
||||
arg.VmName,
|
||||
arg.EventType,
|
||||
arg.EventKey,
|
||||
arg.DatacenterId,
|
||||
arg.DatacenterName,
|
||||
@@ -65,35 +94,39 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
|
||||
&i.DatacenterId,
|
||||
&i.ComputeResourceId,
|
||||
&i.VmName,
|
||||
&i.EventType,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createInventory = `-- name: CreateInventory :one
|
||||
INSERT INTO "Inventory" (
|
||||
"Name", "Vcenter", "VmId", "EventKey", "EventId", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
|
||||
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder
|
||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||
`
|
||||
|
||||
type CreateInventoryParams struct {
|
||||
Name string
|
||||
Vcenter string
|
||||
VmId sql.NullString
|
||||
VmUuid sql.NullString
|
||||
EventKey sql.NullString
|
||||
EventId sql.NullString
|
||||
CloudId sql.NullString
|
||||
CreationTime sql.NullInt64
|
||||
ResourcePool sql.NullString
|
||||
VmType sql.NullString
|
||||
IsTemplate interface{}
|
||||
Datacenter sql.NullString
|
||||
Cluster sql.NullString
|
||||
Folder sql.NullString
|
||||
ProvisionedDisk sql.NullFloat64
|
||||
InitialVcpus sql.NullInt64
|
||||
InitialRam sql.NullInt64
|
||||
SrmPlaceholder sql.NullInt64
|
||||
SrmPlaceholder interface{}
|
||||
PoweredOn interface{}
|
||||
}
|
||||
|
||||
func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams) (Inventory, error) {
|
||||
@@ -101,11 +134,13 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
||||
arg.Name,
|
||||
arg.Vcenter,
|
||||
arg.VmId,
|
||||
arg.VmUuid,
|
||||
arg.EventKey,
|
||||
arg.EventId,
|
||||
arg.CloudId,
|
||||
arg.CreationTime,
|
||||
arg.ResourcePool,
|
||||
arg.VmType,
|
||||
arg.IsTemplate,
|
||||
arg.Datacenter,
|
||||
arg.Cluster,
|
||||
arg.Folder,
|
||||
@@ -113,6 +148,7 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
||||
arg.InitialVcpus,
|
||||
arg.InitialRam,
|
||||
arg.SrmPlaceholder,
|
||||
arg.PoweredOn,
|
||||
)
|
||||
var i Inventory
|
||||
err := row.Scan(
|
||||
@@ -121,7 +157,7 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
@@ -132,34 +168,86 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createInventoryHistory = `-- name: CreateInventoryHistory :one
|
||||
INSERT INTO "InventoryHistory" (
|
||||
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING Hid, InventoryId, ReportDate, UpdateTime, PreviousVcpus, PreviousRam, PreviousResourcePool, PreviousProvisionedDisk
|
||||
`
|
||||
|
||||
type CreateInventoryHistoryParams struct {
|
||||
InventoryId sql.NullInt64
|
||||
ReportDate sql.NullInt64
|
||||
UpdateTime sql.NullInt64
|
||||
PreviousVcpus sql.NullInt64
|
||||
PreviousRam sql.NullInt64
|
||||
PreviousResourcePool sql.NullString
|
||||
PreviousProvisionedDisk sql.NullFloat64
|
||||
}
|
||||
|
||||
func (q *Queries) CreateInventoryHistory(ctx context.Context, arg CreateInventoryHistoryParams) (InventoryHistory, error) {
|
||||
row := q.db.QueryRowContext(ctx, createInventoryHistory,
|
||||
arg.InventoryId,
|
||||
arg.ReportDate,
|
||||
arg.UpdateTime,
|
||||
arg.PreviousVcpus,
|
||||
arg.PreviousRam,
|
||||
arg.PreviousResourcePool,
|
||||
arg.PreviousProvisionedDisk,
|
||||
)
|
||||
var i InventoryHistory
|
||||
err := row.Scan(
|
||||
&i.Hid,
|
||||
&i.InventoryId,
|
||||
&i.ReportDate,
|
||||
&i.UpdateTime,
|
||||
&i.PreviousVcpus,
|
||||
&i.PreviousRam,
|
||||
&i.PreviousResourcePool,
|
||||
&i.PreviousProvisionedDisk,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createUpdate = `-- name: CreateUpdate :one
|
||||
INSERT INTO "Updates" (
|
||||
"InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool"
|
||||
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
|
||||
) VALUES(
|
||||
?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId
|
||||
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
|
||||
`
|
||||
|
||||
type CreateUpdateParams struct {
|
||||
InventoryId sql.NullInt64
|
||||
EventKey sql.NullString
|
||||
EventId sql.NullString
|
||||
UpdateTime sql.NullInt64
|
||||
UpdateType string
|
||||
NewVcpus sql.NullInt64
|
||||
NewRam sql.NullInt64
|
||||
NewResourcePool sql.NullString
|
||||
InventoryId sql.NullInt64
|
||||
Name sql.NullString
|
||||
EventKey sql.NullString
|
||||
EventId sql.NullString
|
||||
UpdateTime sql.NullInt64
|
||||
UpdateType string
|
||||
NewVcpus sql.NullInt64
|
||||
NewRam sql.NullInt64
|
||||
NewResourcePool sql.NullString
|
||||
NewProvisionedDisk sql.NullFloat64
|
||||
UserName sql.NullString
|
||||
PlaceholderChange sql.NullString
|
||||
RawChangeString []byte
|
||||
}
|
||||
|
||||
func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Updates, error) {
|
||||
row := q.db.QueryRowContext(ctx, createUpdate,
|
||||
arg.InventoryId,
|
||||
arg.Name,
|
||||
arg.EventKey,
|
||||
arg.EventId,
|
||||
arg.UpdateTime,
|
||||
@@ -167,6 +255,10 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
|
||||
arg.NewVcpus,
|
||||
arg.NewRam,
|
||||
arg.NewResourcePool,
|
||||
arg.NewProvisionedDisk,
|
||||
arg.UserName,
|
||||
arg.PlaceholderChange,
|
||||
arg.RawChangeString,
|
||||
)
|
||||
var i Updates
|
||||
err := row.Scan(
|
||||
@@ -179,12 +271,17 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
|
||||
&i.NewResourcePool,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.NewProvisionedDisk,
|
||||
&i.UserName,
|
||||
&i.PlaceholderChange,
|
||||
&i.Name,
|
||||
&i.RawChangeString,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getInventoryByName = `-- name: GetInventoryByName :many
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
WHERE "Name" = ?
|
||||
`
|
||||
|
||||
@@ -203,7 +300,7 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
@@ -214,7 +311,59 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getInventoryByVcenter = `-- name: GetInventoryByVcenter :many
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
WHERE "Vcenter" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetInventoryByVcenter(ctx context.Context, vcenter string) ([]Inventory, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getInventoryByVcenter, vcenter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Inventory
|
||||
for rows.Next() {
|
||||
var i Inventory
|
||||
if err := rows.Scan(
|
||||
&i.Iid,
|
||||
&i.Name,
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
&i.VmType,
|
||||
&i.Datacenter,
|
||||
&i.Cluster,
|
||||
&i.Folder,
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -230,12 +379,12 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
|
||||
}
|
||||
|
||||
const getInventoryEventId = `-- name: GetInventoryEventId :one
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
|
||||
WHERE "EventId" = ? LIMIT 1
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
WHERE "CloudId" = ? LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullString) (Inventory, error) {
|
||||
row := q.db.QueryRowContext(ctx, getInventoryEventId, eventid)
|
||||
func (q *Queries) GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (Inventory, error) {
|
||||
row := q.db.QueryRowContext(ctx, getInventoryEventId, cloudid)
|
||||
var i Inventory
|
||||
err := row.Scan(
|
||||
&i.Iid,
|
||||
@@ -243,7 +392,7 @@ func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullStrin
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
@@ -254,18 +403,75 @@ func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullStrin
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getInventoryVcUrl = `-- name: GetInventoryVcUrl :many
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
WHERE "Vcenter" = ?1
|
||||
`
|
||||
|
||||
func (q *Queries) GetInventoryVcUrl(ctx context.Context, vc string) ([]Inventory, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getInventoryVcUrl, vc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Inventory
|
||||
for rows.Next() {
|
||||
var i Inventory
|
||||
if err := rows.Scan(
|
||||
&i.Iid,
|
||||
&i.Name,
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
&i.VmType,
|
||||
&i.Datacenter,
|
||||
&i.Cluster,
|
||||
&i.Folder,
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getInventoryVmId = `-- name: GetInventoryVmId :one
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
|
||||
WHERE "VmId" = ? LIMIT 1
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
WHERE "VmId" = ?1 AND "Datacenter" = ?2
|
||||
`
|
||||
|
||||
func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (Inventory, error) {
|
||||
row := q.db.QueryRowContext(ctx, getInventoryVmId, vmid)
|
||||
type GetInventoryVmIdParams struct {
|
||||
VmId sql.NullString
|
||||
DatacenterName sql.NullString
|
||||
}
|
||||
|
||||
func (q *Queries) GetInventoryVmId(ctx context.Context, arg GetInventoryVmIdParams) (Inventory, error) {
|
||||
row := q.db.QueryRowContext(ctx, getInventoryVmId, arg.VmId, arg.DatacenterName)
|
||||
var i Inventory
|
||||
err := row.Scan(
|
||||
&i.Iid,
|
||||
@@ -273,7 +479,7 @@ func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (In
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
@@ -284,13 +490,266 @@ func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (In
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getInventoryVmUuid = `-- name: GetInventoryVmUuid :one
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
WHERE "VmUuid" = ?1 AND "Datacenter" = ?2
|
||||
`
|
||||
|
||||
type GetInventoryVmUuidParams struct {
|
||||
VmUuid sql.NullString
|
||||
DatacenterName sql.NullString
|
||||
}
|
||||
|
||||
func (q *Queries) GetInventoryVmUuid(ctx context.Context, arg GetInventoryVmUuidParams) (Inventory, error) {
|
||||
row := q.db.QueryRowContext(ctx, getInventoryVmUuid, arg.VmUuid, arg.DatacenterName)
|
||||
var i Inventory
|
||||
err := row.Scan(
|
||||
&i.Iid,
|
||||
&i.Name,
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
&i.VmType,
|
||||
&i.Datacenter,
|
||||
&i.Cluster,
|
||||
&i.Folder,
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getReportInventory = `-- name: GetReportInventory :many
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
ORDER BY "CreationTime"
|
||||
`
|
||||
|
||||
func (q *Queries) GetReportInventory(ctx context.Context) ([]Inventory, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getReportInventory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Inventory
|
||||
for rows.Next() {
|
||||
var i Inventory
|
||||
if err := rows.Scan(
|
||||
&i.Iid,
|
||||
&i.Name,
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
&i.VmType,
|
||||
&i.Datacenter,
|
||||
&i.Cluster,
|
||||
&i.Folder,
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getReportUpdates = `-- name: GetReportUpdates :many
|
||||
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM "Updates"
|
||||
ORDER BY "UpdateTime"
|
||||
`
|
||||
|
||||
func (q *Queries) GetReportUpdates(ctx context.Context) ([]Updates, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getReportUpdates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Updates
|
||||
for rows.Next() {
|
||||
var i Updates
|
||||
if err := rows.Scan(
|
||||
&i.Uid,
|
||||
&i.InventoryId,
|
||||
&i.UpdateTime,
|
||||
&i.UpdateType,
|
||||
&i.NewVcpus,
|
||||
&i.NewRam,
|
||||
&i.NewResourcePool,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.NewProvisionedDisk,
|
||||
&i.UserName,
|
||||
&i.PlaceholderChange,
|
||||
&i.Name,
|
||||
&i.RawChangeString,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getVmUpdates = `-- name: GetVmUpdates :many
|
||||
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM "Updates"
|
||||
WHERE "UpdateType" = ?1 AND "InventoryId" = ?2
|
||||
`
|
||||
|
||||
type GetVmUpdatesParams struct {
|
||||
UpdateType string
|
||||
InventoryId sql.NullInt64
|
||||
}
|
||||
|
||||
func (q *Queries) GetVmUpdates(ctx context.Context, arg GetVmUpdatesParams) ([]Updates, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getVmUpdates, arg.UpdateType, arg.InventoryId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Updates
|
||||
for rows.Next() {
|
||||
var i Updates
|
||||
if err := rows.Scan(
|
||||
&i.Uid,
|
||||
&i.InventoryId,
|
||||
&i.UpdateTime,
|
||||
&i.UpdateType,
|
||||
&i.NewVcpus,
|
||||
&i.NewRam,
|
||||
&i.NewResourcePool,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.NewProvisionedDisk,
|
||||
&i.UserName,
|
||||
&i.PlaceholderChange,
|
||||
&i.Name,
|
||||
&i.RawChangeString,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const inventoryCleanup = `-- name: InventoryCleanup :exec
|
||||
DELETE FROM "Inventory"
|
||||
WHERE "VmId" = ?1 AND "Datacenter" = ?2
|
||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||
`
|
||||
|
||||
type InventoryCleanupParams struct {
|
||||
VmId sql.NullString
|
||||
DatacenterName sql.NullString
|
||||
}
|
||||
|
||||
func (q *Queries) InventoryCleanup(ctx context.Context, arg InventoryCleanupParams) error {
|
||||
_, err := q.db.ExecContext(ctx, inventoryCleanup, arg.VmId, arg.DatacenterName)
|
||||
return err
|
||||
}
|
||||
|
||||
const inventoryCleanupTemplates = `-- name: InventoryCleanupTemplates :exec
|
||||
DELETE FROM "Inventory"
|
||||
WHERE "IsTemplate" = "TRUE"
|
||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||
`
|
||||
|
||||
func (q *Queries) InventoryCleanupTemplates(ctx context.Context) error {
|
||||
_, err := q.db.ExecContext(ctx, inventoryCleanupTemplates)
|
||||
return err
|
||||
}
|
||||
|
||||
const inventoryCleanupVcenter = `-- name: InventoryCleanupVcenter :exec
|
||||
DELETE FROM "Inventory"
|
||||
WHERE "Vcenter" = ?1
|
||||
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
|
||||
`
|
||||
|
||||
func (q *Queries) InventoryCleanupVcenter(ctx context.Context, vc string) error {
|
||||
_, err := q.db.ExecContext(ctx, inventoryCleanupVcenter, vc)
|
||||
return err
|
||||
}
|
||||
|
||||
const inventoryMarkDeleted = `-- name: InventoryMarkDeleted :exec
|
||||
UPDATE "Inventory"
|
||||
SET "DeletionTime" = ?1
|
||||
WHERE "VmId" = ?2 AND "Datacenter" = ?3
|
||||
`
|
||||
|
||||
type InventoryMarkDeletedParams struct {
|
||||
DeletionTime sql.NullInt64
|
||||
VmId sql.NullString
|
||||
DatacenterName sql.NullString
|
||||
}
|
||||
|
||||
func (q *Queries) InventoryMarkDeleted(ctx context.Context, arg InventoryMarkDeletedParams) error {
|
||||
_, err := q.db.ExecContext(ctx, inventoryMarkDeleted, arg.DeletionTime, arg.VmId, arg.DatacenterName)
|
||||
return err
|
||||
}
|
||||
|
||||
const inventoryUpdate = `-- name: InventoryUpdate :exec
|
||||
UPDATE "Inventory"
|
||||
SET "VmUuid" = ?1, "SrmPlaceholder" = ?2
|
||||
WHERE "Iid" = ?3
|
||||
`
|
||||
|
||||
type InventoryUpdateParams struct {
|
||||
Uuid sql.NullString
|
||||
SrmPlaceholder interface{}
|
||||
Iid int64
|
||||
}
|
||||
|
||||
func (q *Queries) InventoryUpdate(ctx context.Context, arg InventoryUpdateParams) error {
|
||||
_, err := q.db.ExecContext(ctx, inventoryUpdate, arg.Uuid, arg.SrmPlaceholder, arg.Iid)
|
||||
return err
|
||||
}
|
||||
|
||||
const listEvents = `-- name: ListEvents :many
|
||||
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName FROM "Events"
|
||||
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events"
|
||||
ORDER BY "EventTime"
|
||||
`
|
||||
|
||||
@@ -318,6 +777,7 @@ func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
|
||||
&i.DatacenterId,
|
||||
&i.ComputeResourceId,
|
||||
&i.VmName,
|
||||
&i.EventType,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -333,7 +793,7 @@ func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
|
||||
}
|
||||
|
||||
const listInventory = `-- name: ListInventory :many
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
|
||||
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||
ORDER BY "Name"
|
||||
`
|
||||
|
||||
@@ -352,7 +812,7 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
|
||||
&i.Vcenter,
|
||||
&i.VmId,
|
||||
&i.EventKey,
|
||||
&i.EventId,
|
||||
&i.CloudId,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.ResourcePool,
|
||||
@@ -363,7 +823,10 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.InitialRam,
|
||||
&i.IsTemplate,
|
||||
&i.PoweredOn,
|
||||
&i.SrmPlaceholder,
|
||||
&i.VmUuid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -379,13 +842,14 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
|
||||
}
|
||||
|
||||
const listUnprocessedEvents = `-- name: ListUnprocessedEvents :many
|
||||
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName FROM "Events"
|
||||
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events"
|
||||
WHERE "Processed" = 0
|
||||
AND "EventTime" > ?1
|
||||
ORDER BY "EventTime"
|
||||
`
|
||||
|
||||
func (q *Queries) ListUnprocessedEvents(ctx context.Context) ([]Events, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents)
|
||||
func (q *Queries) ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]Events, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents, eventtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -408,6 +872,7 @@ func (q *Queries) ListUnprocessedEvents(ctx context.Context) ([]Events, error) {
|
||||
&i.DatacenterId,
|
||||
&i.ComputeResourceId,
|
||||
&i.VmName,
|
||||
&i.EventType,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -1,25 +0,0 @@
|
||||
-- name: GetAuthor :one
|
||||
SELECT * FROM authors
|
||||
WHERE id = ? LIMIT 1;
|
||||
|
||||
-- name: ListAuthors :many
|
||||
SELECT * FROM authors
|
||||
ORDER BY name;
|
||||
|
||||
-- name: CreateAuthor :one
|
||||
INSERT INTO authors (
|
||||
name, bio
|
||||
) VALUES (
|
||||
?, ?
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateAuthor :exec
|
||||
UPDATE authors
|
||||
SET name = ?,
|
||||
bio = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: DeleteAuthor :exec
|
||||
DELETE FROM authors
|
||||
WHERE id = ?;
|
29
go.mod
29
go.mod
@@ -1,15 +1,17 @@
|
||||
module vctp
|
||||
|
||||
go 1.23.1
|
||||
go 1.23.2
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.778
|
||||
github.com/go-co-op/gocron/v2 v2.11.0
|
||||
github.com/go-co-op/gocron/v2 v2.12.1
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/pressly/goose/v3 v3.22.0
|
||||
github.com/vmware/govmomi v0.43.0
|
||||
modernc.org/sqlite v1.33.0
|
||||
github.com/pressly/goose/v3 v3.22.1
|
||||
github.com/vmware/govmomi v0.44.1
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
modernc.org/sqlite v1.33.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -19,16 +21,25 @@ require (
|
||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect
|
||||
modernc.org/libc v1.61.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
|
78
go.sum
78
go.sum
@@ -6,8 +6,8 @@ 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
|
||||
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
|
||||
github.com/go-co-op/gocron/v2 v2.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjjy1uw=
|
||||
github.com/go-co-op/gocron/v2 v2.12.1/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -24,6 +24,10 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -32,49 +36,107 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pressly/goose/v3 v3.22.0 h1:wd/7kNiPTuNAztWun7iaB98DrhulbWPrzMAaw2DEZNw=
|
||||
github.com/pressly/goose/v3 v3.22.0/go.mod h1:yJM3qwSj2pp7aAaCvso096sguezamNb2OBgxCnh/EYg=
|
||||
github.com/pressly/goose/v3 v3.22.1 h1:2zICEfr1O3yTP9BRZMGPj7qFxQ+ik6yeo+z1LMuioLc=
|
||||
github.com/pressly/goose/v3 v3.22.1/go.mod h1:xtMpbstWyCpyH+0cxLTMCENWBG+0CSxvTsXhW95d5eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/vmware/govmomi v0.43.0 h1:7Kg3Bkdly+TrE67BYXzRq7ZrDnn7xqpKX95uEh2f9Go=
|
||||
github.com/vmware/govmomi v0.43.0/go.mod h1:IOv5nTXCPqH9qVJAlRuAGffogaLsNs8aF+e7vLgsHJU=
|
||||
github.com/vmware/govmomi v0.44.1 h1:Hbt0nvVY8fnp3DGRJHngLflTos/uRrW54lhmJ/zKZFc=
|
||||
github.com/vmware/govmomi v0.44.1/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ=
|
||||
github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE=
|
||||
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
|
||||
github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a h1:CfbpOLEo2IwNzJdMvE8aiRbPMxoTpgAJeyePh0SmO8M=
|
||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY=
|
||||
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
|
||||
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk=
|
||||
modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
303
internal/report/create.go
Normal file
303
internal/report/create.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
"vctp/db"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func CreateInventoryReport(logger *slog.Logger, Database db.Database, ctx context.Context) ([]byte, error) {
|
||||
//var xlsx *excelize.File
|
||||
sheetName := "Inventory Report"
|
||||
var buffer bytes.Buffer
|
||||
var cell string
|
||||
|
||||
logger.Debug("Querying inventory table")
|
||||
results, err := Database.Queries().GetReportInventory(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Unable to query inventory table", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
logger.Error("Empty inventory results")
|
||||
return nil, fmt.Errorf("Empty inventory results")
|
||||
}
|
||||
|
||||
// Create excel workbook
|
||||
xlsx := excelize.NewFile()
|
||||
err = xlsx.SetSheetName("Sheet1", sheetName)
|
||||
if err != nil {
|
||||
logger.Error("Error setting sheet name", "error", err, "sheet_name", sheetName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the document properties
|
||||
err = xlsx.SetDocProps(&excelize.DocProperties{
|
||||
Creator: "json2excel",
|
||||
Created: time.Now().Format(time.RFC3339),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Error setting document properties", "error", err, "sheet_name", sheetName)
|
||||
}
|
||||
|
||||
// Use reflection to determine column headings from the first item
|
||||
firstItem := results[0]
|
||||
v := reflect.ValueOf(firstItem)
|
||||
typeOfItem := v.Type()
|
||||
|
||||
// Create column headers dynamically
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
column := string(rune('A'+i)) + "1" // A1, B1, C1, etc.
|
||||
xlsx.SetCellValue(sheetName, column, typeOfItem.Field(i).Name)
|
||||
}
|
||||
|
||||
// Set autofilter on heading row
|
||||
cell, _ = excelize.CoordinatesToCellName(v.NumField(), 1)
|
||||
filterRange := "A1:" + cell
|
||||
logger.Debug("Setting autofilter", "range", filterRange)
|
||||
// As per docs any filters applied need to be manually processed by us (eg hiding rows with blanks)
|
||||
err = xlsx.AutoFilter(sheetName, filterRange, nil)
|
||||
if err != nil {
|
||||
logger.Error("Error setting autofilter", "error", err)
|
||||
}
|
||||
|
||||
// Bold top row
|
||||
headerStyle, err := xlsx.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{
|
||||
Bold: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Error generating header style", "error", err)
|
||||
} else {
|
||||
err = xlsx.SetRowStyle(sheetName, 1, 1, headerStyle)
|
||||
if err != nil {
|
||||
logger.Error("Error setting header style", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the Excel file with data from the Inventory table
|
||||
for i, item := range results {
|
||||
v = reflect.ValueOf(item)
|
||||
for j := 0; j < v.NumField(); j++ {
|
||||
column := string(rune('A'+j)) + strconv.Itoa(i+2) // Start from row 2
|
||||
value := getFieldValue(v.Field(j))
|
||||
xlsx.SetCellValue(sheetName, column, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Freeze top row
|
||||
err = xlsx.SetPanes(sheetName, &excelize.Panes{
|
||||
Freeze: true,
|
||||
Split: false,
|
||||
XSplit: 0,
|
||||
YSplit: 1,
|
||||
TopLeftCell: "A2",
|
||||
ActivePane: "bottomLeft",
|
||||
Selection: []excelize.Selection{
|
||||
{SQRef: "A2", ActiveCell: "A2", Pane: "bottomLeft"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Error freezing top row", "error", err)
|
||||
}
|
||||
|
||||
// Set column autowidth
|
||||
/*
|
||||
err = SetColAutoWidth(xlsx, sheetName)
|
||||
if err != nil {
|
||||
fmt.Printf("Error setting auto width : '%s'\n", err)
|
||||
}
|
||||
*/
|
||||
|
||||
// Save the Excel file into a byte buffer
|
||||
if err := xlsx.Write(&buffer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func CreateUpdatesReport(logger *slog.Logger, Database db.Database, ctx context.Context) ([]byte, error) {
|
||||
//var xlsx *excelize.File
|
||||
sheetName := "Updates Report"
|
||||
var buffer bytes.Buffer
|
||||
var cell string
|
||||
|
||||
logger.Debug("Querying updates table")
|
||||
results, err := Database.Queries().GetReportUpdates(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Unable to query updates table", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
logger.Error("Empty updates results")
|
||||
return nil, fmt.Errorf("Empty updates results")
|
||||
}
|
||||
|
||||
// Create excel workbook
|
||||
xlsx := excelize.NewFile()
|
||||
err = xlsx.SetSheetName("Sheet1", sheetName)
|
||||
if err != nil {
|
||||
logger.Error("Error setting sheet name", "error", err, "sheet_name", sheetName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the document properties
|
||||
err = xlsx.SetDocProps(&excelize.DocProperties{
|
||||
Creator: "json2excel",
|
||||
Created: time.Now().Format(time.RFC3339),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Error setting document properties", "error", err, "sheet_name", sheetName)
|
||||
}
|
||||
|
||||
// Use reflection to determine column headings from the first item
|
||||
firstItem := results[0]
|
||||
v := reflect.ValueOf(firstItem)
|
||||
typeOfItem := v.Type()
|
||||
|
||||
// Create column headers dynamically
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
column := string(rune('A'+i)) + "1" // A1, B1, C1, etc.
|
||||
xlsx.SetCellValue(sheetName, column, typeOfItem.Field(i).Name)
|
||||
}
|
||||
|
||||
// Set autofilter on heading row
|
||||
cell, _ = excelize.CoordinatesToCellName(v.NumField(), 1)
|
||||
filterRange := "A1:" + cell
|
||||
logger.Debug("Setting autofilter", "range", filterRange)
|
||||
// As per docs any filters applied need to be manually processed by us (eg hiding rows with blanks)
|
||||
err = xlsx.AutoFilter(sheetName, filterRange, nil)
|
||||
if err != nil {
|
||||
logger.Error("Error setting autofilter", "error", err)
|
||||
}
|
||||
|
||||
// Bold top row
|
||||
headerStyle, err := xlsx.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{
|
||||
Bold: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Error generating header style", "error", err)
|
||||
} else {
|
||||
err = xlsx.SetRowStyle(sheetName, 1, 1, headerStyle)
|
||||
if err != nil {
|
||||
logger.Error("Error setting header style", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the Excel file with data from the Inventory table
|
||||
for i, item := range results {
|
||||
v = reflect.ValueOf(item)
|
||||
for j := 0; j < v.NumField(); j++ {
|
||||
column := string(rune('A'+j)) + strconv.Itoa(i+2) // Start from row 2
|
||||
value := getFieldValue(v.Field(j))
|
||||
xlsx.SetCellValue(sheetName, column, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Freeze top row
|
||||
err = xlsx.SetPanes(sheetName, &excelize.Panes{
|
||||
Freeze: true,
|
||||
Split: false,
|
||||
XSplit: 0,
|
||||
YSplit: 1,
|
||||
TopLeftCell: "A2",
|
||||
ActivePane: "bottomLeft",
|
||||
Selection: []excelize.Selection{
|
||||
{SQRef: "A2", ActiveCell: "A2", Pane: "bottomLeft"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Error freezing top row", "error", err)
|
||||
}
|
||||
|
||||
// Set column autowidth
|
||||
/*
|
||||
err = SetColAutoWidth(xlsx, sheetName)
|
||||
if err != nil {
|
||||
fmt.Printf("Error setting auto width : '%s'\n", err)
|
||||
}
|
||||
*/
|
||||
|
||||
// Save the Excel file into a byte buffer
|
||||
if err := xlsx.Write(&buffer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// Helper function to get the actual value of sql.Null types
|
||||
func getFieldValue(field reflect.Value) interface{} {
|
||||
switch field.Kind() {
|
||||
case reflect.Struct:
|
||||
// Handle sql.Null types based on their concrete type
|
||||
switch field.Interface().(type) {
|
||||
case sql.NullString:
|
||||
ns := field.Interface().(sql.NullString)
|
||||
if ns.Valid {
|
||||
return ns.String
|
||||
}
|
||||
return ""
|
||||
case sql.NullInt64:
|
||||
ni := field.Interface().(sql.NullInt64)
|
||||
if ni.Valid {
|
||||
return ni.Int64
|
||||
}
|
||||
return -1
|
||||
case sql.NullFloat64:
|
||||
nf := field.Interface().(sql.NullFloat64)
|
||||
if nf.Valid {
|
||||
return nf.Float64
|
||||
}
|
||||
return nil
|
||||
case sql.NullBool:
|
||||
nb := field.Interface().(sql.NullBool)
|
||||
if nb.Valid {
|
||||
return nb.Bool
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return field.Interface() // Return the value as-is for non-sql.Null types
|
||||
}
|
||||
|
||||
// Taken from https://github.com/qax-os/excelize/issues/92#issuecomment-821578446
|
||||
func SetColAutoWidth(xlsx *excelize.File, sheetName string) error {
|
||||
// Autofit all columns according to their text content
|
||||
cols, err := xlsx.GetCols(sheetName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for idx, col := range cols {
|
||||
largestWidth := 0
|
||||
for _, rowCell := range col {
|
||||
cellWidth := utf8.RuneCountInString(rowCell) + 2 // + 2 for margin
|
||||
if cellWidth > largestWidth {
|
||||
largestWidth = cellWidth
|
||||
}
|
||||
}
|
||||
//fmt.Printf("SetColAutoWidth calculated largest width for column index '%d' is '%d'\n", idx, largestWidth)
|
||||
name, err := excelize.ColumnNumberToName(idx + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xlsx.SetColWidth(sheetName, name, name, float64(largestWidth))
|
||||
}
|
||||
// No errors at this point
|
||||
return nil
|
||||
}
|
80
internal/secrets/secrets.go
Normal file
80
internal/secrets/secrets.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type Secrets struct {
|
||||
Logger *slog.Logger
|
||||
EncryptionKey []byte
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, key []byte) *Secrets {
|
||||
return &Secrets{
|
||||
Logger: logger,
|
||||
EncryptionKey: key,
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt function that encrypts data using AES256-GCM and returns base64 encoded ciphertext
|
||||
func (s *Secrets) Encrypt(plainText []byte) (string, error) {
|
||||
block, err := aes.NewCipher(s.EncryptionKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create a new GCM cipher
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create a nonce
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Encrypt the plaintext using AES256-GCM
|
||||
cipherText := gcm.Seal(nonce, nonce, plainText, nil)
|
||||
|
||||
// Return the base64 encoded ciphertext
|
||||
return base64.StdEncoding.EncodeToString(cipherText), nil
|
||||
}
|
||||
|
||||
// Decrypt function that decrypts base64 encoded AES256-GCM ciphertext
|
||||
func (s *Secrets) Decrypt(base64CipherText string) ([]byte, error) {
|
||||
// Decode the base64 ciphertext
|
||||
cipherText, err := base64.StdEncoding.DecodeString(base64CipherText)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(s.EncryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new GCM cipher
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract the nonce from the ciphertext
|
||||
nonceSize := gcm.NonceSize()
|
||||
nonce, cipherText := cipherText[:nonceSize], cipherText[nonceSize:]
|
||||
|
||||
// Decrypt the ciphertext
|
||||
plainText, err := gcm.Open(nil, nonce, cipherText, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plainText, nil
|
||||
}
|
67
internal/settings/settings.go
Normal file
67
internal/settings/settings.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"vctp/internal/utils"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
SettingsPath string
|
||||
Logger *slog.Logger
|
||||
Values *SettingsYML
|
||||
}
|
||||
|
||||
// SettingsYML struct holds various runtime data that is too cumbersome to specify via command line, eg replacement properties
|
||||
type SettingsYML struct {
|
||||
Settings struct {
|
||||
TenantsToFilter []string `yaml:"tenants_to_filter"`
|
||||
NodeChargeClusters []string `yaml:"node_charge_clusters"`
|
||||
SrmActiveActiveVms []string `yaml:"srm_activeactive_vms"`
|
||||
VcenterAddresses []string `yaml:"vcenter_addresses"`
|
||||
} `yaml:"settings"`
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, settingsPath string) *Settings {
|
||||
return &Settings{
|
||||
SettingsPath: utils.GetFilePath(settingsPath),
|
||||
Logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Settings) ReadYMLSettings() error {
|
||||
// Create config structure
|
||||
var settings SettingsYML
|
||||
|
||||
// Check for empty filename
|
||||
if len(s.SettingsPath) == 0 {
|
||||
return errors.New("settings file path not specified")
|
||||
}
|
||||
|
||||
//path := utils.GetFilePath(settingsPath)
|
||||
|
||||
// Open config file
|
||||
file, err := os.Open(s.SettingsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open settings file : '%s'", err)
|
||||
}
|
||||
s.Logger.Debug("Opened settings yaml file", "file_path", s.SettingsPath)
|
||||
defer file.Close()
|
||||
|
||||
// Init new YAML decode
|
||||
d := yaml.NewDecoder(file)
|
||||
|
||||
// Start YAML decoding from file
|
||||
if err := d.Decode(&settings); err != nil {
|
||||
return fmt.Errorf("unable to decode settings file : '%s'", err)
|
||||
}
|
||||
|
||||
s.Logger.Debug("Updating settings", "settings", settings)
|
||||
s.Values = &settings
|
||||
|
||||
return nil
|
||||
}
|
429
internal/tasks/monitorVcenter.go
Normal file
429
internal/tasks/monitorVcenter.go
Normal file
@@ -0,0 +1,429 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"vctp/db/queries"
|
||||
"vctp/internal/utils"
|
||||
"vctp/internal/vcenter"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// use gocron to check vcenters for VMs or updates we don't know about
|
||||
func (c *CronTask) RunVcenterPoll(ctx context.Context, logger *slog.Logger) error {
|
||||
var matchFound bool
|
||||
|
||||
// reload settings in case vcenter list has changed
|
||||
c.Settings.ReadYMLSettings()
|
||||
|
||||
for _, url := range c.Settings.Values.Settings.VcenterAddresses {
|
||||
c.Logger.Debug("connecting to vcenter", "url", url)
|
||||
vc := vcenter.New(c.Logger, c.VcCreds)
|
||||
vc.Login(url)
|
||||
|
||||
// Get list of VMs from vcenter
|
||||
vcVms, err := vc.GetAllVmReferences()
|
||||
|
||||
// Get list of VMs from inventory table
|
||||
c.Logger.Debug("Querying inventory table")
|
||||
results, err := c.Database.Queries().GetInventoryByVcenter(ctx, url)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to query inventory table", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
c.Logger.Error("Empty inventory results")
|
||||
return fmt.Errorf("Empty inventory results")
|
||||
}
|
||||
|
||||
// Iterate VMs from vcenter and see if they were in the database
|
||||
for _, vm := range vcVms {
|
||||
matchFound = false
|
||||
|
||||
// Skip any vCLS VMs
|
||||
if strings.HasPrefix(vm.Name(), "vCLS-") {
|
||||
//c.Logger.Debug("Skipping internal VM", "vm_name", vm.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO - should we compare the UUID as well?
|
||||
for _, dbvm := range results {
|
||||
if dbvm.VmId.String == vm.Reference().Value {
|
||||
//c.Logger.Debug("Found match for VM", "vm_name", dbvm.Name, "id", dbvm.VmId.String)
|
||||
matchFound = true
|
||||
|
||||
// Get the full VM object
|
||||
vmObj, err := vc.ConvertObjToMoVM(vm)
|
||||
if err != nil {
|
||||
c.Logger.Error("Failed to find VM in vcenter", "vm_id", dbvm.VmId.String, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if vmObj.Config == nil {
|
||||
c.Logger.Error("VM has no config properties", "vm_id", dbvm.VmId.String, "vm_name", vmObj.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that this is definitely the right VM
|
||||
if dbvm.VmUuid.String == vmObj.Config.Uuid {
|
||||
// TODO - compare database against current values, create update record if not matching
|
||||
err = c.UpdateVmInventory(vmObj, vc, ctx, dbvm)
|
||||
} else {
|
||||
c.Logger.Error("VM uuid doesn't match database record", "vm_name", dbvm.Name, "id", dbvm.VmId.String, "vc_uuid", vmObj.Config.Uuid, "db_uuid", dbvm.VmUuid.String)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matchFound {
|
||||
c.Logger.Debug("Need to add VM to inventory table", "MoRef", vm.Reference())
|
||||
vmObj, err := vc.ConvertObjToMoVM(vm)
|
||||
if err != nil {
|
||||
c.Logger.Error("Received error getting vm maangedobject", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// retrieve VM properties and insert into inventory
|
||||
err = c.AddVmToInventory(vmObj, vc, ctx)
|
||||
if err != nil {
|
||||
c.Logger.Error("Received error with VM add", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// add sleep to slow down mass VM additions
|
||||
utils.SleepWithContext(ctx, (10 * time.Millisecond))
|
||||
}
|
||||
}
|
||||
c.Logger.Debug("Finished checking vcenter", "url", url)
|
||||
vc.Logout()
|
||||
}
|
||||
|
||||
c.Logger.Debug("Finished polling vcenters")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateVmInventory will compare database against current vcenter values, and create update record if not matching
|
||||
func (c *CronTask) UpdateVmInventory(vmObj *mo.VirtualMachine, vc *vcenter.Vcenter, ctx context.Context, dbVm queries.Inventory) error {
|
||||
var (
|
||||
err error
|
||||
numVcpus int32
|
||||
numRam int32
|
||||
srmPlaceholder string
|
||||
updateType string
|
||||
rpName string
|
||||
existingUpdateFound bool
|
||||
)
|
||||
|
||||
// TODO - how to prevent creating a new record every polling cycle?
|
||||
|
||||
params := queries.CreateUpdateParams{
|
||||
InventoryId: sql.NullInt64{Int64: dbVm.Iid, Valid: dbVm.Iid > 0},
|
||||
}
|
||||
srmPlaceholder = "FALSE" // default value
|
||||
updateType = "unknown" // default value
|
||||
existingUpdateFound = false // default value
|
||||
numRam = vmObj.Config.Hardware.MemoryMB
|
||||
numVcpus = vmObj.Config.Hardware.NumCPU
|
||||
|
||||
if numRam != int32(dbVm.InitialRam.Int64) {
|
||||
params.NewRam = sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0}
|
||||
updateType = "reconfigure"
|
||||
}
|
||||
|
||||
if numVcpus != int32(dbVm.InitialVcpus.Int64) {
|
||||
params.NewVcpus = sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0}
|
||||
updateType = "reconfigure"
|
||||
}
|
||||
|
||||
// Determine if the VM is a normal VM or an SRM placeholder
|
||||
if vmObj.Config.ManagedBy != nil && vmObj.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
|
||||
if vmObj.Config.ManagedBy.Type == "placeholderVm" {
|
||||
c.Logger.Debug("VM is a placeholder")
|
||||
srmPlaceholder = "TRUE"
|
||||
} else {
|
||||
//c.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObj.Config.ManagedBy)
|
||||
}
|
||||
}
|
||||
|
||||
if srmPlaceholder != dbVm.SrmPlaceholder {
|
||||
c.Logger.Debug("VM has changed placeholder type", "db_value", dbVm.SrmPlaceholder, "current_Value", srmPlaceholder)
|
||||
params.PlaceholderChange = sql.NullString{String: srmPlaceholder, Valid: srmPlaceholder != ""}
|
||||
if updateType == "unknown" {
|
||||
updateType = "srm"
|
||||
}
|
||||
}
|
||||
|
||||
rpName, err = vc.GetVmResourcePool(*vmObj)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to determine resource pool name", "error", err)
|
||||
}
|
||||
if rpName != dbVm.ResourcePool.String {
|
||||
c.Logger.Debug("VM has changed resource pool", "db_value", dbVm.ResourcePool.String, "current_Value", rpName)
|
||||
params.NewResourcePool = sql.NullString{String: rpName, Valid: rpName != ""}
|
||||
if updateType == "unknown" {
|
||||
updateType = "move"
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - should we bother to check if disk space has changed?
|
||||
|
||||
if updateType != "unknown" {
|
||||
|
||||
// TODO query updates table to see if there is already an update of this type and the new value
|
||||
|
||||
checkParams := queries.GetVmUpdatesParams{
|
||||
InventoryId: sql.NullInt64{Int64: dbVm.Iid, Valid: dbVm.Iid > 0},
|
||||
UpdateType: updateType,
|
||||
}
|
||||
|
||||
existingUpdates, err := c.Database.Queries().GetVmUpdates(ctx, checkParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
c.Logger.Debug("No update records found")
|
||||
} else {
|
||||
c.Logger.Error("Unbale to query database for vm update records", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range existingUpdates {
|
||||
// check if we already recorded this same update
|
||||
if u.UpdateType == updateType {
|
||||
switch u.UpdateType {
|
||||
case "srm":
|
||||
if u.PlaceholderChange.String == srmPlaceholder {
|
||||
c.Logger.Debug("SRM update already exists for vm", "update_value", u.PlaceholderChange.String, "inventory_id", u.InventoryId.Int64, "vm_name", u.Name.String)
|
||||
existingUpdateFound = true
|
||||
}
|
||||
case "move":
|
||||
if u.NewResourcePool.String == rpName {
|
||||
c.Logger.Debug("Resource pool update already exists for vm", "update_value", u.NewResourcePool.String, "inventory_id", u.InventoryId.Int64, "vm_name", u.Name.String)
|
||||
existingUpdateFound = true
|
||||
}
|
||||
case "reconfigure":
|
||||
if u.NewRam.Int64 == int64(numRam) || u.NewVcpus.Int64 == int64(numVcpus) {
|
||||
c.Logger.Debug("RAM/vCPU update already exists for vm", "update_ram", u.NewRam.Int64, "update_vcpu", u.NewVcpus.Int64, "inventory_id", u.InventoryId.Int64, "vm_name", u.Name.String)
|
||||
existingUpdateFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !existingUpdateFound {
|
||||
params.UpdateType = updateType
|
||||
updateTime := time.Now().Unix()
|
||||
params.UpdateTime = sql.NullInt64{Int64: updateTime, Valid: updateTime > 0}
|
||||
c.Logger.Info("Detected new change in VM, inserting update record into database", "update_type", updateType, "params", params)
|
||||
|
||||
result, err := c.Database.Queries().CreateUpdate(ctx, params)
|
||||
if err != nil {
|
||||
c.Logger.Error("Failed creating database record", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
c.Logger.Debug("created database record", "insert_result", result)
|
||||
// add sleep to slow down mass VM additions
|
||||
utils.SleepWithContext(ctx, (10 * time.Millisecond))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CronTask) AddVmToInventory(vmObject *mo.VirtualMachine, vc *vcenter.Vcenter, ctx context.Context) error {
|
||||
var (
|
||||
numVcpus int32
|
||||
numRam int32
|
||||
totalDiskGB float64
|
||||
creationTS int64
|
||||
srmPlaceholder string
|
||||
foundVmConfig bool
|
||||
isTemplate string
|
||||
poweredOn string
|
||||
folderPath string
|
||||
clusterName string
|
||||
err error
|
||||
)
|
||||
|
||||
if vmObject == nil {
|
||||
return errors.New("can't process empty vm object")
|
||||
}
|
||||
|
||||
c.Logger.Debug("found VM")
|
||||
|
||||
/*
|
||||
if vmObject.Name == "DBRaaS_testVMTemplate" {
|
||||
c.Logger.Debug("Found problematic VM")
|
||||
//prettyPrint(vmObject)
|
||||
}
|
||||
*/
|
||||
|
||||
// calculate VM properties we want to store
|
||||
if vmObject.Config != nil {
|
||||
// Skip any template VMs
|
||||
if vmObject.Config.Template {
|
||||
c.Logger.Debug("Not adding templates to inventory")
|
||||
return nil
|
||||
} else {
|
||||
isTemplate = "FALSE"
|
||||
}
|
||||
|
||||
numRam = vmObject.Config.Hardware.MemoryMB
|
||||
numVcpus = vmObject.Config.Hardware.NumCPU
|
||||
srmPlaceholder = "FALSE" // Default assumption
|
||||
|
||||
// Calculate creation date
|
||||
if vmObject.Config.CreateDate.IsZero() {
|
||||
c.Logger.Debug("Creation date not available for this VM")
|
||||
} else {
|
||||
creationTS = vmObject.Config.CreateDate.Unix()
|
||||
}
|
||||
|
||||
// Calculate disk size
|
||||
var totalDiskBytes int64
|
||||
|
||||
// Calculate the total disk allocated in GB
|
||||
for _, device := range vmObject.Config.Hardware.Device {
|
||||
if disk, ok := device.(*types.VirtualDisk); ok {
|
||||
|
||||
// Print the filename of the backing device
|
||||
if backing, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
||||
c.Logger.Debug("Adding disk", "size_bytes", disk.CapacityInBytes, "backing_file", backing.FileName)
|
||||
} else {
|
||||
c.Logger.Debug("Adding disk, unknown backing type", "size_bytes", disk.CapacityInBytes)
|
||||
}
|
||||
|
||||
totalDiskBytes += disk.CapacityInBytes
|
||||
}
|
||||
}
|
||||
totalDiskGB = float64(totalDiskBytes / 1024 / 1024 / 1024)
|
||||
c.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB)
|
||||
|
||||
// Determine if the VM is a normal VM or an SRM placeholder
|
||||
if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
|
||||
if vmObject.Config.ManagedBy.Type == "placeholderVm" {
|
||||
c.Logger.Debug("VM is a placeholder")
|
||||
srmPlaceholder = "TRUE"
|
||||
} else {
|
||||
c.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObject.Config.ManagedBy)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the full folder path of the VM
|
||||
folderPath, err = vc.GetVMFolderPath(*vmObject)
|
||||
if err != nil {
|
||||
c.Logger.Error("failed to get vm folder path", "error", err)
|
||||
folderPath = ""
|
||||
} else {
|
||||
c.Logger.Debug("Found vm folder path", "folder_path", folderPath)
|
||||
}
|
||||
|
||||
foundVmConfig = true
|
||||
} else {
|
||||
c.Logger.Warn("Empty VM config")
|
||||
}
|
||||
|
||||
//c.Logger.Debug("VM has runtime data", "power_state", vmObject.Runtime.PowerState)
|
||||
if vmObject.Runtime.PowerState == "poweredOff" {
|
||||
poweredOn = "FALSE"
|
||||
} else {
|
||||
poweredOn = "TRUE"
|
||||
}
|
||||
|
||||
rpName, err := vc.GetVmResourcePool(*vmObject)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to determine resource pool name", "error", err)
|
||||
}
|
||||
|
||||
// Get VM's host and use that to determine cluster
|
||||
//c.Logger.Debug("Checking for VM host by runtime data", "runtime", vmObject.Runtime)
|
||||
clusterName, err = vc.GetClusterFromHost(vmObject.Runtime.Host)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to determine cluster name", "error", err)
|
||||
} else {
|
||||
c.Logger.Debug("cluster", "name", clusterName)
|
||||
}
|
||||
|
||||
dcName, err := vc.GetDatacenterForVM(*vmObject)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to determine datacenter name", "error", err)
|
||||
} else {
|
||||
c.Logger.Debug("dc", "name", dcName)
|
||||
}
|
||||
|
||||
if foundVmConfig {
|
||||
c.Logger.Debug("Adding to Inventory table", "vm_name", vmObject.Name, "vcpus", numVcpus, "ram", numRam)
|
||||
|
||||
params := queries.CreateInventoryParams{
|
||||
Name: vmObject.Name,
|
||||
Vcenter: vc.Vurl,
|
||||
VmId: sql.NullString{String: vmObject.Reference().Value, Valid: vmObject.Reference().Value != ""},
|
||||
Datacenter: sql.NullString{String: dcName, Valid: dcName != ""},
|
||||
Cluster: sql.NullString{String: clusterName, Valid: clusterName != ""},
|
||||
CreationTime: sql.NullInt64{Int64: creationTS, Valid: creationTS > 0},
|
||||
InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0},
|
||||
InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0},
|
||||
ProvisionedDisk: sql.NullFloat64{Float64: totalDiskGB, Valid: totalDiskGB > 0},
|
||||
Folder: sql.NullString{String: folderPath, Valid: folderPath != ""},
|
||||
ResourcePool: sql.NullString{String: rpName, Valid: rpName != ""},
|
||||
VmUuid: sql.NullString{String: vmObject.Config.Uuid, Valid: vmObject.Config.Uuid != ""},
|
||||
SrmPlaceholder: srmPlaceholder,
|
||||
IsTemplate: isTemplate,
|
||||
PoweredOn: poweredOn,
|
||||
}
|
||||
|
||||
c.Logger.Debug("database params", "params", params)
|
||||
// Insert the new inventory record into the database
|
||||
result, err := c.Database.Queries().CreateInventory(ctx, params)
|
||||
if err != nil {
|
||||
c.Logger.Error("unable to perform database insert", "error", err)
|
||||
} else {
|
||||
c.Logger.Debug("created database record", "insert_result", result)
|
||||
}
|
||||
} else {
|
||||
c.Logger.Debug("Not adding to Inventory due to missing vcenter config property", "vm_name", vmObject.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// prettyPrint comes from https://gist.github.com/sfate/9d45f6c5405dc4c9bf63bf95fe6d1a7c
|
||||
func prettyPrint(args ...interface{}) {
|
||||
var caller string
|
||||
|
||||
timeNow := time.Now().Format("01-02-2006 15:04:05")
|
||||
prefix := fmt.Sprintf("[%s] %s -- ", "PrettyPrint", timeNow)
|
||||
_, fileName, fileLine, ok := runtime.Caller(1)
|
||||
|
||||
if ok {
|
||||
caller = fmt.Sprintf("%s:%d", fileName, fileLine)
|
||||
} else {
|
||||
caller = ""
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s%s\n", prefix, caller)
|
||||
|
||||
if len(args) == 2 {
|
||||
label := args[0]
|
||||
value := args[1]
|
||||
|
||||
s, _ := json.MarshalIndent(value, "", "\t")
|
||||
fmt.Printf("%s%s: %s\n", prefix, label, string(s))
|
||||
} else {
|
||||
s, _ := json.MarshalIndent(args, "", "\t")
|
||||
fmt.Printf("%s%s\n", prefix, string(s))
|
||||
}
|
||||
}
|
@@ -4,38 +4,40 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
"vctp/db"
|
||||
"vctp/db/queries"
|
||||
"vctp/internal/vcenter"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// Handler handles requests.
|
||||
type CronTask struct {
|
||||
Logger *slog.Logger
|
||||
Database db.Database
|
||||
}
|
||||
|
||||
// use gocron to check events in the Events table
|
||||
func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
|
||||
var (
|
||||
//unixTimestamp int64
|
||||
numVcpus int32
|
||||
numRam int32
|
||||
totalDiskGB float64
|
||||
srmPlaceholder int
|
||||
srmPlaceholder string
|
||||
foundVm bool
|
||||
isTemplate string
|
||||
poweredOn string
|
||||
folderPath string
|
||||
rpName string
|
||||
vmUuid string
|
||||
)
|
||||
|
||||
logger.Debug("Started Events processing", "time", time.Now())
|
||||
dateCmp := time.Now().AddDate(0, 0, -1).Unix()
|
||||
logger.Debug("Started Events processing", "time", time.Now(), "since", dateCmp)
|
||||
|
||||
// Query events table
|
||||
events, err := c.Database.Queries().ListUnprocessedEvents(ctx)
|
||||
events, err := c.Database.Queries().ListUnprocessedEvents(ctx,
|
||||
sql.NullInt64{Int64: dateCmp, Valid: dateCmp > 0})
|
||||
if err != nil {
|
||||
logger.Error("Unable to query for unprocessed events", "error", err)
|
||||
return nil // TODO - what to do with this error?
|
||||
} else {
|
||||
logger.Debug("Successfully queried for unprocessed events", "count", len(events))
|
||||
}
|
||||
|
||||
for _, evt := range events {
|
||||
@@ -44,8 +46,8 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
|
||||
// TODO - get a list of unique vcenters, then process each event in batches
|
||||
// to avoid doing unnecessary login/logout of vcenter
|
||||
|
||||
c.Logger.Debug("connecting to vcenter")
|
||||
vc := vcenter.New(c.Logger)
|
||||
//c.Logger.Debug("connecting to vcenter")
|
||||
vc := vcenter.New(c.Logger, c.VcCreds)
|
||||
vc.Login(evt.Source)
|
||||
|
||||
//datacenter = evt.DatacenterName.String
|
||||
@@ -53,53 +55,116 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
|
||||
|
||||
if err != nil {
|
||||
c.Logger.Error("Can't locate vm in vCenter", "vmID", evt.VmId.String, "error", err)
|
||||
continue
|
||||
} else if vmObject == nil {
|
||||
c.Logger.Debug("didn't find VM", "vm_id", evt.VmId.String)
|
||||
numRam = 0
|
||||
numVcpus = 0
|
||||
totalDiskGB = 0
|
||||
} else {
|
||||
c.Logger.Debug("found VM")
|
||||
srmPlaceholder = 0 // Default assumption
|
||||
//prettyPrint(vmObject)
|
||||
|
||||
// calculate VM properties we want to store
|
||||
if vmObject.Vm.Config != nil {
|
||||
numRam = vmObject.Vm.Config.Hardware.MemoryMB
|
||||
//numVcpus = vmObject.Vm.Config.Hardware.NumCPU * vmObject.Vm.Config.Hardware.NumCoresPerSocket
|
||||
numVcpus = vmObject.Vm.Config.Hardware.NumCPU
|
||||
// TODO - if VM name ends with -tmp or -phVm then we mark this record as processed and stop trying to find a VM that doesnt exist anymore
|
||||
|
||||
// Calculate the total disk allocated in GB
|
||||
for _, device := range vmObject.Vm.Config.Hardware.Device {
|
||||
if disk, ok := device.(*types.VirtualDisk); ok {
|
||||
totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB
|
||||
}
|
||||
if strings.HasSuffix(evt.VmName.String, "-phVm") || strings.HasSuffix(evt.VmName.String, "-tmp") {
|
||||
c.Logger.Info("VM name indicates temporary VM, marking as processed", "vm_name", evt.VmName.String)
|
||||
|
||||
err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err)
|
||||
} else {
|
||||
//c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
|
||||
}
|
||||
|
||||
// Determine if the VM is a normal VM or an SRM placeholder
|
||||
if vmObject.Vm.Config.ManagedBy != nil && vmObject.Vm.Config.ManagedBy.Type == "com.vmware.vcDr" {
|
||||
c.Logger.Debug("VM ManagedBy indicates managed by SRM")
|
||||
srmPlaceholder = 1
|
||||
}
|
||||
|
||||
foundVm = true
|
||||
} else {
|
||||
c.Logger.Error("Empty VM config")
|
||||
}
|
||||
|
||||
/*
|
||||
numRam = 0
|
||||
numVcpus = 0
|
||||
totalDiskGB = 0
|
||||
isTemplate = "FALSE"
|
||||
folderPath = ""
|
||||
vmUuid = ""
|
||||
*/
|
||||
continue
|
||||
}
|
||||
|
||||
//c.Logger.Debug("found VM")
|
||||
srmPlaceholder = "FALSE" // Default assumption
|
||||
//prettyPrint(vmObject)
|
||||
|
||||
// calculate VM properties we want to store
|
||||
if vmObject.Config != nil {
|
||||
numRam = vmObject.Config.Hardware.MemoryMB
|
||||
numVcpus = vmObject.Config.Hardware.NumCPU
|
||||
vmUuid = vmObject.Config.Uuid
|
||||
|
||||
var totalDiskBytes int64
|
||||
|
||||
// Calculate the total disk allocated in GB
|
||||
for _, device := range vmObject.Config.Hardware.Device {
|
||||
if disk, ok := device.(*types.VirtualDisk); ok {
|
||||
|
||||
// Print the filename of the backing device
|
||||
if _, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
||||
//c.Logger.Debug("Adding disk", "size_bytes", disk.CapacityInBytes, "backing_file", backing.FileName)
|
||||
} else {
|
||||
//c.Logger.Debug("Adding disk, unknown backing type", "size_bytes", disk.CapacityInBytes)
|
||||
}
|
||||
|
||||
totalDiskBytes += disk.CapacityInBytes
|
||||
//totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB
|
||||
}
|
||||
}
|
||||
totalDiskGB = float64(totalDiskBytes / 1024 / 1024 / 1024)
|
||||
c.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB)
|
||||
|
||||
// Determine if the VM is a normal VM or an SRM placeholder
|
||||
if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
|
||||
if vmObject.Config.ManagedBy.Type == "placeholderVm" {
|
||||
c.Logger.Debug("VM is a placeholder")
|
||||
srmPlaceholder = "TRUE"
|
||||
} else {
|
||||
c.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObject.Config.ManagedBy)
|
||||
}
|
||||
}
|
||||
|
||||
if vmObject.Config.Template {
|
||||
isTemplate = "TRUE"
|
||||
} else {
|
||||
isTemplate = "FALSE"
|
||||
}
|
||||
|
||||
// Retrieve the full folder path of the VM
|
||||
folderPath, err = vc.GetVMFolderPath(*vmObject)
|
||||
if err != nil {
|
||||
c.Logger.Error("failed to get vm folder path", "error", err)
|
||||
folderPath = ""
|
||||
} else {
|
||||
c.Logger.Debug("Found vm folder path", "folder_path", folderPath)
|
||||
}
|
||||
|
||||
// Retrieve the resource pool of the VM
|
||||
rpName, _ = vc.GetVmResourcePool(*vmObject)
|
||||
|
||||
foundVm = true
|
||||
} else {
|
||||
c.Logger.Error("Empty VM config")
|
||||
}
|
||||
|
||||
//c.Logger.Debug("VM has runtime data", "power_state", vmObject.Runtime.PowerState)
|
||||
if vmObject.Runtime.PowerState == "poweredOff" {
|
||||
poweredOn = "FALSE"
|
||||
} else {
|
||||
poweredOn = "TRUE"
|
||||
}
|
||||
|
||||
err = vc.Logout()
|
||||
if err != nil {
|
||||
c.Logger.Error("unable to logout of vcenter", "error", err)
|
||||
}
|
||||
|
||||
if foundVm {
|
||||
c.Logger.Debug("Simulate adding to Inventory", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", evt.DatacenterId.String)
|
||||
c.Logger.Debug("Adding to Inventory table", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", evt.DatacenterId.String)
|
||||
|
||||
params := queries.CreateInventoryParams{
|
||||
Name: vmObject.Vm.Name,
|
||||
Name: vmObject.Name,
|
||||
Vcenter: evt.Source,
|
||||
EventId: sql.NullString{String: evt.CloudId, Valid: evt.CloudId != ""},
|
||||
CloudId: sql.NullString{String: evt.CloudId, Valid: evt.CloudId != ""},
|
||||
EventKey: sql.NullString{String: evt.EventKey.String, Valid: evt.EventKey.Valid},
|
||||
VmId: sql.NullString{String: evt.VmId.String, Valid: evt.VmId.Valid},
|
||||
Datacenter: sql.NullString{String: evt.DatacenterName.String, Valid: evt.DatacenterName.Valid},
|
||||
@@ -108,26 +173,29 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
|
||||
InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0},
|
||||
InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0},
|
||||
ProvisionedDisk: sql.NullFloat64{Float64: totalDiskGB, Valid: totalDiskGB > 0},
|
||||
Folder: sql.NullString{String: vmObject.FolderPath, Valid: vmObject.FolderPath != ""},
|
||||
ResourcePool: sql.NullString{String: vmObject.ResourcePool, Valid: vmObject.ResourcePool != ""},
|
||||
SrmPlaceholder: sql.NullInt64{Int64: int64(srmPlaceholder), Valid: true},
|
||||
Folder: sql.NullString{String: folderPath, Valid: folderPath != ""},
|
||||
ResourcePool: sql.NullString{String: rpName, Valid: rpName != ""},
|
||||
VmUuid: sql.NullString{String: vmUuid, Valid: vmUuid != ""},
|
||||
SrmPlaceholder: srmPlaceholder,
|
||||
IsTemplate: isTemplate,
|
||||
PoweredOn: poweredOn,
|
||||
}
|
||||
|
||||
c.Logger.Debug("database params", "params", params)
|
||||
//c.Logger.Debug("database params", "params", params)
|
||||
|
||||
// Insert the new inventory record into the database
|
||||
result, err := c.Database.Queries().CreateInventory(ctx, params)
|
||||
_, err := c.Database.Queries().CreateInventory(ctx, params)
|
||||
if err != nil {
|
||||
c.Logger.Error("unable to perform database insert", "error", err)
|
||||
} else {
|
||||
c.Logger.Debug("created database record", "insert_result", result)
|
||||
//c.Logger.Debug("created database record", "insert_result", result)
|
||||
|
||||
// mark this event as processed
|
||||
err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err)
|
||||
} else {
|
||||
c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
|
||||
//c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
16
internal/tasks/tasks.go
Normal file
16
internal/tasks/tasks.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"vctp/db"
|
||||
"vctp/internal/settings"
|
||||
"vctp/internal/vcenter"
|
||||
)
|
||||
|
||||
// CronTask stores runtime information to be used by tasks
|
||||
type CronTask struct {
|
||||
Logger *slog.Logger
|
||||
Database db.Database
|
||||
Settings *settings.Settings
|
||||
VcCreds *vcenter.VcenterLogin
|
||||
}
|
@@ -1,11 +1,13 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const rsaBits = 4096
|
||||
@@ -53,3 +55,14 @@ func FileExists(filename string) bool {
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
func SleepWithContext(ctx context.Context, d time.Duration) {
|
||||
timer := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
@@ -19,44 +20,52 @@ import (
|
||||
)
|
||||
|
||||
type Vcenter struct {
|
||||
Logger *slog.Logger
|
||||
ctx context.Context
|
||||
client *govmomi.Client
|
||||
Logger *slog.Logger
|
||||
Vurl string
|
||||
ctx context.Context
|
||||
client *govmomi.Client
|
||||
credentials *VcenterLogin
|
||||
}
|
||||
|
||||
type VcenterLogin struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type VmProperties struct {
|
||||
Vm mo.VirtualMachine
|
||||
ResourcePool string
|
||||
FolderPath string
|
||||
//Datacenter string
|
||||
}
|
||||
|
||||
// New creates a new Vcenter with the given logger
|
||||
func New(logger *slog.Logger) *Vcenter {
|
||||
func New(logger *slog.Logger, creds *VcenterLogin) *Vcenter {
|
||||
|
||||
//ctx, cancel := context.WithCancel(context.Background())
|
||||
//defer cancel()
|
||||
|
||||
return &Vcenter{
|
||||
Logger: logger,
|
||||
ctx: context.Background(),
|
||||
Logger: logger,
|
||||
ctx: context.Background(),
|
||||
credentials: creds,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Vcenter) Login(vUrl string) error {
|
||||
var insecure bool
|
||||
|
||||
// TODO - fix this
|
||||
insecureString := os.Getenv("VCENTER_INSECURE")
|
||||
username := os.Getenv("VCENTER_USERNAME")
|
||||
password := os.Getenv("VCENTER_PASSWORD")
|
||||
//username := os.Getenv("VCENTER_USERNAME")
|
||||
//password := os.Getenv("VCENTER_PASSWORD")
|
||||
|
||||
// Connect to vCenter
|
||||
u, err := soap.ParseURL(vUrl)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing vCenter URL: %s", err)
|
||||
}
|
||||
v.Vurl = vUrl
|
||||
|
||||
u.User = url.UserPassword(username, password)
|
||||
u.User = url.UserPassword(v.credentials.Username, v.credentials.Password)
|
||||
|
||||
/*
|
||||
c, err := govmomi.NewClient(ctx, u, insecure)
|
||||
@@ -79,7 +88,7 @@ func (v *Vcenter) Login(vUrl string) error {
|
||||
|
||||
v.client = c
|
||||
|
||||
v.Logger.Debug("successfully connected to vCenter", "url", vUrl, "username", username)
|
||||
v.Logger.Debug("successfully connected to vCenter", "url", vUrl, "username", v.credentials.Username)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -102,6 +111,236 @@ func (v *Vcenter) Logout() error {
|
||||
|
||||
}
|
||||
|
||||
func (v *Vcenter) GetAllVmReferences() ([]*object.VirtualMachine, error) {
|
||||
var results []*object.VirtualMachine
|
||||
finder := find.NewFinder(v.client.Client, true)
|
||||
|
||||
m := view.NewManager(v.client.Client)
|
||||
|
||||
vms, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer vms.Destroy(v.ctx)
|
||||
|
||||
// List all datacenters
|
||||
datacenters, err := finder.DatacenterList(v.ctx, "*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list datacenters: %w", err)
|
||||
}
|
||||
|
||||
for _, dc := range datacenters {
|
||||
v.Logger.Debug("Getting VMs in", "datacenter", dc.Name())
|
||||
// Set the current datacenter
|
||||
finder.SetDatacenter(dc)
|
||||
|
||||
// Get the list of all virtual machines in the current datacenter
|
||||
vms, err := finder.VirtualMachineList(v.ctx, "*")
|
||||
if err != nil {
|
||||
v.Logger.Error("Failed to list VMs in", "datacenter", dc.Name(), "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, vm := range vms {
|
||||
//vmRef := vm.Reference()
|
||||
//v.Logger.Debug("result", "vm", vm, "MoRef", vmRef, "path", vm.InventoryPath)
|
||||
results = append(results, vm)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
v.Logger.Debug("Found VM references", "count", len(results))
|
||||
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (v *Vcenter) ConvertObjToMoVM(vmObj *object.VirtualMachine) (*mo.VirtualMachine, error) {
|
||||
// Use the InventoryPath to extract the datacenter name and VM path
|
||||
inventoryPath := vmObj.InventoryPath
|
||||
parts := strings.SplitN(inventoryPath, "/", 3)
|
||||
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("invalid InventoryPath: %s", inventoryPath)
|
||||
}
|
||||
|
||||
// The first part of the path is the datacenter name
|
||||
datacenterName := parts[1]
|
||||
|
||||
// Finder to search for datacenter and VM
|
||||
finder := find.NewFinder(v.client.Client, true)
|
||||
|
||||
// Find the specific datacenter by name
|
||||
datacenter, err := finder.Datacenter(v.ctx, fmt.Sprintf("/%s", datacenterName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find datacenter %s: %w", datacenterName, err)
|
||||
}
|
||||
|
||||
// Set the found datacenter in the finder
|
||||
finder.SetDatacenter(datacenter)
|
||||
|
||||
// Now retrieve the VM using its ManagedObjectReference
|
||||
vmRef := vmObj.Reference()
|
||||
|
||||
// Retrieve the full mo.VirtualMachine object for the reference
|
||||
var moVM mo.VirtualMachine
|
||||
err = v.client.RetrieveOne(v.ctx, vmRef, nil, &moVM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve VM %s in datacenter %s: %w", vmObj.Name(), datacenterName, err)
|
||||
}
|
||||
|
||||
// Return the found mo.VirtualMachine object
|
||||
//v.Logger.Debug("Found VM in datacenter", "vm_name", moVM.Name, "dc_name", datacenterName)
|
||||
return &moVM, nil
|
||||
}
|
||||
|
||||
func (v *Vcenter) ConvertObjToMoHost(hostObj *object.HostSystem) (*mo.HostSystem, error) {
|
||||
// Use the InventoryPath to extract the datacenter name and Host path
|
||||
inventoryPath := hostObj.InventoryPath
|
||||
parts := strings.SplitN(inventoryPath, "/", 3)
|
||||
v.Logger.Debug("inventory path", "parts", parts)
|
||||
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("invalid InventoryPath: %s", inventoryPath)
|
||||
}
|
||||
|
||||
// The first part of the path is the datacenter name
|
||||
datacenterName := parts[1]
|
||||
|
||||
// Finder to search for datacenter and VM
|
||||
finder := find.NewFinder(v.client.Client, true)
|
||||
|
||||
// Find the specific datacenter by name
|
||||
datacenter, err := finder.Datacenter(v.ctx, fmt.Sprintf("/%s", datacenterName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find datacenter %s: %w", datacenterName, err)
|
||||
}
|
||||
|
||||
// Set the found datacenter in the finder
|
||||
finder.SetDatacenter(datacenter)
|
||||
|
||||
// Now retrieve the VM using its ManagedObjectReference
|
||||
hostRef := hostObj.Reference()
|
||||
|
||||
// Retrieve the full mo.HostSystem object for the reference
|
||||
var moHost mo.HostSystem
|
||||
err = v.client.RetrieveOne(v.ctx, hostRef, nil, &moHost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve Host %s in datacenter %s: %w", hostObj.Name(), datacenterName, err)
|
||||
}
|
||||
|
||||
// Return the found mo.HostSystem object
|
||||
v.Logger.Debug("Found Host in datacenter", "host_name", moHost.Name, "dc_name", datacenterName)
|
||||
return &moHost, nil
|
||||
}
|
||||
|
||||
func (v *Vcenter) GetHostSystemObject(hostRef types.ManagedObjectReference) (*mo.HostSystem, error) {
|
||||
finder := find.NewFinder(v.client.Client, true)
|
||||
|
||||
// List all datacenters
|
||||
datacenters, err := finder.DatacenterList(v.ctx, "*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list datacenters: %w", err)
|
||||
}
|
||||
|
||||
for _, dc := range datacenters {
|
||||
v.Logger.Debug("Checking dc for host", "name", dc.Name(), "hostRef", hostRef.String())
|
||||
// Set the current datacenter
|
||||
finder.SetDatacenter(dc)
|
||||
|
||||
var hs mo.HostSystem
|
||||
err := v.client.RetrieveOne(v.ctx, hostRef, nil, &hs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
v.Logger.Debug("Found hostsystem", "name", hs.Name)
|
||||
return &hs, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Function to find the cluster or compute resource from a host reference
|
||||
func (v *Vcenter) GetClusterFromHost(hostRef *types.ManagedObjectReference) (string, error) {
|
||||
// Get the host object
|
||||
host, err := v.GetHostSystemObject(*hostRef)
|
||||
if err != nil {
|
||||
v.Logger.Error("cant get host", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
v.Logger.Debug("host parent", "parent", host.Parent)
|
||||
|
||||
if host.Parent.Type == "ClusterComputeResource" {
|
||||
// Retrieve properties of the compute resource
|
||||
var moCompute mo.ComputeResource
|
||||
err = v.client.RetrieveOne(v.ctx, *host.Parent, nil, &moCompute)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to retrieve compute resource: %w", err)
|
||||
}
|
||||
v.Logger.Debug("VM is on host in cluster/compute resource", "name", moCompute.Name)
|
||||
return moCompute.Name, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Function to determine the datacenter a VM belongs to
|
||||
func (v *Vcenter) GetDatacenterForVM(vm mo.VirtualMachine) (string, error) {
|
||||
// Start with the VM's parent reference
|
||||
ref := vm.Reference()
|
||||
|
||||
// Traverse the inventory hierarchy upwards to find the datacenter
|
||||
for {
|
||||
// Get the parent reference of the current object
|
||||
parentRef, err := v.getParent(ref)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get parent object: %w", err)
|
||||
}
|
||||
|
||||
// If we get a nil parent reference, it means we've hit the root without finding the datacenter
|
||||
if parentRef == nil {
|
||||
return "", fmt.Errorf("failed to find datacenter for VM")
|
||||
}
|
||||
|
||||
// Check if the parent is a Datacenter
|
||||
switch parentRef.Type {
|
||||
case "Datacenter":
|
||||
// If we found a Datacenter, retrieve its properties
|
||||
datacenter := object.NewDatacenter(v.client.Client, *parentRef)
|
||||
var moDC mo.Datacenter
|
||||
err = v.client.RetrieveOne(v.ctx, datacenter.Reference(), nil, &moDC)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to retrieve datacenter: %w", err)
|
||||
}
|
||||
|
||||
//log.Printf("VM is in datacenter: %s", moDC.Name)
|
||||
v.Logger.Debug("VM datacenter found", "vm_name", vm.Name, "dc_name", moDC.Name)
|
||||
return moDC.Name, nil
|
||||
|
||||
default:
|
||||
// Continue traversing upwards if not a Datacenter
|
||||
ref = *parentRef
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get the parent ManagedObjectReference of a given object
|
||||
func (v *Vcenter) getParent(ref types.ManagedObjectReference) (*types.ManagedObjectReference, error) {
|
||||
// Retrieve the object's properties
|
||||
var obj mo.ManagedEntity
|
||||
err := v.client.RetrieveOne(v.ctx, ref, []string{"parent"}, &obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve parent of object: %w", err)
|
||||
}
|
||||
|
||||
// Return the parent reference
|
||||
if obj.Parent != nil {
|
||||
return obj.Parent, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) {
|
||||
m := view.NewManager(v.client.Client)
|
||||
|
||||
@@ -177,11 +416,9 @@ func (v *Vcenter) FindVMByID(vmID string) (*VmProperties, error) {
|
||||
return nil, fmt.Errorf("VM with ID %s not found in any datacenter", vmID)
|
||||
}
|
||||
|
||||
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmProperties, error) {
|
||||
//var dcName string
|
||||
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*mo.VirtualMachine, error) {
|
||||
var err error
|
||||
resourcePool := ""
|
||||
vmFolderPath := ""
|
||||
//resourcePool := ""
|
||||
v.Logger.Debug("searching for vm id", "vm_id", vmID, "datacenter_id", dcID)
|
||||
|
||||
finder := find.NewFinder(v.client.Client, true)
|
||||
@@ -211,51 +448,39 @@ func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmPropert
|
||||
//err := v.client.RetrieveOne(v.ctx, vmRef, []string{"config", "name"}, &vm)
|
||||
err = v.client.RetrieveOne(v.ctx, vmRef, nil, &vm)
|
||||
if err == nil {
|
||||
v.Logger.Debug("Found VM")
|
||||
//v.Logger.Debug("Found VM")
|
||||
|
||||
// Retrieve the resource pool the VM is in
|
||||
if vm.ResourcePool != nil {
|
||||
rp := object.NewResourcePool(v.client.Client, *vm.ResourcePool)
|
||||
rpName, err := rp.ObjectName(v.ctx)
|
||||
if err != nil {
|
||||
v.Logger.Error("failed to get resource pool name", "error", err)
|
||||
} else {
|
||||
v.Logger.Debug("Found resource pool name", "rp_name", rpName)
|
||||
resourcePool = rpName
|
||||
}
|
||||
return &vm, nil
|
||||
|
||||
}
|
||||
|
||||
// Retrieve the full folder path of the VM
|
||||
folderPath, err := v.getVMFolderPath(vm)
|
||||
if err != nil {
|
||||
v.Logger.Error("failed to get vm folder path", "error", err)
|
||||
} else {
|
||||
v.Logger.Debug("Found vm folder path", "folder_path", folderPath)
|
||||
vmFolderPath = folderPath
|
||||
}
|
||||
|
||||
return &VmProperties{
|
||||
//Datacenter: dcName,
|
||||
Vm: vm,
|
||||
ResourcePool: resourcePool,
|
||||
FolderPath: vmFolderPath,
|
||||
}, nil
|
||||
} else if _, ok := err.(*find.NotFoundError); !ok {
|
||||
// If the error is not a NotFoundError, return it
|
||||
//return nil, fmt.Errorf("failed to retrieve VM with ID %s in datacenter %s: %w", vmID, dc.Name(), err)
|
||||
v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
|
||||
//v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
|
||||
return nil, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to retrieve VM: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
//v.Logger.Info("Unable to find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
|
||||
//return nil, nil
|
||||
// Helper function to retrieve the resource pool for the VM
|
||||
func (v *Vcenter) GetVmResourcePool(vm mo.VirtualMachine) (string, error) {
|
||||
var resourcePool string
|
||||
if vm.ResourcePool != nil {
|
||||
rp := object.NewResourcePool(v.client.Client, *vm.ResourcePool)
|
||||
rpName, err := rp.ObjectName(v.ctx)
|
||||
if err != nil {
|
||||
v.Logger.Error("failed to get resource pool name", "error", err)
|
||||
return resourcePool, err
|
||||
} else {
|
||||
//v.Logger.Debug("Found resource pool name", "rp_name", rpName)
|
||||
resourcePool = rpName
|
||||
}
|
||||
}
|
||||
return resourcePool, nil
|
||||
}
|
||||
|
||||
// Helper function to retrieve the full folder path for the VM
|
||||
func (v *Vcenter) getVMFolderPath(vm mo.VirtualMachine) (string, error) {
|
||||
func (v *Vcenter) GetVMFolderPath(vm mo.VirtualMachine) (string, error) {
|
||||
//finder := find.NewFinder(v.client.Client, true)
|
||||
|
||||
v.Logger.Debug("Commencing vm folder path search")
|
||||
@@ -268,7 +493,7 @@ func (v *Vcenter) getVMFolderPath(vm mo.VirtualMachine) (string, error) {
|
||||
|
||||
// Traverse the folder hierarchy to build the full folder path
|
||||
folderPath := ""
|
||||
v.Logger.Debug("parent is", "parent", parentRef)
|
||||
//v.Logger.Debug("parent is", "parent", parentRef)
|
||||
|
||||
for parentRef.Type != "Datacenter" {
|
||||
// Retrieve the parent object
|
||||
@@ -293,7 +518,7 @@ func (v *Vcenter) getVMFolderPath(vm mo.VirtualMachine) (string, error) {
|
||||
//if folder, ok := parentObj.(*object.Folder); ok {
|
||||
if parentObj.Parent != nil {
|
||||
parentRef = parentObj.Parent
|
||||
v.Logger.Debug("Parent uplevel is", "ref", parentRef)
|
||||
//v.Logger.Debug("Parent uplevel is", "ref", parentRef)
|
||||
} else {
|
||||
return "", fmt.Errorf("unexpected parent type: %s", parentObj.Reference().Type)
|
||||
}
|
||||
|
132
main.go
132
main.go
@@ -8,8 +8,11 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
"vctp/db"
|
||||
"vctp/internal/secrets"
|
||||
"vctp/internal/settings"
|
||||
"vctp/internal/tasks"
|
||||
utils "vctp/internal/utils"
|
||||
"vctp/internal/vcenter"
|
||||
"vctp/log"
|
||||
"vctp/server"
|
||||
"vctp/server/router"
|
||||
@@ -19,10 +22,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
bindDisableTls bool
|
||||
sha1ver string // sha1 revision used to build the program
|
||||
buildTime string // when the executable was built
|
||||
cronFrequency time.Duration
|
||||
bindDisableTls bool
|
||||
sha1ver string // sha1 revision used to build the program
|
||||
buildTime string // when the executable was built
|
||||
cronFrequency time.Duration
|
||||
cronInvFrequency time.Duration
|
||||
encryptionKey = []byte("5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa")
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -54,17 +59,21 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Prepare the task scheduler
|
||||
s, err := gocron.NewScheduler()
|
||||
if err != nil {
|
||||
logger.Error("failed to create scheduler", "error", err)
|
||||
os.Exit(1)
|
||||
// Load settings from yaml
|
||||
settingsFile := os.Getenv("SETTINGS_FILE")
|
||||
if settingsFile == "" {
|
||||
settingsFile = "settings.yaml"
|
||||
}
|
||||
|
||||
// Pass useful information to the cron jobs
|
||||
c := &tasks.CronTask{
|
||||
Logger: logger,
|
||||
Database: database,
|
||||
// TODO - how to pass this to the other packages that will need this info?
|
||||
s := settings.New(logger, settingsFile)
|
||||
err = s.ReadYMLSettings()
|
||||
//s, err := settings.ReadYMLSettings(logger, settingsFile)
|
||||
if err != nil {
|
||||
logger.Error("failed to open yaml settings file", "error", err, "filename", settingsFile)
|
||||
//os.Exit(1)
|
||||
} else {
|
||||
logger.Debug("Loaded yaml settings", "contents", s)
|
||||
}
|
||||
|
||||
// Determine bind IP
|
||||
@@ -78,7 +87,7 @@ func main() {
|
||||
bindPort = "9443"
|
||||
}
|
||||
bindAddress := fmt.Sprint(bindIP, ":", bindPort)
|
||||
slog.Info("Will listen on address", "ip", bindIP, "port", bindPort)
|
||||
//logger.Info("Will listen on address", "ip", bindIP, "port", bindPort)
|
||||
|
||||
// Determine bind disable TLS
|
||||
bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS")
|
||||
@@ -103,52 +112,115 @@ func main() {
|
||||
|
||||
// Generate certificate if required
|
||||
if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) {
|
||||
slog.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename)
|
||||
logger.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename)
|
||||
utils.GenerateCerts(tlsCertFilename, tlsKeyFilename)
|
||||
}
|
||||
|
||||
cronFrequencyString := os.Getenv("VCENTER_POLLING_SECONDS")
|
||||
// Load vcenter credentials from .env
|
||||
a := secrets.New(logger, encryptionKey)
|
||||
vcEp := os.Getenv("VCENTER_PASSWORD")
|
||||
if len(vcEp) == 0 {
|
||||
logger.Error("No vcenter password configured")
|
||||
os.Exit(1)
|
||||
}
|
||||
vcPass, err := a.Decrypt(vcEp)
|
||||
if err != nil {
|
||||
logger.Error("failed to decrypt vcenter credentials. Assuming un-encrypted", "error", err)
|
||||
vcPass = []byte(vcEp)
|
||||
//os.Exit(1)
|
||||
}
|
||||
|
||||
creds := vcenter.VcenterLogin{
|
||||
//insecureString := os.Getenv("VCENTER_INSECURE")
|
||||
Username: os.Getenv("VCENTER_USERNAME"),
|
||||
Password: string(vcPass),
|
||||
}
|
||||
|
||||
// Prepare the task scheduler
|
||||
c, err := gocron.NewScheduler()
|
||||
if err != nil {
|
||||
logger.Error("failed to create scheduler", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Pass useful information to the cron jobs
|
||||
ct := &tasks.CronTask{
|
||||
Logger: logger,
|
||||
Database: database,
|
||||
Settings: s,
|
||||
VcCreds: &creds,
|
||||
}
|
||||
|
||||
cronFrequencyString := os.Getenv("VCENTER_EVENT_POLLING_SECONDS")
|
||||
if cronFrequencyString != "" {
|
||||
cronFrequency, err = time.ParseDuration(cronFrequencyString)
|
||||
if err != nil {
|
||||
slog.Error("Can't convert VCENTER_POLLING_SECONDS value to time duration. Defaulting to 60s", "value", cronFrequencyString, "error", err)
|
||||
slog.Error("Can't convert VCENTER_EVENT_POLLING_SECONDS value to time duration. Defaulting to 60s", "value", cronFrequencyString, "error", err)
|
||||
cronFrequency = time.Second * 60
|
||||
}
|
||||
} else {
|
||||
cronFrequency = time.Second * 60
|
||||
}
|
||||
logger.Debug("Setting VM polling cronjob frequency to", "frequency", cronFrequency)
|
||||
logger.Debug("Setting VM event polling cronjob frequency to", "frequency", cronFrequency)
|
||||
|
||||
// start background processing
|
||||
cronInventoryFrequencyString := os.Getenv("VCENTER_INVENTORY_POLLING_SECONDS")
|
||||
if cronInventoryFrequencyString != "" {
|
||||
cronInvFrequency, err = time.ParseDuration(cronInventoryFrequencyString)
|
||||
if err != nil {
|
||||
slog.Error("Can't convert VCENTER_INVENTORY_POLLING_SECONDS value to time duration. Defaulting to 7200", "value", cronInventoryFrequencyString, "error", err)
|
||||
cronInvFrequency = time.Second * 7200
|
||||
}
|
||||
} else {
|
||||
cronInvFrequency = time.Second * 7200
|
||||
}
|
||||
logger.Debug("Setting VM inventory polling cronjob frequency to", "frequency", cronInvFrequency)
|
||||
|
||||
// start background processing for events stored in events table
|
||||
startsAt := time.Now().Add(time.Second * 10)
|
||||
job, err := s.NewJob(
|
||||
job, err := c.NewJob(
|
||||
gocron.DurationJob(cronFrequency),
|
||||
gocron.NewTask(func() {
|
||||
c.RunVmCheck(ctx, logger)
|
||||
ct.RunVmCheck(ctx, logger)
|
||||
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||
gocron.WithStartAt(gocron.WithStartDateTime(startsAt)),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("failed to start cron jobs", "error", err)
|
||||
logger.Error("failed to start event processing cron job", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Debug("Created event processing cron job", "job", job.ID(), "starting_at", startsAt)
|
||||
|
||||
slog.Debug("Created cron job", "job", job)
|
||||
// start background checks of vcenter inventory
|
||||
startsAt2 := time.Now().Add(cronInvFrequency)
|
||||
job2, err := c.NewJob(
|
||||
gocron.DurationJob(cronInvFrequency),
|
||||
gocron.NewTask(func() {
|
||||
ct.RunVcenterPoll(ctx, logger)
|
||||
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||
gocron.WithStartAt(gocron.WithStartDateTime(startsAt2)),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("failed to start vcenter inventory cron job", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Debug("Created vcenter inventory cron job", "job", job2.ID(), "starting_at", startsAt2)
|
||||
|
||||
s.Start()
|
||||
// start cron scheduler
|
||||
c.Start()
|
||||
|
||||
// Start server
|
||||
r := router.New(logger, database, buildTime, sha1ver, runtime.Version(), &creds, a, s)
|
||||
svr := server.New(
|
||||
logger,
|
||||
s,
|
||||
c,
|
||||
cancel,
|
||||
bindAddress,
|
||||
server.WithRouter(router.New(logger, database, buildTime, sha1ver, runtime.Version())),
|
||||
server.WithRouter(r),
|
||||
server.SetTls(bindDisableTls),
|
||||
server.SetCertificate(tlsCertFilename),
|
||||
server.SetPrivateKey(tlsKeyFilename),
|
||||
)
|
||||
|
||||
svr.DisableTls(bindDisableTls)
|
||||
svr.SetCertificate(tlsCertFilename)
|
||||
svr.SetPrivateKey(tlsKeyFilename)
|
||||
//logger.Debug("Server configured", "object", svr)
|
||||
|
||||
svr.StartAndWait()
|
||||
|
||||
|
61
server/handler/encryptData.go
Normal file
61
server/handler/encryptData.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h *Handler) EncryptData(w http.ResponseWriter, r *http.Request) {
|
||||
//ctx := context.Background()
|
||||
var cipherText string
|
||||
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.Logger.Error("Invalid data received", "error", err)
|
||||
fmt.Fprintf(w, "Invalid data received")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
h.Logger.Debug("received input data", "length", len(reqBody))
|
||||
}
|
||||
|
||||
// get the json input
|
||||
var input map[string]string
|
||||
if err := json.Unmarshal(reqBody, &input); err != nil {
|
||||
h.Logger.Error("unable to unmarshal json", "error", err)
|
||||
prettyPrint(reqBody)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Unable to unmarshal JSON in request body: '%s'", err),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
h.Logger.Debug("successfully decoded JSON")
|
||||
//prettyPrint(input)
|
||||
}
|
||||
|
||||
//cipher, err := h.Secret.Encrypt()
|
||||
for k := range input {
|
||||
//h.Logger.Debug("foo", "key", k, "value", input[k])
|
||||
cipherText, err = h.Secret.Encrypt([]byte(input[k]))
|
||||
if err != nil {
|
||||
h.Logger.Error("Unable to encrypt", "error", err)
|
||||
} else {
|
||||
h.Logger.Debug("Encrypted plaintext", "length", len(input[k]), "ciphertext", cipherText)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": cipherText,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return the result
|
||||
|
||||
}
|
@@ -5,6 +5,9 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"vctp/db"
|
||||
"vctp/internal/secrets"
|
||||
"vctp/internal/settings"
|
||||
"vctp/internal/vcenter"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
@@ -16,6 +19,9 @@ type Handler struct {
|
||||
BuildTime string
|
||||
SHA1Ver string
|
||||
GoVersion string
|
||||
VcCreds *vcenter.VcenterLogin
|
||||
Secret *secrets.Secrets
|
||||
Settings *settings.Settings
|
||||
}
|
||||
|
||||
func (h *Handler) html(ctx context.Context, w http.ResponseWriter, status int, t templ.Component) {
|
||||
|
61
server/handler/reportDownload.go
Normal file
61
server/handler/reportDownload.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"vctp/internal/report"
|
||||
)
|
||||
|
||||
func (h *Handler) InventoryReportDownload(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Generate the XLSX report
|
||||
reportData, err := report.CreateInventoryReport(h.Logger, h.Database, ctx)
|
||||
if err != nil {
|
||||
h.Logger.Error("Failed to create report", "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Unable to create xlsx report: '%s'", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Set HTTP headers to indicate file download
|
||||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="inventory_report.xlsx"`)
|
||||
w.Header().Set("File-Name", "inventory_report.xlsx")
|
||||
|
||||
// Write the XLSX file to the HTTP response
|
||||
w.Write(reportData)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateReportDownload(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Generate the XLSX report
|
||||
reportData, err := report.CreateUpdatesReport(h.Logger, h.Database, ctx)
|
||||
if err != nil {
|
||||
h.Logger.Error("Failed to create report", "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Unable to create xlsx report: '%s'", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Set HTTP headers to indicate file download
|
||||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="updates_report.xlsx"`)
|
||||
w.Header().Set("File-Name", "updates_report.xlsx")
|
||||
|
||||
// Write the XLSX file to the HTTP response
|
||||
w.Write(reportData)
|
||||
}
|
42
server/handler/updateCleanup.go
Normal file
42
server/handler/updateCleanup.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
||||
func (h *Handler) UpdateCleanup(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
// Get the current time
|
||||
now := time.Now()
|
||||
// Get the start of the current day (midnight today)
|
||||
midnightToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
// Convert to Unix time
|
||||
unixTime := midnightToday.Unix()
|
||||
|
||||
// create the database parameters
|
||||
params := queries.CleanupUpdatesParams{
|
||||
UpdateType: "diskchange",
|
||||
UpdateTime: sql.NullInt64{Int64: unixTime, Valid: unixTime > 0},
|
||||
}
|
||||
|
||||
h.Logger.Debug("database params", "params", params)
|
||||
err := h.Database.Queries().CleanupUpdates(context.Background(), params)
|
||||
*/
|
||||
|
||||
//err := h.Database.Queries().InventoryCleanupTemplates(context.Background())
|
||||
err := h.Database.Queries().CleanupUpdatesNullVm(context.Background())
|
||||
|
||||
if err != nil {
|
||||
h.Logger.Error("Error received cleaning updates table", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Delete Request unsuccessful %s\n", err)
|
||||
} else {
|
||||
h.Logger.Debug("Processed update cleanup successfully")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// TODO - return some JSON
|
||||
fmt.Fprintf(w, "Processed update cleanup successfully")
|
||||
}
|
||||
}
|
76
server/handler/vcCleanup.go
Normal file
76
server/handler/vcCleanup.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Remove a specified VM from the inventory
|
||||
func (h *Handler) VcCleanup(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get the parameters
|
||||
vcUrl := r.URL.Query().Get("vc_url")
|
||||
if vcUrl != "" {
|
||||
h.Logger.Debug("Checking inventory table for vCenter", "url", vcUrl)
|
||||
_, err := h.Database.Queries().GetInventoryVcUrl(ctx, vcUrl)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
h.Logger.Error("No VMs found for vcenter", "url", vcUrl)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("No match to vcenter details specified. vc_url: '%s'", vcUrl),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
h.Logger.Error("Error checking for vcenter to cleanup", "error", err, "url", vcUrl)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Error checking for vcenter to cleanup. error: '%s'", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// delete the VMs
|
||||
err = h.Database.Queries().InventoryCleanupVcenter(ctx, vcUrl)
|
||||
if err != nil {
|
||||
h.Logger.Error("Error cleaning up VMs from Inventory table", "error", err, "url", vcUrl)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Error cleaning up VMs from Inventory table. error: '%s'", err),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
// Successful cleanup
|
||||
h.Logger.Debug("VMs successfully removed from inventory for vcenter", "url", vcUrl)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": fmt.Sprintf("Removed VMs from Inventory table for vcenter '%s'", vcUrl),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
h.Logger.Error("Parameters not correctly specified", "url", vcUrl)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Parameters not correctly specified. vc_url: '%s'", vcUrl),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
91
server/handler/vmCleanup.go
Normal file
91
server/handler/vmCleanup.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"vctp/db/queries"
|
||||
)
|
||||
|
||||
// Remove a specified VM from the inventory
|
||||
func (h *Handler) VmCleanup(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get the parameters
|
||||
vmId := r.URL.Query().Get("vm_id")
|
||||
datacenterName := r.URL.Query().Get("datacenter_name")
|
||||
if vmId != "" && datacenterName != "" {
|
||||
// check that the VM exists in the inventory
|
||||
invParams := queries.GetInventoryVmIdParams{
|
||||
VmId: sql.NullString{String: vmId, Valid: vmId != ""},
|
||||
DatacenterName: sql.NullString{String: datacenterName, Valid: datacenterName != ""},
|
||||
}
|
||||
h.Logger.Debug("Checking inventory table for VM record", "params", invParams)
|
||||
vm, err := h.Database.Queries().GetInventoryVmId(ctx, invParams)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
h.Logger.Error("No VM found matching parameters", "vm_id", vmId, "datacenter_name", datacenterName)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("No match to VM details specified. vm_id: '%s', datacenter_name: '%s'", vmId, datacenterName),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
h.Logger.Error("Error checking for VM to cleanup", "error", err, "vm_id", vmId, "datacenter_name", datacenterName)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Error checking for VM to cleanup. error: '%s'", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// delete the VM
|
||||
|
||||
// create the database parameters
|
||||
params := queries.InventoryCleanupParams{
|
||||
VmId: sql.NullString{String: vmId, Valid: vmId != ""},
|
||||
DatacenterName: sql.NullString{String: datacenterName, Valid: datacenterName != ""},
|
||||
}
|
||||
|
||||
h.Logger.Debug("database params", "params", params)
|
||||
err = h.Database.Queries().InventoryCleanup(ctx, params)
|
||||
if err != nil {
|
||||
h.Logger.Error("Error cleaning up VM from Inventory table", "error", err, "vm_id", vmId, "datacenter_name", datacenterName)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Error cleaning up VM from Inventory table. error: '%s'", err),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
// Successful cleanup
|
||||
h.Logger.Debug("VM successfully removed from inventory", "vm_name", vm.Name, "iid", vm.Iid, "vm_id", vmId, "datacenter_name", datacenterName)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": fmt.Sprintf("VM '%s' removed from Inventory table", vm.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
h.Logger.Error("Parameters not correctly specified", "vm_id", vmId, "datacenter_name", datacenterName)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Parameters not correctly specified. vm_id: '%s', datacenter_name: '%s'", vmId, datacenterName),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
@@ -14,8 +14,8 @@ import (
|
||||
models "vctp/server/models"
|
||||
)
|
||||
|
||||
// VmCreate receives the CloudEvent for a VM creation
|
||||
func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
||||
// VmCreateEvent receives the CloudEvent for a VM creation
|
||||
func (h *Handler) VmCreateEvent(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
unixTimestamp int64
|
||||
//numVcpus int32
|
||||
@@ -33,7 +33,7 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
||||
h.Logger.Debug("received input data", "length", len(reqBody))
|
||||
}
|
||||
|
||||
// Decode the JSON body into vmModel struct
|
||||
// Decode the JSON body into CloudEventReceived struct
|
||||
var event models.CloudEventReceived
|
||||
if err := json.Unmarshal(reqBody, &event); err != nil {
|
||||
h.Logger.Error("unable to decode json", "error", err)
|
||||
@@ -41,7 +41,7 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
} else {
|
||||
h.Logger.Debug("successfully decoded JSON")
|
||||
prettyPrint(event)
|
||||
//prettyPrint(event)
|
||||
}
|
||||
|
||||
e := event.CloudEvent.Data
|
||||
@@ -60,63 +60,14 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
||||
unixTimestamp = eventTime.Unix()
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO - initiate govmomi query of source vcenter to discover related data
|
||||
h.Logger.Debug("connecting to vcenter")
|
||||
vc := vcenter.New(h.Logger)
|
||||
vc.Login(event.CloudEvent.Source)
|
||||
//vmObject, err := vc.FindVMByName(vm.CloudEvent.Data.VM.Name)
|
||||
//vmObject, err := vc.FindVMByID(vm.CloudEvent.Data.VM.VM.Value)
|
||||
vmObject, err := vc.FindVMByIDWithDatacenter(e.VM.VM.Value, e.Datacenter.Datacenter.Value)
|
||||
|
||||
if err != nil {
|
||||
h.Logger.Error("Can't locate vm in vCenter", "vmID", e.VM.VM.Value, "error", err)
|
||||
} else if vmObject == nil {
|
||||
h.Logger.Debug("didn't find VM", "vm_id", e.VM.VM.Value)
|
||||
numRam = 0
|
||||
numVcpus = 0
|
||||
datacenter = e.Datacenter.Name
|
||||
} else {
|
||||
h.Logger.Debug("found VM")
|
||||
//prettyPrint(vmObject)
|
||||
|
||||
// calculate VM properties we want to store
|
||||
if vmObject.Vm.Config != nil {
|
||||
numRam = vmObject.Vm.Config.Hardware.MemoryMB
|
||||
numVcpus = vmObject.Vm.Config.Hardware.NumCPU * vmObject.Vm.Config.Hardware.NumCoresPerSocket
|
||||
} else {
|
||||
h.Logger.Error("Empty VM config")
|
||||
}
|
||||
|
||||
}
|
||||
err = vc.Logout()
|
||||
if err != nil {
|
||||
h.Logger.Error("unable to logout of vcenter", "error", err)
|
||||
}
|
||||
*/
|
||||
|
||||
// Create an instance of CreateInventoryParams
|
||||
h.Logger.Debug("Creating database parameters")
|
||||
|
||||
/*
|
||||
params := queries.CreateInventoryParams{
|
||||
Name: e.VM.Name,
|
||||
Vcenter: event.CloudEvent.Source,
|
||||
EventId: sql.NullString{String: event.CloudEvent.ID, Valid: event.CloudEvent.ID != ""},
|
||||
EventKey: sql.NullString{String: strconv.Itoa(e.Key), Valid: strconv.Itoa(e.Key) != ""},
|
||||
VmId: sql.NullString{String: e.VM.VM.Value, Valid: e.VM.VM.Value != ""},
|
||||
Datacenter: sql.NullString{String: datacenter, Valid: datacenter != ""},
|
||||
Cluster: sql.NullString{String: e.ComputeResource.Name, Valid: e.ComputeResource.Name != ""},
|
||||
CreationTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0},
|
||||
InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0},
|
||||
InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0},
|
||||
}
|
||||
*/
|
||||
|
||||
params2 := queries.CreateEventParams{
|
||||
params := queries.CreateEventParams{
|
||||
Source: event.CloudEvent.Source,
|
||||
CloudId: event.CloudEvent.ID,
|
||||
EventTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0},
|
||||
EventType: sql.NullString{String: event.CloudEvent.Type, Valid: event.CloudEvent.Type != ""},
|
||||
ChainId: strconv.Itoa(e.ChainID),
|
||||
VmId: sql.NullString{String: e.VM.VM.Value, Valid: e.VM.VM.Value != ""},
|
||||
VmName: sql.NullString{String: e.VM.Name, Valid: e.VM.Name != ""},
|
||||
@@ -128,10 +79,10 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
||||
UserName: sql.NullString{String: e.UserName, Valid: e.UserName != ""},
|
||||
}
|
||||
|
||||
h.Logger.Debug("database params", "params", params2)
|
||||
h.Logger.Debug("database params", "params", params)
|
||||
|
||||
// Insert the new inventory record into the database
|
||||
result, err := h.Database.Queries().CreateEvent(context.Background(), params2)
|
||||
result, err := h.Database.Queries().CreateEvent(context.Background(), params)
|
||||
if err != nil {
|
||||
h.Logger.Error("unable to perform database insert", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
@@ -1,21 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
||||
func (h *Handler) VmDelete(w http.ResponseWriter, r *http.Request) {
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Invalid data received")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.Logger.Debug("received delete request", "body", string(reqBody))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Delete Request (%d): %v\n", len(reqBody), string(reqBody))
|
||||
}
|
76
server/handler/vmDeleteEvent.go
Normal file
76
server/handler/vmDeleteEvent.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
"vctp/db/queries"
|
||||
models "vctp/server/models"
|
||||
)
|
||||
|
||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
||||
func (h *Handler) VmDeleteEvent(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
deletedTimestamp int64
|
||||
)
|
||||
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.Logger.Error("Invalid data received", "error", err)
|
||||
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 CloudEventReceived struct
|
||||
var event models.CloudEventReceived
|
||||
if err := json.Unmarshal(reqBody, &event); err != nil {
|
||||
h.Logger.Error("unable to decode json", "error", err)
|
||||
prettyPrint(event)
|
||||
http.Error(w, "Invalid JSON body", http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
h.Logger.Debug("successfully decoded deletion type cloud event", "vm_id", event.CloudEvent.Data.VM.VM.Value)
|
||||
}
|
||||
|
||||
// Use the event CreatedTime to update the DeletionTime column in the VM inventory table
|
||||
// Parse the datetime string to a time.Time object
|
||||
eventTime, err := time.Parse(time.RFC3339, event.CloudEvent.Data.CreatedTime)
|
||||
if err != nil {
|
||||
h.Logger.Warn("unable to convert cloud event time to timestamp", "error", err)
|
||||
deletedTimestamp = time.Now().Unix()
|
||||
} else {
|
||||
// Convert to Unix timestamp
|
||||
deletedTimestamp = eventTime.Unix()
|
||||
}
|
||||
|
||||
// create the database parameters
|
||||
params := queries.InventoryMarkDeletedParams{
|
||||
DeletionTime: sql.NullInt64{Int64: deletedTimestamp, Valid: deletedTimestamp > 0},
|
||||
VmId: sql.NullString{String: event.CloudEvent.Data.VM.VM.Value, Valid: event.CloudEvent.Data.VM.VM.Value != ""},
|
||||
DatacenterName: sql.NullString{String: event.CloudEvent.Data.Datacenter.Name, Valid: event.CloudEvent.Data.Datacenter.Name != ""},
|
||||
}
|
||||
h.Logger.Debug("database params", "params", params)
|
||||
err = h.Database.Queries().InventoryMarkDeleted(context.Background(), params)
|
||||
|
||||
if err != nil {
|
||||
h.Logger.Error("Error received marking VM as deleted", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Delete Request unsuccessful %s\n", err)
|
||||
} else {
|
||||
h.Logger.Debug("Processed VM Deletion event successfully")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// TODO - return some JSON
|
||||
fmt.Fprintf(w, "Processed VM Deletion event successfully")
|
||||
}
|
||||
|
||||
//h.Logger.Debug("received delete request", "body", string(reqBody))
|
||||
//w.WriteHeader(http.StatusOK)
|
||||
//fmt.Fprintf(w, "Delete Request (%d): %v\n", len(reqBody), string(reqBody))
|
||||
}
|
@@ -1,21 +1,92 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"vctp/db"
|
||||
queries "vctp/db/queries"
|
||||
models "vctp/server/models"
|
||||
)
|
||||
|
||||
// VmImport is used for bulk import of existing VMs
|
||||
func (h *Handler) VmImport(w http.ResponseWriter, r *http.Request) {
|
||||
// Read request body
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Invalid data received")
|
||||
h.Logger.Error("Invalid data received", "length", len(reqBody), "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Invalid data received: '%s'", err),
|
||||
})
|
||||
return
|
||||
|
||||
} else {
|
||||
h.Logger.Debug("received input data", "length", len(reqBody))
|
||||
}
|
||||
|
||||
h.Logger.Debug("received import request", "body", string(reqBody))
|
||||
// Decode the JSON body into CloudEventReceived struct
|
||||
var inData models.ImportReceived
|
||||
if err := json.Unmarshal(reqBody, &inData); err != nil {
|
||||
h.Logger.Error("Unable to decode json request body", "length", len(reqBody), "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Unable to decode json request body: '%s'", err),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
//h.Logger.Debug("successfully decoded JSON")
|
||||
//prettyPrint(inData)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Query Inventory table for this VM before adding it
|
||||
h.Logger.Debug("Checking inventory table for VM record")
|
||||
invParams := queries.GetInventoryVmIdParams{
|
||||
VmId: sql.NullString{String: inData.VmId, Valid: inData.VmId != ""},
|
||||
DatacenterName: sql.NullString{String: inData.Datacenter, Valid: inData.Datacenter != ""},
|
||||
}
|
||||
_, err = h.Database.Queries().GetInventoryVmId(ctx, invParams)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// do the insert
|
||||
// Create an instance of CreateInventoryParams
|
||||
var params queries.CreateInventoryParams
|
||||
|
||||
// Convert vmModel to CreateInventoryParams using the utility function
|
||||
db.ConvertToSQLParams(&inData, ¶ms)
|
||||
//prettyPrint(params)
|
||||
|
||||
// Insert the new inventory record into the database
|
||||
result, err := h.Database.Queries().CreateInventory(ctx, params)
|
||||
if err != nil {
|
||||
h.Logger.Error("unable to perform database insert", "error", err)
|
||||
} else {
|
||||
h.Logger.Debug("created database record", "insert_result", result)
|
||||
}
|
||||
|
||||
} else {
|
||||
h.Logger.Error("unable to check inventory for vm", "error", err, "vm_id", inData.VmId, "datacenter_name", inData.Datacenter)
|
||||
}
|
||||
} else {
|
||||
h.Logger.Info("not adding vm to inventory table since record alraedy exists", "vm_id", inData.VmId, "datacenter_name", inData.Datacenter)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Import Request (%d): %v\n", len(reqBody), string(reqBody))
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": fmt.Sprintf("Successfully processed import request for VM '%s'", inData.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// VmModify receives the CloudEvent for a VM modification or move
|
||||
func (h *Handler) VmModify(w http.ResponseWriter, r *http.Request) {
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Invalid data received")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.Logger.Debug("received update request", "body", string(reqBody))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Update Request (%d): %v\n", len(reqBody), string(reqBody))
|
||||
}
|
571
server/handler/vmModifyEvent.go
Normal file
571
server/handler/vmModifyEvent.go
Normal file
@@ -0,0 +1,571 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"vctp/db/queries"
|
||||
"vctp/internal/vcenter"
|
||||
models "vctp/server/models"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// VmModifyEvent receives the CloudEvent for a VM modification or move
|
||||
func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
|
||||
var configChanges []map[string]string
|
||||
params := queries.CreateUpdateParams{}
|
||||
var unixTimestamp int64
|
||||
|
||||
re := regexp.MustCompile(`/([^/]+)/[^/]+\.vmdk$`)
|
||||
ctx := context.Background()
|
||||
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.Logger.Error("Invalid data received", "length", len(reqBody), "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Invalid data received: '%s'", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the JSON body into CloudEventReceived struct
|
||||
var event models.CloudEventReceived
|
||||
if err := json.Unmarshal(reqBody, &event); err != nil {
|
||||
h.Logger.Error("Unable to decode json request body", "length", len(reqBody), "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Unable to decode json request body: '%s'", err),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
//h.Logger.Debug("successfully decoded JSON")
|
||||
//prettyPrint(event)
|
||||
}
|
||||
|
||||
if event.CloudEvent.Data.ConfigChanges == nil {
|
||||
h.Logger.Warn("Received event contains no config change")
|
||||
prettyPrint(event)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": fmt.Sprintf("Received update event successfully but no config changes were found"),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
h.Logger.Info("Received event contains config change info", "source", event.CloudEvent.Source,
|
||||
"id", event.CloudEvent.ID,
|
||||
"vm", event.CloudEvent.Data.VM.Name, "user_name", event.CloudEvent.Data.UserName)
|
||||
|
||||
// Try to decode the config changes data
|
||||
var testConfig models.ConfigSpec
|
||||
if err := json.Unmarshal(*event.CloudEvent.Data.ConfigSpec, &testConfig); err != nil {
|
||||
h.Logger.Warn("unable to decode ConfigSpec json", "error", err)
|
||||
} else {
|
||||
//h.Logger.Debug("successfully decoded ConfigSpec JSON")
|
||||
}
|
||||
|
||||
configChanges = h.processConfigChanges(event.CloudEvent.Data.ConfigChanges.Modified)
|
||||
//prettyPrint(configChanges)
|
||||
|
||||
var changeFound = false
|
||||
// Only interested in vCPU or ram changes currently
|
||||
for _, change := range configChanges {
|
||||
//fmt.Printf("Type: %s, New Value: %s\n", change["type"], change["newValue"])
|
||||
switch change["type"] {
|
||||
case "config.hardware.numCPU":
|
||||
i, err := strconv.ParseInt(change["newValue"], 10, 64)
|
||||
if err != nil {
|
||||
h.Logger.Error("Unable to convert new value to int64", "new_value", change["newValue"])
|
||||
} else {
|
||||
changeFound = true
|
||||
params.NewVcpus = sql.NullInt64{Int64: i, Valid: i > 0}
|
||||
params.UpdateType = "reconfigure"
|
||||
}
|
||||
case "config.hardware.memoryMB":
|
||||
i, err := strconv.ParseInt(change["newValue"], 10, 64)
|
||||
if err != nil {
|
||||
h.Logger.Error("Unable to convert new value to int64", "new_value", change["newValue"])
|
||||
} else {
|
||||
changeFound = true
|
||||
params.NewRam = sql.NullInt64{Int64: i, Valid: i > 0}
|
||||
params.UpdateType = "reconfigure"
|
||||
}
|
||||
case "config.managedBy": // This changes when a VM becomes a placeholder or vice versa
|
||||
if change["newValue"] == "(extensionKey = \"com.vmware.vcDr\", type = \"placeholderVm\")" {
|
||||
params.PlaceholderChange = sql.NullString{String: "placeholderVm", Valid: true}
|
||||
h.Logger.Debug("placeholderVm")
|
||||
changeFound = true
|
||||
params.UpdateType = "srm"
|
||||
} else if change["newValue"] == "<unset>" {
|
||||
params.PlaceholderChange = sql.NullString{String: "Vm", Valid: true}
|
||||
h.Logger.Debug("vm")
|
||||
changeFound = true
|
||||
params.UpdateType = "srm"
|
||||
} else if change["newValue"] == "testVm" {
|
||||
h.Logger.Debug("testVm")
|
||||
params.PlaceholderChange = sql.NullString{String: "testVm", Valid: true}
|
||||
changeFound = true
|
||||
params.UpdateType = "srm"
|
||||
} else {
|
||||
h.Logger.Error("Unexpected value for managedBy configuration", "new_value", change["newValue"])
|
||||
}
|
||||
|
||||
// map[newValue:(extensionKey = \"com.vmware.vcDr\", type = \"placeholderVm\") type:config.managedBy]
|
||||
// map[newValue:\"testVm\" type:config.managedBy.type]
|
||||
// [map[newValue:\"placeholderVm\" type:config.managedBy.type]
|
||||
// map[newValue:<unset> type:config.managedBy]
|
||||
|
||||
// config.managedBy.type: "testVm" -> "placeholderVm"
|
||||
|
||||
// TODO - track when this happens, maybe need a new database column?
|
||||
case "config.managedBy.type":
|
||||
h.Logger.Debug("config.managedBy.type")
|
||||
if change["newValue"] == "testVm" {
|
||||
h.Logger.Debug("testVm")
|
||||
params.PlaceholderChange = sql.NullString{String: "testVm", Valid: true}
|
||||
changeFound = true
|
||||
params.UpdateType = "srm"
|
||||
} else if change["newValue"] == "\\\"placeholderVm\\\"" {
|
||||
h.Logger.Debug("placeholderVm")
|
||||
params.PlaceholderChange = sql.NullString{String: "placeholderVm", Valid: true}
|
||||
changeFound = true
|
||||
params.UpdateType = "srm"
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a disk was added (or maybe removed?)
|
||||
if strings.Contains(change["type"], "config.hardware.device") &&
|
||||
(strings.Contains(event.CloudEvent.Data.FullFormattedMessage, ".vmdk") ||
|
||||
strings.Contains(event.CloudEvent.Data.FullFormattedMessage, "capacityInKB")) {
|
||||
|
||||
var diskChangeFound = false
|
||||
|
||||
if testConfig.DeviceChange != nil {
|
||||
for i := range testConfig.DeviceChange {
|
||||
if testConfig.DeviceChange[i].Device.Backing != nil {
|
||||
h.Logger.Debug("Found backing in configspec", "backing", testConfig.DeviceChange[i].Device.Backing)
|
||||
|
||||
// Find the match
|
||||
backingFile := testConfig.DeviceChange[i].Device.Backing.FileName
|
||||
matches := re.FindStringSubmatch(backingFile)
|
||||
if len(matches) < 2 {
|
||||
h.Logger.Warn("unable to match regex", "backing_filename", backingFile, "match_count", len(matches))
|
||||
} else {
|
||||
h.Logger.Debug("Matched regex", "disk_owner", matches[1])
|
||||
|
||||
if strings.ToLower(matches[1]) == strings.ToLower(event.CloudEvent.Data.VM.Name) {
|
||||
h.Logger.Debug("This disk belongs to this VM")
|
||||
changeFound = true
|
||||
diskChangeFound = true
|
||||
|
||||
// don't need to keep searching through the rest of the backing devices in this VM
|
||||
break
|
||||
|
||||
} else {
|
||||
h.Logger.Debug("This disk belongs to a different VM, don't record this config change")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a disk change belonging to this VM then recalculate the disk size
|
||||
if diskChangeFound {
|
||||
params.UpdateType = "diskchange"
|
||||
diskSize := h.calculateNewDiskSize(event)
|
||||
params.NewProvisionedDisk = sql.NullFloat64{Float64: diskSize, Valid: diskSize > 0}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only create a database record if we found one of the config changes we were interested in
|
||||
if changeFound {
|
||||
// Parse the datetime string to a time.Time object
|
||||
eventTime, err := time.Parse(time.RFC3339, event.CloudEvent.Data.CreatedTime)
|
||||
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()
|
||||
}
|
||||
|
||||
// lookup Iid from Inventory table for this VM
|
||||
// also figure out what to do if we didn't find an entry for this VM in the Inventory table. Create one?
|
||||
h.Logger.Debug("Checking inventory table for VM record")
|
||||
invParams := queries.GetInventoryVmIdParams{
|
||||
VmId: sql.NullString{String: event.CloudEvent.Data.VM.VM.Value, Valid: event.CloudEvent.Data.VM.VM.Value != ""},
|
||||
DatacenterName: sql.NullString{String: event.CloudEvent.Data.Datacenter.Name, Valid: event.CloudEvent.Data.Datacenter.Name != ""},
|
||||
}
|
||||
invResult, err := h.Database.Queries().GetInventoryVmId(ctx, invParams)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// TODO Add a record to the inventory table for this VM
|
||||
h.Logger.Info("Received VM modify event for a VM not currently in the inventory. Adding to inventory")
|
||||
|
||||
iid, err2 := h.AddVmToInventory(event, ctx, unixTimestamp)
|
||||
if err2 != nil {
|
||||
h.Logger.Error("Received error adding VM to inventory", "error", err2)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Valid request but experienced error adding vm id '%s' in datacenter name '%s' to inventory table : %s",
|
||||
event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name, err2),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if iid > 0 {
|
||||
params.InventoryId = sql.NullInt64{Int64: iid, Valid: iid > 0}
|
||||
} else {
|
||||
h.Logger.Error("Received zero for inventory id when adding VM to inventory")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Valid request but received zero result when adding vm id '%s' in datacenter name '%s' to inventory table",
|
||||
event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
h.Logger.Error("unable to find existing inventory record for this VM", "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Valid request but could not locate vm id '%s' and datacenter name '%s' within inventory table : %s",
|
||||
event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name, err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
params.InventoryId = sql.NullInt64{Int64: invResult.Iid, Valid: invResult.Iid > 0}
|
||||
}
|
||||
|
||||
// Check current disk size from Inventory table and don't create an update if the size is still the same
|
||||
if params.UpdateType == "diskChange" && invResult.ProvisionedDisk.Float64 == params.NewProvisionedDisk.Float64 {
|
||||
h.Logger.Info("VM update type was for disk size but current size of VM matches inventory record, no need for update record",
|
||||
"vm_name", invResult.Name, "db_value", invResult.ProvisionedDisk.Float64, "new_value", params.NewProvisionedDisk.Float64)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": fmt.Sprintf("Successfully processed vm modify event"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// populate other parameters for the Update database record
|
||||
params.Name = sql.NullString{String: event.CloudEvent.Data.VM.Name, Valid: event.CloudEvent.Data.VM.Name != ""}
|
||||
params.RawChangeString = []byte(event.CloudEvent.Data.ConfigChanges.Modified)
|
||||
params.EventId = sql.NullString{String: event.CloudEvent.ID, Valid: event.CloudEvent.ID != ""}
|
||||
params.EventKey = sql.NullString{String: strconv.Itoa(event.CloudEvent.Data.Key), Valid: event.CloudEvent.Data.Key > 0}
|
||||
params.UpdateTime = sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0}
|
||||
params.UserName = sql.NullString{String: event.CloudEvent.Data.UserName, Valid: event.CloudEvent.Data.UserName != ""}
|
||||
|
||||
// Create the Update database record
|
||||
h.Logger.Debug("Adding Update record", "params", params)
|
||||
result, err := h.Database.Queries().CreateUpdate(ctx, 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
|
||||
} else {
|
||||
h.Logger.Debug("created database record", "insert_result", result)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": fmt.Sprintf("Successfully processed vm modify event"),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
h.Logger.Debug("Didn't find any configuration changes of interest", "id", event.CloudEvent.ID,
|
||||
"vm", event.CloudEvent.Data.VM.Name, "config_changes", configChanges)
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
fmt.Fprintf(w, "Processed update event but no config changes were of interest\n")
|
||||
//prettyPrint(event.CloudEvent.Data.ConfigSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) processConfigChanges(configChanges string) []map[string]string {
|
||||
// Split the string on one or more consecutive newline characters
|
||||
changes := regexp.MustCompile(`\n+`).Split(configChanges, -1)
|
||||
|
||||
// Regular expression to match config type and the new value after '->' or '<-'
|
||||
// examples:
|
||||
// "config.memoryHotAddEnabled: true -\u003e false; \n\nconfig.cpuHotAddEnabled: true -\u003e false; \n\n"
|
||||
// "config.hardware.device(1000).device: (2000, 2001, 2002) -> (2000, 2001, 2002, 2003);"
|
||||
// "config.hardware.numCPU: 2 -\u003e 1; \n\nconfig.hardware.memoryMB: 4096 -\u003e 3072;"
|
||||
// "config.hardware.device(4000).deviceInfo.summary: \"nsx.LogicalSwitch: 618884fd-7e8f-4c02-9a0d-2af36b5296a1\" -> \"DVSwitch: 50 18 92 03 a1 54 8f 8c-f2 b1 87 0f 97 5b d3 17\";"
|
||||
//re := regexp.MustCompile(`(?P<type>[^\s]+): [^-]+-[><] (?P<newValue>[^;]+);`)
|
||||
re := regexp.MustCompile(`(?P<type>[^\s]+): .*?-[><] (?P<newValue>[^;]+);`)
|
||||
|
||||
// Result will hold a list of changes with type and new value
|
||||
var result []map[string]string
|
||||
matchFound := false
|
||||
|
||||
for _, change := range changes {
|
||||
// Trim any extra spaces and skip empty lines
|
||||
change = strings.TrimSpace(change)
|
||||
//h.Logger.Debug("Processing config change element", "substring", change)
|
||||
if change == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the matches using the regex
|
||||
match := re.FindStringSubmatch(change)
|
||||
if len(match) > 0 {
|
||||
matchFound = true
|
||||
// Create a map with 'type' and 'newValue'
|
||||
changeMap := map[string]string{
|
||||
"type": match[1], // config type
|
||||
"newValue": match[2], // new value after -> or <-
|
||||
}
|
||||
//h.Logger.Debug("Adding new entry to output", "map", changeMap)
|
||||
result = append(result, changeMap)
|
||||
} else {
|
||||
h.Logger.Warn("No regex matches for string", "input", change)
|
||||
}
|
||||
}
|
||||
|
||||
if !matchFound {
|
||||
h.Logger.Info("No matches found for config change string", "input", configChanges)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *Handler) calculateNewDiskSize(event models.CloudEventReceived) float64 {
|
||||
var diskSize float64
|
||||
var totalDiskBytes int64
|
||||
h.Logger.Debug("connecting to vcenter")
|
||||
vc := vcenter.New(h.Logger, h.VcCreds)
|
||||
vc.Login(event.CloudEvent.Source)
|
||||
|
||||
vmObject, err := vc.FindVMByIDWithDatacenter(event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Datacenter.Value)
|
||||
|
||||
if err != nil {
|
||||
h.Logger.Error("Can't locate vm in vCenter", "vmID", event.CloudEvent.Data.VM.VM.Value, "error", err)
|
||||
} else {
|
||||
if vmObject.Config != nil {
|
||||
h.Logger.Debug("Found VM with config, calculating new total disk size", "vmID", event.CloudEvent.Data.VM.VM.Value)
|
||||
|
||||
// Calculate the total disk allocated in GB
|
||||
for _, device := range vmObject.Config.Hardware.Device {
|
||||
if disk, ok := device.(*types.VirtualDisk); ok {
|
||||
|
||||
// Print the filename of the backing device
|
||||
if backing, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
||||
h.Logger.Debug("Adding disk", "size_bytes", disk.CapacityInBytes, "backing_file", backing.FileName)
|
||||
} else {
|
||||
h.Logger.Debug("Adding disk, unknown backing type", "size_bytes", disk.CapacityInBytes)
|
||||
}
|
||||
|
||||
//diskSize += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB
|
||||
totalDiskBytes += disk.CapacityInBytes
|
||||
}
|
||||
}
|
||||
diskSize = float64(totalDiskBytes / 1024 / 1024 / 1024)
|
||||
h.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", diskSize)
|
||||
}
|
||||
}
|
||||
|
||||
err = vc.Logout()
|
||||
if err != nil {
|
||||
h.Logger.Error("unable to logout of vcenter", "error", err)
|
||||
}
|
||||
|
||||
h.Logger.Debug("Calculated new disk size", "value", diskSize)
|
||||
|
||||
return diskSize
|
||||
}
|
||||
|
||||
// AddVmToInventory adds a vm from a received cloudevent and returns the inventoryid and any error message
|
||||
func (h *Handler) AddVmToInventory(evt models.CloudEventReceived, ctx context.Context, unixTimestamp int64) (int64, error) {
|
||||
var (
|
||||
numVcpus int32
|
||||
numRam int32
|
||||
totalDiskGB float64
|
||||
srmPlaceholder string
|
||||
foundVm bool
|
||||
isTemplate string
|
||||
poweredOn string
|
||||
folderPath string
|
||||
rpName string
|
||||
vmUuid string
|
||||
)
|
||||
//c.Logger.Debug("connecting to vcenter")
|
||||
vc := vcenter.New(h.Logger, h.VcCreds)
|
||||
vc.Login(evt.CloudEvent.Source)
|
||||
|
||||
//datacenter = evt.DatacenterName.String
|
||||
vmObject, err := vc.FindVMByIDWithDatacenter(evt.CloudEvent.Data.VM.VM.Value, evt.CloudEvent.Data.Datacenter.Datacenter.Value)
|
||||
|
||||
if err != nil {
|
||||
h.Logger.Error("Can't locate vm in vCenter", "vmID", evt.CloudEvent.Data.VM.VM.Value, "error", err)
|
||||
return 0, err
|
||||
} else if vmObject == nil {
|
||||
h.Logger.Debug("didn't find VM", "vm_id", evt.CloudEvent.Data.VM.VM.Value)
|
||||
return 0, nil
|
||||
|
||||
}
|
||||
|
||||
//c.Logger.Debug("found VM")
|
||||
srmPlaceholder = "FALSE" // Default assumption
|
||||
//prettyPrint(vmObject)
|
||||
|
||||
// calculate VM properties we want to store
|
||||
if vmObject.Config != nil {
|
||||
numRam = vmObject.Config.Hardware.MemoryMB
|
||||
numVcpus = vmObject.Config.Hardware.NumCPU
|
||||
vmUuid = vmObject.Config.Uuid
|
||||
|
||||
var totalDiskBytes int64
|
||||
|
||||
// Calculate the total disk allocated in GB
|
||||
for _, device := range vmObject.Config.Hardware.Device {
|
||||
if disk, ok := device.(*types.VirtualDisk); ok {
|
||||
|
||||
// Print the filename of the backing device
|
||||
if _, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
||||
//c.Logger.Debug("Adding disk", "size_bytes", disk.CapacityInBytes, "backing_file", backing.FileName)
|
||||
} else {
|
||||
//c.Logger.Debug("Adding disk, unknown backing type", "size_bytes", disk.CapacityInBytes)
|
||||
}
|
||||
|
||||
totalDiskBytes += disk.CapacityInBytes
|
||||
//totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB
|
||||
}
|
||||
}
|
||||
totalDiskGB = float64(totalDiskBytes / 1024 / 1024 / 1024)
|
||||
h.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB)
|
||||
|
||||
// Determine if the VM is a normal VM or an SRM placeholder
|
||||
if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
|
||||
if vmObject.Config.ManagedBy.Type == "placeholderVm" {
|
||||
h.Logger.Debug("VM is a placeholder")
|
||||
srmPlaceholder = "TRUE"
|
||||
} else {
|
||||
h.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObject.Config.ManagedBy)
|
||||
}
|
||||
}
|
||||
|
||||
if vmObject.Config.Template {
|
||||
isTemplate = "TRUE"
|
||||
} else {
|
||||
isTemplate = "FALSE"
|
||||
}
|
||||
|
||||
// Retrieve the full folder path of the VM
|
||||
folderPath, err = vc.GetVMFolderPath(*vmObject)
|
||||
if err != nil {
|
||||
h.Logger.Error("failed to get vm folder path", "error", err)
|
||||
folderPath = ""
|
||||
} else {
|
||||
h.Logger.Debug("Found vm folder path", "folder_path", folderPath)
|
||||
}
|
||||
|
||||
// Retrieve the resource pool of the VM
|
||||
rpName, _ = vc.GetVmResourcePool(*vmObject)
|
||||
|
||||
foundVm = true
|
||||
} else {
|
||||
h.Logger.Error("Empty VM config")
|
||||
}
|
||||
|
||||
//c.Logger.Debug("VM has runtime data", "power_state", vmObject.Runtime.PowerState)
|
||||
if vmObject.Runtime.PowerState == "poweredOff" {
|
||||
poweredOn = "FALSE"
|
||||
} else {
|
||||
poweredOn = "TRUE"
|
||||
}
|
||||
|
||||
err = vc.Logout()
|
||||
if err != nil {
|
||||
h.Logger.Error("unable to logout of vcenter", "error", err)
|
||||
}
|
||||
|
||||
if foundVm {
|
||||
e := evt.CloudEvent
|
||||
h.Logger.Debug("Adding to Inventory table", "vm_name", e.Data.VM.Name, "vcpus", numVcpus, "ram", numRam, "dc", e.Data.Datacenter.Datacenter.Value)
|
||||
|
||||
insertParams := queries.CreateInventoryParams{
|
||||
Name: e.Data.VM.Name,
|
||||
Vcenter: evt.CloudEvent.Source,
|
||||
CloudId: sql.NullString{String: e.ID, Valid: e.ID != ""},
|
||||
EventKey: sql.NullString{String: strconv.Itoa(e.Data.Key), Valid: e.Data.Key > 0},
|
||||
VmId: sql.NullString{String: e.Data.VM.VM.Value, Valid: e.Data.VM.VM.Value != ""},
|
||||
Datacenter: sql.NullString{String: e.Data.Datacenter.Name, Valid: e.Data.Datacenter.Name != ""},
|
||||
Cluster: sql.NullString{String: e.Data.ComputeResource.Name, Valid: e.Data.ComputeResource.Name != ""},
|
||||
CreationTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0},
|
||||
InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0},
|
||||
InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0},
|
||||
ProvisionedDisk: sql.NullFloat64{Float64: totalDiskGB, Valid: totalDiskGB > 0},
|
||||
Folder: sql.NullString{String: folderPath, Valid: folderPath != ""},
|
||||
ResourcePool: sql.NullString{String: rpName, Valid: rpName != ""},
|
||||
VmUuid: sql.NullString{String: vmUuid, Valid: vmUuid != ""},
|
||||
SrmPlaceholder: srmPlaceholder,
|
||||
IsTemplate: isTemplate,
|
||||
PoweredOn: poweredOn,
|
||||
}
|
||||
|
||||
/*
|
||||
params := queries.CreateInventoryParams{
|
||||
Name: vmObject.Name,
|
||||
Vcenter: evt.Source,
|
||||
CloudId: sql.NullString{String: evt.CloudId, Valid: evt.CloudId != ""},
|
||||
EventKey: sql.NullString{String: evt.EventKey.String, Valid: evt.EventKey.Valid},
|
||||
VmId: sql.NullString{String: evt.VmId.String, Valid: evt.VmId.Valid},
|
||||
Datacenter: sql.NullString{String: evt.DatacenterName.String, Valid: evt.DatacenterName.Valid},
|
||||
Cluster: sql.NullString{String: evt.ComputeResourceName.String, Valid: evt.ComputeResourceName.Valid},
|
||||
CreationTime: sql.NullInt64{Int64: evt.EventTime.Int64, Valid: evt.EventTime.Valid},
|
||||
InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0},
|
||||
InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0},
|
||||
ProvisionedDisk: sql.NullFloat64{Float64: totalDiskGB, Valid: totalDiskGB > 0},
|
||||
Folder: sql.NullString{String: folderPath, Valid: folderPath != ""},
|
||||
ResourcePool: sql.NullString{String: rpName, Valid: rpName != ""},
|
||||
VmUuid: sql.NullString{String: vmUuid, Valid: vmUuid != ""},
|
||||
SrmPlaceholder: srmPlaceholder,
|
||||
IsTemplate: isTemplate,
|
||||
PoweredOn: poweredOn,
|
||||
}
|
||||
*/
|
||||
|
||||
//c.Logger.Debug("database params", "params", params)
|
||||
|
||||
// Insert the new inventory record into the database
|
||||
record, err := h.Database.Queries().CreateInventory(ctx, insertParams)
|
||||
if err != nil {
|
||||
h.Logger.Error("unable to perform database insert", "error", err)
|
||||
return 0, err
|
||||
} else {
|
||||
//c.Logger.Debug("created database record", "insert_result", result)
|
||||
return record.Iid, nil
|
||||
}
|
||||
} else {
|
||||
h.Logger.Debug("Not adding to Inventory due to missing vcenter config property", "vm_name", evt.CloudEvent.Data.VM.Name)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
147
server/handler/vmMoveEvent.go
Normal file
147
server/handler/vmMoveEvent.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"vctp/db/queries"
|
||||
models "vctp/server/models"
|
||||
)
|
||||
|
||||
func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) {
|
||||
params := queries.CreateUpdateParams{}
|
||||
var unixTimestamp int64
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.Logger.Error("Invalid data received", "error", err)
|
||||
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 CloudEventReceived struct
|
||||
var event models.CloudEventReceived
|
||||
if err := json.Unmarshal(reqBody, &event); err != nil {
|
||||
h.Logger.Error("unable to unmarshal json", "error", err)
|
||||
prettyPrint(reqBody)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Unable to unmarshal JSON in request body: '%s'", err),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
h.Logger.Debug("successfully decoded JSON")
|
||||
//prettyPrint(event)
|
||||
}
|
||||
|
||||
if event.CloudEvent.Data.OldParent == nil || event.CloudEvent.Data.NewParent == nil {
|
||||
h.Logger.Error("No resource pool data found in cloud event")
|
||||
prettyPrint(event)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("CloudEvent missing resource pool data"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.Logger.Debug("Checking inventory table for VM record")
|
||||
invParams := queries.GetInventoryVmIdParams{
|
||||
VmId: sql.NullString{String: event.CloudEvent.Data.VM.VM.Value, Valid: event.CloudEvent.Data.VM.VM.Value != ""},
|
||||
DatacenterName: sql.NullString{String: event.CloudEvent.Data.Datacenter.Name, Valid: event.CloudEvent.Data.Datacenter.Name != ""},
|
||||
}
|
||||
invResult, err := h.Database.Queries().GetInventoryVmId(ctx, invParams)
|
||||
|
||||
if err != nil {
|
||||
// If a VM is being moved it must exist, so lets add an inventory record for this VM
|
||||
h.Logger.Error("unable to find existing inventory record for this VM", "error", err)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// Add a record to the inventory table for this VM
|
||||
h.Logger.Info("Received VM modify event for a VM not currently in the inventory. Adding to inventory")
|
||||
|
||||
iid, err2 := h.AddVmToInventory(event, ctx, unixTimestamp)
|
||||
if err2 != nil {
|
||||
h.Logger.Error("Received error adding VM to inventory", "error", err2)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Valid request but experienced error adding vm id '%s' in datacenter name '%s' to inventory table : %s",
|
||||
event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name, err2),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if iid > 0 {
|
||||
params.InventoryId = sql.NullInt64{Int64: iid, Valid: iid > 0}
|
||||
} else {
|
||||
h.Logger.Error("Received zero for inventory id when adding VM to inventory")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Valid request but received zero result when adding vm id '%s' in datacenter name '%s' to inventory table",
|
||||
event.CloudEvent.Data.VM.VM.Value, event.CloudEvent.Data.Datacenter.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.InventoryId = sql.NullInt64{Int64: invResult.Iid, Valid: invResult.Iid > 0}
|
||||
}
|
||||
|
||||
// Parse the datetime string to a time.Time object
|
||||
eventTime, err := time.Parse(time.RFC3339, event.CloudEvent.Data.CreatedTime)
|
||||
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()
|
||||
}
|
||||
|
||||
// populate other parameters for the Update database record
|
||||
params.NewResourcePool = sql.NullString{String: event.CloudEvent.Data.NewParent.Name, Valid: event.CloudEvent.Data.NewParent.Name != ""}
|
||||
params.UpdateType = "move"
|
||||
params.EventId = sql.NullString{String: event.CloudEvent.ID, Valid: event.CloudEvent.ID != ""}
|
||||
params.EventKey = sql.NullString{String: strconv.Itoa(event.CloudEvent.Data.Key), Valid: event.CloudEvent.Data.Key > 0}
|
||||
params.UpdateTime = sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0}
|
||||
params.UserName = sql.NullString{String: event.CloudEvent.Data.UserName, Valid: event.CloudEvent.Data.UserName != ""}
|
||||
|
||||
// Create the Update database record
|
||||
result, err := h.Database.Queries().CreateUpdate(ctx, params)
|
||||
if err != nil {
|
||||
h.Logger.Error("unable to perform database insert", "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "ERROR",
|
||||
"message": fmt.Sprintf("Unable to insert move event into database: '%s'", err),
|
||||
})
|
||||
return
|
||||
|
||||
} else {
|
||||
h.Logger.Debug("created database record", "insert_result", result)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
//fmt.Fprintf(w, "Processed update event: %v\n", result)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "OK",
|
||||
"message": fmt.Sprintf("Successfully processed move event"),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
114
server/handler/vmUpdateDetails.go
Normal file
114
server/handler/vmUpdateDetails.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"vctp/db/queries"
|
||||
"vctp/internal/vcenter"
|
||||
)
|
||||
|
||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
||||
func (h *Handler) VmUpdateDetails(w http.ResponseWriter, r *http.Request) {
|
||||
var matchFound bool
|
||||
var inventoryId int64
|
||||
var srmPlaceholder string
|
||||
var vmUuid string
|
||||
var dbUuid string
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// reload settings in case vcenter list has changed
|
||||
h.Settings.ReadYMLSettings()
|
||||
|
||||
for _, url := range h.Settings.Values.Settings.VcenterAddresses {
|
||||
h.Logger.Debug("connecting to vcenter", "url", url)
|
||||
vc := vcenter.New(h.Logger, h.VcCreds)
|
||||
vc.Login(url)
|
||||
|
||||
// Get list of VMs from vcenter
|
||||
vms, err := vc.GetAllVmReferences()
|
||||
|
||||
// Get list of VMs from inventory table
|
||||
h.Logger.Debug("Querying inventory table")
|
||||
results, err := h.Database.Queries().GetInventoryByVcenter(ctx, url)
|
||||
if err != nil {
|
||||
h.Logger.Error("Unable to query inventory table", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Unable to query inventory table %s\n", err)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
h.Logger.Error("Empty inventory results")
|
||||
}
|
||||
|
||||
// Iterate VMs from vcenter and see if they were in the database
|
||||
for _, vm := range vms {
|
||||
matchFound = false
|
||||
inventoryId = 0
|
||||
srmPlaceholder = "FALSE" // Default assumption
|
||||
vmUuid = ""
|
||||
|
||||
for _, dbvm := range results {
|
||||
if dbvm.VmId.String == vm.Reference().Value {
|
||||
h.Logger.Debug("Found VM in database", "vm_name", dbvm.Name, "id", dbvm.VmId.String)
|
||||
matchFound = true
|
||||
inventoryId = dbvm.Iid
|
||||
dbUuid = dbvm.VmUuid.String
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matchFound {
|
||||
//h.Logger.Debug("Need to update VM in inventory table", "MoRef", vm.Reference())
|
||||
vmObj, err := vc.ConvertObjToMoVM(vm)
|
||||
if err != nil {
|
||||
h.Logger.Error("Received error getting vm managedobject", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if vmObj.Config != nil {
|
||||
vmUuid = vmObj.Config.Uuid
|
||||
|
||||
// Determine if the VM is a normal VM or an SRM placeholder
|
||||
if vmObj.Config.ManagedBy != nil && vmObj.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
|
||||
if vmObj.Config.ManagedBy.Type == "placeholderVm" {
|
||||
h.Logger.Debug("VM is a placeholder")
|
||||
srmPlaceholder = "TRUE"
|
||||
} else {
|
||||
h.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObj.Config.ManagedBy)
|
||||
}
|
||||
}
|
||||
|
||||
if srmPlaceholder == "TRUE" || vmUuid != dbUuid {
|
||||
h.Logger.Debug("Need to update vm", "name", vmObj.Name, "srm_placeholder", srmPlaceholder, "uuid", vmUuid)
|
||||
params := queries.InventoryUpdateParams{
|
||||
Iid: inventoryId,
|
||||
SrmPlaceholder: srmPlaceholder,
|
||||
Uuid: sql.NullString{String: vmUuid, Valid: vmUuid != ""},
|
||||
}
|
||||
|
||||
h.Logger.Debug("database params", "params", params)
|
||||
err := h.Database.Queries().InventoryUpdate(context.Background(), params)
|
||||
|
||||
if err != nil {
|
||||
h.Logger.Error("Error received updating inventory for VM", "name", vmObj.Name, "error", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
h.Logger.Warn("VM no longer present in vcenter or missing config values", "MoRef", vm.Reference())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
h.Logger.Debug("Processed vm update successfully")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// TODO - return some JSON
|
||||
fmt.Fprintf(w, "Processed vm update successfully")
|
||||
}
|
@@ -24,6 +24,7 @@ func NewLoggingMiddleware(logger *slog.Logger, handler http.Handler) *LoggingMid
|
||||
func (l *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
l.handler.ServeHTTP(w, r)
|
||||
|
||||
l.logger.Debug(
|
||||
"Request recieved",
|
||||
slog.String("method", r.Method),
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type CloudEventReceived struct {
|
||||
CloudEvent struct {
|
||||
@@ -8,7 +10,7 @@ type CloudEventReceived struct {
|
||||
Specversion string `json:"specversion"`
|
||||
Source string `json:"source"`
|
||||
Type string `json:"type"`
|
||||
Time string `json:"time"`
|
||||
Time string `json:"time"` // Modified from time.Time
|
||||
Data struct {
|
||||
ChainID int `json:"ChainId"`
|
||||
ChangeTag string `json:"ChangeTag"`
|
||||
@@ -19,7 +21,7 @@ type CloudEventReceived struct {
|
||||
} `json:"ComputeResource"`
|
||||
Name string `json:"Name"`
|
||||
} `json:"ComputeResource"`
|
||||
CreatedTime time.Time `json:"CreatedTime"`
|
||||
CreatedTime string `json:"CreatedTime"` // Modified from time.Time
|
||||
Datacenter struct {
|
||||
Datacenter struct {
|
||||
Type string `json:"Type"`
|
||||
@@ -37,28 +39,220 @@ type CloudEventReceived struct {
|
||||
} `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 {
|
||||
Key int `json:"Key"`
|
||||
Net interface{} `json:"Net"`
|
||||
NewParent *CloudEventResourcePool `json:"NewParent"`
|
||||
OldParent *CloudEventResourcePool `json:"OldParent"`
|
||||
SrcTemplate *CloudEventVm `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"`
|
||||
ConfigSpec any `json:"configSpec"`
|
||||
ConfigChanges struct {
|
||||
Modified string `json:"modified"`
|
||||
} `json:"configChanges"`
|
||||
ConfigSpec *json.RawMessage `json:"configSpec"`
|
||||
ConfigChanges *ConfigChangesReceived `json:"configChanges"` // Modified to separate struct
|
||||
} `json:"data"`
|
||||
} `json:"cloudEvent"`
|
||||
}
|
||||
|
||||
type CloudEventResourcePool struct {
|
||||
Name string `json:"Name"`
|
||||
ResourcePool struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"ResourcePool"`
|
||||
}
|
||||
|
||||
type CloudEventVm struct {
|
||||
Name string `json:"Name"`
|
||||
VM struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Vm"`
|
||||
}
|
||||
|
||||
type ImportReceived struct {
|
||||
Name string `json:"Name"`
|
||||
Vcenter string `json:"Vcenter"`
|
||||
VmId string `json:"VmId"`
|
||||
InitialRam int `json:"InitialRam"`
|
||||
PowerState int `json:"PowerState"`
|
||||
CreationTime int `json:"CreationTime"`
|
||||
InitialVcpus int `json:"InitialVcpus"`
|
||||
ProvisionedDisk float64 `json:"ProvisionedDisk"`
|
||||
Folder string `json:"Folder"`
|
||||
ResourcePool string `json:"ResourcePool"`
|
||||
Datacenter string `json:"Datacenter"`
|
||||
Cluster string `json:"Cluster"`
|
||||
}
|
||||
|
||||
type ConfigChangesReceived struct {
|
||||
Modified string `json:"modified"`
|
||||
}
|
||||
|
||||
// This probably needs more fields added so not in use yet
|
||||
type ConfigSpec struct {
|
||||
AlternateGuestName string `json:"AlternateGuestName"`
|
||||
Annotation string `json:"Annotation"`
|
||||
BootOptions any `json:"BootOptions"`
|
||||
ChangeTrackingEnabled any `json:"ChangeTrackingEnabled"`
|
||||
ChangeVersion string `json:"ChangeVersion"`
|
||||
ConsolePreferences any `json:"ConsolePreferences"`
|
||||
CPUAffinity any `json:"CpuAffinity"`
|
||||
CPUAllocation any `json:"CpuAllocation"`
|
||||
CPUFeatureMask any `json:"CpuFeatureMask"`
|
||||
CPUHotAddEnabled any `json:"CpuHotAddEnabled"`
|
||||
CPUHotRemoveEnabled any `json:"CpuHotRemoveEnabled"`
|
||||
CreateDate string `json:"CreateDate"` // Modified from time.Time
|
||||
Crypto any `json:"Crypto"`
|
||||
DeviceChange []struct {
|
||||
Backing any `json:"Backing"`
|
||||
Device struct {
|
||||
Backing *BackingSpec `json:"Backing,omitempty"`
|
||||
CapacityInBytes int `json:"CapacityInBytes"`
|
||||
CapacityInKB int `json:"CapacityInKB"`
|
||||
Connectable struct {
|
||||
AllowGuestControl bool `json:"AllowGuestControl"`
|
||||
Connected bool `json:"Connected"`
|
||||
MigrateConnect string `json:"MigrateConnect"`
|
||||
StartConnected bool `json:"StartConnected"`
|
||||
Status string `json:"Status"`
|
||||
} `json:"Connectable"`
|
||||
ControllerKey int `json:"ControllerKey"`
|
||||
DeviceInfo struct {
|
||||
Label string `json:"Label"`
|
||||
Summary string `json:"Summary"`
|
||||
} `json:"DeviceInfo"`
|
||||
ExternalID string `json:"ExternalId"`
|
||||
MacAddress string `json:"MacAddress"`
|
||||
ResourceAllocation struct {
|
||||
Limit int `json:"Limit"`
|
||||
Reservation int `json:"Reservation"`
|
||||
Share struct {
|
||||
Level string `json:"Level"`
|
||||
Shares int `json:"Shares"`
|
||||
} `json:"Share"`
|
||||
} `json:"ResourceAllocation"`
|
||||
SlotInfo any `json:"SlotInfo"`
|
||||
UnitNumber int `json:"UnitNumber"`
|
||||
UptCompatibilityEnabled bool `json:"UptCompatibilityEnabled"`
|
||||
WakeOnLanEnabled bool `json:"WakeOnLanEnabled"`
|
||||
DiskObjectID string `json:"DiskObjectId"`
|
||||
Iofilter any `json:"Iofilter"`
|
||||
Key int `json:"Key"`
|
||||
NativeUnmanagedLinkedClone any `json:"NativeUnmanagedLinkedClone"`
|
||||
Shares any `json:"Shares"`
|
||||
StorageIOAllocation struct {
|
||||
Limit int `json:"Limit"`
|
||||
Reservation any `json:"Reservation"`
|
||||
Shares struct {
|
||||
Level string `json:"Level"`
|
||||
Shares int `json:"Shares"`
|
||||
} `json:"Shares"`
|
||||
} `json:"StorageIOAllocation"`
|
||||
VDiskID any `json:"VDiskId"`
|
||||
VFlashCacheConfigInfo any `json:"VFlashCacheConfigInfo"`
|
||||
} `json:"Device,omitempty"`
|
||||
FileOperation string `json:"FileOperation"`
|
||||
Operation string `json:"Operation"`
|
||||
Profile []struct {
|
||||
ProfileData struct {
|
||||
ExtensionKey string `json:"ExtensionKey"`
|
||||
ObjectData string `json:"ObjectData"` // Modified from time.Time
|
||||
} `json:"ProfileData"`
|
||||
ProfileID string `json:"ProfileId"`
|
||||
ProfileParams any `json:"ProfileParams"`
|
||||
ReplicationSpec any `json:"ReplicationSpec"`
|
||||
} `json:"Profile"`
|
||||
} `json:"DeviceChange"`
|
||||
ExtraConfig any `json:"ExtraConfig"`
|
||||
Files struct {
|
||||
FtMetadataDirectory string `json:"FtMetadataDirectory"`
|
||||
LogDirectory string `json:"LogDirectory"`
|
||||
SnapshotDirectory string `json:"SnapshotDirectory"`
|
||||
SuspendDirectory string `json:"SuspendDirectory"`
|
||||
VMPathName string `json:"VmPathName"`
|
||||
} `json:"Files"`
|
||||
Firmware string `json:"Firmware"`
|
||||
Flags any `json:"Flags"`
|
||||
FtInfo any `json:"FtInfo"`
|
||||
GuestAutoLockEnabled any `json:"GuestAutoLockEnabled"`
|
||||
GuestID string `json:"GuestId"`
|
||||
GuestMonitoringModeInfo any `json:"GuestMonitoringModeInfo"`
|
||||
InstanceUUID string `json:"InstanceUuid"`
|
||||
LatencySensitivity any `json:"LatencySensitivity"`
|
||||
LocationID string `json:"LocationId"`
|
||||
ManagedBy any `json:"ManagedBy"`
|
||||
MaxMksConnections int `json:"MaxMksConnections"`
|
||||
MemoryAffinity any `json:"MemoryAffinity"`
|
||||
MemoryAllocation any `json:"MemoryAllocation"`
|
||||
MemoryHotAddEnabled any `json:"MemoryHotAddEnabled"`
|
||||
MemoryMB int `json:"MemoryMB"`
|
||||
MemoryReservationLockedToMax any `json:"MemoryReservationLockedToMax"`
|
||||
MessageBusTunnelEnabled any `json:"MessageBusTunnelEnabled"`
|
||||
MigrateEncryption string `json:"MigrateEncryption"`
|
||||
Name string `json:"Name"`
|
||||
NestedHVEnabled any `json:"NestedHVEnabled"`
|
||||
NetworkShaper any `json:"NetworkShaper"`
|
||||
NpivDesiredNodeWwns int `json:"NpivDesiredNodeWwns"`
|
||||
NpivDesiredPortWwns int `json:"NpivDesiredPortWwns"`
|
||||
NpivNodeWorldWideName any `json:"NpivNodeWorldWideName"`
|
||||
NpivOnNonRdmDisks any `json:"NpivOnNonRdmDisks"`
|
||||
NpivPortWorldWideName any `json:"NpivPortWorldWideName"`
|
||||
NpivTemporaryDisabled any `json:"NpivTemporaryDisabled"`
|
||||
NpivWorldWideNameOp string `json:"NpivWorldWideNameOp"`
|
||||
NpivWorldWideNameType string `json:"NpivWorldWideNameType"`
|
||||
NumCPUs int `json:"NumCPUs"`
|
||||
NumCoresPerSocket int `json:"NumCoresPerSocket"`
|
||||
PowerOpInfo any `json:"PowerOpInfo"`
|
||||
RepConfig any `json:"RepConfig"`
|
||||
ScheduledHardwareUpgradeInfo any `json:"ScheduledHardwareUpgradeInfo"`
|
||||
SevEnabled any `json:"SevEnabled"`
|
||||
SgxInfo any `json:"SgxInfo"`
|
||||
SwapPlacement string `json:"SwapPlacement"`
|
||||
Tools any `json:"Tools"`
|
||||
UUID string `json:"Uuid"`
|
||||
VAppConfig any `json:"VAppConfig"`
|
||||
VAppConfigRemoved any `json:"VAppConfigRemoved"`
|
||||
VAssertsEnabled any `json:"VAssertsEnabled"`
|
||||
VPMCEnabled any `json:"VPMCEnabled"`
|
||||
VcpuConfig any `json:"VcpuConfig"`
|
||||
Version string `json:"Version"`
|
||||
VirtualICH7MPresent any `json:"VirtualICH7MPresent"`
|
||||
VirtualSMCPresent any `json:"VirtualSMCPresent"`
|
||||
VMProfile any `json:"VmProfile"`
|
||||
}
|
||||
|
||||
type BackingSpec struct {
|
||||
Port struct {
|
||||
ConnectionCookie int `json:"ConnectionCookie"`
|
||||
PortKey string `json:"PortKey"`
|
||||
PortgroupKey string `json:"PortgroupKey"`
|
||||
SwitchUUID string `json:"SwitchUuid"`
|
||||
} `json:"Port"`
|
||||
BackingObjectID string `json:"BackingObjectId"`
|
||||
ChangeID string `json:"ChangeId"`
|
||||
ContentID string `json:"ContentId"`
|
||||
Datastore struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Datastore"`
|
||||
DeltaDiskFormat string `json:"DeltaDiskFormat"`
|
||||
DeltaDiskFormatVariant string `json:"DeltaDiskFormatVariant"`
|
||||
DeltaGrainSize int `json:"DeltaGrainSize"`
|
||||
DigestEnabled any `json:"DigestEnabled"`
|
||||
DiskMode string `json:"DiskMode"`
|
||||
EagerlyScrub bool `json:"EagerlyScrub"`
|
||||
FileName string `json:"FileName"`
|
||||
KeyID any `json:"KeyId"`
|
||||
Parent any `json:"Parent"`
|
||||
Sharing string `json:"Sharing"`
|
||||
Split any `json:"Split"`
|
||||
ThinProvisioned bool `json:"ThinProvisioned"`
|
||||
UUID string `json:"Uuid"`
|
||||
WriteThrough any `json:"WriteThrough"`
|
||||
}
|
||||
|
@@ -3,29 +3,59 @@ package router
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"vctp/db"
|
||||
"vctp/dist"
|
||||
"vctp/internal/secrets"
|
||||
"vctp/internal/settings"
|
||||
"vctp/internal/vcenter"
|
||||
"vctp/server/handler"
|
||||
"vctp/server/middleware"
|
||||
)
|
||||
|
||||
func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver string, goVersion string) http.Handler {
|
||||
func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver string, goVersion string, creds *vcenter.VcenterLogin, secret *secrets.Secrets, settings *settings.Settings) http.Handler {
|
||||
h := &handler.Handler{
|
||||
Logger: logger,
|
||||
Database: database,
|
||||
BuildTime: buildTime,
|
||||
SHA1Ver: sha1ver,
|
||||
GoVersion: goVersion,
|
||||
VcCreds: creds,
|
||||
Secret: secret,
|
||||
Settings: settings,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.Handle("/assets/", middleware.CacheMiddleware(http.FileServer(http.FS(dist.AssetsDir))))
|
||||
mux.HandleFunc("/", h.Home)
|
||||
mux.HandleFunc("/api/event/vm/create", h.VmCreate)
|
||||
mux.HandleFunc("/api/event/vm/modify", h.VmModify)
|
||||
mux.HandleFunc("/api/event/vm/delete", h.VmDelete)
|
||||
mux.HandleFunc("/api/event/vm/create", h.VmCreateEvent)
|
||||
mux.HandleFunc("/api/event/vm/modify", h.VmModifyEvent)
|
||||
mux.HandleFunc("/api/event/vm/move", h.VmMoveEvent)
|
||||
mux.HandleFunc("/api/event/vm/delete", h.VmDeleteEvent)
|
||||
mux.HandleFunc("/api/import/vm", h.VmImport)
|
||||
// Use this when we need to manually remove a VM from the database to clean up
|
||||
mux.HandleFunc("/api/inventory/vm/delete", h.VmCleanup)
|
||||
|
||||
// add missing data to VMs
|
||||
//mux.HandleFunc("/api/inventory/vm/update", h.VmUpdateDetails)
|
||||
|
||||
// temporary endpoint
|
||||
mux.HandleFunc("/api/cleanup/updates", h.UpdateCleanup)
|
||||
//mux.HandleFunc("/api/cleanup/vcenter", h.VcCleanup)
|
||||
|
||||
mux.HandleFunc("/api/report/inventory", h.InventoryReportDownload)
|
||||
mux.HandleFunc("/api/report/updates", h.UpdateReportDownload)
|
||||
|
||||
// endpoint for encrypting vcenter credential
|
||||
mux.HandleFunc("/api/encrypt", h.EncryptData)
|
||||
|
||||
// Register pprof handlers
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
|
||||
return middleware.NewLoggingMiddleware(logger, mux)
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ type Server struct {
|
||||
disableTls bool
|
||||
tlsCertFilename string
|
||||
tlsKeyFilename string
|
||||
encryptionKey string
|
||||
}
|
||||
|
||||
// New creates a new server with the given logger, address and options.
|
||||
@@ -41,22 +42,28 @@ func New(logger *slog.Logger, cron gocron.Scheduler, cancel context.CancelFunc,
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
Addr: addr,
|
||||
//WriteTimeout: 120 * time.Second,
|
||||
WriteTimeout: 0,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
TLSConfig: tlsConfig,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&Server{srv: srv})
|
||||
}
|
||||
|
||||
return &Server{
|
||||
// Set the initial server values
|
||||
server := &Server{
|
||||
srv: srv,
|
||||
logger: logger,
|
||||
cron: cron,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
// Apply any options
|
||||
for _, opt := range opts {
|
||||
opt(server)
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
// Option represents a server option.
|
||||
@@ -83,19 +90,33 @@ func WithRouter(handler http.Handler) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// DisableTls sets the disable tls value
|
||||
func (s *Server) DisableTls(disableTls bool) {
|
||||
s.disableTls = disableTls
|
||||
// SetKey sets the encryption key we use when generating secrets
|
||||
func SetKey(key string) Option {
|
||||
return func(s *Server) {
|
||||
s.encryptionKey = key
|
||||
}
|
||||
}
|
||||
|
||||
// SetTls sets the disable tls value
|
||||
func SetTls(disableTls bool) Option {
|
||||
return func(s *Server) {
|
||||
s.disableTls = disableTls
|
||||
}
|
||||
}
|
||||
|
||||
// SetCertificate sets the path to the certificate used for TLS, in PEM format
|
||||
func (s *Server) SetCertificate(tlsCertFilename string) {
|
||||
s.tlsCertFilename = tlsCertFilename
|
||||
func SetCertificate(tlsCertFilename string) Option {
|
||||
return func(s *Server) {
|
||||
//fmt.Printf("Setting tlsCertFilename to '%s'\n", tlsCertFilename)
|
||||
s.tlsCertFilename = tlsCertFilename
|
||||
}
|
||||
}
|
||||
|
||||
// SetPrivateKey sets the path to the private key used for TLS, in PEM format
|
||||
func (s *Server) SetPrivateKey(tlsKeyFilename string) {
|
||||
s.tlsKeyFilename = tlsKeyFilename
|
||||
func SetPrivateKey(tlsKeyFilename string) Option {
|
||||
return func(s *Server) {
|
||||
s.tlsKeyFilename = tlsKeyFilename
|
||||
}
|
||||
}
|
||||
|
||||
// StartAndWait starts the server and waits for a signal to shut down.
|
||||
@@ -111,12 +132,14 @@ func (s *Server) Start() {
|
||||
if s.disableTls {
|
||||
s.logger.Info("starting server", "port", s.srv.Addr)
|
||||
if err := s.srv.ListenAndServe(); err != nil {
|
||||
s.logger.Warn("failed to start server", "error", err)
|
||||
s.logger.Error("failed to start server", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} 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 && err != http.ErrServerClosed {
|
||||
s.logger.Warn("failed to start server", "error", err)
|
||||
s.logger.Error("failed to start server", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
8
settings.yaml
Normal file
8
settings.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
settings:
|
||||
tenants_to_filter:
|
||||
- "DecomVM"
|
||||
node_charge_clusters:
|
||||
- ".*CMD.*"
|
||||
srm_activeactive_vms:
|
||||
vcenter_addresses:
|
||||
- "https://vc.lab.local/sdk"
|
736
vm.json
736
vm.json
@@ -1,736 +0,0 @@
|
||||
{
|
||||
"self": {
|
||||
"type": "VirtualMachine",
|
||||
"value": "vm-13671"
|
||||
},
|
||||
"value": null,
|
||||
"availableField": null,
|
||||
"parent": null,
|
||||
"customValue": null,
|
||||
"overallStatus": "",
|
||||
"configStatus": "",
|
||||
"configIssue": null,
|
||||
"effectiveRole": null,
|
||||
"permission": null,
|
||||
"name": "minecraft",
|
||||
"disabledMethod": null,
|
||||
"recentTask": null,
|
||||
"declaredAlarmState": null,
|
||||
"triggeredAlarmState": null,
|
||||
"alarmActionsEnabled": null,
|
||||
"tag": null,
|
||||
"capability": {
|
||||
"snapshotOperationsSupported": false,
|
||||
"multipleSnapshotsSupported": false,
|
||||
"snapshotConfigSupported": false,
|
||||
"poweredOffSnapshotsSupported": false,
|
||||
"memorySnapshotsSupported": false,
|
||||
"revertToSnapshotSupported": false,
|
||||
"quiescedSnapshotsSupported": false,
|
||||
"disableSnapshotsSupported": false,
|
||||
"lockSnapshotsSupported": false,
|
||||
"consolePreferencesSupported": false,
|
||||
"cpuFeatureMaskSupported": false,
|
||||
"s1AcpiManagementSupported": false,
|
||||
"settingScreenResolutionSupported": false,
|
||||
"toolsAutoUpdateSupported": false,
|
||||
"vmNpivWwnSupported": false,
|
||||
"npivWwnOnNonRdmVmSupported": false,
|
||||
"swapPlacementSupported": false,
|
||||
"toolsSyncTimeSupported": false,
|
||||
"virtualMmuUsageSupported": false,
|
||||
"diskSharesSupported": false,
|
||||
"bootOptionsSupported": false,
|
||||
"settingVideoRamSizeSupported": false
|
||||
},
|
||||
"config": {
|
||||
"changeVersion": "2024-05-24T06:41:25.868508Z",
|
||||
"modified": "1970-01-01T00:00:00Z",
|
||||
"name": "minecraft",
|
||||
"guestFullName": "Ubuntu Linux (32-bit)",
|
||||
"version": "vmx-08",
|
||||
"uuid": "422598c8-5ab7-63e7-ba34-8111936fea59",
|
||||
"createDate": "1970-01-01T00:00:00Z",
|
||||
"instanceUuid": "50251955-6d1b-8954-ae28-50284dd4b44e",
|
||||
"npivTemporaryDisabled": true,
|
||||
"locationId": "564d4d65-27af-07d1-d627-70d056c7f233",
|
||||
"template": false,
|
||||
"guestId": "ubuntuGuest",
|
||||
"alternateGuestName": "",
|
||||
"files": {
|
||||
"vmPathName": "[freenas] minecraft/minecraft.vmx",
|
||||
"snapshotDirectory": "[freenas] minecraft/",
|
||||
"suspendDirectory": "[freenas] minecraft/",
|
||||
"logDirectory": "[freenas] minecraft/"
|
||||
},
|
||||
"tools": {
|
||||
"toolsVersion": 10362,
|
||||
"afterPowerOn": true,
|
||||
"afterResume": true,
|
||||
"beforeGuestStandby": true,
|
||||
"beforeGuestShutdown": true,
|
||||
"toolsUpgradePolicy": "upgradeAtPowerCycle",
|
||||
"syncTimeWithHostAllowed": true,
|
||||
"syncTimeWithHost": true,
|
||||
"lastInstallInfo": {
|
||||
"counter": 6
|
||||
}
|
||||
},
|
||||
"flags": {
|
||||
"enableLogging": true,
|
||||
"useToe": false,
|
||||
"runWithDebugInfo": false,
|
||||
"monitorType": "release",
|
||||
"htSharing": "any",
|
||||
"snapshotDisabled": false,
|
||||
"snapshotLocked": false,
|
||||
"diskUuidEnabled": false,
|
||||
"snapshotPowerOffBehavior": "powerOff",
|
||||
"recordReplayEnabled": false,
|
||||
"faultToleranceType": "unset",
|
||||
"vvtdEnabled": false,
|
||||
"vbsEnabled": false
|
||||
},
|
||||
"defaultPowerOps": {
|
||||
"powerOffType": "soft",
|
||||
"suspendType": "hard",
|
||||
"resetType": "soft",
|
||||
"defaultPowerOffType": "soft",
|
||||
"defaultSuspendType": "hard",
|
||||
"defaultResetType": "soft",
|
||||
"standbyAction": "checkpoint"
|
||||
},
|
||||
"hardware": {
|
||||
"numCPU": 1,
|
||||
"numCoresPerSocket": 1,
|
||||
"autoCoresPerSocket": false,
|
||||
"memoryMB": 3072,
|
||||
"virtualICH7MPresent": false,
|
||||
"virtualSMCPresent": false,
|
||||
"device": [
|
||||
{
|
||||
"key": 100,
|
||||
"deviceInfo": {
|
||||
"label": "PCI controller 0",
|
||||
"summary": "PCI controller 0"
|
||||
},
|
||||
"busNumber": 0,
|
||||
"device": [
|
||||
500,
|
||||
12000,
|
||||
1000,
|
||||
4000
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": 200,
|
||||
"deviceInfo": {
|
||||
"label": "IDE 0",
|
||||
"summary": "IDE 0"
|
||||
},
|
||||
"busNumber": 0
|
||||
},
|
||||
{
|
||||
"key": 201,
|
||||
"deviceInfo": {
|
||||
"label": "IDE 1",
|
||||
"summary": "IDE 1"
|
||||
},
|
||||
"busNumber": 1,
|
||||
"device": [
|
||||
3002
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": 300,
|
||||
"deviceInfo": {
|
||||
"label": "PS2 controller 0",
|
||||
"summary": "PS2 controller 0"
|
||||
},
|
||||
"busNumber": 0,
|
||||
"device": [
|
||||
600,
|
||||
700
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": 400,
|
||||
"deviceInfo": {
|
||||
"label": "SIO controller 0",
|
||||
"summary": "SIO controller 0"
|
||||
},
|
||||
"busNumber": 0,
|
||||
"device": [
|
||||
8000
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": 500,
|
||||
"deviceInfo": {
|
||||
"label": "Video card ",
|
||||
"summary": "Video card"
|
||||
},
|
||||
"controllerKey": 100,
|
||||
"unitNumber": 0,
|
||||
"videoRamSizeInKB": 4096,
|
||||
"numDisplays": 1,
|
||||
"useAutoDetect": false,
|
||||
"enable3DSupport": false,
|
||||
"use3dRenderer": "automatic",
|
||||
"graphicsMemorySizeInKB": 262144
|
||||
},
|
||||
{
|
||||
"key": 600,
|
||||
"deviceInfo": {
|
||||
"label": "Keyboard ",
|
||||
"summary": "Keyboard"
|
||||
},
|
||||
"controllerKey": 300,
|
||||
"unitNumber": 0
|
||||
},
|
||||
{
|
||||
"key": 700,
|
||||
"deviceInfo": {
|
||||
"label": "Pointing device",
|
||||
"summary": "Pointing device; Device"
|
||||
},
|
||||
"backing": {
|
||||
"deviceName": "",
|
||||
"useAutoDetect": false,
|
||||
"hostPointingDevice": "autodetect"
|
||||
},
|
||||
"controllerKey": 300,
|
||||
"unitNumber": 1
|
||||
},
|
||||
{
|
||||
"key": 1000,
|
||||
"deviceInfo": {
|
||||
"label": "SCSI controller 0",
|
||||
"summary": "LSI Logic"
|
||||
},
|
||||
"slotInfo": {
|
||||
"pciSlotNumber": 16
|
||||
},
|
||||
"controllerKey": 100,
|
||||
"unitNumber": 3,
|
||||
"busNumber": 0,
|
||||
"device": [
|
||||
2000
|
||||
],
|
||||
"hotAddRemove": true,
|
||||
"sharedBus": "noSharing",
|
||||
"scsiCtlrUnitNumber": 7
|
||||
},
|
||||
{
|
||||
"key": 2000,
|
||||
"deviceInfo": {
|
||||
"label": "Hard disk 1",
|
||||
"summary": "62,914,560 KB"
|
||||
},
|
||||
"backing": {
|
||||
"fileName": "[freenas] minecraft/minecraft-000002.vmdk",
|
||||
"datastore": {
|
||||
"type": "Datastore",
|
||||
"value": "datastore-6035"
|
||||
},
|
||||
"diskMode": "persistent",
|
||||
"split": false,
|
||||
"writeThrough": false,
|
||||
"thinProvisioned": true,
|
||||
"eagerlyScrub": false,
|
||||
"uuid": "6000C294-55e8-39b9-1852-00dcfd299398",
|
||||
"contentId": "b739ed36bfc510b2f1c70000e628ed40",
|
||||
"parent": {
|
||||
"fileName": "[freenas] minecraft/minecraft-000001.vmdk",
|
||||
"datastore": {
|
||||
"type": "Datastore",
|
||||
"value": "datastore-6035"
|
||||
},
|
||||
"diskMode": "persistent",
|
||||
"thinProvisioned": true,
|
||||
"eagerlyScrub": false,
|
||||
"uuid": "6000C294-55e8-39b9-1852-00dcfd299398",
|
||||
"contentId": "45ff3b3b3935d631facb7eadbb916797",
|
||||
"parent": {
|
||||
"fileName": "[freenas] minecraft/minecraft.vmdk",
|
||||
"datastore": {
|
||||
"type": "Datastore",
|
||||
"value": "datastore-6035"
|
||||
},
|
||||
"diskMode": "persistent",
|
||||
"thinProvisioned": true,
|
||||
"eagerlyScrub": false,
|
||||
"uuid": "6000C294-55e8-39b9-1852-00dcfd299398",
|
||||
"contentId": "8e6ae3c31967c906303f7b3fe1b379f8",
|
||||
"digestEnabled": false
|
||||
},
|
||||
"deltaDiskFormat": "redoLogFormat",
|
||||
"digestEnabled": false,
|
||||
"deltaDiskFormatVariant": "vmfsSparseVariant"
|
||||
},
|
||||
"deltaDiskFormat": "redoLogFormat",
|
||||
"digestEnabled": false,
|
||||
"deltaDiskFormatVariant": "vmfsSparseVariant",
|
||||
"sharing": "sharingNone"
|
||||
},
|
||||
"controllerKey": 1000,
|
||||
"unitNumber": 0,
|
||||
"capacityInKB": 62914560,
|
||||
"capacityInBytes": 64424509440,
|
||||
"shares": {
|
||||
"shares": 1000,
|
||||
"level": "normal"
|
||||
},
|
||||
"storageIOAllocation": {
|
||||
"limit": -1,
|
||||
"shares": {
|
||||
"shares": 1000,
|
||||
"level": "normal"
|
||||
},
|
||||
"reservation": 0
|
||||
},
|
||||
"diskObjectId": "271-2000",
|
||||
"vDiskVersion": 1,
|
||||
"nativeUnmanagedLinkedClone": false,
|
||||
"guestReadOnly": false
|
||||
},
|
||||
{
|
||||
"key": 3002,
|
||||
"deviceInfo": {
|
||||
"label": "CD/DVD drive 1",
|
||||
"summary": "ISO [] /usr/lib/vmware/isoimages/linux.iso"
|
||||
},
|
||||
"backing": {
|
||||
"fileName": "[] /usr/lib/vmware/isoimages/linux.iso"
|
||||
},
|
||||
"connectable": {
|
||||
"startConnected": false,
|
||||
"allowGuestControl": true,
|
||||
"connected": false,
|
||||
"status": "untried"
|
||||
},
|
||||
"controllerKey": 201,
|
||||
"unitNumber": 0
|
||||
},
|
||||
{
|
||||
"key": 4000,
|
||||
"deviceInfo": {
|
||||
"label": "Network adapter 1",
|
||||
"summary": "DVSwitch: 50 02 bc 27 fa 1b 2c d1-0d 43 fb 29 46 60 c4 0b"
|
||||
},
|
||||
"backing": {
|
||||
"port": {
|
||||
"switchUuid": "50 02 bc 27 fa 1b 2c d1-0d 43 fb 29 46 60 c4 0b",
|
||||
"portgroupKey": "dvportgroup-4031",
|
||||
"portKey": "102",
|
||||
"connectionCookie": 209430599
|
||||
}
|
||||
},
|
||||
"connectable": {
|
||||
"migrateConnect": "unset",
|
||||
"startConnected": true,
|
||||
"allowGuestControl": true,
|
||||
"connected": false,
|
||||
"status": "untried"
|
||||
},
|
||||
"slotInfo": {
|
||||
"pciSlotNumber": 32
|
||||
},
|
||||
"controllerKey": 100,
|
||||
"unitNumber": 7,
|
||||
"addressType": "assigned",
|
||||
"macAddress": "00:50:56:a5:16:29",
|
||||
"wakeOnLanEnabled": true,
|
||||
"resourceAllocation": {
|
||||
"reservation": 0,
|
||||
"share": {
|
||||
"shares": 50,
|
||||
"level": "normal"
|
||||
},
|
||||
"limit": -1
|
||||
},
|
||||
"uptCompatibilityEnabled": false
|
||||
},
|
||||
{
|
||||
"key": 8000,
|
||||
"deviceInfo": {
|
||||
"label": "Floppy drive 1",
|
||||
"summary": "Remote"
|
||||
},
|
||||
"backing": {
|
||||
"deviceName": "",
|
||||
"useAutoDetect": false
|
||||
},
|
||||
"connectable": {
|
||||
"startConnected": false,
|
||||
"allowGuestControl": true,
|
||||
"connected": false,
|
||||
"status": "untried"
|
||||
},
|
||||
"controllerKey": 400,
|
||||
"unitNumber": 0
|
||||
},
|
||||
{
|
||||
"key": 12000,
|
||||
"deviceInfo": {
|
||||
"label": "VMCI device",
|
||||
"summary": "Device on the virtual machine PCI bus that provides support for the virtual machine communication interface"
|
||||
},
|
||||
"slotInfo": {
|
||||
"pciSlotNumber": 33
|
||||
},
|
||||
"controllerKey": 100,
|
||||
"unitNumber": 17,
|
||||
"id": -1821382055,
|
||||
"allowUnrestrictedCommunication": false,
|
||||
"filterEnable": true
|
||||
}
|
||||
],
|
||||
"motherboardLayout": "i440bxHostBridge",
|
||||
"simultaneousThreads": 1
|
||||
},
|
||||
"cpuAllocation": {
|
||||
"reservation": 0,
|
||||
"expandableReservation": false,
|
||||
"limit": -1,
|
||||
"shares": {
|
||||
"shares": 1000,
|
||||
"level": "normal"
|
||||
}
|
||||
},
|
||||
"memoryAllocation": {
|
||||
"reservation": 0,
|
||||
"expandableReservation": false,
|
||||
"limit": -1,
|
||||
"shares": {
|
||||
"shares": 30720,
|
||||
"level": "normal"
|
||||
}
|
||||
},
|
||||
"latencySensitivity": {
|
||||
"level": "normal"
|
||||
},
|
||||
"memoryHotAddEnabled": false,
|
||||
"cpuHotAddEnabled": false,
|
||||
"cpuHotRemoveEnabled": false,
|
||||
"extraConfig": [
|
||||
{
|
||||
"key": "svga.present",
|
||||
"value": "TRUE"
|
||||
},
|
||||
{
|
||||
"key": "vmci.filter.enable",
|
||||
"value": "TRUE"
|
||||
},
|
||||
{
|
||||
"key": "tools.guest.desktop.autolock",
|
||||
"value": "FALSE"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge0.present",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge4.present",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge4.virtualDev",
|
||||
"value": "pcieRootPort"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge4.functions",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge5.present",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge5.virtualDev",
|
||||
"value": "pcieRootPort"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge5.functions",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge6.present",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge6.virtualDev",
|
||||
"value": "pcieRootPort"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge6.functions",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge7.present",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge7.virtualDev",
|
||||
"value": "pcieRootPort"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge7.functions",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"key": "hpet0.present",
|
||||
"value": "TRUE"
|
||||
},
|
||||
{
|
||||
"key": "nvram",
|
||||
"value": "minecraft.nvram"
|
||||
},
|
||||
{
|
||||
"key": "virtualHW.productCompatibility",
|
||||
"value": "hosted"
|
||||
},
|
||||
{
|
||||
"key": "scsi0.pciSlotNumber",
|
||||
"value": "16"
|
||||
},
|
||||
{
|
||||
"key": "ethernet0.pciSlotNumber",
|
||||
"value": "32"
|
||||
},
|
||||
{
|
||||
"key": "vmci0.pciSlotNumber",
|
||||
"value": "33"
|
||||
},
|
||||
{
|
||||
"key": "snapshot.action",
|
||||
"value": "keep"
|
||||
},
|
||||
{
|
||||
"key": "sched.cpu.latencySensitivity",
|
||||
"value": "normal"
|
||||
},
|
||||
{
|
||||
"key": "replay.supported",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge0.pciSlotNumber",
|
||||
"value": "17"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge4.pciSlotNumber",
|
||||
"value": "21"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge5.pciSlotNumber",
|
||||
"value": "22"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge6.pciSlotNumber",
|
||||
"value": "23"
|
||||
},
|
||||
{
|
||||
"key": "pciBridge7.pciSlotNumber",
|
||||
"value": "24"
|
||||
},
|
||||
{
|
||||
"key": "tools.remindInstall",
|
||||
"value": "FALSE"
|
||||
},
|
||||
{
|
||||
"key": "hostCPUID.0",
|
||||
"value": "00000016756e65476c65746e49656e69"
|
||||
},
|
||||
{
|
||||
"key": "hostCPUID.1",
|
||||
"value": "000906ea001008007ffafbffbfebfbff"
|
||||
},
|
||||
{
|
||||
"key": "hostCPUID.80000001",
|
||||
"value": "0000000000000000000001212c100800"
|
||||
},
|
||||
{
|
||||
"key": "guestCPUID.0",
|
||||
"value": "0000000d756e65476c65746e49656e69"
|
||||
},
|
||||
{
|
||||
"key": "guestCPUID.1",
|
||||
"value": "000406f00001080096d832030f8bfbff"
|
||||
},
|
||||
{
|
||||
"key": "guestCPUID.80000001",
|
||||
"value": "00000000000000000000010128100800"
|
||||
},
|
||||
{
|
||||
"key": "userCPUID.0",
|
||||
"value": "0000000d756e65476c65746e49656e69"
|
||||
},
|
||||
{
|
||||
"key": "userCPUID.1",
|
||||
"value": "000406f00001080096d832030f8bfbff"
|
||||
},
|
||||
{
|
||||
"key": "userCPUID.80000001",
|
||||
"value": "00000000000000000000010128100800"
|
||||
},
|
||||
{
|
||||
"key": "evcCompatibilityMode",
|
||||
"value": "TRUE"
|
||||
},
|
||||
{
|
||||
"key": "vmotion.checkpointFBSize",
|
||||
"value": "4194304"
|
||||
},
|
||||
{
|
||||
"key": "softPowerOff",
|
||||
"value": "TRUE"
|
||||
},
|
||||
{
|
||||
"key": "toolsInstallManager.updateCounter",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"key": "toolsInstallManager.lastInstallError",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "numa.autosize.vcpu.maxPerVirtualNode",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"key": "numa.autosize.cookie",
|
||||
"value": "10001"
|
||||
},
|
||||
{
|
||||
"key": "sched.swap.derivedName",
|
||||
"value": "/vmfs/volumes/a79e4951-d31e6955/minecraft/minecraft-4bfd4f1d.vswp"
|
||||
},
|
||||
{
|
||||
"key": "scsi0:0.redo",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"key": "monitor.phys_bits_used",
|
||||
"value": "40"
|
||||
},
|
||||
{
|
||||
"key": "viv.moid",
|
||||
"value": "91a88fc3-5936-4bfb-a6f8-018e073fcefb:vm-13671:XMmrS0IZV2SrrGL4GiADh//vraEEXaRMaC4vxsNBPOI="
|
||||
},
|
||||
{
|
||||
"key": "vmxstats.filename",
|
||||
"value": "minecraft.scoreboard"
|
||||
},
|
||||
{
|
||||
"key": "tools.capability.verifiedSamlToken",
|
||||
"value": "TRUE"
|
||||
},
|
||||
{
|
||||
"key": "vmware.tools.internalversion",
|
||||
"value": "10362"
|
||||
},
|
||||
{
|
||||
"key": "vmware.tools.requiredversion",
|
||||
"value": "12389"
|
||||
},
|
||||
{
|
||||
"key": "migrate.hostLogState",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"key": "migrate.migrationId",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "migrate.hostLog",
|
||||
"value": "minecraft-662da845.hlog"
|
||||
}
|
||||
],
|
||||
"datastoreUrl": [
|
||||
{
|
||||
"name": "freenas",
|
||||
"url": "/vmfs/volumes/a79e4951-d31e6955"
|
||||
}
|
||||
],
|
||||
"swapPlacement": "inherit",
|
||||
"bootOptions": {
|
||||
"enterBIOSSetup": false,
|
||||
"efiSecureBootEnabled": false,
|
||||
"bootRetryEnabled": false,
|
||||
"bootRetryDelay": 10000,
|
||||
"networkBootProtocol": "ipv4"
|
||||
},
|
||||
"changeTrackingEnabled": false,
|
||||
"firmware": "bios",
|
||||
"maxMksConnections": 40,
|
||||
"guestAutoLockEnabled": false,
|
||||
"memoryReservationLockedToMax": false,
|
||||
"nestedHVEnabled": false,
|
||||
"vPMCEnabled": false,
|
||||
"scheduledHardwareUpgradeInfo": {
|
||||
"upgradePolicy": "never",
|
||||
"scheduledHardwareUpgradeStatus": "none"
|
||||
},
|
||||
"messageBusTunnelEnabled": false,
|
||||
"guestIntegrityInfo": {
|
||||
"enabled": false
|
||||
},
|
||||
"migrateEncryption": "opportunistic",
|
||||
"sgxInfo": {
|
||||
"epcSize": 0,
|
||||
"flcMode": "unlocked",
|
||||
"requireAttestation": false
|
||||
},
|
||||
"ftEncryptionMode": "ftEncryptionOpportunistic",
|
||||
"guestMonitoringModeInfo": {},
|
||||
"sevEnabled": false,
|
||||
"numaInfo": {
|
||||
"coresPerNumaNode": 0,
|
||||
"autoCoresPerNumaNode": true
|
||||
},
|
||||
"vmOpNotificationToAppEnabled": false,
|
||||
"vmOpNotificationTimeout": -1,
|
||||
"deviceGroups": {},
|
||||
"fixedPassthruHotPlugEnabled": false
|
||||
},
|
||||
"layout": null,
|
||||
"layoutEx": null,
|
||||
"storage": null,
|
||||
"environmentBrowser": {
|
||||
"type": "",
|
||||
"value": ""
|
||||
},
|
||||
"resourcePool": null,
|
||||
"parentVApp": null,
|
||||
"resourceConfig": null,
|
||||
"runtime": {
|
||||
"connectionState": "",
|
||||
"powerState": "",
|
||||
"toolsInstallerMounted": false,
|
||||
"numMksConnections": 0
|
||||
},
|
||||
"guest": null,
|
||||
"summary": {
|
||||
"runtime": {
|
||||
"connectionState": "",
|
||||
"powerState": "",
|
||||
"toolsInstallerMounted": false,
|
||||
"numMksConnections": 0
|
||||
},
|
||||
"config": {
|
||||
"name": "",
|
||||
"template": false,
|
||||
"vmPathName": ""
|
||||
},
|
||||
"quickStats": {
|
||||
"guestHeartbeatStatus": ""
|
||||
},
|
||||
"overallStatus": ""
|
||||
},
|
||||
"datastore": null,
|
||||
"network": null,
|
||||
"snapshot": null,
|
||||
"rootSnapshot": null,
|
||||
"guestHeartbeatStatus": ""
|
||||
}
|
Reference in New Issue
Block a user