Compare commits

..

87 Commits

Author SHA1 Message Date
dc120f2bdc logging
Some checks failed
continuous-integration/drone/push Build is passing
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / End-to-End (push) Has been cancelled
CI / Publish Docker (push) Has been cancelled
2024-10-16 16:35:37 +11:00
c8ae94fb43 logging adjustments
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-16 16:34:30 +11:00
b46369811b change srm match
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-16 12:26:10 +11:00
fb40abfd48 re-enable endpoint to remove updates with no associated inventory record
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-15 17:05:14 +11:00
b07ed9ee09 handle moves of VMs not in inventory
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-15 16:56:06 +11:00
2a9489619d bugfix
Some checks are pending
CI / End-to-End (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-15 16:22:49 +11:00
f86ec3d615 create vm in inventory when receiving modify event for vm we dont know about
Some checks failed
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is passing
2024-10-15 16:06:54 +11:00
309db2f1a6 add more srm checks
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-15 12:43:47 +11:00
37d921f635 new versions
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-15 09:03:36 +11:00
c9375f3099 improve logic for temporary VM renames
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-15 08:51:35 +11:00
81271873f3 reduce some logging
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-14 20:54:52 +11:00
665750548f improve handling of disk change modify events
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-14 15:26:05 +11:00
ce1f28d9c3 avoid unnecessary disk size calculations
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-14 15:20:02 +11:00
1ecdb10cf7 fix vcenter update task to avoid re-adding update records for previous updates
Some checks are pending
CI / Publish Docker (push) Blocked by required conditions
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
continuous-integration/drone/push Build is passing
2024-10-14 12:02:31 +11:00
cc6601146a vcenter poll job now creates update records for changed VMs
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-14 09:24:28 +11:00
9cdde0b278 cleanup templates
Some checks failed
continuous-integration/drone/push Build is passing
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / End-to-End (push) Has been cancelled
CI / Publish Docker (push) Has been cancelled
2024-10-02 21:23:54 +10:00
f9b8e25c2f add raw string to database
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-02 13:37:59 +10:00
f94339446d fix migration
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-02 13:25:06 +10:00
77c1928436 cleanup updates with no vm reference
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-02 13:17:29 +10:00
f80dfe9027 add vm name and placeholder change to updates table
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-02 13:14:09 +10:00
b9c1f65971 minor adjustments
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-10-02 08:29:49 +10:00
3bc7f922d3 add notes [CI SKIP]
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / End-to-End (push) Has been cancelled
CI / Publish Docker (push) Has been cancelled
2024-09-30 21:39:04 +10:00
f28fed831a module update
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 17:11:25 +10:00
8f43603613 disable write timeout
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 15:38:29 +10:00
380707cf23 add vm inventory update endpoint
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 15:30:38 +10:00
6a41528f41 don't add templates to inventory
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 12:48:14 +10:00
5875550802 more logging
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 12:41:04 +10:00
7665227ac6 improve error handling
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 12:32:54 +10:00
9802419713 add temp cleanup function
Some checks are pending
CI / Test (push) Waiting to run
CI / Lint (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 12:26:52 +10:00
ea63ffa178 various improvements
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 12:01:39 +10:00
6f5d21fa71 increase timeouts
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 11:08:38 +10:00
3d86092816 logging
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 11:01:37 +10:00
5afbe9bb30 logging tweaks
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 10:58:45 +10:00
c4eedb55b7 implement vc inventory scanning
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-30 10:36:23 +10:00
a91642b450 improve responses
Some checks failed
continuous-integration/drone/push Build is passing
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / End-to-End (push) Has been cancelled
CI / Publish Docker (push) Has been cancelled
2024-09-27 20:27:35 +10:00
fb47006809 add debug endpoints
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 20:14:52 +10:00
3501967c9e add ability to store/create encrypted vcenter password
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 17:02:02 +10:00
5a00f4a8c7 improve disk change detection
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 13:58:33 +10:00
a7dc838c83 troubleshoot disk size calculation
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 12:44:23 +10:00
d76bcf5ca5 handle resource pool move
Some checks are pending
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
CI / Lint (push) Waiting to run
continuous-integration/drone/push Build is passing
2024-09-27 12:25:28 +10:00
78e1da3149 add formatting to reports
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 11:41:09 +10:00
a18cca1f0e update logging
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 11:24:12 +10:00
c691763430 update database schema to avoid bool confusion
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 11:07:51 +10:00
b371e28469 fix excel report
Some checks are pending
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-27 09:17:52 +10:00
54ff68590c improve logging
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 21:29:42 +10:00
f88b812fa9 test creating excel report from inventory table
Some checks failed
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is failing
2024-09-26 21:22:45 +10:00
dcbbff830d add settings yaml
Some checks are pending
continuous-integration/drone/push Build is passing
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
2024-09-26 17:24:36 +10:00
44c4bb2d66 actually fix
Some checks are pending
continuous-integration/drone/push Build is passing
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
2024-09-26 15:17:52 +10:00
3b0206b1e9 improve regex for config changes
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 15:16:56 +10:00
bc93fa4bad fix model
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 15:11:34 +10:00
0c2aecd989 less logging
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 14:35:12 +10:00
e1703e401b more json output
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 14:28:38 +10:00
8931cb4891 store event type
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 14:21:07 +10:00
00d474b937 add vm cleanup endpoint
Some checks are pending
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
CI / Lint (push) Waiting to run
continuous-integration/drone/push Build is passing
2024-09-26 13:29:02 +10:00
f712c7254f add username data to updates table
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 12:31:14 +10:00
dd13fd6759 add temp endpoint for db cleanup
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-26 12:15:39 +10:00
3b53455343 gzip output binary
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 21:07:47 +10:00
2354d85a37 less log spam
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 21:05:46 +10:00
b8abc7e6fd logic improvement
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 21:02:17 +10:00
8e399de31e more checking
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 20:59:06 +10:00
fd64990e8e test unmarshal configspec
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 20:48:29 +10:00
3c5aa418df handle db insert
Some checks are pending
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 14:59:18 +10:00
7cc16819f7 also handle float64
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 13:12:18 +10:00
c7c890f6bb only check unprocessed events from last 1 day
Some checks are pending
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
CI / Lint (push) Waiting to run
continuous-integration/drone/push Build is passing
2024-09-25 12:55:13 +10:00
47bc8acace update
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 12:50:55 +10:00
2bae3e7541 test converting import data to db params
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-25 12:42:45 +10:00
08568e3600 log more info about disk additions
Some checks failed
continuous-integration/drone/push Build is passing
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / End-to-End (push) Has been cancelled
CI / Publish Docker (push) Has been cancelled
2024-09-16 16:54:59 +10:00
c122e775a3 capture new disk size
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 15:04:31 +10:00
fb4a7a790d slightly more logging
Some checks are pending
continuous-integration/drone/push Build is passing
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
2024-09-16 14:43:16 +10:00
c0e6eec89d improve regex
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 14:24:37 +10:00
6d86a93539 rename eventid to cloudid
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 14:20:14 +10:00
a84c403a69 reduce the log spam
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 13:56:22 +10:00
e47718cd7f dont query vm folder path unless we need to
Some checks are pending
continuous-integration/drone/push Build is passing
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
2024-09-16 13:52:56 +10:00
4efdf50433 add more fields to the Update database record
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 12:55:34 +10:00
d2aac0c6d4 make responses more meaningful
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 12:18:47 +10:00
afb85ff34a more cleanup
Some checks are pending
continuous-integration/drone/push Build is passing
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
2024-09-16 12:07:33 +10:00
659347ad87 tidy up
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 12:04:14 +10:00
cfa9c45e56 add pprof
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 12:02:14 +10:00
32e3bc6e66 handle vm config modified
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 11:40:06 +10:00
ab24b5f6b9 update regex
Some checks are pending
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
CI / Lint (push) Waiting to run
continuous-integration/drone/push Build is passing
2024-09-16 11:05:02 +10:00
56cf2e8366 improve regex
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 10:54:02 +10:00
282459ccf8 add code to handle deletion event
Some checks are pending
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
CI / Lint (push) Waiting to run
continuous-integration/drone/push Build is passing
2024-09-16 10:50:05 +10:00
57980a860a add vm power state and template
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 10:14:46 +10:00
85bb431de1 use pointer for configchanges struct
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 09:01:03 +10:00
19d5b2406e add code for VmBeingModified endpoint
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-16 08:40:24 +10:00
40fb860385 add css
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / End-to-End (push) Waiting to run
CI / Publish Docker (push) Blocked by required conditions
continuous-integration/drone/push Build is passing
2024-09-15 11:10:09 +10:00
1cb36be02c updates 2024-09-15 10:51:48 +10:00
57 changed files with 5366 additions and 1059 deletions

View File

@@ -23,14 +23,16 @@ do
output_name+='.exe'
fi
echo "build commences"
starttime=$(TZ=Australia/Sydney date +%Y-%m-%dT%T%z)
echo "build commences at $starttime"
env GOOS=$GOOS GOARCH=$GOARCH go build -trimpath -ldflags="-X main.sha1ver=$commit -X main.buildTime=$buildtime" -o build/$output_name $package
if [ $? -ne 0 ]; then
echo 'An error has occurred! Aborting the script execution...'
exit 1
fi
gzip build/$output_name
echo "build complete at $buildtime : $output_name"
sha256sum build/$output_name > build/${output_name}_checksum.txt
sha256sum build/${output_name}.gz > build/${output_name}_checksum.txt
done
ls -lah build

2
.gitignore vendored
View File

@@ -37,7 +37,7 @@ appengine-generated/
/components/*/*.txt
.idea
*.iml
dist/assets/css/
#dist/assets/css/
*.sqlite3*
tmp/
pb_data/

View File

@@ -3,7 +3,7 @@ package core
templ Footer() {
<footer class="fixed p-1 bottom-0 bg-gray-100 w-full border-t">
<div class="rounded-lg p-4 text-xs italic text-gray-700 text-center">
&copy; Go Fullstack
&copy; Nathan Coad (nathan.coad@dell.com)
</div>
</footer>
}

View File

@@ -29,7 +29,7 @@ func Footer() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"fixed p-1 bottom-0 bg-gray-100 w-full border-t\"><div class=\"rounded-lg p-4 text-xs italic text-gray-700 text-center\">&copy; 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\">&copy; Nathan Coad (nathan.coad@dell.com)</div></footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -6,8 +6,8 @@ templ Header() {
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="Hello world"/>
<title>Test Page</title>
<meta name="description" content="vCTP API endpoint"/>
<title>vCTP API</title>
<script src="/assets/js/htmx@v2.0.2.min.js"></script>
<link href={ "/assets/css/output@" + version.Value + ".css" } rel="stylesheet"/>
</head>

View File

@@ -31,7 +31,7 @@ func Header() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"Hello world\"><title>Test Page</title><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><link href=\"")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"vCTP API endpoint\"><title>vCTP API</title><script src=\"/assets/js/htmx@v2.0.2.min.js\"></script><link href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -82,19 +82,48 @@ func ConvertToSQLParams(input interface{}, output interface{}) {
continue
}
// Handle fields of type sql.NullString, sql.NullInt64, and normal string/int64 fields
switch outputField.Type() {
case reflect.TypeOf(sql.NullString{}):
// Handle sql.NullString
if inputField.Kind() == reflect.Ptr && inputField.IsNil() {
outputField.Set(reflect.ValueOf(sql.NullString{Valid: false}))
} else {
outputField.Set(reflect.ValueOf(sql.NullString{String: inputField.String(), Valid: true}))
}
case reflect.TypeOf(sql.NullInt64{}):
// Handle sql.NullInt64
if inputField.Int() == 0 {
outputField.Set(reflect.ValueOf(sql.NullInt64{Valid: false}))
} else {
outputField.Set(reflect.ValueOf(sql.NullInt64{Int64: inputField.Int(), Valid: true}))
}
case reflect.TypeOf(sql.NullFloat64{}):
// Handle sql.NullFloat64
if inputField.Float() == 0 {
outputField.Set(reflect.ValueOf(sql.NullFloat64{Valid: false}))
} else {
outputField.Set(reflect.ValueOf(sql.NullFloat64{Float64: inputField.Float(), Valid: true}))
}
case reflect.TypeOf(""):
// Handle normal string fields
if inputField.Kind() == reflect.Ptr && inputField.IsNil() {
outputField.SetString("") // Set to empty string if input is nil
} else {
outputField.SetString(inputField.String())
}
case reflect.TypeOf(int64(0)):
// Handle normal int64 fields
outputField.SetInt(inputField.Int())
case reflect.TypeOf(float64(0)):
// Handle normal float64 fields
outputField.SetFloat(inputField.Float())
}
}
}

View File

@@ -2,6 +2,7 @@ package db
import (
"database/sql"
"fmt"
"log/slog"
"vctp/db/queries"
@@ -36,6 +37,8 @@ func (d *LocalDB) Logger() *slog.Logger {
}
func (d *LocalDB) Close() error {
fmt.Println("Shutting database")
d.logger.Debug("test")
return d.db.Close()
}
@@ -79,6 +82,7 @@ func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) {
}
for _, pragma := range pragmas {
logger.Debug("Setting pragma", "pragma", pragma)
_, err := db.Exec(pragma)
if err != nil {
logger.Error("failed to execute pragma statement", "stmt", pragma, "error", err)

View File

@@ -10,8 +10,8 @@ ALTER TABLE "Events" ADD COLUMN VmName TEXT;
-- +goose Down
-- +goose StatementBegin
ALTER TABLE "Events" DROP COLUMN VmName;
ALTER TABLE "Updates" DROP COLUMN ComputeResourceId;
ALTER TABLE "Updates" DROP COLUMN DatacenterId;
ALTER TABLE "Events" DROP COLUMN ComputeResourceId;
ALTER TABLE "Events" DROP COLUMN DatacenterId;
ALTER TABLE "Events" RENAME COLUMN ComputeResourceName to ComputeResource;
ALTER TABLE "Events" RENAME COLUMN DatacenterName to Datacenter;
-- +goose StatementEnd

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -23,6 +23,7 @@ type Events struct {
DatacenterId sql.NullString
ComputeResourceId sql.NullString
VmName sql.NullString
EventType sql.NullString
}
type Inventory struct {
@@ -31,7 +32,7 @@ type Inventory struct {
Vcenter string
VmId sql.NullString
EventKey sql.NullString
EventId sql.NullString
CloudId sql.NullString
CreationTime sql.NullInt64
DeletionTime sql.NullInt64
ResourcePool sql.NullString
@@ -42,17 +43,36 @@ type Inventory struct {
ProvisionedDisk sql.NullFloat64
InitialVcpus sql.NullInt64
InitialRam sql.NullInt64
SrmPlaceholder sql.NullInt64
IsTemplate interface{}
PoweredOn interface{}
SrmPlaceholder interface{}
VmUuid sql.NullString
}
type InventoryHistory struct {
Hid int64
InventoryId sql.NullInt64
ReportDate sql.NullInt64
UpdateTime sql.NullInt64
PreviousVcpus sql.NullInt64
PreviousRam sql.NullInt64
PreviousResourcePool sql.NullString
PreviousProvisionedDisk sql.NullFloat64
}
type Updates struct {
Uid int64
InventoryId sql.NullInt64
UpdateTime sql.NullInt64
UpdateType string
NewVcpus sql.NullInt64
NewRam sql.NullInt64
NewResourcePool sql.NullString
EventKey sql.NullString
EventId sql.NullString
Uid int64
InventoryId sql.NullInt64
UpdateTime sql.NullInt64
UpdateType string
NewVcpus sql.NullInt64
NewRam sql.NullInt64
NewResourcePool sql.NullString
EventKey sql.NullString
EventId sql.NullString
NewProvisionedDisk sql.NullFloat64
UserName sql.NullString
PlaceholderChange sql.NullString
Name sql.NullString
RawChangeString []byte
}

View File

@@ -2,39 +2,98 @@
SELECT * FROM "Inventory"
ORDER BY "Name";
-- name: GetReportInventory :many
SELECT * FROM "Inventory"
ORDER BY "CreationTime";
-- name: GetInventoryByName :many
SELECT * FROM "Inventory"
WHERE "Name" = ?;
-- name: GetInventoryByVcenter :many
SELECT * FROM "Inventory"
WHERE "Vcenter" = ?;
-- name: GetInventoryVmId :one
SELECT * FROM "Inventory"
WHERE "VmId" = ? LIMIT 1;
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
-- name: GetInventoryVmUuid :one
SELECT * FROM "Inventory"
WHERE "VmUuid" = sqlc.arg('vmUuid') AND "Datacenter" = sqlc.arg('datacenterName');
-- name: GetInventoryVcUrl :many
SELECT * FROM "Inventory"
WHERE "Vcenter" = sqlc.arg('vc');
-- name: GetInventoryEventId :one
SELECT * FROM "Inventory"
WHERE "EventId" = ? LIMIT 1;
WHERE "CloudId" = ? LIMIT 1;
-- name: CreateInventory :one
INSERT INTO "Inventory" (
"Name", "Vcenter", "VmId", "EventKey", "EventId", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
) VALUES(
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
RETURNING *;
-- name: InventoryUpdate :exec
UPDATE "Inventory"
SET "VmUuid" = sqlc.arg('uuid'), "SrmPlaceholder" = sqlc.arg('srmPlaceholder')
WHERE "Iid" = sqlc.arg('iid');
-- name: InventoryMarkDeleted :exec
UPDATE "Inventory"
SET "DeletionTime" = sqlc.arg('deletionTime')
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName');
-- name: InventoryCleanup :exec
DELETE FROM "Inventory"
WHERE "VmId" = sqlc.arg('vmId') AND "Datacenter" = sqlc.arg('datacenterName')
RETURNING *;
-- name: InventoryCleanupVcenter :exec
DELETE FROM "Inventory"
WHERE "Vcenter" = sqlc.arg('vc')
RETURNING *;
-- name: InventoryCleanupTemplates :exec
DELETE FROM "Inventory"
WHERE "IsTemplate" = "TRUE"
RETURNING *;
-- name: CreateUpdate :one
INSERT INTO "Updates" (
"InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool"
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
) VALUES(
?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
RETURNING *;
-- name: GetReportUpdates :many
SELECT * FROM "Updates"
ORDER BY "UpdateTime";
-- name: GetVmUpdates :many
SELECT * FROM "Updates"
WHERE "UpdateType" = sqlc.arg('updateType') AND "InventoryId" = sqlc.arg('InventoryId');
-- name: CleanupUpdates :exec
DELETE FROM "Updates"
WHERE "UpdateType" = sqlc.arg('updateType') AND "UpdateTime" <= sqlc.arg('updateTime')
RETURNING *;
-- name: CleanupUpdatesNullVm :exec
DELETE FROM "Updates"
WHERE "InventoryId" IS NULL
RETURNING *;
-- name: CreateEvent :one
INSERT INTO "Events" (
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
) VALUES(
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
RETURNING *;
@@ -45,9 +104,18 @@ ORDER BY "EventTime";
-- name: ListUnprocessedEvents :many
SELECT * FROM "Events"
WHERE "Processed" = 0
AND "EventTime" > sqlc.arg('eventTime')
ORDER BY "EventTime";
-- name: UpdateEventsProcessed :exec
UPDATE "Events"
SET "Processed" = 1
WHERE "Eid" = sqlc.arg('eid');
WHERE "Eid" = sqlc.arg('eid');
-- name: CreateInventoryHistory :one
INSERT INTO "InventoryHistory" (
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
) VALUES(
?, ?, ?, ?, ?, ?, ?
)
RETURNING *;

View File

@@ -10,13 +10,40 @@ import (
"database/sql"
)
const cleanupUpdates = `-- name: CleanupUpdates :exec
DELETE FROM "Updates"
WHERE "UpdateType" = ?1 AND "UpdateTime" <= ?2
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
`
type CleanupUpdatesParams struct {
UpdateType string
UpdateTime sql.NullInt64
}
func (q *Queries) CleanupUpdates(ctx context.Context, arg CleanupUpdatesParams) error {
_, err := q.db.ExecContext(ctx, cleanupUpdates, arg.UpdateType, arg.UpdateTime)
return err
}
const cleanupUpdatesNullVm = `-- name: CleanupUpdatesNullVm :exec
DELETE FROM "Updates"
WHERE "InventoryId" IS NULL
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
`
func (q *Queries) CleanupUpdatesNullVm(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, cleanupUpdatesNullVm)
return err
}
const createEvent = `-- name: CreateEvent :one
INSERT INTO "Events" (
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
"CloudId", "Source", "EventTime", "ChainId", "VmId", "VmName", "EventType", "EventKey", "DatacenterId", "DatacenterName", "ComputeResourceId", "ComputeResourceName", "UserName"
) VALUES(
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
RETURNING Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName
RETURNING Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType
`
type CreateEventParams struct {
@@ -26,6 +53,7 @@ type CreateEventParams struct {
ChainId string
VmId sql.NullString
VmName sql.NullString
EventType sql.NullString
EventKey sql.NullString
DatacenterId sql.NullString
DatacenterName sql.NullString
@@ -42,6 +70,7 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
arg.ChainId,
arg.VmId,
arg.VmName,
arg.EventType,
arg.EventKey,
arg.DatacenterId,
arg.DatacenterName,
@@ -65,35 +94,39 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
&i.DatacenterId,
&i.ComputeResourceId,
&i.VmName,
&i.EventType,
)
return i, err
}
const createInventory = `-- name: CreateInventory :one
INSERT INTO "Inventory" (
"Name", "Vcenter", "VmId", "EventKey", "EventId", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
"Name", "Vcenter", "VmId", "VmUuid", "EventKey", "CloudId", "CreationTime", "ResourcePool", "VmType", "IsTemplate", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder", "PoweredOn"
) VALUES(
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
RETURNING Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
`
type CreateInventoryParams struct {
Name string
Vcenter string
VmId sql.NullString
VmUuid sql.NullString
EventKey sql.NullString
EventId sql.NullString
CloudId sql.NullString
CreationTime sql.NullInt64
ResourcePool sql.NullString
VmType sql.NullString
IsTemplate interface{}
Datacenter sql.NullString
Cluster sql.NullString
Folder sql.NullString
ProvisionedDisk sql.NullFloat64
InitialVcpus sql.NullInt64
InitialRam sql.NullInt64
SrmPlaceholder sql.NullInt64
SrmPlaceholder interface{}
PoweredOn interface{}
}
func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams) (Inventory, error) {
@@ -101,11 +134,13 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
arg.Name,
arg.Vcenter,
arg.VmId,
arg.VmUuid,
arg.EventKey,
arg.EventId,
arg.CloudId,
arg.CreationTime,
arg.ResourcePool,
arg.VmType,
arg.IsTemplate,
arg.Datacenter,
arg.Cluster,
arg.Folder,
@@ -113,6 +148,7 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
arg.InitialVcpus,
arg.InitialRam,
arg.SrmPlaceholder,
arg.PoweredOn,
)
var i Inventory
err := row.Scan(
@@ -121,7 +157,7 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.EventId,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
@@ -132,34 +168,86 @@ func (q *Queries) CreateInventory(ctx context.Context, arg CreateInventoryParams
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
)
return i, err
}
const createInventoryHistory = `-- name: CreateInventoryHistory :one
INSERT INTO "InventoryHistory" (
"InventoryId", "ReportDate", "UpdateTime", "PreviousVcpus", "PreviousRam", "PreviousResourcePool", "PreviousProvisionedDisk"
) VALUES(
?, ?, ?, ?, ?, ?, ?
)
RETURNING Hid, InventoryId, ReportDate, UpdateTime, PreviousVcpus, PreviousRam, PreviousResourcePool, PreviousProvisionedDisk
`
type CreateInventoryHistoryParams struct {
InventoryId sql.NullInt64
ReportDate sql.NullInt64
UpdateTime sql.NullInt64
PreviousVcpus sql.NullInt64
PreviousRam sql.NullInt64
PreviousResourcePool sql.NullString
PreviousProvisionedDisk sql.NullFloat64
}
func (q *Queries) CreateInventoryHistory(ctx context.Context, arg CreateInventoryHistoryParams) (InventoryHistory, error) {
row := q.db.QueryRowContext(ctx, createInventoryHistory,
arg.InventoryId,
arg.ReportDate,
arg.UpdateTime,
arg.PreviousVcpus,
arg.PreviousRam,
arg.PreviousResourcePool,
arg.PreviousProvisionedDisk,
)
var i InventoryHistory
err := row.Scan(
&i.Hid,
&i.InventoryId,
&i.ReportDate,
&i.UpdateTime,
&i.PreviousVcpus,
&i.PreviousRam,
&i.PreviousResourcePool,
&i.PreviousProvisionedDisk,
)
return i, err
}
const createUpdate = `-- name: CreateUpdate :one
INSERT INTO "Updates" (
"InventoryId", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool"
"InventoryId", "Name", "EventKey", "EventId", "UpdateTime", "UpdateType", "NewVcpus", "NewRam", "NewResourcePool", "NewProvisionedDisk", "UserName", "PlaceholderChange", "RawChangeString"
) VALUES(
?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId
RETURNING Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString
`
type CreateUpdateParams struct {
InventoryId sql.NullInt64
EventKey sql.NullString
EventId sql.NullString
UpdateTime sql.NullInt64
UpdateType string
NewVcpus sql.NullInt64
NewRam sql.NullInt64
NewResourcePool sql.NullString
InventoryId sql.NullInt64
Name sql.NullString
EventKey sql.NullString
EventId sql.NullString
UpdateTime sql.NullInt64
UpdateType string
NewVcpus sql.NullInt64
NewRam sql.NullInt64
NewResourcePool sql.NullString
NewProvisionedDisk sql.NullFloat64
UserName sql.NullString
PlaceholderChange sql.NullString
RawChangeString []byte
}
func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Updates, error) {
row := q.db.QueryRowContext(ctx, createUpdate,
arg.InventoryId,
arg.Name,
arg.EventKey,
arg.EventId,
arg.UpdateTime,
@@ -167,6 +255,10 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
arg.NewVcpus,
arg.NewRam,
arg.NewResourcePool,
arg.NewProvisionedDisk,
arg.UserName,
arg.PlaceholderChange,
arg.RawChangeString,
)
var i Updates
err := row.Scan(
@@ -179,12 +271,17 @@ func (q *Queries) CreateUpdate(ctx context.Context, arg CreateUpdateParams) (Upd
&i.NewResourcePool,
&i.EventKey,
&i.EventId,
&i.NewProvisionedDisk,
&i.UserName,
&i.PlaceholderChange,
&i.Name,
&i.RawChangeString,
)
return i, err
}
const getInventoryByName = `-- name: GetInventoryByName :many
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
WHERE "Name" = ?
`
@@ -203,7 +300,7 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.EventId,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
@@ -214,7 +311,59 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getInventoryByVcenter = `-- name: GetInventoryByVcenter :many
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
WHERE "Vcenter" = ?
`
func (q *Queries) GetInventoryByVcenter(ctx context.Context, vcenter string) ([]Inventory, error) {
rows, err := q.db.QueryContext(ctx, getInventoryByVcenter, vcenter)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Inventory
for rows.Next() {
var i Inventory
if err := rows.Scan(
&i.Iid,
&i.Name,
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
&i.VmType,
&i.Datacenter,
&i.Cluster,
&i.Folder,
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
); err != nil {
return nil, err
}
@@ -230,12 +379,12 @@ func (q *Queries) GetInventoryByName(ctx context.Context, name string) ([]Invent
}
const getInventoryEventId = `-- name: GetInventoryEventId :one
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
WHERE "EventId" = ? LIMIT 1
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
WHERE "CloudId" = ? LIMIT 1
`
func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullString) (Inventory, error) {
row := q.db.QueryRowContext(ctx, getInventoryEventId, eventid)
func (q *Queries) GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (Inventory, error) {
row := q.db.QueryRowContext(ctx, getInventoryEventId, cloudid)
var i Inventory
err := row.Scan(
&i.Iid,
@@ -243,7 +392,7 @@ func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullStrin
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.EventId,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
@@ -254,18 +403,75 @@ func (q *Queries) GetInventoryEventId(ctx context.Context, eventid sql.NullStrin
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
)
return i, err
}
const getInventoryVcUrl = `-- name: GetInventoryVcUrl :many
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
WHERE "Vcenter" = ?1
`
func (q *Queries) GetInventoryVcUrl(ctx context.Context, vc string) ([]Inventory, error) {
rows, err := q.db.QueryContext(ctx, getInventoryVcUrl, vc)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Inventory
for rows.Next() {
var i Inventory
if err := rows.Scan(
&i.Iid,
&i.Name,
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
&i.VmType,
&i.Datacenter,
&i.Cluster,
&i.Folder,
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getInventoryVmId = `-- name: GetInventoryVmId :one
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
WHERE "VmId" = ? LIMIT 1
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
WHERE "VmId" = ?1 AND "Datacenter" = ?2
`
func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (Inventory, error) {
row := q.db.QueryRowContext(ctx, getInventoryVmId, vmid)
type GetInventoryVmIdParams struct {
VmId sql.NullString
DatacenterName sql.NullString
}
func (q *Queries) GetInventoryVmId(ctx context.Context, arg GetInventoryVmIdParams) (Inventory, error) {
row := q.db.QueryRowContext(ctx, getInventoryVmId, arg.VmId, arg.DatacenterName)
var i Inventory
err := row.Scan(
&i.Iid,
@@ -273,7 +479,7 @@ func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (In
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.EventId,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
@@ -284,13 +490,266 @@ func (q *Queries) GetInventoryVmId(ctx context.Context, vmid sql.NullString) (In
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
)
return i, err
}
const getInventoryVmUuid = `-- name: GetInventoryVmUuid :one
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
WHERE "VmUuid" = ?1 AND "Datacenter" = ?2
`
type GetInventoryVmUuidParams struct {
VmUuid sql.NullString
DatacenterName sql.NullString
}
func (q *Queries) GetInventoryVmUuid(ctx context.Context, arg GetInventoryVmUuidParams) (Inventory, error) {
row := q.db.QueryRowContext(ctx, getInventoryVmUuid, arg.VmUuid, arg.DatacenterName)
var i Inventory
err := row.Scan(
&i.Iid,
&i.Name,
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
&i.VmType,
&i.Datacenter,
&i.Cluster,
&i.Folder,
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
)
return i, err
}
const getReportInventory = `-- name: GetReportInventory :many
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
ORDER BY "CreationTime"
`
func (q *Queries) GetReportInventory(ctx context.Context) ([]Inventory, error) {
rows, err := q.db.QueryContext(ctx, getReportInventory)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Inventory
for rows.Next() {
var i Inventory
if err := rows.Scan(
&i.Iid,
&i.Name,
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
&i.VmType,
&i.Datacenter,
&i.Cluster,
&i.Folder,
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getReportUpdates = `-- name: GetReportUpdates :many
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM "Updates"
ORDER BY "UpdateTime"
`
func (q *Queries) GetReportUpdates(ctx context.Context) ([]Updates, error) {
rows, err := q.db.QueryContext(ctx, getReportUpdates)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Updates
for rows.Next() {
var i Updates
if err := rows.Scan(
&i.Uid,
&i.InventoryId,
&i.UpdateTime,
&i.UpdateType,
&i.NewVcpus,
&i.NewRam,
&i.NewResourcePool,
&i.EventKey,
&i.EventId,
&i.NewProvisionedDisk,
&i.UserName,
&i.PlaceholderChange,
&i.Name,
&i.RawChangeString,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getVmUpdates = `-- name: GetVmUpdates :many
SELECT Uid, InventoryId, UpdateTime, UpdateType, NewVcpus, NewRam, NewResourcePool, EventKey, EventId, NewProvisionedDisk, UserName, PlaceholderChange, Name, RawChangeString FROM "Updates"
WHERE "UpdateType" = ?1 AND "InventoryId" = ?2
`
type GetVmUpdatesParams struct {
UpdateType string
InventoryId sql.NullInt64
}
func (q *Queries) GetVmUpdates(ctx context.Context, arg GetVmUpdatesParams) ([]Updates, error) {
rows, err := q.db.QueryContext(ctx, getVmUpdates, arg.UpdateType, arg.InventoryId)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Updates
for rows.Next() {
var i Updates
if err := rows.Scan(
&i.Uid,
&i.InventoryId,
&i.UpdateTime,
&i.UpdateType,
&i.NewVcpus,
&i.NewRam,
&i.NewResourcePool,
&i.EventKey,
&i.EventId,
&i.NewProvisionedDisk,
&i.UserName,
&i.PlaceholderChange,
&i.Name,
&i.RawChangeString,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const inventoryCleanup = `-- name: InventoryCleanup :exec
DELETE FROM "Inventory"
WHERE "VmId" = ?1 AND "Datacenter" = ?2
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
`
type InventoryCleanupParams struct {
VmId sql.NullString
DatacenterName sql.NullString
}
func (q *Queries) InventoryCleanup(ctx context.Context, arg InventoryCleanupParams) error {
_, err := q.db.ExecContext(ctx, inventoryCleanup, arg.VmId, arg.DatacenterName)
return err
}
const inventoryCleanupTemplates = `-- name: InventoryCleanupTemplates :exec
DELETE FROM "Inventory"
WHERE "IsTemplate" = "TRUE"
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
`
func (q *Queries) InventoryCleanupTemplates(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, inventoryCleanupTemplates)
return err
}
const inventoryCleanupVcenter = `-- name: InventoryCleanupVcenter :exec
DELETE FROM "Inventory"
WHERE "Vcenter" = ?1
RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid
`
func (q *Queries) InventoryCleanupVcenter(ctx context.Context, vc string) error {
_, err := q.db.ExecContext(ctx, inventoryCleanupVcenter, vc)
return err
}
const inventoryMarkDeleted = `-- name: InventoryMarkDeleted :exec
UPDATE "Inventory"
SET "DeletionTime" = ?1
WHERE "VmId" = ?2 AND "Datacenter" = ?3
`
type InventoryMarkDeletedParams struct {
DeletionTime sql.NullInt64
VmId sql.NullString
DatacenterName sql.NullString
}
func (q *Queries) InventoryMarkDeleted(ctx context.Context, arg InventoryMarkDeletedParams) error {
_, err := q.db.ExecContext(ctx, inventoryMarkDeleted, arg.DeletionTime, arg.VmId, arg.DatacenterName)
return err
}
const inventoryUpdate = `-- name: InventoryUpdate :exec
UPDATE "Inventory"
SET "VmUuid" = ?1, "SrmPlaceholder" = ?2
WHERE "Iid" = ?3
`
type InventoryUpdateParams struct {
Uuid sql.NullString
SrmPlaceholder interface{}
Iid int64
}
func (q *Queries) InventoryUpdate(ctx context.Context, arg InventoryUpdateParams) error {
_, err := q.db.ExecContext(ctx, inventoryUpdate, arg.Uuid, arg.SrmPlaceholder, arg.Iid)
return err
}
const listEvents = `-- name: ListEvents :many
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName FROM "Events"
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events"
ORDER BY "EventTime"
`
@@ -318,6 +777,7 @@ func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
&i.DatacenterId,
&i.ComputeResourceId,
&i.VmName,
&i.EventType,
); err != nil {
return nil, err
}
@@ -333,7 +793,7 @@ func (q *Queries) ListEvents(ctx context.Context) ([]Events, error) {
}
const listInventory = `-- name: ListInventory :many
SELECT Iid, Name, Vcenter, VmId, EventKey, EventId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder FROM "Inventory"
SELECT Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid FROM "Inventory"
ORDER BY "Name"
`
@@ -352,7 +812,7 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
&i.Vcenter,
&i.VmId,
&i.EventKey,
&i.EventId,
&i.CloudId,
&i.CreationTime,
&i.DeletionTime,
&i.ResourcePool,
@@ -363,7 +823,10 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.InitialRam,
&i.IsTemplate,
&i.PoweredOn,
&i.SrmPlaceholder,
&i.VmUuid,
); err != nil {
return nil, err
}
@@ -379,13 +842,14 @@ func (q *Queries) ListInventory(ctx context.Context) ([]Inventory, error) {
}
const listUnprocessedEvents = `-- name: ListUnprocessedEvents :many
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName FROM "Events"
SELECT Eid, CloudId, Source, EventTime, ChainId, VmId, EventKey, DatacenterName, ComputeResourceName, UserName, Processed, DatacenterId, ComputeResourceId, VmName, EventType FROM "Events"
WHERE "Processed" = 0
AND "EventTime" > ?1
ORDER BY "EventTime"
`
func (q *Queries) ListUnprocessedEvents(ctx context.Context) ([]Events, error) {
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents)
func (q *Queries) ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]Events, error) {
rows, err := q.db.QueryContext(ctx, listUnprocessedEvents, eventtime)
if err != nil {
return nil, err
}
@@ -408,6 +872,7 @@ func (q *Queries) ListUnprocessedEvents(ctx context.Context) ([]Events, error) {
&i.DatacenterId,
&i.ComputeResourceId,
&i.VmName,
&i.EventType,
); err != nil {
return nil, err
}

View File

@@ -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
View 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

File diff suppressed because one or more lines are too long

788
dist/assets/css/output@dev.css vendored Normal file
View 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
View File

@@ -1,15 +1,17 @@
module vctp
go 1.23.1
go 1.23.2
require (
github.com/a-h/templ v0.2.778
github.com/go-co-op/gocron/v2 v2.11.0
github.com/go-co-op/gocron/v2 v2.12.1
github.com/jmoiron/sqlx v1.4.0
github.com/joho/godotenv v1.5.1
github.com/pressly/goose/v3 v3.22.0
github.com/vmware/govmomi v0.43.0
modernc.org/sqlite v1.33.0
github.com/pressly/goose/v3 v3.22.1
github.com/vmware/govmomi v0.44.1
github.com/xuri/excelize/v2 v2.9.0
gopkg.in/yaml.v2 v2.4.0
modernc.org/sqlite v1.33.1
)
require (
@@ -19,16 +21,25 @@ require (
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/tools v0.25.0 // indirect
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect
modernc.org/libc v1.61.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect

78
go.sum
View File

@@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/go-co-op/gocron/v2 v2.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjjy1uw=
github.com/go-co-op/gocron/v2 v2.12.1/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -24,6 +24,10 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -32,49 +36,107 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pressly/goose/v3 v3.22.0 h1:wd/7kNiPTuNAztWun7iaB98DrhulbWPrzMAaw2DEZNw=
github.com/pressly/goose/v3 v3.22.0/go.mod h1:yJM3qwSj2pp7aAaCvso096sguezamNb2OBgxCnh/EYg=
github.com/pressly/goose/v3 v3.22.1 h1:2zICEfr1O3yTP9BRZMGPj7qFxQ+ik6yeo+z1LMuioLc=
github.com/pressly/goose/v3 v3.22.1/go.mod h1:xtMpbstWyCpyH+0cxLTMCENWBG+0CSxvTsXhW95d5eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vmware/govmomi v0.43.0 h1:7Kg3Bkdly+TrE67BYXzRq7ZrDnn7xqpKX95uEh2f9Go=
github.com/vmware/govmomi v0.43.0/go.mod h1:IOv5nTXCPqH9qVJAlRuAGffogaLsNs8aF+e7vLgsHJU=
github.com/vmware/govmomi v0.44.1 h1:Hbt0nvVY8fnp3DGRJHngLflTos/uRrW54lhmJ/zKZFc=
github.com/vmware/govmomi v0.44.1/go.mod h1:uoLVU9zlXC4p4GmLVG+ZJmBC0Gn3Q7mytOJvi39OhxA=
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ=
github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE=
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a h1:CfbpOLEo2IwNzJdMvE8aiRbPMxoTpgAJeyePh0SmO8M=
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk=
modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

303
internal/report/create.go Normal file
View 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
}

View 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
}

View 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
}

View 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))
}
}

View File

@@ -2,35 +2,42 @@ package tasks
import (
"context"
"database/sql"
"log/slog"
"strings"
"time"
"vctp/db"
"vctp/db/queries"
"vctp/internal/vcenter"
)
// Handler handles requests.
type CronTask struct {
Logger *slog.Logger
Database db.Database
}
"github.com/vmware/govmomi/vim25/types"
)
// use gocron to check events in the Events table
func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
var (
//unixTimestamp int64
numVcpus int32
numRam int32
datacenter string
foundVm bool
numVcpus int32
numRam int32
totalDiskGB float64
srmPlaceholder string
foundVm bool
isTemplate string
poweredOn string
folderPath string
rpName string
vmUuid string
)
logger.Debug("Started Events processing", "time", time.Now())
dateCmp := time.Now().AddDate(0, 0, -1).Unix()
logger.Debug("Started Events processing", "time", time.Now(), "since", dateCmp)
// Query events table
events, err := c.Database.Queries().ListUnprocessedEvents(ctx)
events, err := c.Database.Queries().ListUnprocessedEvents(ctx,
sql.NullInt64{Int64: dateCmp, Valid: dateCmp > 0})
if err != nil {
logger.Error("Unable to query for unprocessed events", "error", err)
return nil // TODO - what to do with this error?
} else {
logger.Debug("Successfully queried for unprocessed events", "count", len(events))
}
for _, evt := range events {
@@ -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
// to avoid doing unnecessary login/logout of vcenter
c.Logger.Debug("connecting to vcenter")
vc := vcenter.New(c.Logger)
//c.Logger.Debug("connecting to vcenter")
vc := vcenter.New(c.Logger, c.VcCreds)
vc.Login(evt.Source)
datacenter = evt.DatacenterName.String
//datacenter = evt.DatacenterName.String
vmObject, err := vc.FindVMByIDWithDatacenter(evt.VmId.String, evt.DatacenterId.String)
if err != nil {
c.Logger.Error("Can't locate vm in vCenter", "vmID", evt.VmId.String, "error", err)
continue
} else if vmObject == nil {
c.Logger.Debug("didn't find VM", "vm_id", evt.VmId.String)
numRam = 0
numVcpus = 0
} else {
c.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
numVcpus = vmObject.Vm.Config.Hardware.NumCPU
foundVm = true
} else {
c.Logger.Error("Empty VM config")
// 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
numVcpus = 0
totalDiskGB = 0
isTemplate = "FALSE"
folderPath = ""
vmUuid = ""
*/
continue
}
//c.Logger.Debug("found VM")
srmPlaceholder = "FALSE" // Default assumption
//prettyPrint(vmObject)
// calculate VM properties we want to store
if vmObject.Config != nil {
numRam = vmObject.Config.Hardware.MemoryMB
numVcpus = vmObject.Config.Hardware.NumCPU
vmUuid = vmObject.Config.Uuid
var totalDiskBytes int64
// Calculate the total disk allocated in GB
for _, device := range vmObject.Config.Hardware.Device {
if disk, ok := device.(*types.VirtualDisk); ok {
// Print the filename of the backing device
if _, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
//c.Logger.Debug("Adding disk", "size_bytes", disk.CapacityInBytes, "backing_file", backing.FileName)
} else {
//c.Logger.Debug("Adding disk, unknown backing type", "size_bytes", disk.CapacityInBytes)
}
totalDiskBytes += disk.CapacityInBytes
//totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB
}
}
totalDiskGB = float64(totalDiskBytes / 1024 / 1024 / 1024)
c.Logger.Debug("Converted total disk size", "bytes", totalDiskBytes, "GB", totalDiskGB)
// Determine if the VM is a normal VM or an SRM placeholder
if vmObject.Config.ManagedBy != nil && vmObject.Config.ManagedBy.ExtensionKey == "com.vmware.vcDr" {
if vmObject.Config.ManagedBy.Type == "placeholderVm" {
c.Logger.Debug("VM is a placeholder")
srmPlaceholder = "TRUE"
} else {
c.Logger.Debug("VM is managed by SRM but not a placeholder", "details", vmObject.Config.ManagedBy)
}
}
if vmObject.Config.Template {
isTemplate = "TRUE"
} else {
isTemplate = "FALSE"
}
// Retrieve the full folder path of the VM
folderPath, err = vc.GetVMFolderPath(*vmObject)
if err != nil {
c.Logger.Error("failed to get vm folder path", "error", err)
folderPath = ""
} else {
c.Logger.Debug("Found vm folder path", "folder_path", folderPath)
}
// Retrieve the resource pool of the VM
rpName, _ = vc.GetVmResourcePool(*vmObject)
foundVm = true
} else {
c.Logger.Error("Empty VM config")
}
//c.Logger.Debug("VM has runtime data", "power_state", vmObject.Runtime.PowerState)
if vmObject.Runtime.PowerState == "poweredOff" {
poweredOn = "FALSE"
} else {
poweredOn = "TRUE"
}
err = vc.Logout()
if err != nil {
c.Logger.Error("unable to logout of vcenter", "error", err)
}
if foundVm {
c.Logger.Debug("Simulate adding to Inventory", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", datacenter)
c.Logger.Debug("Adding to Inventory table", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", evt.DatacenterId.String)
// mark this event as processed
err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid)
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 mark this event as processed", "event_id", evt.Eid, "error", err)
c.Logger.Error("unable to perform database insert", "error", err)
} else {
c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
//c.Logger.Debug("created database record", "insert_result", result)
// mark this event as processed
err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid)
if err != nil {
c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err)
} else {
//c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
}
}
} 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
View 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
}

