more file structure

This commit is contained in:
2024-09-11 21:24:34 +10:00
parent a5196bb321
commit 88c9cb3eef
17 changed files with 649 additions and 40 deletions

6
.gitignore vendored
View File

@@ -1 +1,5 @@
data.sqlite
data.sqlite
data.sqlite-journal
.env
vctp
vctp.log

View File

@@ -0,0 +1,43 @@
package common
import (
"fmt"
"net"
"net/http"
)
// Display some information about this API for the default page
// TODO - static fileserver with docs
func HomeLink(w http.ResponseWriter, r *http.Request) {
//w.WriteHeader(http.StatusNotImplemented)
fmt.Fprintf(w, "VM Chargeback Tracking Program. API interface only. See Nathan Coad (nathan.coad@dell.com) for further details. ")
}
// Display error message for invalid requests
func HandleNotFound(w http.ResponseWriter, r *http.Request) {
ip, err := IPFromRequest(r)
if err != nil {
fmt.Println("Error", err)
http.Error(w, "VM Chargeback Tracking Program.. Invalid Path Specified.", http.StatusNotFound)
return
}
fmt.Printf("Request from IP %s\n", ip.String())
http.Error(w, "VM Chargeback Tracking Program. Invalid Path Specified.", http.StatusNotFound)
// TODO - investigate rate limiting for invalid requests
}
// IPFromRequest extracts the user IP address from req, if present.
// @see https://blog.golang.org/context/userip/userip.go
func IPFromRequest(req *http.Request) (net.IP, error) {
ip, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
}
userIP := net.ParseIP(ip)
if userIP == nil {
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
}
return userIP, nil
}

View File

@@ -0,0 +1,32 @@
package vm
import (
"fmt"
"io"
"log/slog"
"net/http"
"github.com/gorilla/mux"
)
// TODO godoc
func VmCreateHandler(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
}
slog.Debug("received create request", "body", string(reqBody))
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Create Request (%d): %v\n", len(reqBody), string(reqBody))
}
// TODO godoc
func VmRemoveHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Remove Request: %v\n", vars)
}

58
api/resource/vm/model.go Normal file
View File

@@ -0,0 +1,58 @@
package vm
import "time"
type CloudEventReceived struct {
ID string `json:"id"`
Specversion string `json:"specversion"`
Source string `json:"source"`
Type string `json:"type"`
Time time.Time `json:"time"`
Data struct {
ChainID int `json:"ChainId"`
ChangeTag string `json:"ChangeTag"`
ComputeResource struct {
ComputeResource struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"ComputeResource"`
Name string `json:"Name"`
} `json:"ComputeResource"`
CreatedTime time.Time `json:"CreatedTime"`
Datacenter struct {
Datacenter struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Datacenter"`
Name string `json:"Name"`
} `json:"Datacenter"`
Ds interface{} `json:"Ds"`
Dvs interface{} `json:"Dvs"`
FullFormattedMessage string `json:"FullFormattedMessage"`
Host struct {
Host struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Host"`
Name string `json:"Name"`
} `json:"Host"`
Key int `json:"Key"`
Net interface{} `json:"Net"`
SrcTemplate struct {
Name string `json:"Name"`
VM struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Vm"`
} `json:"SrcTemplate"`
Template bool `json:"Template"`
UserName string `json:"UserName"`
VM struct {
Name string `json:"Name"`
VM struct {
Type string `json:"Type"`
Value string `json:"Value"`
} `json:"Vm"`
} `json:"Vm"`
} `json:"data"`
}

25
api/router/router.go Normal file
View File

@@ -0,0 +1,25 @@
package router
import (
"net/http"
"vm-ctp/api/resource/common"
"vm-ctp/api/resource/vm"
"github.com/gorilla/mux"
)
func New() *mux.Router {
r := mux.NewRouter()
// If nothing more specific is requested then just display some version information
r.HandleFunc("/", common.HomeLink)
s := r.PathPrefix("/api").Subrouter()
s.HandleFunc("/event/vm/create", vm.VmCreateHandler).Methods("POST") // receive VM creation event from Direktiv
s.HandleFunc("/event/vm/remove", vm.VmRemoveHandler).Methods("POST") // receive VM creation event from Direktiv
// Not found handler
r.NotFoundHandler = http.HandlerFunc(common.HandleNotFound)
return r
}

