repo init

This commit is contained in:
2024-12-13 16:37:20 -06:00
commit 4d98678275
12 changed files with 305 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

13
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"cSpell.words": ["quickvast", "vmem"],
"cSpell.ignorePaths": [
"package-lock.json",
"node_modules",
"vscode-extension",
".git/{info,lfs,logs,refs,objects}/**",
".git/{index,*refs,*HEAD}",
".vscode",
".vscode-insiders",
"/home/linuxbrew/**"
]
}

25
README.md Normal file
View File

@@ -0,0 +1,25 @@
# quickvast
CLI tools for managing Vast.ai instances.
What I want from this tool:
- [ ] Quickly choose machine types from a refined, customized list of options.
- This would not just be a filter, it would be a customized, weighted scoring system allowing me to quickly choose the best machine for my needs.
- It would provide warnings for machines about why they may not be the best choice, such as low RAM, low driver versions, etc.
- It would have a nice, colorful UI allowing me to quickly see the best options.
- At first this would be a single weighted config, one that could be hard-coded into the tool, but later I could add multiple configs via files perhaps
- [ ] Quickly create a new instance.
- Using the same feature above, quickly create an instance.
- Being able to upload scripts and profiles into the machine easily.
- Being able to choose and configure the 'template' of the machine.
- [ ] Quickly manage instances.
- Being able to quickly retrieve necessary connection information, open the browser for tooling, and copy authentication details.
- Being able to quickly SSH into the machine.
- [ ] Quickly destroy instances.
- Being able to destroy instances quickly.
- [ ] Long term monitoring for pricing
- My concern is that I might leave an instance running for too long and rack up a huge bill.
- I would like each instance to have a time limit for my usage, both a soft and hard limit.
- The soft limit will send me a message on Discord, and the hard limit will destroy the instance after another message.
-

42
api/client.go Normal file
View File

@@ -0,0 +1,42 @@
package api
import "net/http"
// Client handles API communication with the Vast.ai API
type Client struct {
apiKey string
baseURL string
httpClient *http.Client
}
// Error represents an API error response
type APIError struct {
Success bool `json:"success"`
Error string `json:"error"`
Msg string `json:"msg"`
}
// NewClient creates a new Vast.ai API client
func NewClient(apiKey string) *Client {
return &Client{
apiKey: apiKey,
baseURL: "https://console.vast.ai/api/v0",
httpClient: &http.Client{},
}
}
// Apply applies the Bearer token to the request
func (c *Client) Apply(req *http.Request) {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
// 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)
if err != nil {
return nil, err
}
c.Apply(req)
return c.httpClient.Do(req)
}

3
api/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module xevion.dev/quickvast/api
go 1.23.3

52
api/instances.go Normal file
View File

@@ -0,0 +1,52 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
)
// InstanceGetResponse represents the response from GET /instances
type InstanceGetResponse struct {
Instances []Instance `json:"instances"`
}
// GetInstances retrieves all instances for the authenticated user
func (c *Client) GetInstances() (*InstanceGetResponse, error) {
resp, err := c.makeRequest(http.MethodGet, "/instances", nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result InstanceGetResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
// DeleteInstance removes an instance by ID
func (c *Client) DeleteInstance(id int) error {
path := fmt.Sprintf("/instances/%d", id)
resp, err := c.makeRequest(http.MethodDelete, path, nil)
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// PutInstance updates an instance's status
func (c *Client) PutInstance(id int, status string) error {
path := fmt.Sprintf("/instances/%d", id)
data := map[string]string{"status": status}
resp, err := c.makeRequest(http.MethodPut, path, data)
if err != nil {
return err
}
resp.Body.Close()
return nil
}

99
api/types.go Normal file
View File

@@ -0,0 +1,99 @@
package api
type PortMapping struct {
HostIp string `json:"HostIp"`
HostPort string `json:"HostPort"`
}
type Ports struct {
TCP22 []PortMapping `json:"22/tcp"`
TCP8080 []PortMapping `json:"8080/tcp"`
UDP8080 []PortMapping `json:"8080/udp"`
}
type Instance 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 int64 `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 float64 `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 int64 `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"`
SSHIdx string `json:"ssh_idx"`
SSHHost string `json:"ssh_host"`
SSHPort int `json:"ssh_port"`
ActualStatus string `json:"actual_status"`
IntendedStatus string `json:"intended_status"`
CurState string `json:"cur_state"`
NextState string `json:"next_state"`
ImageUUID string `json:"image_uuid"`
ImageArgs []string `json:"image_args"`
ImageRuntype string `json:"image_runtype"`
ExtraEnv string `json:"extra_env"`
OnStart string `json:"onstart"`
Label *string `json:"label"`
JupyterToken string `json:"jupyter_token"`
StatusMsg string `json:"status_msg"`
GPUUtil float64 `json:"gpu_util"`
DiskUtil float64 `json:"disk_util"`
GPUTemp float64 `json:"gpu_temp"`
LocalIPAddrs string `json:"local_ipaddrs"`
DirectPortEnd int `json:"direct_port_end"`
DirectPortStart int `json:"direct_port_start"`
CPUUtil float64 `json:"cpu_util"`
MemUsage float64 `json:"mem_usage"`
MemLimit float64 `json:"mem_limit"`
VMemUsage float64 `json:"vmem_usage"`
MachineDirSSHPort int `json:"machine_dir_ssh_port"`
Ports Ports `json:"ports"`
}

43
cmd/quickvast/main.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"fmt"
"log"
"os"
"xevion.dev/quickvast/api"
"github.com/joho/godotenv"
)
func main() {
// Load .env file
if err := godotenv.Load(); err != nil {
log.Fatal("Error loading .env file")
}
// Get API key from environment
apiKey := os.Getenv("VASTAI_API_KEY")
if apiKey == "" {
log.Fatal("VASTAI_API_KEY not found in environment")
}
// Create client
client := api.NewClient(apiKey)
// Get instances
resp, err := client.GetInstances()
if err != nil {
log.Fatalf("Error getting instances: %v", err)
}
if len(resp.Instances) == 0 {
fmt.Println("No instances found")
return
}
// Print instances
for _, instance := range resp.Instances {
fmt.Printf("Instance %d: %+v\n", instance.ID, instance)
}
}

9
go.mod Normal file
View File

@@ -0,0 +1,9 @@
module quickvast
go 1.23.3
require (
github.com/joho/godotenv v1.5.1 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
)

6
go.sum Normal file
View File

@@ -0,0 +1,6 @@
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=

6
go.work Normal file
View File

@@ -0,0 +1,6 @@
go 1.23.3
use (
.
./api
)

6
go.work.sum Normal file
View File

@@ -0,0 +1,6 @@
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=