View File

@@ -1,11 +1,13 @@
package utils
import (
"context"
"log"
"log/slog"
"net"
"os"
"path/filepath"
"time"
)
const rsaBits = 4096
@@ -53,3 +55,14 @@ func FileExists(filename string) bool {
}
return !info.IsDir()
}
func SleepWithContext(ctx context.Context, d time.Duration) {
timer := time.NewTimer(d)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
case <-timer.C:
}
}

View File

@@ -7,6 +7,8 @@ import (
"log/slog"
"net/url"
"os"
"path"
"strings"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
@@ -18,42 +20,52 @@ import (
)
type Vcenter struct {
Logger *slog.Logger
ctx context.Context
client *govmomi.Client
Logger *slog.Logger
Vurl string
ctx context.Context
client *govmomi.Client
credentials *VcenterLogin
}
type VcenterLogin struct {
Username string
Password string
}
type VmProperties struct {
Vm mo.VirtualMachine
//Datacenter string
Vm mo.VirtualMachine
ResourcePool string
}
// New creates a new Vcenter with the given logger
func New(logger *slog.Logger) *Vcenter {
func New(logger *slog.Logger, creds *VcenterLogin) *Vcenter {
//ctx, cancel := context.WithCancel(context.Background())
//defer cancel()
return &Vcenter{
Logger: logger,
ctx: context.Background(),
Logger: logger,
ctx: context.Background(),
credentials: creds,
}
}
func (v *Vcenter) Login(vUrl string) error {
var insecure bool
// TODO - fix this
insecureString := os.Getenv("VCENTER_INSECURE")
username := os.Getenv("VCENTER_USERNAME")
password := os.Getenv("VCENTER_PASSWORD")
//username := os.Getenv("VCENTER_USERNAME")
//password := os.Getenv("VCENTER_PASSWORD")
// Connect to vCenter
u, err := soap.ParseURL(vUrl)
if err != nil {
log.Fatalf("Error parsing vCenter URL: %s", err)
}
v.Vurl = vUrl
u.User = url.UserPassword(username, password)
u.User = url.UserPassword(v.credentials.Username, v.credentials.Password)
/*
c, err := govmomi.NewClient(ctx, u, insecure)
@@ -76,7 +88,7 @@ func (v *Vcenter) Login(vUrl string) error {
v.client = c
v.Logger.Debug("successfully connected to vCenter", "url", vUrl, "username", username)
v.Logger.Debug("successfully connected to vCenter", "url", vUrl, "username", v.credentials.Username)
return nil
}
@@ -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) {
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)
}
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmProperties, error) {
//var dcName string
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*mo.VirtualMachine, error) {
var err error
//resourcePool := ""
v.Logger.Debug("searching for vm id", "vm_id", vmID, "datacenter_id", dcID)
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
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
vmRef := types.ManagedObjectReference{
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, nil, &vm)
if err == nil {
v.Logger.Debug("Found VM")
return &VmProperties{
//Datacenter: dcName,
Vm: vm,
}, nil
//v.Logger.Debug("Found VM")
return &vm, nil
} else if _, ok := err.(*find.NotFoundError); !ok {
// If the error is not a NotFoundError, return it
//return nil, fmt.Errorf("failed to retrieve VM with ID %s in datacenter %s: %w", vmID, dc.Name(), err)
v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
//v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
return nil, nil
} else {
return nil, fmt.Errorf("failed to retrieve VM: %w", err)
}
v.Logger.Info("Unable to find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
return nil, nil
}
// Helper function to retrieve the resource pool for the VM
func (v *Vcenter) GetVmResourcePool(vm mo.VirtualMachine) (string, error) {
var resourcePool string
if vm.ResourcePool != nil {
rp := object.NewResourcePool(v.client.Client, *vm.ResourcePool)
rpName, err := rp.ObjectName(v.ctx)
if err != nil {
v.Logger.Error("failed to get resource pool name", "error", err)
return resourcePool, err
} else {
//v.Logger.Debug("Found resource pool name", "rp_name", rpName)
resourcePool = rpName
}
}
return resourcePool, nil
}
// Helper function to retrieve the full folder path for the VM
func (v *Vcenter) GetVMFolderPath(vm mo.VirtualMachine) (string, error) {
//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
}

139
main.go
View File

@@ -8,8 +8,11 @@ import (
"runtime"
"time"
"vctp/db"
"vctp/internal/secrets"
"vctp/internal/settings"
"vctp/internal/tasks"
utils "vctp/internal/utils"
"vctp/internal/vcenter"
"vctp/log"
"vctp/server"
"vctp/server/router"
@@ -19,10 +22,12 @@ import (
)
var (
bindDisableTls bool
sha1ver string // sha1 revision used to build the program
buildTime string // when the executable was built
cronFrequency time.Duration
bindDisableTls bool
sha1ver string // sha1 revision used to build the program
buildTime string // when the executable was built
cronFrequency time.Duration
cronInvFrequency time.Duration
encryptionKey = []byte("5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa")
)
func main() {
@@ -47,23 +52,28 @@ func main() {
os.Exit(1)
}
defer database.Close()
//defer database.DB().Close()
if err = db.Migrate(database); err != nil {
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)
}
// Pass useful information to the cron jobs
c := &tasks.CronTask{
Logger: logger,
Database: database,
// Load settings from yaml
settingsFile := os.Getenv("SETTINGS_FILE")
if settingsFile == "" {
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
@@ -77,7 +87,7 @@ func main() {
bindPort = "9443"
}
bindAddress := fmt.Sprint(bindIP, ":", bindPort)
slog.Info("Will listen on address", "ip", bindIP, "port", bindPort)
//logger.Info("Will listen on address", "ip", bindIP, "port", bindPort)
// Determine bind disable TLS
bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS")
@@ -102,52 +112,117 @@ func main() {
// Generate certificate if required
if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) {
slog.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename)
logger.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename)
utils.GenerateCerts(tlsCertFilename, tlsKeyFilename)
}
cronFrequencyString := os.Getenv("VCENTER_POLLING_SECONDS")
// Load vcenter credentials from .env
a := secrets.New(logger, encryptionKey)
vcEp := os.Getenv("VCENTER_PASSWORD")
if len(vcEp) == 0 {
logger.Error("No vcenter password configured")
os.Exit(1)
}
vcPass, err := a.Decrypt(vcEp)
if err != nil {
logger.Error("failed to decrypt vcenter credentials. Assuming un-encrypted", "error", err)
vcPass = []byte(vcEp)
//os.Exit(1)
}
creds := vcenter.VcenterLogin{
//insecureString := os.Getenv("VCENTER_INSECURE")
Username: os.Getenv("VCENTER_USERNAME"),
Password: string(vcPass),
}
// Prepare the task scheduler
c, err := gocron.NewScheduler()
if err != nil {
logger.Error("failed to create scheduler", "error", err)
os.Exit(1)
}
// Pass useful information to the cron jobs
ct := &tasks.CronTask{
Logger: logger,
Database: database,
Settings: s,
VcCreds: &creds,
}
cronFrequencyString := os.Getenv("VCENTER_EVENT_POLLING_SECONDS")
if cronFrequencyString != "" {
cronFrequency, err = time.ParseDuration(cronFrequencyString)
if err != nil {
slog.Error("Can't convert VCENTER_POLLING_SECONDS value to time duration. Defaulting to 60s", "value", cronFrequencyString, "error", err)
slog.Error("Can't convert VCENTER_EVENT_POLLING_SECONDS value to time duration. Defaulting to 60s", "value", cronFrequencyString, "error", err)
cronFrequency = time.Second * 60
}
} else {
cronFrequency = time.Second * 60
}
logger.Debug("Setting VM polling cronjob frequency to", "frequency", cronFrequency)
logger.Debug("Setting VM event polling cronjob frequency to", "frequency", cronFrequency)
// start background processing
cronInventoryFrequencyString := os.Getenv("VCENTER_INVENTORY_POLLING_SECONDS")
if cronInventoryFrequencyString != "" {
cronInvFrequency, err = time.ParseDuration(cronInventoryFrequencyString)
if err != nil {
slog.Error("Can't convert VCENTER_INVENTORY_POLLING_SECONDS value to time duration. Defaulting to 7200", "value", cronInventoryFrequencyString, "error", err)
cronInvFrequency = time.Second * 7200
}
} else {
cronInvFrequency = time.Second * 7200
}
logger.Debug("Setting VM inventory polling cronjob frequency to", "frequency", cronInvFrequency)
// start background processing for events stored in events table
startsAt := time.Now().Add(time.Second * 10)
job, err := s.NewJob(
job, err := c.NewJob(
gocron.DurationJob(cronFrequency),
gocron.NewTask(func() {
c.RunVmCheck(ctx, logger)
ct.RunVmCheck(ctx, logger)
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
gocron.WithStartAt(gocron.WithStartDateTime(startsAt)),
)
if err != nil {
logger.Error("failed to start cron jobs", "error", err)
logger.Error("failed to start event processing cron job", "error", err)
os.Exit(1)
}
logger.Debug("Created event processing cron job", "job", job.ID(), "starting_at", startsAt)
slog.Debug("Created cron job", "job", job)
// start background checks of vcenter inventory
startsAt2 := time.Now().Add(cronInvFrequency)
job2, err := c.NewJob(
gocron.DurationJob(cronInvFrequency),
gocron.NewTask(func() {
ct.RunVcenterPoll(ctx, logger)
}), gocron.WithSingletonMode(gocron.LimitModeReschedule),
gocron.WithStartAt(gocron.WithStartDateTime(startsAt2)),
)
if err != nil {
logger.Error("failed to start vcenter inventory cron job", "error", err)
os.Exit(1)
}
logger.Debug("Created vcenter inventory cron job", "job", job2.ID(), "starting_at", startsAt2)
s.Start()
// start cron scheduler
c.Start()
// Start server
r := router.New(logger, database, buildTime, sha1ver, runtime.Version(), &creds, a, s)
svr := server.New(
logger,
s,
c,
cancel,
bindAddress,
server.WithRouter(router.New(logger, database, buildTime, sha1ver, runtime.Version())),
server.WithRouter(r),
server.SetTls(bindDisableTls),
server.SetCertificate(tlsCertFilename),
server.SetPrivateKey(tlsKeyFilename),
)
svr.DisableTls(bindDisableTls)
svr.SetCertificate(tlsCertFilename)
svr.SetPrivateKey(tlsKeyFilename)
//logger.Debug("Server configured", "object", svr)
svr.StartAndWait()
os.Exit(0)
}

View 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
}

View File

@@ -5,6 +5,9 @@ import (
"log/slog"
"net/http"
"vctp/db"
"vctp/internal/secrets"
"vctp/internal/settings"
"vctp/internal/vcenter"
"github.com/a-h/templ"
)
@@ -16,6 +19,9 @@ type Handler struct {
BuildTime string
SHA1Ver string
GoVersion string
VcCreds *vcenter.VcenterLogin
Secret *secrets.Secrets
Settings *settings.Settings
}
func (h *Handler) html(ctx context.Context, w http.ResponseWriter, status int, t templ.Component) {

View 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)
}

View 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")
}
}

View 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
}
}

View 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
}
}

View File

@@ -14,8 +14,8 @@ import (
models "vctp/server/models"
)
// VmCreate receives the CloudEvent for a VM creation
func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
// VmCreateEvent receives the CloudEvent for a VM creation
func (h *Handler) VmCreateEvent(w http.ResponseWriter, r *http.Request) {
var (
unixTimestamp int64
//numVcpus int32
@@ -33,7 +33,7 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
h.Logger.Debug("received input data", "length", len(reqBody))
}
// Decode the JSON body into vmModel struct
// Decode the JSON body into CloudEventReceived struct
var event models.CloudEventReceived
if err := json.Unmarshal(reqBody, &event); err != nil {
h.Logger.Error("unable to decode json", "error", err)
@@ -41,7 +41,7 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
return
} else {
h.Logger.Debug("successfully decoded JSON")
prettyPrint(event)
//prettyPrint(event)
}
e := event.CloudEvent.Data
@@ -60,63 +60,14 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
unixTimestamp = eventTime.Unix()
}
/*
// TODO - initiate govmomi query of source vcenter to discover related data
h.Logger.Debug("connecting to vcenter")
vc := vcenter.New(h.Logger)
vc.Login(event.CloudEvent.Source)
//vmObject, err := vc.FindVMByName(vm.CloudEvent.Data.VM.Name)
//vmObject, err := vc.FindVMByID(vm.CloudEvent.Data.VM.VM.Value)
vmObject, err := vc.FindVMByIDWithDatacenter(e.VM.VM.Value, e.Datacenter.Datacenter.Value)
if err != nil {
h.Logger.Error("Can't locate vm in vCenter", "vmID", e.VM.VM.Value, "error", err)
} else if vmObject == nil {
h.Logger.Debug("didn't find VM", "vm_id", e.VM.VM.Value)
numRam = 0
numVcpus = 0
datacenter = e.Datacenter.Name
} else {
h.Logger.Debug("found VM")
//prettyPrint(vmObject)
// calculate VM properties we want to store
if vmObject.Vm.Config != nil {
numRam = vmObject.Vm.Config.Hardware.MemoryMB
numVcpus = vmObject.Vm.Config.Hardware.NumCPU * vmObject.Vm.Config.Hardware.NumCoresPerSocket
} else {
h.Logger.Error("Empty VM config")
}
}
err = vc.Logout()
if err != nil {
h.Logger.Error("unable to logout of vcenter", "error", err)
}
*/
// Create an instance of CreateInventoryParams
h.Logger.Debug("Creating database parameters")
/*
params := queries.CreateInventoryParams{
Name: e.VM.Name,
Vcenter: event.CloudEvent.Source,
EventId: sql.NullString{String: event.CloudEvent.ID, Valid: event.CloudEvent.ID != ""},
EventKey: sql.NullString{String: strconv.Itoa(e.Key), Valid: strconv.Itoa(e.Key) != ""},
VmId: sql.NullString{String: e.VM.VM.Value, Valid: e.VM.VM.Value != ""},
Datacenter: sql.NullString{String: datacenter, Valid: datacenter != ""},
Cluster: sql.NullString{String: e.ComputeResource.Name, Valid: e.ComputeResource.Name != ""},
CreationTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0},
InitialVcpus: sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0},
InitialRam: sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0},
}
*/
params2 := queries.CreateEventParams{
params := queries.CreateEventParams{
Source: event.CloudEvent.Source,
CloudId: event.CloudEvent.ID,
EventTime: sql.NullInt64{Int64: unixTimestamp, Valid: unixTimestamp > 0},
EventType: sql.NullString{String: event.CloudEvent.Type, Valid: event.CloudEvent.Type != ""},
ChainId: strconv.Itoa(e.ChainID),
VmId: sql.NullString{String: e.VM.VM.Value, Valid: e.VM.VM.Value != ""},
VmName: sql.NullString{String: e.VM.Name, Valid: e.VM.Name != ""},
@@ -128,10 +79,10 @@ func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) {
UserName: sql.NullString{String: e.UserName, Valid: e.UserName != ""},
}
h.Logger.Debug("database params", "params", params2)
h.Logger.Debug("database params", "params", params)
// Insert the new inventory record into the database
result, err := h.Database.Queries().CreateEvent(context.Background(), params2)
result, err := h.Database.Queries().CreateEvent(context.Background(), params)
if err != nil {
h.Logger.Error("unable to perform database insert", "error", err)
w.WriteHeader(http.StatusInternalServerError)

View 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))
}

View 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, &params)
//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
}