30
cert.pem Normal file
View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFIjCCAwqgAwIBAgIRALRk49JcstjX/i+y8wHc4KAwDQYJKoZIhvcNAQELBQAw
DzENMAsGA1UEChMERFRNUzAeFw0yNDA5MTExMDU0NDBaFw0yNTA5MTExMDU0NDBa
MA8xDTALBgNVBAoTBERUTVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQDVVlFCBCo5uJ4+D33Y9iJjz5PbW76ubMSWf+ndBTkUF9Psw+eyzkR1b7EVT2/c
eJfAK2rE6vTAajf9JItRMLCQXPVRf8ecmg/NcVhwuNSKG86TDqGfbNBmBnSZQhEu
JSfMoZvUiYtKd8JY9o8Y7n9BaU3cM0lg57+2TvLsF1WEY4DKr2P46AmhoMnqgC2n
1hokgNmPIuxfWk1N+zWkw0Kec2KiroB4hKiwSIcidR6YaR5/8Iiuorg2O4rhNx1D
lFGHUAIPTpCrpQjoMD3rHcnpGjK6NksO8gFOgjjd9XKpznGk2+bhPxTA9mwoL+qx
E/MdTAjKn+fJB2FYduT8ZlkFSu98WGjZr5/UT72fDemNVMNGfzWVEGgWQCRSHvvI
KzPb8A8GLaSj0CH/jww1KyXFmu8adU7Spg/ZHU9rStgdrGfGmtx6MXLE1a87mEQl
M/suEP7/ikK570FSJh1kop1MdDNPaMxtsRXfGws1JVWMhPzlmReC9jkAbfW5axfd
Nh4jYJm18rqlQR7JDPjusD4zdI+mesoiI45vaG0/b3F63pKYLuREuE0RIpimQlKV
A0+X2RXk8+h9Xpc3Z1pVoHHcoQ3Jw+JM0KScFncaBxczqSo/EQzw4LE9XYyz7Rks
qpU3WzvcVimEpj6flpqobGI63kf1rSkeCaIDLdgUlUJTxQIDAQABo3kwdzAOBgNV
HQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUAa1rnwMtehNK2bVPXKivXzNHIKYwIAYDVR0RBBkwF4IPbmF0
aG1icDE0LmxvY2FshwQKAAGhMA0GCSqGSIb3DQEBCwUAA4ICAQBenuJYnqTcELUP
scHkrVhH0zpmejRk55qBAxIpfR4XFklKr1lt3aHTs3dDbfS9t3ni/S2RVVhVtgWm
pXt3AteYCkA8CbuhSBirtVrDWi/NT6ClZHRgCcfoZzE5uh9HuQOQpGg2iRby/tnG
FU2o8KpYETcCEEuvhlusIg7FTueqCMCvbjsZ8j9PY5nZ0JZmAAqS2g4MR0zqFBfj
wamx3gXLupMdOdcAnqlVkc6UxkWyLvhcy1kpl+E+xzFoOsC0XCLSwFdM2gIB21yd
HcVMOnP38t+ijrErv5prsczuUhP0T3q/XBr5wzc4OBIQYbqjzhP67JaVZ6HU8UWj
GTuUuuZDwSTBoIJ0sDxmoMt9z2j7xFvSeMVKf13CRTR2s5QLPX/1AIyANg88xjQM
jJPGyiaBCYZZJbjH2SwwKuyqOaph8yz140QgxMNi6yymbruFBk9Gj9b6jDklFSM5
kHpmC4Gs8apL57odLnwl/Bf6A51z6BtZe6vzKl33ED5Zq0AlgIh3cICsXMwBTB+1
6xGFVpyqjKLe4p0laDrJh26O2wCjZdztU71pTIKU+UNhxCssXmy8Dw7qm82xgPxK
EENv6jK8jArVAyCJcmLxzhT2cvaN0NnqVRc6zDvY3K+KzG1Q7AcpJGeaM0BZym7l
9so3Qdv2RdPWtgdNX4EMDwxTUaeD6g==
-----END CERTIFICATE-----

View File

