mirror of
https://github.com/Xevion/vastly.git
synced 2025-12-06 01:16:50 -06:00
testing, pre-wails
This commit is contained in:
@@ -1,27 +1,37 @@
|
||||
package api
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Client handles API communication with the Vast.ai API
|
||||
type Client struct {
|
||||
apiKey string
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
// Error represents an API error response
|
||||
type APIError struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
Msg string `json:"msg"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
// NewClient creates a new Vast.ai API client
|
||||
func NewClient(apiKey string) *Client {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
sugar := logger.Sugar()
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
baseURL: "https://console.vast.ai/api/v0",
|
||||
httpClient: &http.Client{},
|
||||
logger: sugar,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +42,68 @@ func (c *Client) Apply(req *http.Request) {
|
||||
|
||||
// makeRequest creates and sends an HTTP request with the provided method, path and body
|
||||
func (c *Client) makeRequest(method, path string, body interface{}) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, c.baseURL+path, nil)
|
||||
c.logger.Debugw("making request",
|
||||
"method", method,
|
||||
"path", path,
|
||||
"body", body,
|
||||
)
|
||||
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewBuffer(jsonBody)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.baseURL+path, bodyReader)
|
||||
if err != nil {
|
||||
c.logger.Errorw("failed to create request",
|
||||
"error", err,
|
||||
"method", method,
|
||||
"path", path,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Apply(req)
|
||||
return c.httpClient.Do(req)
|
||||
res, err := c.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
c.logger.Errorw("failed to send request",
|
||||
"error", err,
|
||||
"method", method,
|
||||
"path", path,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// debug print response
|
||||
res_body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
var apiError APIError
|
||||
if err := json.NewDecoder(bytes.NewBuffer(res_body)).Decode(&apiError); err != nil {
|
||||
c.logger.Errorw("failed to decode error response",
|
||||
"error", err,
|
||||
)
|
||||
} else {
|
||||
c.logger.Errorw("api error",
|
||||
"error", apiError,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
c.logger.Debugw("response",
|
||||
"status", res.Status,
|
||||
"status_code", res.StatusCode,
|
||||
)
|
||||
}
|
||||
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(res_body))
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -5,22 +5,6 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ComparableInteger struct {
|
||||
eq *int `json:"eq,omitempty"`
|
||||
lt *int `json:"lt,omitempty"`
|
||||
le *int `json:"le,omitempty"`
|
||||
gt *int `json:"gt,omitempty"`
|
||||
ge *int `json:"ge,omitempty"`
|
||||
}
|
||||
|
||||
type ComparableFloat struct {
|
||||
eq *float64 `json:"eq,omitempty"`
|
||||
lt *float64 `json:"lt,omitempty"`
|
||||
le *float64 `json:"le,omitempty"`
|
||||
gt *float64 `json:"gt,omitempty"`
|
||||
ge *float64 `json:"ge,omitempty"`
|
||||
}
|
||||
|
||||
type AdvancedSearch struct {
|
||||
Verified *bool `json:"verified,omitempty"`
|
||||
ComputeCap *ComparableInteger `json:"compute_cap,omitempty"`
|
||||
@@ -69,75 +53,12 @@ func NewSearch() *AdvancedSearch {
|
||||
}
|
||||
}
|
||||
|
||||
type Offer struct {
|
||||
IsBid bool `json:"is_bid"`
|
||||
InetUpBilled *float64 `json:"inet_up_billed"`
|
||||
InetDownBilled *float64 `json:"inet_down_billed"`
|
||||
External bool `json:"external"`
|
||||
Webpage *string `json:"webpage"`
|
||||
Logo string `json:"logo"`
|
||||
Rentable bool `json:"rentable"`
|
||||
ComputeCap int `json:"compute_cap"`
|
||||
DriverVersion string `json:"driver_version"`
|
||||
CudaMaxGood int `json:"cuda_max_good"`
|
||||
MachineID int `json:"machine_id"`
|
||||
HostingType *string `json:"hosting_type"`
|
||||
PublicIPAddr string `json:"public_ipaddr"`
|
||||
Geolocation string `json:"geolocation"`
|
||||
FlopsPerDPHTotal float64 `json:"flops_per_dphtotal"`
|
||||
DLPerfPerDPHTotal float64 `json:"dlperf_per_dphtotal"`
|
||||
Reliability2 float64 `json:"reliability2"`
|
||||
HostRunTime int `json:"host_run_time"`
|
||||
HostID int `json:"host_id"`
|
||||
ID int `json:"id"`
|
||||
BundleID int `json:"bundle_id"`
|
||||
NumGPUs int `json:"num_gpus"`
|
||||
TotalFlops float64 `json:"total_flops"`
|
||||
MinBid float64 `json:"min_bid"`
|
||||
DPHBase float64 `json:"dph_base"`
|
||||
DPHTotal float64 `json:"dph_total"`
|
||||
GPUName string `json:"gpu_name"`
|
||||
GPURam int `json:"gpu_ram"`
|
||||
GPUDisplayActive bool `json:"gpu_display_active"`
|
||||
GPUMemBw float64 `json:"gpu_mem_bw"`
|
||||
BwNVLink int `json:"bw_nvlink"`
|
||||
DirectPortCount int `json:"direct_port_count"`
|
||||
GPULanes int `json:"gpu_lanes"`
|
||||
PCIeBw float64 `json:"pcie_bw"`
|
||||
PCIGen int `json:"pci_gen"`
|
||||
DLPerf float64 `json:"dlperf"`
|
||||
CPUName string `json:"cpu_name"`
|
||||
MoboName string `json:"mobo_name"`
|
||||
CPURam int `json:"cpu_ram"`
|
||||
CPUCores int `json:"cpu_cores"`
|
||||
CPUCoresEffective int `json:"cpu_cores_effective"`
|
||||
GPUFrac float64 `json:"gpu_frac"`
|
||||
HasAVX int `json:"has_avx"`
|
||||
DiskSpace float64 `json:"disk_space"`
|
||||
DiskName string `json:"disk_name"`
|
||||
DiskBw float64 `json:"disk_bw"`
|
||||
InetUp float64 `json:"inet_up"`
|
||||
InetDown float64 `json:"inet_down"`
|
||||
StartDate float64 `json:"start_date"`
|
||||
EndDate *float64 `json:"end_date"`
|
||||
Duration *float64 `json:"duration"`
|
||||
StorageCost float64 `json:"storage_cost"`
|
||||
InetUpCost float64 `json:"inet_up_cost"`
|
||||
InetDownCost float64 `json:"inet_down_cost"`
|
||||
StorageTotalCost float64 `json:"storage_total_cost"`
|
||||
Verification string `json:"verification"`
|
||||
Score float64 `json:"score"`
|
||||
Rented bool `json:"rented"`
|
||||
BundledResults int `json:"bundled_results"`
|
||||
PendingCount int `json:"pending_count"`
|
||||
}
|
||||
|
||||
type SearchResponse struct {
|
||||
Offers []Offer `json:"offers"`
|
||||
}
|
||||
|
||||
func (c *Client) Search(search *AdvancedSearch) (*SearchResponse, error) {
|
||||
resp, err := c.makeRequest(http.MethodPost, "/bundles/", nil)
|
||||
resp, err := c.makeRequest(http.MethodPost, "/bundles/", search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
25
api/types.go
25
api/types.go
@@ -1,5 +1,7 @@
|
||||
package api
|
||||
|
||||
import "fmt"
|
||||
|
||||
type PortMapping struct {
|
||||
HostIp string `json:"HostIp"`
|
||||
HostPort string `json:"HostPort"`
|
||||
@@ -42,7 +44,7 @@ type Instance struct {
|
||||
GPURam int `json:"gpu_ram"`
|
||||
GPUDisplayActive bool `json:"gpu_display_active"`
|
||||
GPUMemBW float64 `json:"gpu_mem_bw"`
|
||||
BWNVLink int `json:"bw_nvlink"`
|
||||
BWNVLink float64 `json:"bw_nvlink"`
|
||||
DirectPortCount int `json:"direct_port_count"`
|
||||
GPULanes int `json:"gpu_lanes"`
|
||||
PCIeBW float64 `json:"pcie_bw"`
|
||||
@@ -108,11 +110,12 @@ type Offer struct {
|
||||
Rentable bool `json:"rentable"`
|
||||
ComputeCap int `json:"compute_cap"`
|
||||
DriverVersion string `json:"driver_version"`
|
||||
CudaMaxGood int `json:"cuda_max_good"`
|
||||
CudaMaxGood float64 `json:"cuda_max_good"`
|
||||
MachineID int `json:"machine_id"`
|
||||
HostingType *string `json:"hosting_type"`
|
||||
HostingType *float64 `json:"hosting_type"`
|
||||
PublicIPAddr string `json:"public_ipaddr"`
|
||||
Geolocation string `json:"geolocation"`
|
||||
Geocode *int64 `json:"geolocode"`
|
||||
FlopsPerDPHTotal float64 `json:"flops_per_dphtotal"`
|
||||
DLPerfPerDPHTotal float64 `json:"dlperf_per_dphtotal"`
|
||||
Reliability2 float64 `json:"reliability2"`
|
||||
@@ -129,17 +132,17 @@ type Offer struct {
|
||||
GPURam int `json:"gpu_ram"`
|
||||
GPUDisplayActive bool `json:"gpu_display_active"`
|
||||
GPUMemBw float64 `json:"gpu_mem_bw"`
|
||||
BwNVLink int `json:"bw_nvlink"`
|
||||
BwNVLink float64 `json:"bw_nvlink"`
|
||||
DirectPortCount int `json:"direct_port_count"`
|
||||
GPULanes int `json:"gpu_lanes"`
|
||||
PCIeBw float64 `json:"pcie_bw"`
|
||||
PCIGen int `json:"pci_gen"`
|
||||
PCIGen float64 `json:"pci_gen"`
|
||||
DLPerf float64 `json:"dlperf"`
|
||||
CPUName string `json:"cpu_name"`
|
||||
MoboName string `json:"mobo_name"`
|
||||
CPURam int `json:"cpu_ram"`
|
||||
CPUCores int `json:"cpu_cores"`
|
||||
CPUCoresEffective int `json:"cpu_cores_effective"`
|
||||
CPUCores float64 `json:"cpu_cores"`
|
||||
CPUCoresEffective float64 `json:"cpu_cores_effective"`
|
||||
GPUFrac float64 `json:"gpu_frac"`
|
||||
HasAVX int `json:"has_avx"`
|
||||
DiskSpace float64 `json:"disk_space"`
|
||||
@@ -160,3 +163,11 @@ type Offer struct {
|
||||
BundledResults int `json:"bundled_results"`
|
||||
PendingCount int `json:"pending_count"`
|
||||
}
|
||||
|
||||
func (o *Offer) String() string {
|
||||
geocode := 0
|
||||
if o.Geocode != nil {
|
||||
geocode = int(*o.Geocode)
|
||||
}
|
||||
return fmt.Sprintf("[%s] %s %s %d", o.GPUName, o.PublicIPAddr, o.Geolocation, geocode)
|
||||
}
|
||||
|
||||
@@ -3,3 +3,19 @@ package api
|
||||
func Pointer[T any](d T) *T {
|
||||
return &d
|
||||
}
|
||||
|
||||
type Comparable[T comparable] struct {
|
||||
Eq *T `json:"eq,omitempty"`
|
||||
Lt *T `json:"lt,omitempty"`
|
||||
Le *T `json:"le,omitempty"`
|
||||
Gt *T `json:"gt,omitempty"`
|
||||
Ge *T `json:"ge,omitempty"`
|
||||
}
|
||||
|
||||
// Then you can use these type aliases for convenience
|
||||
type ComparableInteger = Comparable[int]
|
||||
type ComparableFloat = Comparable[float64]
|
||||
|
||||
func Ge[T comparable](d T) *Comparable[T] {
|
||||
return &Comparable[T]{Ge: Pointer(d)}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"xevion.dev/quickvast/api"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer logger.Sync()
|
||||
|
||||
sugar := logger.Sugar()
|
||||
// Load .env file
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
sugar.Fatal(err)
|
||||
}
|
||||
|
||||
// Get API key from environment
|
||||
apiKey := os.Getenv("VASTAI_API_KEY")
|
||||
if apiKey == "" {
|
||||
log.Fatal("VASTAI_API_KEY not found in environment")
|
||||
sugar.Fatal("VASTAI_API_KEY not found in environment")
|
||||
}
|
||||
|
||||
// Create client
|
||||
client := api.NewClient(apiKey)
|
||||
|
||||
// Create search
|
||||
search := api.NewSearch()
|
||||
// search.CPUCores = api.Ge(8)
|
||||
|
||||
// Perform search
|
||||
sugar.Infow("Searching", "search", search)
|
||||
resp, err := client.Search(search)
|
||||
if err != nil {
|
||||
sugar.Fatal(err)
|
||||
}
|
||||
|
||||
// Print offers
|
||||
sugar.Infof("Offers: %d", len(resp.Offers))
|
||||
for _, offer := range resp.Offers {
|
||||
sugar.Info(offer.String())
|
||||
}
|
||||
|
||||
sugar.Info("Done")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user