diff --git a/.gitignore b/.gitignore index 6d79957..386168d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -data.sqlite \ No newline at end of file +data.sqlite +data.sqlite-journal +.env +vctp +vctp.log \ No newline at end of file diff --git a/api/resource/common/handler.go b/api/resource/common/handler.go new file mode 100644 index 0000000..e732f25 --- /dev/null +++ b/api/resource/common/handler.go @@ -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 +} diff --git a/api/resource/vm/handler.go b/api/resource/vm/handler.go new file mode 100644 index 0000000..0e894f6 --- /dev/null +++ b/api/resource/vm/handler.go @@ -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) +} diff --git a/api/resource/vm/model.go b/api/resource/vm/model.go new file mode 100644 index 0000000..02642f9 --- /dev/null +++ b/api/resource/vm/model.go @@ -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"` +} diff --git a/api/router/router.go b/api/router/router.go new file mode 100644 index 0000000..a045075 --- /dev/null +++ b/api/router/router.go @@ -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 +} diff --git a/cert.pem b/cert.pem new file mode 100644 index 0000000..2152cf1 --- /dev/null +++ b/cert.pem @@ -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----- diff --git a/cmd/api/main.go b/cmd/api/main.go index 06ab7d0..e78ca70 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -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) +} diff --git a/data.sqlite-journal b/data.sqlite-journal deleted file mode 100644 index d090de2..0000000 Binary files a/data.sqlite-journal and /dev/null differ diff --git a/go.mod b/go.mod index 7b8bd80..e37c833 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8fa2a1a --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/utils/certOperations.go b/internal/utils/certOperations.go new file mode 100644 index 0000000..7455992 --- /dev/null +++ b/internal/utils/certOperations.go @@ -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) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..fd2e074 --- /dev/null +++ b/internal/utils/utils.go @@ -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() +} diff --git a/privkey.pem b/privkey.pem new file mode 100644 index 0000000..65c3310 --- /dev/null +++ b/privkey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +Lp15oQrT2tvdis2K06zLyv9LJUFsvA== +-----END PRIVATE KEY----- diff --git a/vm/db/models.go b/vm/db/models.go index b48da56..ef578fa 100644 --- a/vm/db/models.go +++ b/vm/db/models.go @@ -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 } diff --git a/vm/db/query.sql.go b/vm/db/query.sql.go index 5142213..29d96e3 100644 --- a/vm/db/query.sql.go +++ b/vm/db/query.sql.go @@ -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 diff --git a/vm/query.sql b/vm/query.sql index f6e8294..58073e3 100644 --- a/vm/query.sql +++ b/vm/query.sql @@ -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(?, ?); \ No newline at end of file +values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); \ No newline at end of file diff --git a/vm/schema.sql b/vm/schema.sql index 6f58278..d4eeddd 100644 --- a/vm/schema.sql +++ b/vm/schema.sql @@ -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 ); \ No newline at end of file