@@ -1 +1,158 @@
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"log/slog"
"net/http"
"os"
"os/signal"
"time"
"vm-ctp/api/router"
utils "vm-ctp/internal/utils"
"github.com/gorilla/handlers"
"github.com/joho/godotenv"
)
// For build numbers, from https://blog.kowalczyk.info/article/vEja/embedding-build-number-in-go-executable.html
var sha1ver string // sha1 revision used to build the program
var buildTime string // when the executable was built
var wait time.Duration
var bindDisableTls bool
func main() {
// Load data from environment file
envFilename := utils.GetFilePath(".env")
err := godotenv.Load(envFilename)
if err != nil {
panic("Error loading .env file")
}
// Open connection to logfile
// From https://ispycode.com/GO/Logging/Logging-to-multiple-destinations
logFile := os.Getenv("LOG_FILE")
if logFile == "" {
logFile = "./vctp.log"
}
logfileWriter, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Printf("Unable to write logfile '%s' : '%s'\n", logFile, err)
os.Exit(1)
}
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})
slog.SetDefault(slog.New(h))
//log.SetOutput(logfileWriter)
//log.Printf("vCTP starting execution. Built on %s from sha1 %s. Runtime %s\n", buildTime, sha1ver, runtime.Version())
r := router.New()
// Log everything to stdout in Apache Common Log Format
var loggedRouter http.Handler
loggedRouter = handlers.LoggingHandler(logfileWriter, r)
// Set some options for TLS
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
PreferServerCipherSuites: true,
InsecureSkipVerify: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
}
// Determine bind IP
bindIP := os.Getenv("BIND_IP")
if bindIP == "" {
bindIP = utils.GetOutboundIP().String()
}
// Determine bind port
bindPort := os.Getenv("BIND_PORT")
if bindPort == "" {
bindPort = "9443"
}
bindAddress := fmt.Sprint(bindIP, ":", bindPort)
slog.Info("Will listen on address", "ip", bindIP, "port", bindPort)
// Determine bind disable TLS
bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS")
if bindDisableTlsEnv == "true" {
bindDisableTls = true
}
// Get file names for TLS cert/key
tlsCertFilename := os.Getenv("TLS_CERT_FILE")
if tlsCertFilename != "" {
tlsCertFilename = utils.GetFilePath(tlsCertFilename)
} else {
tlsCertFilename = "./cert.pem"
}
tlsKeyFilename := os.Getenv("TLS_KEY_FILE")
if tlsKeyFilename != "" {
tlsKeyFilename = utils.GetFilePath(tlsKeyFilename)
} else {
tlsKeyFilename = "./privkey.pem"
}
// 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)
utils.GenerateCerts(tlsCertFilename, tlsKeyFilename)
}
// Configure the http server
srv := &http.Server{
Addr: bindAddress,
// Good practice to set timeouts to avoid Slowloris attacks.
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: loggedRouter, // Pass our instance of gorilla/mux in.
TLSConfig: tlsConfig,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
slog.Info("Started listening on", "bindAddress", bindAddress)
// Run our server in a goroutine so that it doesn't block
// Also decide whether to start a TLS or plain http server
go func() {
if bindDisableTls {
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
} else {
if err := srv.ListenAndServeTLS(tlsCertFilename, tlsKeyFilename); err != nil {
log.Println(err)
}
}
}()
c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
// Block until we receive our signal.
<-c
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
slog.Info("shutting down")
os.Exit(0)
}

Binary file not shown.

13
go.mod
View File

@@ -1,3 +1,12 @@
module vm-cbp
module vm-ctp
go 1.21.6
go 1.23.1
require (
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/joho/godotenv v1.5.1
)
require github.com/felixge/httpsnoop v1.0.4 // indirect

10
go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=

View File

