mirror of
https://github.com/Xevion/vastly.git
synced 2025-12-06 17:16:57 -06:00
testing, pre-wails
This commit is contained in:
@@ -1,27 +1,37 @@
|
|||||||
package api
|
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
|
// Client handles API communication with the Vast.ai API
|
||||||
type Client struct {
|
type Client struct {
|
||||||
apiKey string
|
apiKey string
|
||||||
baseURL string
|
baseURL string
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
|
logger *zap.SugaredLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error represents an API error response
|
// Error represents an API error response
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Error string `json:"error"`
|
|
||||||
Msg string `json:"msg"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new Vast.ai API client
|
// NewClient creates a new Vast.ai API client
|
||||||
func NewClient(apiKey string) *Client {
|
func NewClient(apiKey string) *Client {
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
sugar := logger.Sugar()
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
baseURL: "https://console.vast.ai/api/v0",
|
baseURL: "https://console.vast.ai/api/v0",
|
||||||
httpClient: &http.Client{},
|
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
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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"
|
"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 {
|
type AdvancedSearch struct {
|
||||||
Verified *bool `json:"verified,omitempty"`
|
Verified *bool `json:"verified,omitempty"`
|
||||||
ComputeCap *ComparableInteger `json:"compute_cap,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 {
|
type SearchResponse struct {
|
||||||
Offers []Offer `json:"offers"`
|
Offers []Offer `json:"offers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Search(search *AdvancedSearch) (*SearchResponse, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
25
api/types.go
25
api/types.go
@@ -1,5 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type PortMapping struct {
|
type PortMapping struct {
|
||||||
HostIp string `json:"HostIp"`
|
HostIp string `json:"HostIp"`
|
||||||
HostPort string `json:"HostPort"`
|
HostPort string `json:"HostPort"`
|
||||||
@@ -42,7 +44,7 @@ type Instance struct {
|
|||||||
GPURam int `json:"gpu_ram"`
|
GPURam int `json:"gpu_ram"`
|
||||||
GPUDisplayActive bool `json:"gpu_display_active"`
|
GPUDisplayActive bool `json:"gpu_display_active"`
|
||||||
GPUMemBW float64 `json:"gpu_mem_bw"`
|
GPUMemBW float64 `json:"gpu_mem_bw"`
|
||||||
BWNVLink int `json:"bw_nvlink"`
|
BWNVLink float64 `json:"bw_nvlink"`
|
||||||
DirectPortCount int `json:"direct_port_count"`
|
DirectPortCount int `json:"direct_port_count"`
|
||||||
GPULanes int `json:"gpu_lanes"`
|
GPULanes int `json:"gpu_lanes"`
|
||||||
PCIeBW float64 `json:"pcie_bw"`
|
PCIeBW float64 `json:"pcie_bw"`
|
||||||
@@ -108,11 +110,12 @@ type Offer struct {
|
|||||||
Rentable bool `json:"rentable"`
|
Rentable bool `json:"rentable"`
|
||||||
ComputeCap int `json:"compute_cap"`
|
ComputeCap int `json:"compute_cap"`
|
||||||
DriverVersion string `json:"driver_version"`
|
DriverVersion string `json:"driver_version"`
|
||||||
CudaMaxGood int `json:"cuda_max_good"`
|
CudaMaxGood float64 `json:"cuda_max_good"`
|
||||||
MachineID int `json:"machine_id"`
|
MachineID int `json:"machine_id"`
|
||||||
HostingType *string `json:"hosting_type"`
|
HostingType *float64 `json:"hosting_type"`
|
||||||
PublicIPAddr string `json:"public_ipaddr"`
|
PublicIPAddr string `json:"public_ipaddr"`
|
||||||
Geolocation string `json:"geolocation"`
|
Geolocation string `json:"geolocation"`
|
||||||
|
Geocode *int64 `json:"geolocode"`
|
||||||
FlopsPerDPHTotal float64 `json:"flops_per_dphtotal"`
|
FlopsPerDPHTotal float64 `json:"flops_per_dphtotal"`
|
||||||
DLPerfPerDPHTotal float64 `json:"dlperf_per_dphtotal"`
|
DLPerfPerDPHTotal float64 `json:"dlperf_per_dphtotal"`
|
||||||
Reliability2 float64 `json:"reliability2"`
|
Reliability2 float64 `json:"reliability2"`
|
||||||
@@ -129,17 +132,17 @@ type Offer struct {
|
|||||||
GPURam int `json:"gpu_ram"`
|
GPURam int `json:"gpu_ram"`
|
||||||
GPUDisplayActive bool `json:"gpu_display_active"`
|
GPUDisplayActive bool `json:"gpu_display_active"`
|
||||||
GPUMemBw float64 `json:"gpu_mem_bw"`
|
GPUMemBw float64 `json:"gpu_mem_bw"`
|
||||||
BwNVLink int `json:"bw_nvlink"`
|
BwNVLink float64 `json:"bw_nvlink"`
|
||||||
DirectPortCount int `json:"direct_port_count"`
|
DirectPortCount int `json:"direct_port_count"`
|
||||||
GPULanes int `json:"gpu_lanes"`
|
GPULanes int `json:"gpu_lanes"`
|
||||||
PCIeBw float64 `json:"pcie_bw"`
|
PCIeBw float64 `json:"pcie_bw"`
|
||||||
PCIGen int `json:"pci_gen"`
|
PCIGen float64 `json:"pci_gen"`
|
||||||
DLPerf float64 `json:"dlperf"`
|
DLPerf float64 `json:"dlperf"`
|
||||||
CPUName string `json:"cpu_name"`
|
CPUName string `json:"cpu_name"`
|
||||||
MoboName string `json:"mobo_name"`
|
MoboName string `json:"mobo_name"`
|
||||||
CPURam int `json:"cpu_ram"`
|
CPURam int `json:"cpu_ram"`
|
||||||
CPUCores int `json:"cpu_cores"`
|
CPUCores float64 `json:"cpu_cores"`
|
||||||
CPUCoresEffective int `json:"cpu_cores_effective"`
|
CPUCoresEffective float64 `json:"cpu_cores_effective"`
|
||||||
GPUFrac float64 `json:"gpu_frac"`
|
GPUFrac float64 `json:"gpu_frac"`
|
||||||
HasAVX int `json:"has_avx"`
|
HasAVX int `json:"has_avx"`
|
||||||
DiskSpace float64 `json:"disk_space"`
|
DiskSpace float64 `json:"disk_space"`
|
||||||
@@ -160,3 +163,11 @@ type Offer struct {
|
|||||||
BundledResults int `json:"bundled_results"`
|
BundledResults int `json:"bundled_results"`
|
||||||
PendingCount int `json:"pending_count"`
|
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 {
|
func Pointer[T any](d T) *T {
|
||||||
return &d
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
"xevion.dev/quickvast/api"
|
"xevion.dev/quickvast/api"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
sugar := logger.Sugar()
|
||||||
// Load .env file
|
// Load .env file
|
||||||
if err := godotenv.Load(); err != nil {
|
if err := godotenv.Load(); err != nil {
|
||||||
log.Fatal("Error loading .env file")
|
sugar.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get API key from environment
|
// Get API key from environment
|
||||||
apiKey := os.Getenv("VASTAI_API_KEY")
|
apiKey := os.Getenv("VASTAI_API_KEY")
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
log.Fatal("VASTAI_API_KEY not found in environment")
|
sugar.Fatal("VASTAI_API_KEY not found in environment")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
client := api.NewClient(apiKey)
|
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