Compare commits
87 Commits
934c082ba1
...
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 | |||
40fb860385 | |||
1cb36be02c |
@@ -23,14 +23,16 @@ do
|
|||||||
output_name+='.exe'
|
output_name+='.exe'
|
||||||
fi
|
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
|
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
|
if [ $? -ne 0 ]; then
|
||||||
echo 'An error has occurred! Aborting the script execution...'
|
echo 'An error has occurred! Aborting the script execution...'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
gzip build/$output_name
|
||||||
echo "build complete at $buildtime : $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
|
done
|
||||||
|
|
||||||
ls -lah build
|
ls -lah build
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,7 +37,7 @@ appengine-generated/
|
|||||||
/components/*/*.txt
|
/components/*/*.txt
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
dist/assets/css/
|
#dist/assets/css/
|
||||||
*.sqlite3*
|
*.sqlite3*
|
||||||
tmp/
|
tmp/
|
||||||
pb_data/
|
pb_data/
|
||||||
|
@@ -3,7 +3,7 @@ package core
|
|||||||
templ Footer() {
|
templ Footer() {
|
||||||
<footer class="fixed p-1 bottom-0 bg-gray-100 w-full border-t">
|
<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">
|
<div class="rounded-lg p-4 text-xs italic text-gray-700 text-center">
|
||||||
© Go Fullstack
|
© Nathan Coad (nathan.coad@dell.com)
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
}
|
}
|
@@ -29,7 +29,7 @@ func Footer() templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,8 @@ templ Header() {
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<meta name="description" content="Hello world"/>
|
<meta name="description" content="vCTP API endpoint"/>
|
||||||
<title>Test Page</title>
|
<title>vCTP API</title>
|
||||||
<script src="/assets/js/htmx@v2.0.2.min.js"></script>
|
<script src="/assets/js/htmx@v2.0.2.min.js"></script>
|
||||||
<link href={ "/assets/css/output@" + version.Value + ".css" } rel="stylesheet"/>
|
<link href={ "/assets/css/output@" + version.Value + ".css" } rel="stylesheet"/>
|
||||||
</head>
|
</head>
|
||||||
|
@@ -31,7 +31,7 @@ func Header() templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
29
db/db.go
29
db/db.go
@@ -82,19 +82,48 @@ func ConvertToSQLParams(input interface{}, output interface{}) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle fields of type sql.NullString, sql.NullInt64, and normal string/int64 fields
|
||||||
switch outputField.Type() {
|
switch outputField.Type() {
|
||||||
case reflect.TypeOf(sql.NullString{}):
|
case reflect.TypeOf(sql.NullString{}):
|
||||||
|
// Handle sql.NullString
|
||||||
if inputField.Kind() == reflect.Ptr && inputField.IsNil() {
|
if inputField.Kind() == reflect.Ptr && inputField.IsNil() {
|
||||||
outputField.Set(reflect.ValueOf(sql.NullString{Valid: false}))
|
outputField.Set(reflect.ValueOf(sql.NullString{Valid: false}))
|
||||||
} else {
|
} else {
|
||||||
outputField.Set(reflect.ValueOf(sql.NullString{String: inputField.String(), Valid: true}))
|
outputField.Set(reflect.ValueOf(sql.NullString{String: inputField.String(), Valid: true}))
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.TypeOf(sql.NullInt64{}):
|
case reflect.TypeOf(sql.NullInt64{}):
|
||||||
|
// Handle sql.NullInt64
|
||||||
if inputField.Int() == 0 {
|
if inputField.Int() == 0 {
|
||||||
outputField.Set(reflect.ValueOf(sql.NullInt64{Valid: false}))
|
outputField.Set(reflect.ValueOf(sql.NullInt64{Valid: false}))
|
||||||
} else {
|
} else {
|
||||||
outputField.Set(reflect.ValueOf(sql.NullInt64{Int64: inputField.Int(), Valid: true}))
|
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())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"vctp/db/queries"
|
"vctp/db/queries"
|
||||||
|
|
||||||
@@ -36,6 +37,8 @@ func (d *LocalDB) Logger() *slog.Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *LocalDB) Close() error {
|
func (d *LocalDB) Close() error {
|
||||||
|
fmt.Println("Shutting database")
|
||||||
|
d.logger.Debug("test")
|
||||||
return d.db.Close()
|
return d.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +82,7 @@ func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pragma := range pragmas {
|
for _, pragma := range pragmas {
|
||||||
|
logger.Debug("Setting pragma", "pragma", pragma)
|
||||||
_, err := db.Exec(pragma)
|
_, err := db.Exec(pragma)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("failed to execute pragma statement", "stmt", pragma, "error", err)
|
logger.Error("failed to execute pragma statement", "stmt", pragma, "error", err)
|
||||||
|
@@ -10,8 +10,8 @@ ALTER TABLE "Events" ADD COLUMN VmName TEXT;
|
|||||||
-- +goose Down
|
-- +goose Down
|
||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
ALTER TABLE "Events" DROP COLUMN VmName;
|
ALTER TABLE "Events" DROP COLUMN VmName;
|
||||||
ALTER TABLE "Updates" DROP COLUMN ComputeResourceId;
|
ALTER TABLE "Events" DROP COLUMN ComputeResourceId;
|
||||||
ALTER TABLE "Updates" DROP COLUMN DatacenterId;
|
ALTER TABLE "Events" DROP COLUMN DatacenterId;
|
||||||
ALTER TABLE "Events" RENAME COLUMN ComputeResourceName to ComputeResource;
|
ALTER TABLE "Events" RENAME COLUMN ComputeResourceName to ComputeResource;
|
||||||
ALTER TABLE "Events" RENAME COLUMN DatacenterName to Datacenter;
|
ALTER TABLE "Events" RENAME COLUMN DatacenterName to Datacenter;
|
||||||
-- +goose StatementEnd
|
-- +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
|
DatacenterId sql.NullString
|
||||||
ComputeResourceId sql.NullString
|
ComputeResourceId sql.NullString
|
||||||
VmName sql.NullString
|
VmName sql.NullString
|
||||||
|
EventType sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inventory struct {
|
type Inventory struct {
|
||||||
@@ -31,7 +32,7 @@ type Inventory struct {
|
|||||||
Vcenter string
|
Vcenter string
|
||||||
VmId sql.NullString
|
VmId sql.NullString
|
||||||
EventKey sql.NullString
|
EventKey sql.NullString
|
||||||
EventId sql.NullString
|
CloudId sql.NullString
|
||||||
CreationTime sql.NullInt64
|
CreationTime sql.NullInt64
|
||||||
DeletionTime sql.NullInt64
|
DeletionTime sql.NullInt64
|
||||||
ResourcePool sql.NullString
|
ResourcePool sql.NullString
|
||||||
@@ -42,7 +43,21 @@ type Inventory struct {
|
|||||||
ProvisionedDisk sql.NullFloat64
|
ProvisionedDisk sql.NullFloat64
|
||||||
InitialVcpus sql.NullInt64
|
InitialVcpus sql.NullInt64
|
||||||
InitialRam 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 {
|
type Updates struct {
|
||||||
@@ -55,4 +70,9 @@ type Updates struct {
|
|||||||
NewResourcePool sql.NullString
|
NewResourcePool sql.NullString
|
||||||
EventKey sql.NullString
|
EventKey sql.NullString
|
||||||
EventId 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"
|
SELECT * FROM "Inventory"
|
||||||
ORDER BY "Name";
|
ORDER BY "Name";
|
||||||
|
|
||||||
|
-- name: GetReportInventory :many
|
||||||
|
SELECT * FROM "Inventory"
|
||||||
|
ORDER BY "CreationTime";
|
||||||
|
|
||||||
-- name: GetInventoryByName :many
|
-- name: GetInventoryByName :many
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM "Inventory"
|
||||||
WHERE "Name" = ?;
|
WHERE "Name" = ?;
|
||||||
|
|
||||||
|
-- name: GetInventoryByVcenter :many
|
||||||
|
SELECT * FROM "Inventory"
|
||||||
|
WHERE "Vcenter" = ?;
|
||||||
|
|
||||||
-- name: GetInventoryVmId :one
|
-- name: GetInventoryVmId :one
|
||||||
SELECT * FROM "Inventory"
|
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
|
-- name: GetInventoryEventId :one
|
||||||
SELECT * FROM "Inventory"
|
SELECT * FROM "Inventory"
|
||||||
WHERE "EventId" = ? LIMIT 1;
|
WHERE "CloudId" = ? LIMIT 1;
|
||||||
|
|
||||||
-- name: CreateInventory :one
|
-- name: CreateInventory :one
|
||||||
INSERT INTO "Inventory" (
|
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(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
)
|
)
|
||||||
RETURNING *;
|
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
|
-- name: CreateUpdate :one
|
||||||
INSERT INTO "Updates" (
|
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(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
)
|
)
|
||||||
RETURNING *;
|
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
|
-- name: CreateEvent :one
|
||||||
INSERT INTO "Events" (
|
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(
|
) VALUES(
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
)
|
)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
@@ -45,9 +104,18 @@ ORDER BY "EventTime";
|
|||||||
-- name: ListUnprocessedEvents :many
|
-- name: ListUnprocessedEvents :many
|
||||||
SELECT * FROM "Events"
|
SELECT * FROM "Events"
|
||||||
WHERE "Processed" = 0
|
WHERE "Processed" = 0
|
||||||
|
AND "EventTime" > sqlc.arg('eventTime')
|
||||||
ORDER BY "EventTime";
|
ORDER BY "EventTime";
|
||||||
|
|
||||||
-- name: UpdateEventsProcessed :exec
|
-- name: UpdateEventsProcessed :exec
|
||||||
UPDATE "Events"
|
UPDATE "Events"
|
||||||
SET "Processed" = 1
|
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"
|
"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
|
const createEvent = `-- name: CreateEvent :one
|
||||||
INSERT INTO "Events" (
|
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(
|
) 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 {
|
type CreateEventParams struct {
|
||||||
@@ -26,6 +53,7 @@ type CreateEventParams struct {
|
|||||||
ChainId string
|
ChainId string
|
||||||
VmId sql.NullString
|
VmId sql.NullString
|
||||||
VmName sql.NullString
|
VmName sql.NullString
|
||||||
|
EventType sql.NullString
|
||||||
EventKey sql.NullString
|
EventKey sql.NullString
|
||||||
DatacenterId sql.NullString
|
DatacenterId sql.NullString
|
||||||
DatacenterName sql.NullString
|
DatacenterName sql.NullString
|
||||||
@@ -42,6 +70,7 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
|
|||||||
arg.ChainId,
|
arg.ChainId,
|
||||||
arg.VmId,
|
arg.VmId,
|
||||||
arg.VmName,
|
arg.VmName,
|
||||||
|
arg.EventType,
|
||||||
arg.EventKey,
|
arg.EventKey,
|
||||||
arg.DatacenterId,
|
arg.DatacenterId,
|
||||||
arg.DatacenterName,
|
arg.DatacenterName,
|
||||||
@@ -65,35 +94,39 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
|
|||||||
&i.DatacenterId,
|
&i.DatacenterId,
|
||||||
&i.ComputeResourceId,
|
&i.ComputeResourceId,
|
||||||
&i.VmName,
|
&i.VmName,
|
||||||
|
&i.EventType,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const createInventory = `-- name: CreateInventory :one
|
const createInventory = `-- name: CreateInventory :one
|
||||||
INSERT INTO "Inventory" (
|
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(
|
) 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 {
|
type CreateInventoryParams struct {
|
||||||
Name string
|
Name string
|
||||||
Vcenter string
|
Vcenter string
|
||||||
VmId sql.NullString
|
VmId sql.NullString
|
||||||
|
VmUuid sql.NullString
|
||||||
EventKey sql.NullString
|
EventKey sql.NullString
|
||||||
EventId sql.NullString
|
CloudId sql.NullString
|
||||||
CreationTime sql.NullInt64
|
CreationTime sql.NullInt64
|
||||||
ResourcePool sql.NullString
|
ResourcePool sql.NullString
|
||||||
VmType sql.NullString
|
VmType sql.NullString
|
||||||
|
IsTemplate interface{}
|
||||||
Datacenter sql.NullString
|
Datacenter sql.NullString
|
||||||
Cluster sql.NullString
|
Cluster sql.NullString
|
||||||
Folder sql.NullString
|
Folder sql.NullString
|
||||||
ProvisionedDisk sql.NullFloat64
|
ProvisionedDisk sql.NullFloat64
|
||||||
InitialVcpus sql.NullInt64
|
InitialVcpus sql.NullInt64
|
||||||
InitialRam sql.NullInt64
|
InitialRam sql.NullInt64
|
||||||
SrmPlaceholder sql.NullInt64
|
SrmPlaceholder interface{}
|
||||||
|
PoweredOn interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams) (Inventory, error) {
|
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.Name,
|
||||||
arg.Vcenter,
|
arg.Vcenter,
|
||||||
arg.VmId,
|
arg.VmId,
|
||||||
|
arg.VmUuid,
|
||||||
arg.EventKey,
|
arg.EventKey,
|
||||||
arg.EventId,
|
arg.CloudId,
|
||||||
arg.CreationTime,
|
arg.CreationTime,
|
||||||
arg.ResourcePool,
|
arg.ResourcePool,
|
||||||
arg.VmType,
|
arg.VmType,
|
||||||
|
arg.IsTemplate,
|
||||||
arg.Datacenter,
|
arg.Datacenter,
|
||||||
arg.Cluster,
|
arg.Cluster,
|
||||||
arg.Folder,
|
arg.Folder,
|
||||||
@@ -113,6 +148,7 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
|||||||
arg.InitialVcpus,
|
arg.InitialVcpus,
|
||||||
arg.InitialRam,
|
arg.InitialRam,
|
||||||
arg.SrmPlaceholder,
|
arg.SrmPlaceholder,
|
||||||
|
arg.PoweredOn,
|
||||||
)
|
)
|
||||||
var i Inventory
|
var i Inventory
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@@ -121,7 +157,7 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
|||||||
&i.Vcenter,
|
&i.Vcenter,
|
||||||
&i.VmId,
|
&i.VmId,
|
||||||
&i.EventKey,
|
&i.EventKey,
|
||||||
&i.EventId,
|
&i.CloudId,
|
||||||
&i.CreationTime,
|
&i.CreationTime,
|
||||||
&i.DeletionTime,
|
&i.DeletionTime,
|
||||||
&i.ResourcePool,
|
&i.ResourcePool,
|
||||||
@@ -132,22 +168,69 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
|
|||||||
&i.ProvisionedDisk,
|
&i.ProvisionedDisk,
|
||||||
&i.InitialVcpus,
|
&i.InitialVcpus,
|
||||||
&i.InitialRam,
|
&i.InitialRam,
|
||||||
|
&i.IsTemplate,
|
||||||
|
&i.PoweredOn,
|
||||||
&i.SrmPlaceholder,
|
&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
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const createUpdate = `-- name: CreateUpdate :one
|
const createUpdate = `-- name: CreateUpdate :one
|
||||||
INSERT INTO "Updates" (
|
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(
|
) 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 {
|
type CreateUpdateParams struct {
|
||||||
InventoryId sql.NullInt64
|
InventoryId sql.NullInt64
|
||||||
|
Name sql.NullString
|
||||||
EventKey sql.NullString
|
EventKey sql.NullString
|
||||||
EventId sql.NullString
|
EventId sql.NullString
|
||||||
UpdateTime sql.NullInt64
|
UpdateTime sql.NullInt64
|
||||||
@@ -155,11 +238,16 @@ type CreateUpdateParams struct {
|
|||||||
NewVcpus sql.NullInt64
|
NewVcpus sql.NullInt64
|
||||||
NewRam sql.NullInt64
|
NewRam sql.NullInt64
|
||||||
NewResourcePool sql.NullString
|
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) {
|
func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Updates, error) {
|
||||||
row := q.db.QueryRowContext(ctx, createUpdate,
|
row := q.db.QueryRowContext(ctx, createUpdate,
|
||||||
arg.InventoryId,
|
arg.InventoryId,
|
||||||
|
arg.Name,
|
||||||
arg.EventKey,
|
arg.EventKey,
|
||||||
arg.EventId,
|
arg.EventId,
|
||||||
arg.UpdateTime,
|
arg.UpdateTime,
|
||||||
@@ -167,6 +255,10 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
|
|||||||
arg.NewVcpus,
|
arg.NewVcpus,
|
||||||
arg.NewRam,
|
arg.NewRam,
|
||||||
arg.NewResourcePool,
|
arg.NewResourcePool,
|
||||||
|
arg.NewProvisionedDisk,
|
||||||
|
arg.UserName,
|
||||||
|
arg.PlaceholderChange,
|
||||||
|
arg.RawChangeString,
|
||||||
)
|
)
|
||||||
var i Updates
|
var i Updates
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@@ -179,12 +271,17 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
|
|||||||
&i.NewResourcePool,
|
&i.NewResourcePool,
|
||||||
&i.EventKey,
|
&i.EventKey,
|
||||||
&i.EventId,
|
&i.EventId,
|
||||||
|
&i.NewProvisionedDisk,
|
||||||
|
&i.UserName,
|
||||||
|
&i.PlaceholderChange,
|
||||||
|
&i.Name,
|
||||||
|
&i.RawChangeString,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryByName = `-- name: GetInventoryByName :many
|
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" = ?
|
WHERE "Name" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -203,7 +300,7 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
|
|||||||
&i.Vcenter,
|
&i.Vcenter,
|
||||||
&i.VmId,
|
&i.VmId,
|
||||||
&i.EventKey,
|
&i.EventKey,
|
||||||
&i.EventId,
|
&i.CloudId,
|
||||||
&i.CreationTime,
|
&i.CreationTime,
|
||||||
&i.DeletionTime,
|
&i.DeletionTime,
|
||||||
&i.ResourcePool,
|
&i.ResourcePool,
|
||||||
@@ -214,7 +311,59 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
|
|||||||
&i.ProvisionedDisk,
|
&i.ProvisionedDisk,
|
||||||
&i.InitialVcpus,
|
&i.InitialVcpus,
|
||||||
&i.InitialRam,
|
&i.InitialRam,
|
||||||
|
&i.IsTemplate,
|
||||||
|
&i.PoweredOn,
|
||||||
&i.SrmPlaceholder,
|
&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 {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -230,12 +379,12 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getInventoryEventId = `-- name: GetInventoryEventId :one
|
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"
|
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
|
||||||
WHERE "EventId" = ? LIMIT 1
|
WHERE "CloudId" = ? LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullString) (Inventory, error) {
|
func (q *Queries) GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (Inventory, error) {
|
||||||
row := q.db.QueryRowContext(ctx, getInventoryEventId, eventid)
|
row := q.db.QueryRowContext(ctx, getInventoryEventId, cloudid)
|
||||||
var i Inventory
|
var i Inventory
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Iid,
|
&i.Iid,
|
||||||
@@ -243,7 +392,7 @@ func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullStrin
|
|||||||
&i.Vcenter,
|
&i.Vcenter,
|
||||||
&i.VmId,
|
&i.VmId,
|
||||||
&i.EventKey,
|
&i.EventKey,
|
||||||
&i.EventId,
|
&i.CloudId,
|
||||||
&i.CreationTime,
|
&i.CreationTime,
|
||||||
&i.DeletionTime,
|
&i.DeletionTime,
|
||||||
&i.ResourcePool,
|
&i.ResourcePool,
|
||||||
@@ -254,18 +403,75 @@ func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullStrin
|
|||||||
&i.ProvisionedDisk,
|
&i.ProvisionedDisk,
|
||||||
&i.InitialVcpus,
|
&i.InitialVcpus,
|
||||||
&i.InitialRam,
|
&i.InitialRam,
|
||||||
|
&i.IsTemplate,
|
||||||
|
&i.PoweredOn,
|
||||||
&i.SrmPlaceholder,
|
&i.SrmPlaceholder,
|
||||||
|
&i.VmUuid,
|
||||||
)
|
)
|
||||||
return i, err
|
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
|
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"
|
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" = ? LIMIT 1
|
WHERE "VmId" = ?1 AND "Datacenter" = ?2
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (Inventory, error) {
|
type GetInventoryVmIdParams struct {
|
||||||
row := q.db.QueryRowContext(ctx, getInventoryVmId, vmid)
|
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
|
var i Inventory
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Iid,
|
&i.Iid,
|
||||||
@@ -273,7 +479,7 @@ func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (In
|
|||||||
&i.Vcenter,
|
&i.Vcenter,
|
||||||
&i.VmId,
|
&i.VmId,
|
||||||
&i.EventKey,
|
&i.EventKey,
|
||||||
&i.EventId,
|
&i.CloudId,
|
||||||
&i.CreationTime,
|
&i.CreationTime,
|
||||||
&i.DeletionTime,
|
&i.DeletionTime,
|
||||||
&i.ResourcePool,
|
&i.ResourcePool,
|
||||||
@@ -284,13 +490,266 @@ func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (In
|
|||||||
&i.ProvisionedDisk,
|
&i.ProvisionedDisk,
|
||||||
&i.InitialVcpus,
|
&i.InitialVcpus,
|
||||||
&i.InitialRam,
|
&i.InitialRam,
|
||||||
|
&i.IsTemplate,
|
||||||
|
&i.PoweredOn,
|
||||||
&i.SrmPlaceholder,
|
&i.SrmPlaceholder,
|
||||||
|
&i.VmUuid,
|
||||||
)
|
)
|
||||||
return i, err
|
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
|
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"
|
ORDER BY "EventTime"
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -318,6 +777,7 @@ func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
|
|||||||
&i.DatacenterId,
|
&i.DatacenterId,
|
||||||
&i.ComputeResourceId,
|
&i.ComputeResourceId,
|
||||||
&i.VmName,
|
&i.VmName,
|
||||||
|
&i.EventType,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -333,7 +793,7 @@ func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listInventory = `-- name: ListInventory :many
|
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"
|
ORDER BY "Name"
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -352,7 +812,7 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
|
|||||||
&i.Vcenter,
|
&i.Vcenter,
|
||||||
&i.VmId,
|
&i.VmId,
|
||||||
&i.EventKey,
|
&i.EventKey,
|
||||||
&i.EventId,
|
&i.CloudId,
|
||||||
&i.CreationTime,
|
&i.CreationTime,
|
||||||
&i.DeletionTime,
|
&i.DeletionTime,
|
||||||
&i.ResourcePool,
|
&i.ResourcePool,
|
||||||
@@ -363,7 +823,10 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
|
|||||||
&i.ProvisionedDisk,
|
&i.ProvisionedDisk,
|
||||||
&i.InitialVcpus,
|
&i.InitialVcpus,
|
||||||
&i.InitialRam,
|
&i.InitialRam,
|
||||||
|
&i.IsTemplate,
|
||||||
|
&i.PoweredOn,
|
||||||
&i.SrmPlaceholder,
|
&i.SrmPlaceholder,
|
||||||
|
&i.VmUuid,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -379,13 +842,14 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listUnprocessedEvents = `-- name: ListUnprocessedEvents :many
|
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
|
WHERE "Processed" = 0
|
||||||
|
AND "EventTime" > ?1
|
||||||
ORDER BY "EventTime"
|
ORDER BY "EventTime"
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) ListUnprocessedEvents(ctx context.Context) ([]Events, error) {
|
func (q *Queries) ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]Events, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents)
|
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents, eventtime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -408,6 +872,7 @@ func (q *Queries) ListUnprocessedEvents(ctx context.Context) ([]Events, error) {
|
|||||||
&i.DatacenterId,
|
&i.DatacenterId,
|
||||||
&i.ComputeResourceId,
|
&i.ComputeResourceId,
|
||||||
&i.VmName,
|
&i.VmName,
|
||||||
|
&i.EventType,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
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 = ?;
|
|
536
dist/assets/css/mvp.css
vendored
Normal file
536
dist/assets/css/mvp.css
vendored
Normal file
@@ -0,0 +1,536 @@
|
|||||||
|
/* MVP.css v1.14 - https://github.com/andybrewer/mvp */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--active-brightness: 0.85;
|
||||||
|
--border-radius: 5px;
|
||||||
|
--box-shadow: 2px 2px 10px;
|
||||||
|
--color-accent: #118bee15;
|
||||||
|
--color-bg: #fff;
|
||||||
|
--color-bg-secondary: #e9e9e9;
|
||||||
|
--color-link: #118bee;
|
||||||
|
--color-secondary: #920de9;
|
||||||
|
--color-secondary-accent: #920de90b;
|
||||||
|
--color-shadow: #f4f4f4;
|
||||||
|
--color-table: #118bee;
|
||||||
|
--color-text: #000;
|
||||||
|
--color-text-secondary: #999;
|
||||||
|
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
--hover-brightness: 1.2;
|
||||||
|
--justify-important: center;
|
||||||
|
--justify-table: left;
|
||||||
|
--justify-normal: left;
|
||||||
|
--line-height: 1.5;
|
||||||
|
--width-card: 285px;
|
||||||
|
--width-card-medium: 460px;
|
||||||
|
--width-card-wide: 800px;
|
||||||
|
/* --width-content: 1080px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root[color-mode="user"] {
|
||||||
|
--color-accent: #0097fc4f;
|
||||||
|
--color-bg: #333;
|
||||||
|
--color-bg-secondary: #555;
|
||||||
|
--color-link: #0097fc;
|
||||||
|
--color-secondary: #e20de9;
|
||||||
|
--color-secondary-accent: #e20de94f;
|
||||||
|
--color-shadow: #bbbbbb20;
|
||||||
|
--color-table: #0097fc;
|
||||||
|
--color-text: #f7f7f7;
|
||||||
|
--color-text-secondary: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
article aside {
|
||||||
|
background: var(--color-secondary-accent);
|
||||||
|
border-left: 4px solid var(--color-secondary);
|
||||||
|
padding: 0.01rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
line-height: var(--line-height);
|
||||||
|
margin: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
main {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: var(--width-content);
|
||||||
|
padding: 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
margin: 2rem 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: var(--justify-important);
|
||||||
|
}
|
||||||
|
|
||||||
|
section img,
|
||||||
|
article img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section pre {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
section aside {
|
||||||
|
border: 1px solid var(--color-bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--box-shadow) var(--color-shadow);
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1.25rem;
|
||||||
|
width: var(--width-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
section aside:hover {
|
||||||
|
box-shadow: var(--box-shadow) var(--color-bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headers */
|
||||||
|
article header,
|
||||||
|
div header,
|
||||||
|
main header {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
text-align: var(--justify-important);
|
||||||
|
}
|
||||||
|
|
||||||
|
header a b,
|
||||||
|
header a em,
|
||||||
|
header a i,
|
||||||
|
header a strong {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header nav img {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section header {
|
||||||
|
padding-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
nav {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-weight: bold;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav Dropdown */
|
||||||
|
nav ul li:hover ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li ul {
|
||||||
|
background: var(--color-bg);
|
||||||
|
border: 1px solid var(--color-bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--box-shadow) var(--color-shadow);
|
||||||
|
display: none;
|
||||||
|
height: auto;
|
||||||
|
left: -2px;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
position: absolute;
|
||||||
|
top: 1.7rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li ul::before {
|
||||||
|
/* fill gap above to make mousing over them easier */
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: -0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li ul li,
|
||||||
|
nav ul li ul li a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
code,
|
||||||
|
samp {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
color: var(--color-text);
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0.1rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
margin: 1.3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
details summary {
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
line-height: var(--line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol li,
|
||||||
|
ul li {
|
||||||
|
padding: 0.2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 1rem 0;
|
||||||
|
max-width: var(--width-card-wide);
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code,
|
||||||
|
pre samp {
|
||||||
|
display: block;
|
||||||
|
max-width: var(--width-card-wide);
|
||||||
|
padding: 0.5rem 2rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
color: var(--color-bg);
|
||||||
|
font-size: xx-small;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0.2rem;
|
||||||
|
padding: 0.2rem 0.3rem;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
a {
|
||||||
|
color: var(--color-link);
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active {
|
||||||
|
filter: brightness(var(--active-brightness));
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
filter: brightness(var(--hover-brightness));
|
||||||
|
}
|
||||||
|
|
||||||
|
a b,
|
||||||
|
a em,
|
||||||
|
a i,
|
||||||
|
a strong,
|
||||||
|
button,
|
||||||
|
input[type="submit"] {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
display: inline-block;
|
||||||
|
font-size: medium;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input[type="submit"] {
|
||||||
|
font-family: var(--font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active,
|
||||||
|
input[type="submit"]:active {
|
||||||
|
filter: brightness(var(--active-brightness));
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
input[type="submit"]:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
filter: brightness(var(--hover-brightness));
|
||||||
|
}
|
||||||
|
|
||||||
|
a b,
|
||||||
|
a strong,
|
||||||
|
button,
|
||||||
|
input[type="submit"] {
|
||||||
|
background-color: var(--color-link);
|
||||||
|
border: 2px solid var(--color-link);
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
a em,
|
||||||
|
a i {
|
||||||
|
border: 2px solid var(--color-link);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
color: var(--color-link);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article aside a {
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Images */
|
||||||
|
figure {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure figcaption {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
button:disabled,
|
||||||
|
input:disabled {
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
border-color: var(--color-bg-secondary);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[disabled]:hover,
|
||||||
|
input[type="submit"][disabled]:hover {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
border: 1px solid var(--color-bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--box-shadow) var(--color-shadow);
|
||||||
|
display: block;
|
||||||
|
max-width: var(--width-card-wide);
|
||||||
|
min-width: var(--width-card);
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: var(--justify-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
form header {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
label,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
display: block;
|
||||||
|
font-size: inherit;
|
||||||
|
max-width: var(--width-card-wide);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]+label,
|
||||||
|
input[type="radio"]+label {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: normal;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
padding: 0.4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
border: 1px solid var(--color-bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
textarea {
|
||||||
|
width: calc(100% - 1.6rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[readonly],
|
||||||
|
textarea[readonly] {
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popups */
|
||||||
|
dialog {
|
||||||
|
border: 1px solid var(--color-bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--box-shadow) var(--color-shadow);
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 50%;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
table {
|
||||||
|
border: 1px solid var(--color-bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border-spacing: 0;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0;
|
||||||
|
/*white-space: nowrap;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
table td,
|
||||||
|
table th,
|
||||||
|
table tr {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
text-align: var(--justify-table);
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead {
|
||||||
|
background-color: var(--color-table);
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
color: var(--color-bg);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead th:first-child {
|
||||||
|
border-top-left-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead th:last-child {
|
||||||
|
border-top-right-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead th:first-child,
|
||||||
|
table tr td:first-child {
|
||||||
|
text-align: var(--justify-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(even) {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quotes */
|
||||||
|
blockquote {
|
||||||
|
display: block;
|
||||||
|
font-size: x-large;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
margin: 1rem auto;
|
||||||
|
max-width: var(--width-card-medium);
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
text-align: var(--justify-important);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote footer {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
display: block;
|
||||||
|
font-size: small;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbars */
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgb(202, 202, 232) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgb(202, 202, 232);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
1
dist/assets/css/output@0.0.1.css
vendored
Normal file
1
dist/assets/css/output@0.0.1.css
vendored
Normal file
File diff suppressed because one or more lines are too long
788
dist/assets/css/output@dev.css
vendored
Normal file
788
dist/assets/css/output@dev.css
vendored
Normal file
@@ -0,0 +1,788 @@
|
|||||||
|
/*
|
||||||
|
! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||||
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||||
|
*/
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
border-width: 0;
|
||||||
|
/* 2 */
|
||||||
|
border-style: solid;
|
||||||
|
/* 2 */
|
||||||
|
border-color: #e5e7eb;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
--tw-content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use a consistent sensible line-height in all browsers.
|
||||||
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
3. Use a more readable tab size.
|
||||||
|
4. Use the user's configured `sans` font-family by default.
|
||||||
|
5. Use the user's configured `sans` font-feature-settings by default.
|
||||||
|
6. Use the user's configured `sans` font-variation-settings by default.
|
||||||
|
7. Disable tap highlights on iOS
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
:host {
|
||||||
|
line-height: 1.5;
|
||||||
|
/* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
/* 4 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 5 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 6 */
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
/* 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove the margin in all browsers.
|
||||||
|
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Add the correct height in Firefox.
|
||||||
|
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||||
|
3. Ensure horizontal rules are visible by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-top-width: 1px;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr:where([title]) {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the default font size and weight for headings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset links to optimize for opt-in styling instead of opt-out.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font weight in Edge and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use the user's configured `mono` font-family by default.
|
||||||
|
2. Use the user's configured `mono` font-feature-settings by default.
|
||||||
|
3. Use the user's configured `mono` font-variation-settings by default.
|
||||||
|
4. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp,
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 2 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 3 */
|
||||||
|
font-size: 1em;
|
||||||
|
/* 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||||
|
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||||
|
3. Remove gaps between table borders by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
text-indent: 0;
|
||||||
|
/* 1 */
|
||||||
|
border-color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-collapse: collapse;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Change the font styles in all browsers.
|
||||||
|
2. Remove the margin in Firefox and Safari.
|
||||||
|
3. Remove default padding in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-variation-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 100%;
|
||||||
|
/* 1 */
|
||||||
|
font-weight: inherit;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 1 */
|
||||||
|
letter-spacing: inherit;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 1 */
|
||||||
|
margin: 0;
|
||||||
|
/* 2 */
|
||||||
|
padding: 0;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inheritance of text transform in Edge and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Remove default button styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input:where([type='button']),
|
||||||
|
input:where([type='reset']),
|
||||||
|
input:where([type='submit']) {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
background-color: transparent;
|
||||||
|
/* 2 */
|
||||||
|
background-image: none;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the modern Firefox focus style for all focusable elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct vertical alignment in Chrome and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Correct the cursor style of increment and decrement buttons in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type='search'] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
outline-offset: -2px;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
font: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct display in Chrome and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes the default spacing and border for appropriate elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset default styling for dialogs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent resizing textareas horizontally by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||||
|
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the default cursor for buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make sure disabled buttons don't get the pointer cursor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||||
|
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block;
|
||||||
|
/* 1 */
|
||||||
|
vertical-align: middle;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='text'],input:where(:not([type])),[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #6b7280;
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: 0px;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='text']:focus, input:where(:not([type])):focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: #2563eb;
|
||||||
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
border-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||||
|
color: #6b7280;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,textarea::placeholder {
|
||||||
|
color: #6b7280;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-date-and-time-value {
|
||||||
|
min-height: 1.5em;
|
||||||
|
text-align: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||||
|
background-position: right 0.5rem center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 1.5em 1.5em;
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
[multiple],[size]:where(select:not([size="1"])) {
|
||||||
|
background-image: initial;
|
||||||
|
background-position: initial;
|
||||||
|
background-repeat: unset;
|
||||||
|
background-size: initial;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
-webkit-print-color-adjust: unset;
|
||||||
|
print-color-adjust: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox'],[type='radio'] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-origin: border-box;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
color: #2563eb;
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #6b7280;
|
||||||
|
border-width: 1px;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox'] {
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='radio'] {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox']:focus,[type='radio']:focus {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
|
||||||
|
--tw-ring-offset-width: 2px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: #2563eb;
|
||||||
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox']:checked,[type='radio']:checked {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: currentColor;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox']:checked {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
[type='checkbox']:checked {
|
||||||
|
-webkit-appearance: auto;
|
||||||
|
-moz-appearance: auto;
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='radio']:checked {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
[type='radio']:checked {
|
||||||
|
-webkit-appearance: auto;
|
||||||
|
-moz-appearance: auto;
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox']:indeterminate {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: currentColor;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
[type='checkbox']:indeterminate {
|
||||||
|
-webkit-appearance: auto;
|
||||||
|
-moz-appearance: auto;
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='file'] {
|
||||||
|
background: unset;
|
||||||
|
border-color: inherit;
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: unset;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type='file']:focus {
|
||||||
|
outline: 1px solid ButtonText;
|
||||||
|
outline: 1px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, ::before, ::after {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
::backdrop {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-h-screen {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-5xl {
|
||||||
|
font-size: 3rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-indigo-200 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(199 210 254 / var(--tw-text-opacity));
|
||||||
|
}
|
29
go.mod
29
go.mod
@@ -1,15 +1,17 @@
|
|||||||
module vctp
|
module vctp
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/a-h/templ v0.2.778
|
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/jmoiron/sqlx v1.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/pressly/goose/v3 v3.22.0
|
github.com/pressly/goose/v3 v3.22.1
|
||||||
github.com/vmware/govmomi v0.43.0
|
github.com/vmware/govmomi v0.44.1
|
||||||
modernc.org/sqlite v1.33.0
|
github.com/xuri/excelize/v2 v2.9.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
modernc.org/sqlite v1.33.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -19,16 +21,25 @@ require (
|
|||||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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/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/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
|
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/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/tools v0.25.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // 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/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.8.0 // indirect
|
||||||
modernc.org/strutil v1.2.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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
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.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjjy1uw=
|
||||||
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
|
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 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
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=
|
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/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 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
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 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
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/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 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
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 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.1 h1:2zICEfr1O3yTP9BRZMGPj7qFxQ+ik6yeo+z1LMuioLc=
|
||||||
github.com/pressly/goose/v3 v3.22.0/go.mod h1:yJM3qwSj2pp7aAaCvso096sguezamNb2OBgxCnh/EYg=
|
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 h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
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 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
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/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 h1:7Kg3Bkdly+TrE67BYXzRq7ZrDnn7xqpKX95uEh2f9Go=
|
||||||
github.com/vmware/govmomi v0.43.0/go.mod h1:IOv5nTXCPqH9qVJAlRuAGffogaLsNs8aF+e7vLgsHJU=
|
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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
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/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
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 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
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 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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.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 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/cc/v4 v4.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 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
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 h1:CfbpOLEo2IwNzJdMvE8aiRbPMxoTpgAJeyePh0SmO8M=
|
||||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
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 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
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 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||||
modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk=
|
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||||
modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8=
|
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 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
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))
|
||||||
|
}
|
||||||
|
}
|
@@ -2,35 +2,42 @@ package tasks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"vctp/db"
|
"vctp/db/queries"
|
||||||
"vctp/internal/vcenter"
|
"vctp/internal/vcenter"
|
||||||
)
|
|
||||||
|
|
||||||
// Handler handles requests.
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
type CronTask struct {
|
)
|
||||||
Logger *slog.Logger
|
|
||||||
Database db.Database
|
|
||||||
}
|
|
||||||
|
|
||||||
// use gocron to check events in the Events table
|
// use gocron to check events in the Events table
|
||||||
func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
|
func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
|
||||||
var (
|
var (
|
||||||
//unixTimestamp int64
|
|
||||||
numVcpus int32
|
numVcpus int32
|
||||||
numRam int32
|
numRam int32
|
||||||
datacenter string
|
totalDiskGB float64
|
||||||
|
srmPlaceholder string
|
||||||
foundVm bool
|
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
|
// 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 {
|
if err != nil {
|
||||||
logger.Error("Unable to query for unprocessed events", "error", err)
|
logger.Error("Unable to query for unprocessed events", "error", err)
|
||||||
return nil // TODO - what to do with this error?
|
return nil // TODO - what to do with this error?
|
||||||
|
} else {
|
||||||
|
logger.Debug("Successfully queried for unprocessed events", "count", len(events))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, evt := range events {
|
for _, evt := range events {
|
||||||
@@ -39,51 +46,160 @@ func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
|
|||||||
// TODO - get a list of unique vcenters, then process each event in batches
|
// TODO - get a list of unique vcenters, then process each event in batches
|
||||||
// to avoid doing unnecessary login/logout of vcenter
|
// to avoid doing unnecessary login/logout of vcenter
|
||||||
|
|
||||||
c.Logger.Debug("connecting to vcenter")
|
//c.Logger.Debug("connecting to vcenter")
|
||||||
vc := vcenter.New(c.Logger)
|
vc := vcenter.New(c.Logger, c.VcCreds)
|
||||||
vc.Login(evt.Source)
|
vc.Login(evt.Source)
|
||||||
|
|
||||||
datacenter = evt.DatacenterName.String
|
//datacenter = evt.DatacenterName.String
|
||||||
vmObject, err := vc.FindVMByIDWithDatacenter(evt.VmId.String, evt.DatacenterId.String)
|
vmObject, err := vc.FindVMByIDWithDatacenter(evt.VmId.String, evt.DatacenterId.String)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error("Can't locate vm in vCenter", "vmID", evt.VmId.String, "error", err)
|
c.Logger.Error("Can't locate vm in vCenter", "vmID", evt.VmId.String, "error", err)
|
||||||
|
continue
|
||||||
} else if vmObject == nil {
|
} else if vmObject == nil {
|
||||||
c.Logger.Debug("didn't find VM", "vm_id", evt.VmId.String)
|
c.Logger.Debug("didn't find VM", "vm_id", evt.VmId.String)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
numRam = 0
|
numRam = 0
|
||||||
numVcpus = 0
|
numVcpus = 0
|
||||||
} else {
|
totalDiskGB = 0
|
||||||
c.Logger.Debug("found VM")
|
isTemplate = "FALSE"
|
||||||
|
folderPath = ""
|
||||||
|
vmUuid = ""
|
||||||
|
*/
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//c.Logger.Debug("found VM")
|
||||||
|
srmPlaceholder = "FALSE" // Default assumption
|
||||||
//prettyPrint(vmObject)
|
//prettyPrint(vmObject)
|
||||||
|
|
||||||
// calculate VM properties we want to store
|
// calculate VM properties we want to store
|
||||||
if vmObject.Vm.Config != nil {
|
if vmObject.Config != nil {
|
||||||
numRam = vmObject.Vm.Config.Hardware.MemoryMB
|
numRam = vmObject.Config.Hardware.MemoryMB
|
||||||
//numVcpus = vmObject.Vm.Config.Hardware.NumCPU * vmObject.Vm.Config.Hardware.NumCoresPerSocket
|
numVcpus = vmObject.Config.Hardware.NumCPU
|
||||||
numVcpus = vmObject.Vm.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
|
foundVm = true
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Error("Empty VM config")
|
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()
|
err = vc.Logout()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error("unable to logout of vcenter", "error", err)
|
c.Logger.Error("unable to logout of vcenter", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundVm {
|
if foundVm {
|
||||||
c.Logger.Debug("Simulate adding to Inventory", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", datacenter)
|
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.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
|
||||||
|
_, 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)
|
||||||
|
|
||||||
// mark this event as processed
|
// mark this event as processed
|
||||||
err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid)
|
err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err)
|
c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err)
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
|
//c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Debug("Not simulate adding to Inventory due to missing vcenter config property", "vm_name", evt.VmName.String)
|
c.Logger.Debug("Not adding to Inventory due to missing vcenter config property", "vm_name", evt.VmName.String)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const rsaBits = 4096
|
const rsaBits = 4096
|
||||||
@@ -53,3 +55,14 @@ func FileExists(filename string) bool {
|
|||||||
}
|
}
|
||||||
return !info.IsDir()
|
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:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -7,6 +7,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
"github.com/vmware/govmomi"
|
||||||
"github.com/vmware/govmomi/find"
|
"github.com/vmware/govmomi/find"
|
||||||
@@ -19,17 +21,24 @@ import (
|
|||||||
|
|
||||||
type Vcenter struct {
|
type Vcenter struct {
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
|
Vurl string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
client *govmomi.Client
|
client *govmomi.Client
|
||||||
|
credentials *VcenterLogin
|
||||||
|
}
|
||||||
|
|
||||||
|
type VcenterLogin struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmProperties struct {
|
type VmProperties struct {
|
||||||
Vm mo.VirtualMachine
|
Vm mo.VirtualMachine
|
||||||
//Datacenter string
|
ResourcePool string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Vcenter with the given logger
|
// 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())
|
//ctx, cancel := context.WithCancel(context.Background())
|
||||||
//defer cancel()
|
//defer cancel()
|
||||||
@@ -37,23 +46,26 @@ func New(logger *slog.Logger) *Vcenter {
|
|||||||
return &Vcenter{
|
return &Vcenter{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
|
credentials: creds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vcenter) Login(vUrl string) error {
|
func (v *Vcenter) Login(vUrl string) error {
|
||||||
var insecure bool
|
var insecure bool
|
||||||
|
|
||||||
|
// TODO - fix this
|
||||||
insecureString := os.Getenv("VCENTER_INSECURE")
|
insecureString := os.Getenv("VCENTER_INSECURE")
|
||||||
username := os.Getenv("VCENTER_USERNAME")
|
//username := os.Getenv("VCENTER_USERNAME")
|
||||||
password := os.Getenv("VCENTER_PASSWORD")
|
//password := os.Getenv("VCENTER_PASSWORD")
|
||||||
|
|
||||||
// Connect to vCenter
|
// Connect to vCenter
|
||||||
u, err := soap.ParseURL(vUrl)
|
u, err := soap.ParseURL(vUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error parsing vCenter URL: %s", err)
|
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)
|
c, err := govmomi.NewClient(ctx, u, insecure)
|
||||||
@@ -76,7 +88,7 @@ func (v *Vcenter) Login(vUrl string) error {
|
|||||||
|
|
||||||
v.client = c
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@@ -99,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) {
|
func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) {
|
||||||
m := view.NewManager(v.client.Client)
|
m := view.NewManager(v.client.Client)
|
||||||
|
|
||||||
@@ -174,9 +416,9 @@ func (v *Vcenter) FindVMByID(vmID string) (*VmProperties, error) {
|
|||||||
return nil, fmt.Errorf("VM with ID %s not found in any datacenter", vmID)
|
return nil, fmt.Errorf("VM with ID %s not found in any datacenter", vmID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmProperties, error) {
|
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*mo.VirtualMachine, error) {
|
||||||
//var dcName string
|
|
||||||
var err error
|
var err error
|
||||||
|
//resourcePool := ""
|
||||||
v.Logger.Debug("searching for vm id", "vm_id", vmID, "datacenter_id", dcID)
|
v.Logger.Debug("searching for vm id", "vm_id", vmID, "datacenter_id", dcID)
|
||||||
|
|
||||||
finder := find.NewFinder(v.client.Client, true)
|
finder := find.NewFinder(v.client.Client, true)
|
||||||
@@ -196,14 +438,6 @@ func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmPropert
|
|||||||
// Use finder.SetDatacenter to set the datacenter
|
// Use finder.SetDatacenter to set the datacenter
|
||||||
finder.SetDatacenter(datacenter)
|
finder.SetDatacenter(datacenter)
|
||||||
|
|
||||||
/*
|
|
||||||
dcName, err = datacenter.ObjectName(v.ctx)
|
|
||||||
if err != nil {
|
|
||||||
v.Logger.Error("Couldn't find the name of the datacenter", "error", err)
|
|
||||||
dcName = ""
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Create a ManagedObjectReference for the VM
|
// Create a ManagedObjectReference for the VM
|
||||||
vmRef := types.ManagedObjectReference{
|
vmRef := types.ManagedObjectReference{
|
||||||
Type: "VirtualMachine",
|
Type: "VirtualMachine",
|
||||||
@@ -214,19 +448,82 @@ 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, []string{"config", "name"}, &vm)
|
||||||
err = v.client.RetrieveOne(v.ctx, vmRef, nil, &vm)
|
err = v.client.RetrieveOne(v.ctx, vmRef, nil, &vm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
v.Logger.Debug("Found VM")
|
//v.Logger.Debug("Found VM")
|
||||||
return &VmProperties{
|
|
||||||
//Datacenter: dcName,
|
return &vm, nil
|
||||||
Vm: vm,
|
|
||||||
}, nil
|
|
||||||
} else if _, ok := err.(*find.NotFoundError); !ok {
|
} else if _, ok := err.(*find.NotFoundError); !ok {
|
||||||
// If the error is not a NotFoundError, return it
|
// 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)
|
//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 {
|
} else {
|
||||||
return nil, fmt.Errorf("failed to retrieve VM: %w", err)
|
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) {
|
||||||
|
//finder := find.NewFinder(v.client.Client, true)
|
||||||
|
|
||||||
|
v.Logger.Debug("Commencing vm folder path search")
|
||||||
|
|
||||||
|
// Start from the VM's parent
|
||||||
|
parentRef := vm.Parent
|
||||||
|
if parentRef == nil {
|
||||||
|
return "", fmt.Errorf("no parent found for the VM")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the folder hierarchy to build the full folder path
|
||||||
|
folderPath := ""
|
||||||
|
//v.Logger.Debug("parent is", "parent", parentRef)
|
||||||
|
|
||||||
|
for parentRef.Type != "Datacenter" {
|
||||||
|
// Retrieve the parent object
|
||||||
|
//parentObj, err := finder.ObjectReference(v.ctx, *parentRef)
|
||||||
|
//if err != nil {
|
||||||
|
// return "", fmt.Errorf("failed to find parent object in inventory: %w", err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Retrieve the folder name
|
||||||
|
|
||||||
|
var parentObj mo.Folder
|
||||||
|
err := v.client.RetrieveOne(v.ctx, *parentRef, nil, &parentObj)
|
||||||
|
if err != nil {
|
||||||
|
v.Logger.Error("Failed to get object for parent reference", "ref", parentRef)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend the folder name to the path
|
||||||
|
folderPath = path.Join("/", parentObj.Name, folderPath)
|
||||||
|
|
||||||
|
// Move up to the next parent
|
||||||
|
//if folder, ok := parentObj.(*object.Folder); ok {
|
||||||
|
if parentObj.Parent != nil {
|
||||||
|
parentRef = parentObj.Parent
|
||||||
|
//v.Logger.Debug("Parent uplevel is", "ref", parentRef)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("unexpected parent type: %s", parentObj.Reference().Type)
|
||||||
|
}
|
||||||
|
//break
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderPath, nil
|
||||||
}
|
}
|
||||||
|
131
main.go
131
main.go
@@ -8,8 +8,11 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
"vctp/db"
|
"vctp/db"
|
||||||
|
"vctp/internal/secrets"
|
||||||
|
"vctp/internal/settings"
|
||||||
"vctp/internal/tasks"
|
"vctp/internal/tasks"
|
||||||
utils "vctp/internal/utils"
|
utils "vctp/internal/utils"
|
||||||
|
"vctp/internal/vcenter"
|
||||||
"vctp/log"
|
"vctp/log"
|
||||||
"vctp/server"
|
"vctp/server"
|
||||||
"vctp/server/router"
|
"vctp/server/router"
|
||||||
@@ -23,6 +26,8 @@ var (
|
|||||||
sha1ver string // sha1 revision used to build the program
|
sha1ver string // sha1 revision used to build the program
|
||||||
buildTime string // when the executable was built
|
buildTime string // when the executable was built
|
||||||
cronFrequency time.Duration
|
cronFrequency time.Duration
|
||||||
|
cronInvFrequency time.Duration
|
||||||
|
encryptionKey = []byte("5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -47,23 +52,28 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
defer database.Close()
|
||||||
|
//defer database.DB().Close()
|
||||||
|
|
||||||
if err = db.Migrate(database); err != nil {
|
if err = db.Migrate(database); err != nil {
|
||||||
logger.Error("failed to migrate database", "error", err)
|
logger.Error("failed to migrate database", "error", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the task scheduler
|
|
||||||
s, err := gocron.NewScheduler()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to create scheduler", "error", err)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass useful information to the cron jobs
|
// Load settings from yaml
|
||||||
c := &tasks.CronTask{
|
settingsFile := os.Getenv("SETTINGS_FILE")
|
||||||
Logger: logger,
|
if settingsFile == "" {
|
||||||
Database: database,
|
settingsFile = "settings.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Determine bind IP
|
||||||
@@ -77,7 +87,7 @@ func main() {
|
|||||||
bindPort = "9443"
|
bindPort = "9443"
|
||||||
}
|
}
|
||||||
bindAddress := fmt.Sprint(bindIP, ":", bindPort)
|
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
|
// Determine bind disable TLS
|
||||||
bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS")
|
bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS")
|
||||||
@@ -102,52 +112,117 @@ func main() {
|
|||||||
|
|
||||||
// Generate certificate if required
|
// Generate certificate if required
|
||||||
if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) {
|
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)
|
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 != "" {
|
if cronFrequencyString != "" {
|
||||||
cronFrequency, err = time.ParseDuration(cronFrequencyString)
|
cronFrequency, err = time.ParseDuration(cronFrequencyString)
|
||||||
if err != nil {
|
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
|
cronFrequency = time.Second * 60
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cronFrequency = time.Second * 60
|
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)
|
startsAt := time.Now().Add(time.Second * 10)
|
||||||
job, err := s.NewJob(
|
job, err := c.NewJob(
|
||||||
gocron.DurationJob(cronFrequency),
|
gocron.DurationJob(cronFrequency),
|
||||||
gocron.NewTask(func() {
|
gocron.NewTask(func() {
|
||||||
c.RunVmCheck(ctx, logger)
|
ct.RunVmCheck(ctx, logger)
|
||||||
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
gocron.WithStartAt(gocron.WithStartDateTime(startsAt)),
|
gocron.WithStartAt(gocron.WithStartDateTime(startsAt)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
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)
|
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
|
// Start server
|
||||||
|
r := router.New(logger, database, buildTime, sha1ver, runtime.Version(), &creds, a, s)
|
||||||
svr := server.New(
|
svr := server.New(
|
||||||
logger,
|
logger,
|
||||||
s,
|
c,
|
||||||
cancel,
|
cancel,
|
||||||
bindAddress,
|
bindAddress,
|
||||||
server.WithRouter(router.New(logger, database, buildTime, sha1ver, runtime.Version())),
|
server.WithRouter(r),
|
||||||
|
server.SetTls(bindDisableTls),
|
||||||
|
server.SetCertificate(tlsCertFilename),
|
||||||
|
server.SetPrivateKey(tlsKeyFilename),
|
||||||
)
|
)
|
||||||
|
//logger.Debug("Server configured", "object", svr)
|
||||||
svr.DisableTls(bindDisableTls)
|
|
||||||
svr.SetCertificate(tlsCertFilename)
|
|
||||||
svr.SetPrivateKey(tlsKeyFilename)
|
|
||||||
|
|
||||||
svr.StartAndWait()
|
svr.StartAndWait()
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
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"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"vctp/db"
|
"vctp/db"
|
||||||
|
"vctp/internal/secrets"
|
||||||
|
"vctp/internal/settings"
|
||||||
|
"vctp/internal/vcenter"
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
)
|
)
|
||||||
@@ -16,6 +19,9 @@ type Handler struct {
|
|||||||
BuildTime string
|
BuildTime string
|
||||||
SHA1Ver string
|
SHA1Ver string
|
||||||
GoVersion 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) {
|
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"
|
models "vctp/server/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VmCreate receives the CloudEvent for a VM creation
|
// VmCreateEvent receives the CloudEvent for a VM creation
|
||||||
func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) VmCreateEvent(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
unixTimestamp int64
|
unixTimestamp int64
|
||||||
//numVcpus int32
|
//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))
|
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
|
var event models.CloudEventReceived
|
||||||
if err := json.Unmarshal(reqBody, &event); err != nil {
|
if err := json.Unmarshal(reqBody, &event); err != nil {
|
||||||
h.Logger.Error("unable to decode json", "error", err)
|
h.Logger.Error("unable to decode json", "error", err)
|
||||||
@@ -41,7 +41,7 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
h.Logger.Debug("successfully decoded JSON")
|
h.Logger.Debug("successfully decoded JSON")
|
||||||
prettyPrint(event)
|
//prettyPrint(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := event.CloudEvent.Data
|
e := event.CloudEvent.Data
|
||||||
@@ -60,63 +60,14 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
unixTimestamp = eventTime.Unix()
|
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
|
// Create an instance of CreateInventoryParams
|
||||||
h.Logger.Debug("Creating database parameters")
|
h.Logger.Debug("Creating database parameters")
|
||||||
|
|
||||||
/*
|
params := queries.CreateEventParams{
|
||||||
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{
|
|
||||||
Source: event.CloudEvent.Source,
|
Source: event.CloudEvent.Source,
|
||||||
CloudId: event.CloudEvent.ID,
|
CloudId: event.CloudEvent.ID,
|
||||||
EventTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0},
|
EventTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0},
|
||||||
|
EventType: sql.NullString{String: event.CloudEvent.Type, Valid: event.CloudEvent.Type != ""},
|
||||||
ChainId: strconv.Itoa(e.ChainID),
|
ChainId: strconv.Itoa(e.ChainID),
|
||||||
VmId: sql.NullString{String: e.VM.VM.Value, Valid: e.VM.VM.Value != ""},
|
VmId: sql.NullString{String: e.VM.VM.Value, Valid: e.VM.VM.Value != ""},
|
||||||
VmName: sql.NullString{String: e.VM.Name, Valid: e.VM.Name != ""},
|
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 != ""},
|
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
|
// 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 {
|
if err != nil {
|
||||||
h.Logger.Error("unable to perform database insert", "error", err)
|
h.Logger.Error("unable to perform database insert", "error", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
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))
|
||||||
|
}
|
92
server/handler/vmImport.go
Normal file
92
server/handler/vmImport.go
Normal file
@@ -0,0 +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 {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"status": "OK",
|
||||||
|
"message": fmt.Sprintf("Successfully processed import request for VM '%s'", inData.Name),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
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
|
||||||
|
}
|
||||||
|
}
|
@@ -1,21 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VmUpdate receives the CloudEvent for a VM modification or move
|
|
||||||
func (h *Handler) VmUpdate(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))
|
|
||||||
}
|
|
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) {
|
func (l *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
l.handler.ServeHTTP(w, r)
|
l.handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
l.logger.Debug(
|
l.logger.Debug(
|
||||||
"Request recieved",
|
"Request recieved",
|
||||||
slog.String("method", r.Method),
|
slog.String("method", r.Method),
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
type CloudEventReceived struct {
|
type CloudEventReceived struct {
|
||||||
CloudEvent struct {
|
CloudEvent struct {
|
||||||
@@ -8,7 +10,7 @@ type CloudEventReceived struct {
|
|||||||
Specversion string `json:"specversion"`
|
Specversion string `json:"specversion"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Time string `json:"time"`
|
Time string `json:"time"` // Modified from time.Time
|
||||||
Data struct {
|
Data struct {
|
||||||
ChainID int `json:"ChainId"`
|
ChainID int `json:"ChainId"`
|
||||||
ChangeTag string `json:"ChangeTag"`
|
ChangeTag string `json:"ChangeTag"`
|
||||||
@@ -19,7 +21,7 @@ type CloudEventReceived struct {
|
|||||||
} `json:"ComputeResource"`
|
} `json:"ComputeResource"`
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
} `json:"ComputeResource"`
|
} `json:"ComputeResource"`
|
||||||
CreatedTime time.Time `json:"CreatedTime"`
|
CreatedTime string `json:"CreatedTime"` // Modified from time.Time
|
||||||
Datacenter struct {
|
Datacenter struct {
|
||||||
Datacenter struct {
|
Datacenter struct {
|
||||||
Type string `json:"Type"`
|
Type string `json:"Type"`
|
||||||
@@ -39,13 +41,9 @@ type CloudEventReceived struct {
|
|||||||
} `json:"Host"`
|
} `json:"Host"`
|
||||||
Key int `json:"Key"`
|
Key int `json:"Key"`
|
||||||
Net interface{} `json:"Net"`
|
Net interface{} `json:"Net"`
|
||||||
SrcTemplate struct {
|
NewParent *CloudEventResourcePool `json:"NewParent"`
|
||||||
Name string `json:"Name"`
|
OldParent *CloudEventResourcePool `json:"OldParent"`
|
||||||
VM struct {
|
SrcTemplate *CloudEventVm `json:"SrcTemplate"`
|
||||||
Type string `json:"Type"`
|
|
||||||
Value string `json:"Value"`
|
|
||||||
} `json:"Vm"`
|
|
||||||
} `json:"SrcTemplate"`
|
|
||||||
Template bool `json:"Template"`
|
Template bool `json:"Template"`
|
||||||
UserName string `json:"UserName"`
|
UserName string `json:"UserName"`
|
||||||
VM struct {
|
VM struct {
|
||||||
@@ -55,6 +53,206 @@ type CloudEventReceived struct {
|
|||||||
Value string `json:"Value"`
|
Value string `json:"Value"`
|
||||||
} `json:"Vm"`
|
} `json:"Vm"`
|
||||||
} `json:"Vm"`
|
} `json:"Vm"`
|
||||||
|
ConfigSpec *json.RawMessage `json:"configSpec"`
|
||||||
|
ConfigChanges *ConfigChangesReceived `json:"configChanges"` // Modified to separate struct
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
} `json:"cloudEvent"`
|
} `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,27 +3,59 @@ package router
|
|||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
"vctp/db"
|
"vctp/db"
|
||||||
"vctp/dist"
|
"vctp/dist"
|
||||||
|
"vctp/internal/secrets"
|
||||||
|
"vctp/internal/settings"
|
||||||
|
"vctp/internal/vcenter"
|
||||||
"vctp/server/handler"
|
"vctp/server/handler"
|
||||||
"vctp/server/middleware"
|
"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{
|
h := &handler.Handler{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Database: database,
|
Database: database,
|
||||||
BuildTime: buildTime,
|
BuildTime: buildTime,
|
||||||
SHA1Ver: sha1ver,
|
SHA1Ver: sha1ver,
|
||||||
GoVersion: goVersion,
|
GoVersion: goVersion,
|
||||||
|
VcCreds: creds,
|
||||||
|
Secret: secret,
|
||||||
|
Settings: settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.Handle("/assets/", middleware.CacheMiddleware(http.FileServer(http.FS(dist.AssetsDir))))
|
mux.Handle("/assets/", middleware.CacheMiddleware(http.FileServer(http.FS(dist.AssetsDir))))
|
||||||
mux.HandleFunc("/", h.Home)
|
mux.HandleFunc("/", h.Home)
|
||||||
mux.HandleFunc("/api/event/vm/create", h.VmCreate)
|
mux.HandleFunc("/api/event/vm/create", h.VmCreateEvent)
|
||||||
mux.HandleFunc("/api/event/vm/update", h.VmUpdate)
|
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)
|
return middleware.NewLoggingMiddleware(logger, mux)
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,7 @@ type Server struct {
|
|||||||
disableTls bool
|
disableTls bool
|
||||||
tlsCertFilename string
|
tlsCertFilename string
|
||||||
tlsKeyFilename string
|
tlsKeyFilename string
|
||||||
|
encryptionKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new server with the given logger, address and options.
|
// New creates a new server with the given logger, address and options.
|
||||||
@@ -42,21 +43,27 @@ func New(logger *slog.Logger, cron gocron.Scheduler, cancel context.CancelFunc,
|
|||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
WriteTimeout: 15 * time.Second,
|
//WriteTimeout: 120 * time.Second,
|
||||||
ReadTimeout: 15 * time.Second,
|
WriteTimeout: 0,
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
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,
|
srv: srv,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
cron: cron,
|
cron: cron,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply any options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option represents a server option.
|
// Option represents a server option.
|
||||||
@@ -83,20 +90,34 @@ func WithRouter(handler http.Handler) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableTls sets the disable tls value
|
// SetKey sets the encryption key we use when generating secrets
|
||||||
func (s *Server) DisableTls(disableTls bool) {
|
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
|
s.disableTls = disableTls
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetCertificate sets the path to the certificate used for TLS, in PEM format
|
// SetCertificate sets the path to the certificate used for TLS, in PEM format
|
||||||
func (s *Server) SetCertificate(tlsCertFilename string) {
|
func SetCertificate(tlsCertFilename string) Option {
|
||||||
|
return func(s *Server) {
|
||||||
|
//fmt.Printf("Setting tlsCertFilename to '%s'\n", tlsCertFilename)
|
||||||
s.tlsCertFilename = tlsCertFilename
|
s.tlsCertFilename = tlsCertFilename
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetPrivateKey sets the path to the private key used for TLS, in PEM format
|
// SetPrivateKey sets the path to the private key used for TLS, in PEM format
|
||||||
func (s *Server) SetPrivateKey(tlsKeyFilename string) {
|
func SetPrivateKey(tlsKeyFilename string) Option {
|
||||||
|
return func(s *Server) {
|
||||||
s.tlsKeyFilename = tlsKeyFilename
|
s.tlsKeyFilename = tlsKeyFilename
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StartAndWait starts the server and waits for a signal to shut down.
|
// StartAndWait starts the server and waits for a signal to shut down.
|
||||||
func (s *Server) StartAndWait() {
|
func (s *Server) StartAndWait() {
|
||||||
@@ -111,12 +132,14 @@ func (s *Server) Start() {
|
|||||||
if s.disableTls {
|
if s.disableTls {
|
||||||
s.logger.Info("starting server", "port", s.srv.Addr)
|
s.logger.Info("starting server", "port", s.srv.Addr)
|
||||||
if err := s.srv.ListenAndServe(); err != nil {
|
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 {
|
} else {
|
||||||
s.logger.Info("starting TLS server", "port", s.srv.Addr, "cert", s.tlsCertFilename, "key", s.tlsKeyFilename)
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -152,5 +175,5 @@ func (s *Server) GracefulShutdown() {
|
|||||||
// <-ctx.Done() if your application should wait for other services
|
// <-ctx.Done() if your application should wait for other services
|
||||||
// to finalize based on context cancellation.
|
// to finalize based on context cancellation.
|
||||||
s.logger.Info("shutting down")
|
s.logger.Info("shutting down")
|
||||||
os.Exit(0)
|
//os.Exit(0)
|
||||||
}
|
}
|
||||||
|
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