View 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
}

View 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
}
}

View File

@@ -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))
}

View 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")
}

View File

@@ -24,6 +24,7 @@ func NewLoggingMiddleware(logger *slog.Logger, handler http.Handler) *LoggingMid
func (l *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
l.handler.ServeHTTP(w, r)
l.logger.Debug(
"Request recieved",
slog.String("method", r.Method),

View File

@@ -1,6 +1,8 @@
package models
import "time"
import (
"encoding/json"
)
type CloudEventReceived struct {
CloudEvent struct {
@@ -8,7 +10,7 @@ type CloudEventReceived struct {
Specversion string `json:"specversion"`
Source string `json:"source"`
Type string `json:"type"`
Time string `json:"time"`
Time string `json:"time"` // Modified from time.Time
Data struct {
ChainID int `json:"ChainId"`
ChangeTag string `json:"ChangeTag"`
@@ -19,7 +21,7 @@ type CloudEventReceived struct {
} `json:"ComputeResource"`
Name string `json:"Name"`
} `json:"ComputeResource"`
CreatedTime time.Time `json:"CreatedTime"`
CreatedTime string `json:"CreatedTime"` // Modified from time.Time
Datacenter struct {
Datacenter struct {
Type string `json:"Type"`
@@ -37,24 +39,220 @@ type CloudEventReceived struct {
} `json:"Host"`
Name string `json:"Name"`
} `json:"Host"`
Key int `json:"Key"`
Net interface{} `json:"Net"`
SrcTemplate struct {
Name string `json:"Name"`
VM struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Vm"`
} `json:"SrcTemplate"`
Template bool `json:"Template"`
UserName string `json:"UserName"`
VM struct {
Key int `json:"Key"`
Net interface{} `json:"Net"`
NewParent *CloudEventResourcePool `json:"NewParent"`
OldParent *CloudEventResourcePool `json:"OldParent"`
SrcTemplate *CloudEventVm `json:"SrcTemplate"`
Template bool `json:"Template"`
UserName string `json:"UserName"`
VM struct {
Name string `json:"Name"`
VM struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Vm"`
} `json:"Vm"`
ConfigSpec *json.RawMessage `json:"configSpec"`
ConfigChanges *ConfigChangesReceived `json:"configChanges"` // Modified to separate struct
} `json:"data"`
} `json:"cloudEvent"`
}
type CloudEventResourcePool struct {
Name string `json:"Name"`
ResourcePool struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"ResourcePool"`
}
type CloudEventVm struct {
Name string `json:"Name"`
VM struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Vm"`
}
type ImportReceived struct {
Name string `json:"Name"`
Vcenter string `json:"Vcenter"`
VmId string `json:"VmId"`
InitialRam int `json:"InitialRam"`
PowerState int `json:"PowerState"`
CreationTime int `json:"CreationTime"`
InitialVcpus int `json:"InitialVcpus"`
ProvisionedDisk float64 `json:"ProvisionedDisk"`
Folder string `json:"Folder"`
ResourcePool string `json:"ResourcePool"`
Datacenter string `json:"Datacenter"`
Cluster string `json:"Cluster"`
}
type ConfigChangesReceived struct {
Modified string `json:"modified"`
}
// This probably needs more fields added so not in use yet
type ConfigSpec struct {
AlternateGuestName string `json:"AlternateGuestName"`
Annotation string `json:"Annotation"`
BootOptions any `json:"BootOptions"`
ChangeTrackingEnabled any `json:"ChangeTrackingEnabled"`
ChangeVersion string `json:"ChangeVersion"`
ConsolePreferences any `json:"ConsolePreferences"`
CPUAffinity any `json:"CpuAffinity"`
CPUAllocation any `json:"CpuAllocation"`
CPUFeatureMask any `json:"CpuFeatureMask"`
CPUHotAddEnabled any `json:"CpuHotAddEnabled"`
CPUHotRemoveEnabled any `json:"CpuHotRemoveEnabled"`
CreateDate string `json:"CreateDate"` // Modified from time.Time
Crypto any `json:"Crypto"`
DeviceChange []struct {
Backing any `json:"Backing"`
Device struct {
Backing *BackingSpec `json:"Backing,omitempty"`
CapacityInBytes int `json:"CapacityInBytes"`
CapacityInKB int `json:"CapacityInKB"`
Connectable struct {
AllowGuestControl bool `json:"AllowGuestControl"`
Connected bool `json:"Connected"`
MigrateConnect string `json:"MigrateConnect"`
StartConnected bool `json:"StartConnected"`
Status string `json:"Status"`
} `json:"Connectable"`
ControllerKey int `json:"ControllerKey"`
DeviceInfo struct {
Label string `json:"Label"`
Summary string `json:"Summary"`
} `json:"DeviceInfo"`
ExternalID string `json:"ExternalId"`
MacAddress string `json:"MacAddress"`
ResourceAllocation struct {
Limit int `json:"Limit"`
Reservation int `json:"Reservation"`
Share struct {
Level string `json:"Level"`
Shares int `json:"Shares"`
} `json:"Share"`
} `json:"ResourceAllocation"`
SlotInfo any `json:"SlotInfo"`
UnitNumber int `json:"UnitNumber"`
UptCompatibilityEnabled bool `json:"UptCompatibilityEnabled"`
WakeOnLanEnabled bool `json:"WakeOnLanEnabled"`
DiskObjectID string `json:"DiskObjectId"`
Iofilter any `json:"Iofilter"`
Key int `json:"Key"`
NativeUnmanagedLinkedClone any `json:"NativeUnmanagedLinkedClone"`
Shares any `json:"Shares"`
StorageIOAllocation struct {
Limit int `json:"Limit"`
Reservation any `json:"Reservation"`
Shares struct {
Level string `json:"Level"`
Shares int `json:"Shares"`
} `json:"Shares"`
} `json:"StorageIOAllocation"`
VDiskID any `json:"VDiskId"`
VFlashCacheConfigInfo any `json:"VFlashCacheConfigInfo"`
} `json:"Device,omitempty"`
FileOperation string `json:"FileOperation"`
Operation string `json:"Operation"`
Profile []struct {
ProfileData struct {
ExtensionKey string `json:"ExtensionKey"`
ObjectData string `json:"ObjectData"` // Modified from time.Time
} `json:"ProfileData"`
ProfileID string `json:"ProfileId"`
ProfileParams any `json:"ProfileParams"`
ReplicationSpec any `json:"ReplicationSpec"`
} `json:"Profile"`
} `json:"DeviceChange"`
ExtraConfig any `json:"ExtraConfig"`
Files struct {
FtMetadataDirectory string `json:"FtMetadataDirectory"`
LogDirectory string `json:"LogDirectory"`
SnapshotDirectory string `json:"SnapshotDirectory"`
SuspendDirectory string `json:"SuspendDirectory"`
VMPathName string `json:"VmPathName"`
} `json:"Files"`
Firmware string `json:"Firmware"`
Flags any `json:"Flags"`
FtInfo any `json:"FtInfo"`
GuestAutoLockEnabled any `json:"GuestAutoLockEnabled"`
GuestID string `json:"GuestId"`
GuestMonitoringModeInfo any `json:"GuestMonitoringModeInfo"`
InstanceUUID string `json:"InstanceUuid"`
LatencySensitivity any `json:"LatencySensitivity"`
LocationID string `json:"LocationId"`
ManagedBy any `json:"ManagedBy"`
MaxMksConnections int `json:"MaxMksConnections"`
MemoryAffinity any `json:"MemoryAffinity"`
MemoryAllocation any `json:"MemoryAllocation"`
MemoryHotAddEnabled any `json:"MemoryHotAddEnabled"`
MemoryMB int `json:"MemoryMB"`
MemoryReservationLockedToMax any `json:"MemoryReservationLockedToMax"`
MessageBusTunnelEnabled any `json:"MessageBusTunnelEnabled"`
MigrateEncryption string `json:"MigrateEncryption"`
Name string `json:"Name"`
NestedHVEnabled any `json:"NestedHVEnabled"`
NetworkShaper any `json:"NetworkShaper"`
NpivDesiredNodeWwns int `json:"NpivDesiredNodeWwns"`
NpivDesiredPortWwns int `json:"NpivDesiredPortWwns"`
NpivNodeWorldWideName any `json:"NpivNodeWorldWideName"`
NpivOnNonRdmDisks any `json:"NpivOnNonRdmDisks"`
NpivPortWorldWideName any `json:"NpivPortWorldWideName"`
NpivTemporaryDisabled any `json:"NpivTemporaryDisabled"`
NpivWorldWideNameOp string `json:"NpivWorldWideNameOp"`
NpivWorldWideNameType string `json:"NpivWorldWideNameType"`
NumCPUs int `json:"NumCPUs"`
NumCoresPerSocket int `json:"NumCoresPerSocket"`
PowerOpInfo any `json:"PowerOpInfo"`
RepConfig any `json:"RepConfig"`
ScheduledHardwareUpgradeInfo any `json:"ScheduledHardwareUpgradeInfo"`
SevEnabled any `json:"SevEnabled"`
SgxInfo any `json:"SgxInfo"`
SwapPlacement string `json:"SwapPlacement"`
Tools any `json:"Tools"`
UUID string `json:"Uuid"`
VAppConfig any `json:"VAppConfig"`
VAppConfigRemoved any `json:"VAppConfigRemoved"`
VAssertsEnabled any `json:"VAssertsEnabled"`
VPMCEnabled any `json:"VPMCEnabled"`
VcpuConfig any `json:"VcpuConfig"`
Version string `json:"Version"`
VirtualICH7MPresent any `json:"VirtualICH7MPresent"`
VirtualSMCPresent any `json:"VirtualSMCPresent"`
VMProfile any `json:"VmProfile"`
}
type BackingSpec struct {
Port struct {
ConnectionCookie int `json:"ConnectionCookie"`
PortKey string `json:"PortKey"`
PortgroupKey string `json:"PortgroupKey"`
SwitchUUID string `json:"SwitchUuid"`
} `json:"Port"`
BackingObjectID string `json:"BackingObjectId"`
ChangeID string `json:"ChangeId"`
ContentID string `json:"ContentId"`
Datastore struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Datastore"`
DeltaDiskFormat string `json:"DeltaDiskFormat"`
DeltaDiskFormatVariant string `json:"DeltaDiskFormatVariant"`
DeltaGrainSize int `json:"DeltaGrainSize"`
DigestEnabled any `json:"DigestEnabled"`
DiskMode string `json:"DiskMode"`
EagerlyScrub bool `json:"EagerlyScrub"`
FileName string `json:"FileName"`
KeyID any `json:"KeyId"`
Parent any `json:"Parent"`
Sharing string `json:"Sharing"`
Split any `json:"Split"`
ThinProvisioned bool `json:"ThinProvisioned"`
UUID string `json:"Uuid"`
WriteThrough any `json:"WriteThrough"`
}

View File

@@ -3,27 +3,59 @@ package router
import (
"log/slog"
"net/http"
"net/http/pprof"
"vctp/db"
"vctp/dist"
"vctp/internal/secrets"
"vctp/internal/settings"
"vctp/internal/vcenter"
"vctp/server/handler"
"vctp/server/middleware"
)
func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver string, goVersion string) http.Handler {
func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver string, goVersion string, creds *vcenter.VcenterLogin, secret *secrets.Secrets, settings *settings.Settings) http.Handler {
h := &handler.Handler{
Logger: logger,
Database: database,
BuildTime: buildTime,
SHA1Ver: sha1ver,
GoVersion: goVersion,
VcCreds: creds,
Secret: secret,
Settings: settings,
}
mux := http.NewServeMux()
mux.Handle("/assets/", middleware.CacheMiddleware(http.FileServer(http.FS(dist.AssetsDir))))
mux.HandleFunc("/", h.Home)
mux.HandleFunc("/api/event/vm/create", h.VmCreate)
mux.HandleFunc("/api/event/vm/update", h.VmUpdate)
mux.HandleFunc("/api/event/vm/create", h.VmCreateEvent)
mux.HandleFunc("/api/event/vm/modify", h.VmModifyEvent)
mux.HandleFunc("/api/event/vm/move", h.VmMoveEvent)
mux.HandleFunc("/api/event/vm/delete", h.VmDeleteEvent)
mux.HandleFunc("/api/import/vm", h.VmImport)
// Use this when we need to manually remove a VM from the database to clean up
mux.HandleFunc("/api/inventory/vm/delete", h.VmCleanup)
// add missing data to VMs
//mux.HandleFunc("/api/inventory/vm/update", h.VmUpdateDetails)
// temporary endpoint
mux.HandleFunc("/api/cleanup/updates", h.UpdateCleanup)
//mux.HandleFunc("/api/cleanup/vcenter", h.VcCleanup)
mux.HandleFunc("/api/report/inventory", h.InventoryReportDownload)
mux.HandleFunc("/api/report/updates", h.UpdateReportDownload)
// endpoint for encrypting vcenter credential
mux.HandleFunc("/api/encrypt", h.EncryptData)
// Register pprof handlers
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
return middleware.NewLoggingMiddleware(logger, mux)
}

View File

@@ -21,6 +21,7 @@ type Server struct {
disableTls bool
tlsCertFilename string
tlsKeyFilename string
encryptionKey string
}
// New creates a new server with the given logger, address and options.
@@ -41,22 +42,28 @@ func New(logger *slog.Logger, cron gocron.Scheduler, cancel context.CancelFunc,
}
srv := &http.Server{
Addr: addr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
Addr: addr,
//WriteTimeout: 120 * time.Second,
WriteTimeout: 0,
ReadTimeout: 30 * time.Second,
TLSConfig: tlsConfig,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
for _, opt := range opts {
opt(&Server{srv: srv})
}
return &Server{
// Set the initial server values
server := &Server{
srv: srv,
logger: logger,
cron: cron,
cancel: cancel,
}
// Apply any options
for _, opt := range opts {
opt(server)
}
return server
}
// Option represents a server option.
@@ -83,19 +90,33 @@ func WithRouter(handler http.Handler) Option {
}
}
// DisableTls sets the disable tls value
func (s *Server) DisableTls(disableTls bool) {
s.disableTls = disableTls
// SetKey sets the encryption key we use when generating secrets
func SetKey(key string) Option {
return func(s *Server) {
s.encryptionKey = key
}
}
// SetTls sets the disable tls value
func SetTls(disableTls bool) Option {
return func(s *Server) {
s.disableTls = disableTls
}
}
// SetCertificate sets the path to the certificate used for TLS, in PEM format
func (s *Server) SetCertificate(tlsCertFilename string) {
s.tlsCertFilename = tlsCertFilename
func SetCertificate(tlsCertFilename string) Option {
return func(s *Server) {
//fmt.Printf("Setting tlsCertFilename to '%s'\n", tlsCertFilename)
s.tlsCertFilename = tlsCertFilename
}
}
// SetPrivateKey sets the path to the private key used for TLS, in PEM format
func (s *Server) SetPrivateKey(tlsKeyFilename string) {
s.tlsKeyFilename = tlsKeyFilename
func SetPrivateKey(tlsKeyFilename string) Option {
return func(s *Server) {
s.tlsKeyFilename = tlsKeyFilename
}
}
// StartAndWait starts the server and waits for a signal to shut down.
@@ -111,12 +132,14 @@ func (s *Server) Start() {
if s.disableTls {
s.logger.Info("starting server", "port", s.srv.Addr)
if err := s.srv.ListenAndServe(); err != nil {
s.logger.Warn("failed to start server", "error", err)
s.logger.Error("failed to start server", "error", err)
os.Exit(1)
}
} else {
s.logger.Info("starting TLS server", "port", s.srv.Addr, "cert", s.tlsCertFilename, "key", s.tlsKeyFilename)
if err := s.srv.ListenAndServeTLS(s.tlsCertFilename, s.tlsKeyFilename); err != nil && err != http.ErrServerClosed {
s.logger.Warn("failed to start server", "error", err)
s.logger.Error("failed to start server", "error", err)
os.Exit(1)
}
}
}()
@@ -152,5 +175,5 @@ func (s *Server) GracefulShutdown() {
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
s.logger.Info("shutting down")
os.Exit(0)
//os.Exit(0)
}

8
settings.yaml Normal file
View 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
View File

@@ -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": ""
}