@@ -0,0 +1,139 @@
package utils
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"net"
"os"
"path/filepath"
"time"
)
func GenerateCerts(tlsCert string, tlsKey string) {
// @see https://shaneutt.com/blog/golang-ca-and-signed-cert-go/
// @see https://golang.org/src/crypto/tls/generate_cert.go
validFrom := ""
validFor := 365 * 24 * time.Hour
isCA := true
// Get the hostname
hostname, err := os.Hostname()
if err != nil {
panic(err)
}
// Check that the directory exists
relativePath := filepath.Dir(tlsCert)
log.Printf("GenerateCerts relative path for file creation is '%s'\n", relativePath)
_, err = os.Stat(relativePath)
if os.IsNotExist(err) {
log.Printf("Certificate path does not exist, creating %s before generating certificate\n", relativePath)
os.MkdirAll(relativePath, os.ModePerm)
}
// Generate a private key
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
if err != nil {
log.Fatalf("Failed to generate private key: %v", err)
}
var notBefore time.Time
if len(validFrom) == 0 {
notBefore = time.Now()
} else {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom)
if err != nil {
log.Fatalf("Failed to parse creation date: %v", err)
}
}
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("Failed to generate serial number: %v", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"DTMS"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
template.DNSNames = append(template.DNSNames, hostname)
// Add in all the non-local IPs
ifaces, err := net.Interfaces()
if err != nil {
log.Printf("Error enumerating interfaces: %v\n", err)
}
for _, i := range ifaces {
addrs, err := i.Addrs()
if err != nil {
log.Printf("Oops: %v\n", err)
}
for _, address := range addrs {
// check the address type and if it is not a loopback then add it to the list
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
template.IPAddresses = append(template.IPAddresses, ipnet.IP)
}
}
}
}
if isCA {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
log.Fatalf("Failed to create certificate: %v", err)
}
certOut, err := os.Create(tlsCert)
if err != nil {
log.Fatalf("Failed to open %s for writing: %v", tlsCert, err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
log.Fatalf("Failed to write data to %s: %v", tlsCert, err)
}
if err := certOut.Close(); err != nil {
log.Fatalf("Error closing %s: %v", tlsCert, err)
}
log.Printf("wrote %s\n", tlsCert)
keyOut, err := os.OpenFile(tlsKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Failed to open %s for writing: %v", tlsKey, err)
return
}
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
log.Fatalf("Unable to marshal private key: %v", err)
}
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
log.Fatalf("Failed to write data to %s: %v", tlsKey, err)
}
if err := keyOut.Close(); err != nil {
log.Fatalf("Error closing %s: %v", tlsKey, err)
}
log.Printf("wrote %s\n", tlsKey)
}

55
internal/utils/utils.go Normal file
View File

@@ -0,0 +1,55 @@
package utils
import (
"log"
"log/slog"
"net"
"os"
"path/filepath"
)
const rsaBits = 4096
func GetFilePath(path string) string {
// Check for empty filename
if len(path) == 0 {
return ""
}
// check if filename exists
if _, err := os.Stat(path); os.IsNotExist((err)) {
slog.Info("File '%s' not found, searching in same directory as binary", path)
// if not, check that it exists in the same directory as the currently executing binary
ex, err2 := os.Executable()
if err2 != nil {
slog.Error("Error determining binary path : '%s'", err)
return ""
}
binaryPath := filepath.Dir(ex)
path = filepath.Join(binaryPath, path)
}
return path
}
// Get preferred outbound ip of this machine
// @see https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
func GetOutboundIP() net.IP {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP
}
// Check if a file exists from https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go
func FileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}

52
privkey.pem Normal file
View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
Lp15oQrT2tvdis2K06zLyv9LJUFsvA==
-----END PRIVATE KEY-----

View File

@@ -14,20 +14,13 @@ type Vm struct {
Vcenter sql.NullString
CreationTime sql.NullString
DeletionTime sql.NullString
TinRpTime sql.NullFloat64
BronzeRpTime sql.NullFloat64
SilverRpTime sql.NullFloat64
GoldRpTime sql.NullFloat64
ResourcePool sql.NullString
VmType sql.NullString
PoweredOnPct sql.NullInt64
Datacenter sql.NullString
Cluster sql.NullString
Folder sql.NullString
ProvisionedDisk sql.NullFloat64
InitialVcpus sql.NullInt64
AvgVcpus sql.NullFloat64
InitialRam sql.NullInt64
AvgRam sql.NullFloat64
SrmPlaceholder sql.NullInt64
}

View File

@@ -12,22 +12,45 @@ import (
const create = `-- name: Create :execresult
insert into "vm" (
"Name", "Vcenter"
"Name", "Vcenter", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
)
values(?, ?)
values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
type CreateParams struct {
Name sql.NullString
Vcenter sql.NullString
Name sql.NullString
Vcenter sql.NullString
CreationTime sql.NullString
ResourcePool sql.NullString
VmType sql.NullString
Datacenter sql.NullString
Cluster sql.NullString
Folder sql.NullString
ProvisionedDisk sql.NullFloat64
InitialVcpus sql.NullInt64
InitialRam sql.NullInt64
SrmPlaceholder sql.NullInt64
}
func (q *Queries) Create(ctx context.Context, arg CreateParams) (sql.Result, error) {
return q.db.ExecContext(ctx, create, arg.Name, arg.Vcenter)
return q.db.ExecContext(ctx, create,
arg.Name,
arg.Vcenter,
arg.CreationTime,
arg.ResourcePool,
arg.VmType,
arg.Datacenter,
arg.Cluster,
arg.Folder,
arg.ProvisionedDisk,
arg.InitialVcpus,
arg.InitialRam,
arg.SrmPlaceholder,
)
}
const get = `-- name: Get :one
select Id, Name, Vcenter, CreationTime, DeletionTime, TinRpTime, BronzeRpTime, SilverRpTime, GoldRpTime, ResourcePool, VmType, PoweredOnPct, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, AvgVcpus, InitialRam, AvgRam, SrmPlaceholder from "vm" where "Name" = ?
select Id, Name, Vcenter, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder from "vm" where "Name" = ?
`
func (q *Queries) Get(ctx context.Context, name sql.NullString) (Vm, error) {
@@ -39,28 +62,21 @@ func (q *Queries) Get(ctx context.Context, name sql.NullString) (Vm, error) {
&i.Vcenter,
&i.CreationTime,
&i.DeletionTime,
&i.TinRpTime,
&i.BronzeRpTime,
&i.SilverRpTime,
&i.GoldRpTime,
&i.ResourcePool,
&i.VmType,
&i.PoweredOnPct,
&i.Datacenter,
&i.Cluster,
&i.Folder,
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.AvgVcpus,
&i.InitialRam,
&i.AvgRam,
&i.SrmPlaceholder,
)
return i, err
}
const list = `-- name: List :many
select Id, Name, Vcenter, CreationTime, DeletionTime, TinRpTime, BronzeRpTime, SilverRpTime, GoldRpTime, ResourcePool, VmType, PoweredOnPct, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, AvgVcpus, InitialRam, AvgRam, SrmPlaceholder from "vm" order by "Name"
select Id, Name, Vcenter, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder from "vm" order by "Name"
`
func (q *Queries) List(ctx context.Context) ([]Vm, error) {
@@ -78,21 +94,14 @@ func (q *Queries) List(ctx context.Context) ([]Vm, error) {
&i.Vcenter,
&i.CreationTime,
&i.DeletionTime,
&i.TinRpTime,
&i.BronzeRpTime,
&i.SilverRpTime,
&i.GoldRpTime,
&i.ResourcePool,
&i.VmType,
&i.PoweredOnPct,
&i.Datacenter,
&i.Cluster,
&i.Folder,
&i.ProvisionedDisk,
&i.InitialVcpus,
&i.AvgVcpus,
&i.InitialRam,
&i.AvgRam,
&i.SrmPlaceholder,
); err != nil {
return nil, err

View File

@@ -6,6 +6,6 @@ select * from "vm" order by "Name";
-- name: Create :execresult
insert into "vm" (
"Name", "Vcenter"
"Name", "Vcenter", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
)
values(?, ?);
values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);

View File

@@ -4,20 +4,13 @@ CREATE TABLE IF NOT EXISTS "vm" (
"Vcenter" TEXT,
"CreationTime" TEXT,
"DeletionTime" TEXT,
"TinRpTime" REAL,
"BronzeRpTime" REAL,
"SilverRpTime" REAL,
"GoldRpTime" REAL,
"ResourcePool" TEXT,
"VmType" TEXT,
"PoweredOnPct" INTEGER,
"Datacenter" TEXT,
"Cluster" TEXT,
"Folder" TEXT,
"ProvisionedDisk" REAL,
"InitialVcpus" INTEGER,
"AvgVcpus" REAL,
"InitialRam" INTEGER,
"AvgRam" REAL,
"SrmPlaceholder" INTEGER
);