refactor: improve runpod client
- Refactor RunPod client to use environment variables for API key. - Update RunPod example script to reflect changes. - Remove unused gql_builder.v file. - Update README.md to reflect changes. - Improve error handling and logging. - Use json2 for JSON encoding/decoding. - Update dependencies. - Implemented more endpoints for managing pods. Co-authored-by: mahmmoud.hassanein <mahmmoud.hassanein@gmail.com>
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
module runpod
|
||||
|
||||
import json
|
||||
|
||||
pub struct EnvironmentVariableInput {
|
||||
pub mut:
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
// Represents the nested machine structure in the response
|
||||
pub struct Machine {
|
||||
pub:
|
||||
@@ -21,7 +29,7 @@ pub:
|
||||
@[params]
|
||||
pub struct PodFindAndDeployOnDemandRequest {
|
||||
pub mut:
|
||||
cloud_type CloudType @[json: 'cloudType']
|
||||
cloud_type string @[json: 'cloudType']
|
||||
gpu_count int @[json: 'gpuCount']
|
||||
volume_in_gb int @[json: 'volumeInGb']
|
||||
container_disk_in_gb int @[json: 'containerDiskInGb']
|
||||
@@ -36,6 +44,10 @@ pub mut:
|
||||
env []EnvironmentVariableInput @[json: 'env']
|
||||
}
|
||||
|
||||
pub fn (p PodFindAndDeployOnDemandRequest) json_str() string {
|
||||
return json.encode(p)
|
||||
}
|
||||
|
||||
// Create On-Demand Pod
|
||||
pub fn (mut rp RunPod) create_on_demand_pod(input PodFindAndDeployOnDemandRequest) !PodResult {
|
||||
return rp.create_on_demand_pod_request(input)!
|
||||
@@ -49,7 +61,7 @@ pub mut:
|
||||
start_jupyter bool @[json: 'startJupyter']
|
||||
start_ssh bool @[json: 'startSsh']
|
||||
bid_per_gpu f32 @[json: 'bidPerGpu']
|
||||
cloud_type CloudType @[json: 'cloudType']
|
||||
cloud_type string @[json: 'cloudType']
|
||||
container_disk_in_gb int @[json: 'containerDiskInGb']
|
||||
country_code string @[json: 'countryCode'; omitempty]
|
||||
docker_args string @[json: 'dockerArgs'; omitempty]
|
||||
@@ -76,37 +88,82 @@ pub mut:
|
||||
allowed_cuda_versions []string @[json: 'allowedCudaVersions']
|
||||
}
|
||||
|
||||
pub fn (p PodRentInterruptableInput) json_str() string {
|
||||
return json.encode(p)
|
||||
}
|
||||
|
||||
// Create Spot Pod
|
||||
pub fn (mut rp RunPod) create_spot_pod(input PodRentInterruptableInput) !PodResult {
|
||||
return rp.create_spot_pod_request(input)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PodResume {
|
||||
pub struct PodResumeInput {
|
||||
pub mut:
|
||||
pod_id string @[json: 'podId']
|
||||
gpu_count int @[json: 'gpuCount']
|
||||
pod_id string @[json: 'podId'; required]
|
||||
gpu_count int @[json: 'gpuCount']
|
||||
sync_machine bool @[json: 'syncMachine']
|
||||
compute_type string @[json: 'computeType'; omitempty]
|
||||
}
|
||||
|
||||
pub fn (p PodResumeInput) json_str() string {
|
||||
return json.encode(p)
|
||||
}
|
||||
|
||||
// Start On-Demand Pod
|
||||
pub fn (mut rp RunPod) start_on_demand_pod(input PodResume) !PodResult {
|
||||
pub fn (mut rp RunPod) start_on_demand_pod(input PodResumeInput) !PodResult {
|
||||
return rp.start_on_demand_pod_request(input)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PodBidResume {
|
||||
pub struct PodBidResumeInput {
|
||||
pub mut:
|
||||
pod_id string @[json: 'podId']
|
||||
pod_id string @[json: 'podId'; required]
|
||||
gpu_count int @[json: 'gpuCount']
|
||||
bid_per_gpu f32 @[json: 'bidPerGpu']
|
||||
}
|
||||
|
||||
pub fn (p PodBidResumeInput) json_str() string {
|
||||
return json.encode(p)
|
||||
}
|
||||
|
||||
// Start Spot Pod
|
||||
pub fn (mut rp RunPod) start_spot_pod(input PodBidResume) !PodResult {
|
||||
pub fn (mut rp RunPod) start_spot_pod(input PodBidResumeInput) !PodResult {
|
||||
return rp.start_spot_pod_request(input)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PodStopInput {
|
||||
pub:
|
||||
pod_id string @[json: 'podId']
|
||||
increment_version bool @[json: 'incrementVersion']
|
||||
}
|
||||
|
||||
pub fn (p PodStopInput) json_str() string {
|
||||
return json.encode(p)
|
||||
}
|
||||
|
||||
// Stop Pod
|
||||
pub fn (mut rp RunPod) stop_pod(input PodResume) !PodResult {
|
||||
pub fn (mut rp RunPod) stop_pod(input PodStopInput) !PodResult {
|
||||
return rp.stop_pod_request(input)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PodTerminateInput {
|
||||
pub:
|
||||
pod_id string @[json: 'podId']
|
||||
}
|
||||
|
||||
pub fn (mut rp RunPod) terminate_pod(input PodTerminateInput) ! {
|
||||
rp.terminate_pod_request(input)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PodFilter {
|
||||
pub:
|
||||
pod_id string @[json: 'podId'; required]
|
||||
}
|
||||
|
||||
pub fn (mut rp RunPod) get_pod(input PodFilter) !PodResult {
|
||||
return rp.get_pod_request(input)!
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
module runpod
|
||||
|
||||
enum OperationType {
|
||||
query
|
||||
mutation
|
||||
}
|
||||
|
||||
struct QueryBuilder {
|
||||
pub mut:
|
||||
operation OperationType
|
||||
fields []Field
|
||||
variables map[string]string
|
||||
}
|
||||
|
||||
struct Field {
|
||||
name string
|
||||
arguments map[string]string
|
||||
sub_fields []Field
|
||||
}
|
||||
|
||||
fn new_field(name string, args map[string]string, sub_fields []Field) Field {
|
||||
return Field{
|
||||
name: name
|
||||
arguments: args
|
||||
sub_fields: sub_fields
|
||||
}
|
||||
}
|
||||
|
||||
fn build_arguments(args map[string]string) string {
|
||||
if args.len == 0 {
|
||||
return ''
|
||||
}
|
||||
|
||||
mut sb := ''
|
||||
sb += '('
|
||||
|
||||
for key, value in args {
|
||||
if value.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sb += '${key}: ${value}, '
|
||||
}
|
||||
|
||||
return sb.trim_right(', ') + ')'
|
||||
}
|
||||
|
||||
fn build_fields(fields []Field) string {
|
||||
mut sb := ' { '
|
||||
for field in fields {
|
||||
sb += field.name
|
||||
if field.arguments.len > 0 {
|
||||
sb += build_arguments(field.arguments)
|
||||
}
|
||||
|
||||
if field.sub_fields.len > 0 {
|
||||
sb += build_fields(field.sub_fields)
|
||||
}
|
||||
|
||||
sb += ' '
|
||||
}
|
||||
sb += ' } '
|
||||
return sb
|
||||
}
|
||||
|
||||
fn (mut q QueryBuilder) add_operation(operation OperationType, fields []Field, variables map[string]string) {
|
||||
q.operation = operation
|
||||
q.fields = fields
|
||||
q.variables = variables.clone()
|
||||
}
|
||||
|
||||
fn (q QueryBuilder) build_query() string {
|
||||
mut sb := ''
|
||||
sb += '${q.operation}' + ' myOperation'
|
||||
|
||||
if q.variables.len > 0 {
|
||||
sb += build_arguments(q.variables)
|
||||
}
|
||||
|
||||
sb += build_fields(q.fields)
|
||||
return sb
|
||||
}
|
||||
@@ -29,4 +29,14 @@ client...
|
||||
port: 8888
|
||||
```
|
||||
|
||||
**RunPod API Example**
|
||||
|
||||
This script demonstrates creating, stopping, starting, and terminating RunPod pods using the RunPod API. It creates both on-demand and spot pods.
|
||||
|
||||
**Requirements**
|
||||
|
||||
* Environment variable `RUNPOD_API_KEY` set with your RunPod API key
|
||||
|
||||
**How to Run**
|
||||
|
||||
- Find out our example in: examples/develop/runpod/runpod_example.vsh
|
||||
|
||||
@@ -2,6 +2,7 @@ module runpod
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
__global (
|
||||
runpod_global map[string]&RunPod
|
||||
@@ -10,113 +11,88 @@ __global (
|
||||
|
||||
/////////FACTORY
|
||||
|
||||
// ArgsGet represents the arguments for getting a RunPod instance
|
||||
@[params]
|
||||
pub struct ArgsGet {
|
||||
pub mut:
|
||||
name string = 'default' // Name of the RunPod configuration
|
||||
api_key string // RunPod API key
|
||||
name string
|
||||
}
|
||||
|
||||
// The get method gets an existing RunPod instance or creates a new one
|
||||
pub fn get(args_ ArgsGet) !&RunPod {
|
||||
fn args_get(args_ ArgsGet) ArgsGet {
|
||||
mut args := args_
|
||||
|
||||
if args.name == '' {
|
||||
if runpod_default != '' {
|
||||
args.name = runpod_default
|
||||
} else {
|
||||
args.name = 'default'
|
||||
args.name = runpod_default
|
||||
}
|
||||
if args.name == '' {
|
||||
args.name = 'default'
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
pub fn get(args_ ArgsGet) !&RunPod {
|
||||
mut args := args_get(args_)
|
||||
if args.name !in runpod_global {
|
||||
if args.name == 'default' {
|
||||
if !config_exists(args) {
|
||||
if default {
|
||||
config_save(args)!
|
||||
}
|
||||
}
|
||||
config_load(args)!
|
||||
}
|
||||
}
|
||||
|
||||
// Return existing instance if available
|
||||
if args.name in runpod_global {
|
||||
return runpod_global[args.name]
|
||||
return runpod_global[args.name] or {
|
||||
println(runpod_global)
|
||||
panic('could not get config for runpod with name:${args.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// Load from config if exists
|
||||
fn config_exists(args_ ArgsGet) bool {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context() or { panic('bug') }
|
||||
return context.hero_config_exists('runpod', args.name)
|
||||
}
|
||||
|
||||
fn config_load(args_ ArgsGet) ! {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context()!
|
||||
if context.hero_config_exists('runpod', args.name) {
|
||||
mut heroscript := context.hero_config_get('runpod', args.name)!
|
||||
play(heroscript: heroscript)!
|
||||
return runpod_global[args.name] or { return error('Failed to load RunPod config') }
|
||||
}
|
||||
|
||||
// Create new instance if API key provided
|
||||
if args.api_key != '' {
|
||||
mut rp := new(args.api_key)!
|
||||
rp.name = args.name
|
||||
runpod_global[args.name] = rp
|
||||
return rp
|
||||
}
|
||||
|
||||
return error('RunPod API key is required for new instances')
|
||||
mut heroscript := context.hero_config_get('runpod', args.name)!
|
||||
play(heroscript: heroscript)!
|
||||
}
|
||||
|
||||
// save_config saves the RunPod configuration
|
||||
fn save_config(name string, api_key string) ! {
|
||||
fn config_save(args_ ArgsGet) ! {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context()!
|
||||
heroscript := "
|
||||
!!runpod.configure
|
||||
name:'${name}'
|
||||
api_key:'${api_key}'
|
||||
"
|
||||
context.hero_config_set('runpod', name, heroscript)!
|
||||
context.hero_config_set('runpod', args.name, heroscript_default()!)!
|
||||
}
|
||||
|
||||
// set stores a RunPod instance in the global map
|
||||
fn set(rp &RunPod) ! {
|
||||
if rp.api_key == '' {
|
||||
return error('RunPod API key is required')
|
||||
}
|
||||
runpod_global[rp.name] = rp
|
||||
save_config(rp.name, rp.api_key)!
|
||||
fn set(o RunPod) ! {
|
||||
mut o2 := obj_init(o)!
|
||||
runpod_global[o.name] = &o2
|
||||
runpod_default = o.name
|
||||
}
|
||||
|
||||
// PlayArgs represents arguments for playing a RunPod configuration
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
heroscript string // Heroscript configuration
|
||||
heroscript string // if filled in then plbook will be made out of it
|
||||
plbook ?playbook.PlayBook
|
||||
api_key string // RunPod API key
|
||||
reset bool
|
||||
}
|
||||
|
||||
// play processes a RunPod configuration
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
mut args := args_
|
||||
|
||||
if args.heroscript == '' && args.api_key == '' {
|
||||
return error('Either heroscript or API key is required')
|
||||
if args.heroscript == '' {
|
||||
args.heroscript = heroscript_default()!
|
||||
}
|
||||
|
||||
// If API key provided directly, create configuration
|
||||
if args.api_key != '' {
|
||||
save_config(args.name, args.api_key)!
|
||||
mut rp := new(args.api_key)!
|
||||
rp.name = args.name
|
||||
set(rp)!
|
||||
return
|
||||
}
|
||||
|
||||
// Process heroscript configuration
|
||||
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
|
||||
mut actions := plbook.find(filter: 'runpod.configure')!
|
||||
|
||||
if actions.len == 0 {
|
||||
return error('No RunPod configuration found in heroscript')
|
||||
}
|
||||
|
||||
for action in actions {
|
||||
mut params := action.params
|
||||
mut name := params.get_default('name', 'default')!
|
||||
mut api_key := params.get('api_key')!
|
||||
|
||||
mut rp := new(api_key)!
|
||||
rp.name = name
|
||||
set(rp)!
|
||||
mut install_actions := plbook.find(filter: 'runpod.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for install_action in install_actions {
|
||||
mut p := install_action.params
|
||||
cfg_play(p)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,15 @@
|
||||
module runpod
|
||||
|
||||
import x.json2
|
||||
import json
|
||||
import net.http { Method }
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
|
||||
// fn main() {
|
||||
// mut fields := []Field{}
|
||||
// fields << new_field('gpuTypes', {
|
||||
// 'id': '"NVIDIA GeForce RTX 3090"'
|
||||
// }, [
|
||||
// new_field('displayName', {}, []),
|
||||
// new_field('d', {}, []),
|
||||
// new_field('communmemoryInGb', {}, []),
|
||||
// new_field('secureClouityCloud', {}, []),
|
||||
// new_field('lowestPrice', {
|
||||
// 'gpuCount': '1'
|
||||
// }, [
|
||||
// new_field('minimumBidPrice', {}, []),
|
||||
// new_field('uninterruptablePrice', {}, []),
|
||||
// ]),
|
||||
// ])
|
||||
|
||||
// // Create Query Builder
|
||||
// mut builder := QueryBuilder{}
|
||||
// builder.add_operation(.query, fields, {})
|
||||
|
||||
// // Build and print the query
|
||||
// query := builder.build_query()
|
||||
// println('query: ${query}')
|
||||
// }
|
||||
// GraphQL response wrapper
|
||||
struct GqlResponse[T] {
|
||||
pub mut:
|
||||
data map[string]T
|
||||
errors []map[string]string
|
||||
}
|
||||
|
||||
// #### Internally method doing a network call to create a new on-demand pod.
|
||||
// - Build the required query based pn the input sent by the user and send the request.
|
||||
@@ -38,50 +20,39 @@ fn (mut rp RunPod) create_on_demand_pod_request(input PodFindAndDeployOnDemandRe
|
||||
mut fields := []Field{}
|
||||
mut machine_fields := []Field{}
|
||||
mut output_fields := []Field{}
|
||||
mut arguments := map[string]string{}
|
||||
mut builder := QueryBuilder{}
|
||||
|
||||
// $for field in input.fields {
|
||||
// // TODO: Handle option fields
|
||||
// // TODO: Handle the skip chars \
|
||||
machine_fields << new_field(name: 'podHostId')
|
||||
output_fields << new_field(name: 'id')
|
||||
output_fields << new_field(name: 'imageName')
|
||||
output_fields << new_field(name: 'env')
|
||||
output_fields << new_field(name: 'machineId')
|
||||
output_fields << new_field(name: 'desiredStatus')
|
||||
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||
fields << new_field(
|
||||
name: 'podFindAndDeployOnDemand'
|
||||
arguments: {
|
||||
'input': '\$arguments'
|
||||
}
|
||||
sub_fields: output_fields
|
||||
)
|
||||
|
||||
// item := input.$(field.name)
|
||||
// arguments[get_field_name(field)] = '${item}'
|
||||
// }
|
||||
builder.add_operation(
|
||||
operation: .mutation
|
||||
fields: fields
|
||||
variables: {
|
||||
'\$arguments': 'PodFindAndDeployOnDemandInput'
|
||||
}
|
||||
)
|
||||
mut variables := {
|
||||
'arguments': json2.Any(type_to_map(input)!)
|
||||
}
|
||||
query := builder.build_query(variables: variables)
|
||||
|
||||
machine_fields << new_field('podHostId', {}, [])
|
||||
output_fields << new_field('id', {}, [])
|
||||
output_fields << new_field('imageName', {}, [])
|
||||
output_fields << new_field('env', {}, [])
|
||||
output_fields << new_field('machineId', {}, [])
|
||||
output_fields << new_field('desiredStatus', {}, [])
|
||||
output_fields << new_field('machine', {}, machine_fields)
|
||||
fields << new_field('podFindAndDeployOnDemand', {
|
||||
'input': '\$arguments'
|
||||
}, output_fields)
|
||||
|
||||
builder.add_operation(.mutation, fields, {
|
||||
'\$arguments': 'PodFindAndDeployOnDemandInput'
|
||||
})
|
||||
|
||||
query := builder.build_query()
|
||||
encoded_input := json.encode(input)
|
||||
decoded_input := json2.raw_decode(encoded_input)!.as_map()
|
||||
mut q_map := map[string]json2.Any{}
|
||||
mut variables := map[string]json2.Any{}
|
||||
|
||||
variables['arguments'] = decoded_input
|
||||
q_map['query'] = json2.Any(query)
|
||||
q_map['variables'] = json2.Any(variables)
|
||||
|
||||
q := json2.encode(q_map)
|
||||
|
||||
println('query: ${q}')
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', q)!
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||
return response.data['podFindAndDeployOnDemand'] or {
|
||||
return error('Could not find "podFindAndDeployOnDemand" in response data: ${response.data}')
|
||||
}
|
||||
// return PodResult{}
|
||||
}
|
||||
|
||||
// #### Internally method doing a network call to create a new spot pod.
|
||||
@@ -90,17 +61,42 @@ fn (mut rp RunPod) create_on_demand_pod_request(input PodFindAndDeployOnDemandRe
|
||||
// - The data field should contains the pod details same as `PodResult` struct.
|
||||
// - The error field should contain the error message.
|
||||
fn (mut rp RunPod) create_spot_pod_request(input PodRentInterruptableInput) !PodResult {
|
||||
// gql := build_query(
|
||||
// query_type: .mutation
|
||||
// method_name: 'podRentInterruptable'
|
||||
// request_model: input
|
||||
// response_model: PodResult{}
|
||||
// )
|
||||
// response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', gql)!
|
||||
// return response.data['podRentInterruptable'] or {
|
||||
// return error('Could not find "podRentInterruptable" in response data: ${response.data}')
|
||||
// }
|
||||
return PodResult{}
|
||||
mut fields := []Field{}
|
||||
mut machine_fields := []Field{}
|
||||
mut output_fields := []Field{}
|
||||
mut builder := QueryBuilder{}
|
||||
|
||||
machine_fields << new_field(name: 'podHostId')
|
||||
output_fields << new_field(name: 'id')
|
||||
output_fields << new_field(name: 'imageName')
|
||||
output_fields << new_field(name: 'env')
|
||||
output_fields << new_field(name: 'machineId')
|
||||
output_fields << new_field(name: 'desiredStatus')
|
||||
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||
fields << new_field(
|
||||
name: 'podRentInterruptable'
|
||||
arguments: {
|
||||
'input': '\$arguments'
|
||||
}
|
||||
sub_fields: output_fields
|
||||
)
|
||||
|
||||
builder.add_operation(
|
||||
operation: .mutation
|
||||
fields: fields
|
||||
variables: {
|
||||
'\$arguments': 'PodRentInterruptableInput!'
|
||||
}
|
||||
)
|
||||
mut variables := {
|
||||
'arguments': json2.Any(type_to_map(input)!)
|
||||
}
|
||||
query := builder.build_query(variables: variables)
|
||||
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||
return response.data['podRentInterruptable'] or {
|
||||
return error('Could not find "podRentInterruptable" in response data: ${response.data}')
|
||||
}
|
||||
}
|
||||
|
||||
// #### Internally method doing a network call to start on demand pod.
|
||||
@@ -108,18 +104,43 @@ fn (mut rp RunPod) create_spot_pod_request(input PodRentInterruptableInput) !Pod
|
||||
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||
// - The data field should contains the pod details same as `PodResult` struct.
|
||||
// - The error field should contain the error message.
|
||||
fn (mut rp RunPod) start_on_demand_pod_request(input PodResume) !PodResult {
|
||||
// gql := build_query(
|
||||
// query_type: .mutation
|
||||
// method_name: 'podResume'
|
||||
// request_model: input
|
||||
// response_model: PodResult{}
|
||||
// )
|
||||
// response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', gql)!
|
||||
// return response.data['podResume'] or {
|
||||
// return error('Could not find "podResume" in response data: ${response.data}')
|
||||
// }
|
||||
return PodResult{}
|
||||
fn (mut rp RunPod) start_on_demand_pod_request(input PodResumeInput) !PodResult {
|
||||
mut fields := []Field{}
|
||||
mut machine_fields := []Field{}
|
||||
mut output_fields := []Field{}
|
||||
mut builder := QueryBuilder{}
|
||||
|
||||
machine_fields << new_field(name: 'podHostId')
|
||||
output_fields << new_field(name: 'id')
|
||||
output_fields << new_field(name: 'imageName')
|
||||
output_fields << new_field(name: 'env')
|
||||
output_fields << new_field(name: 'machineId')
|
||||
output_fields << new_field(name: 'desiredStatus')
|
||||
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||
fields << new_field(
|
||||
name: 'podResume'
|
||||
arguments: {
|
||||
'input': '\$arguments'
|
||||
}
|
||||
sub_fields: output_fields
|
||||
)
|
||||
|
||||
builder.add_operation(
|
||||
operation: .mutation
|
||||
fields: fields
|
||||
variables: {
|
||||
'\$arguments': 'PodResumeInput!'
|
||||
}
|
||||
)
|
||||
mut variables := {
|
||||
'arguments': json2.Any(type_to_map(input)!)
|
||||
}
|
||||
query := builder.build_query(variables: variables)
|
||||
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||
return response.data['podResume'] or {
|
||||
return error('Could not find "podResume" in response data: ${response.data}')
|
||||
}
|
||||
}
|
||||
|
||||
// #### Internally method doing a network call to start spot pod.
|
||||
@@ -127,18 +148,43 @@ fn (mut rp RunPod) start_on_demand_pod_request(input PodResume) !PodResult {
|
||||
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||
// - The data field should contains the pod details same as `PodResult` struct.
|
||||
// - The error field should contain the error message.
|
||||
fn (mut rp RunPod) start_spot_pod_request(input PodBidResume) !PodResult {
|
||||
// gql := build_query(
|
||||
// query_type: .mutation
|
||||
// method_name: 'podBidResume'
|
||||
// request_model: input
|
||||
// response_model: PodResult{}
|
||||
// )
|
||||
// response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', gql)!
|
||||
// return response.data['podBidResume'] or {
|
||||
// return error('Could not find "podBidResume" in response data: ${response.data}')
|
||||
// }
|
||||
return PodResult{}
|
||||
fn (mut rp RunPod) start_spot_pod_request(input PodBidResumeInput) !PodResult {
|
||||
mut fields := []Field{}
|
||||
mut machine_fields := []Field{}
|
||||
mut output_fields := []Field{}
|
||||
mut builder := QueryBuilder{}
|
||||
|
||||
machine_fields << new_field(name: 'podHostId')
|
||||
output_fields << new_field(name: 'id')
|
||||
output_fields << new_field(name: 'imageName')
|
||||
output_fields << new_field(name: 'env')
|
||||
output_fields << new_field(name: 'machineId')
|
||||
output_fields << new_field(name: 'desiredStatus')
|
||||
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||
fields << new_field(
|
||||
name: 'podBidResume'
|
||||
arguments: {
|
||||
'input': '\$arguments'
|
||||
}
|
||||
sub_fields: output_fields
|
||||
)
|
||||
|
||||
builder.add_operation(
|
||||
operation: .mutation
|
||||
fields: fields
|
||||
variables: {
|
||||
'\$arguments': 'PodBidResumeInput!'
|
||||
}
|
||||
)
|
||||
mut variables := {
|
||||
'arguments': json2.Any(type_to_map(input)!)
|
||||
}
|
||||
query := builder.build_query(variables: variables)
|
||||
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||
return response.data['podBidResume'] or {
|
||||
return error('Could not find "podBidResume" in response data: ${response.data}')
|
||||
}
|
||||
}
|
||||
|
||||
// #### Internally method doing a network call to stop a pod.
|
||||
@@ -146,16 +192,163 @@ fn (mut rp RunPod) start_spot_pod_request(input PodBidResume) !PodResult {
|
||||
// - Decode the response received from the API into two objects `Data` and `Error`.
|
||||
// - The data field should contains the pod details same as `PodResult` struct.
|
||||
// - The error field should contain the error message.
|
||||
fn (mut rp RunPod) stop_pod_request(input PodResume) !PodResult {
|
||||
// gql := build_query(
|
||||
// query_type: .mutation
|
||||
// method_name: 'podStop'
|
||||
// request_model: input
|
||||
// response_model: PodResult{}
|
||||
// )
|
||||
// response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', gql)!
|
||||
// return response.data['podStop'] or {
|
||||
// return error('Could not find "podStop" in response data: ${response.data}')
|
||||
// }
|
||||
return PodResult{}
|
||||
fn (mut rp RunPod) stop_pod_request(input PodStopInput) !PodResult {
|
||||
mut fields := []Field{}
|
||||
mut machine_fields := []Field{}
|
||||
mut output_fields := []Field{}
|
||||
mut builder := QueryBuilder{}
|
||||
|
||||
machine_fields << new_field(name: 'podHostId')
|
||||
output_fields << new_field(name: 'id')
|
||||
output_fields << new_field(name: 'imageName')
|
||||
output_fields << new_field(name: 'env')
|
||||
output_fields << new_field(name: 'machineId')
|
||||
output_fields << new_field(name: 'desiredStatus')
|
||||
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||
fields << new_field(
|
||||
name: 'podStop'
|
||||
arguments: {
|
||||
'input': '\$arguments'
|
||||
}
|
||||
sub_fields: output_fields
|
||||
)
|
||||
|
||||
builder.add_operation(
|
||||
operation: .mutation
|
||||
fields: fields
|
||||
variables: {
|
||||
'\$arguments': 'PodStopInput!'
|
||||
}
|
||||
)
|
||||
mut variables := {
|
||||
'arguments': json2.Any(type_to_map(input)!)
|
||||
}
|
||||
query := builder.build_query(variables: variables)
|
||||
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||
return response.data['podStop'] or {
|
||||
return error('Could not find "podStop" in response data: ${response.data}')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut rp RunPod) terminate_pod_request(input PodTerminateInput) ! {
|
||||
mut fields := []Field{}
|
||||
mut builder := QueryBuilder{}
|
||||
|
||||
fields << new_field(
|
||||
name: 'podTerminate'
|
||||
arguments: {
|
||||
'input': '\$arguments'
|
||||
}
|
||||
)
|
||||
|
||||
builder.add_operation(
|
||||
operation: .mutation
|
||||
fields: fields
|
||||
variables: {
|
||||
'\$arguments': 'PodTerminateInput!'
|
||||
}
|
||||
)
|
||||
mut variables := {
|
||||
'arguments': json2.Any(type_to_map(input)!)
|
||||
}
|
||||
query := builder.build_query(variables: variables)
|
||||
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||
_ := response.data['podTerminate'] or {
|
||||
return error('Could not find "podTerminate" in response data: ${response.data}')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut rp RunPod) get_pod_request(input PodFilter) !PodResult {
|
||||
mut fields := []Field{}
|
||||
mut machine_fields := []Field{}
|
||||
mut output_fields := []Field{}
|
||||
mut builder := QueryBuilder{}
|
||||
|
||||
machine_fields << new_field(name: 'podHostId')
|
||||
output_fields << new_field(name: 'id')
|
||||
output_fields << new_field(name: 'imageName')
|
||||
output_fields << new_field(name: 'env')
|
||||
output_fields << new_field(name: 'machineId')
|
||||
output_fields << new_field(name: 'desiredStatus')
|
||||
output_fields << new_field(name: 'machine', sub_fields: machine_fields)
|
||||
fields << new_field(
|
||||
name: 'pod'
|
||||
arguments: {
|
||||
'input': '\$arguments'
|
||||
}
|
||||
sub_fields: output_fields
|
||||
)
|
||||
|
||||
builder.add_operation(
|
||||
operation: .query
|
||||
fields: fields
|
||||
variables: {
|
||||
'\$arguments': 'PodFilter'
|
||||
}
|
||||
)
|
||||
mut variables := {
|
||||
'arguments': json2.Any(type_to_map(input)!)
|
||||
}
|
||||
query := builder.build_query(variables: variables)
|
||||
|
||||
response := rp.make_request[GqlResponse[PodResult]](.post, '/graphql', query)!
|
||||
return response.data['pod'] or {
|
||||
return error('Could not find "pod" in response data: ${response.data}')
|
||||
}
|
||||
}
|
||||
|
||||
// Represents the main structure for interacting with the RunPod API.
|
||||
// Provides utilities to manage HTTP connections and perform GraphQL queries.
|
||||
fn (mut rp RunPod) httpclient() !&httpconnection.HTTPConnection {
|
||||
mut http_conn := httpconnection.new(
|
||||
name: 'runpod_vclient_${rp.name}'
|
||||
url: rp.base_url
|
||||
cache: true
|
||||
retry: 3
|
||||
)!
|
||||
http_conn.default_header.add(.authorization, 'Bearer ${rp.api_key}')
|
||||
return http_conn
|
||||
}
|
||||
|
||||
// Sends an HTTP request to the RunPod API with the specified method, path, and data.
|
||||
fn (mut rp RunPod) make_request[T](method Method, path string, data string) !T {
|
||||
mut request := httpconnection.Request{
|
||||
prefix: path
|
||||
data: data
|
||||
debug: true
|
||||
dataformat: .json
|
||||
}
|
||||
|
||||
mut http_client := rp.httpclient()!
|
||||
mut response := T{}
|
||||
|
||||
match method {
|
||||
.get {
|
||||
request.method = .get
|
||||
response = http_client.get_json_generic[T](request)!
|
||||
}
|
||||
.post {
|
||||
request.method = .post
|
||||
response = http_client.post_json_generic[T](request)!
|
||||
}
|
||||
.put {
|
||||
request.method = .put
|
||||
response = http_client.put_json_generic[T](request)!
|
||||
}
|
||||
.delete {
|
||||
request.method = .delete
|
||||
response = http_client.delete_json_generic[T](request)!
|
||||
}
|
||||
else {
|
||||
return error('unsupported method: ${method}')
|
||||
}
|
||||
}
|
||||
|
||||
if response.errors.len > 0 {
|
||||
return error('Error while sending the request due to: ${response.errors[0]['message']}')
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
module runpod
|
||||
|
||||
import freeflowuniverse.herolib.data.paramsparser
|
||||
import os
|
||||
|
||||
pub const version = '1.14.3'
|
||||
const singleton = false
|
||||
const default = true
|
||||
@@ -9,7 +12,7 @@ pub fn heroscript_default() !string {
|
||||
return "
|
||||
!!runpod.configure
|
||||
name:'default'
|
||||
api_key:''
|
||||
api_key:'${os.getenv('RUNPOD_API_KEY')}'
|
||||
base_url:'https://api.runpod.io/'
|
||||
"
|
||||
}
|
||||
@@ -23,50 +26,18 @@ pub mut:
|
||||
base_url string = 'https://api.runpod.io/'
|
||||
}
|
||||
|
||||
pub enum CloudType {
|
||||
all
|
||||
secure
|
||||
community
|
||||
}
|
||||
|
||||
fn (ct CloudType) str() string {
|
||||
return match ct {
|
||||
.all {
|
||||
'ALL'
|
||||
}
|
||||
.secure {
|
||||
'SECURE'
|
||||
}
|
||||
.community {
|
||||
'COMMUNITY'
|
||||
}
|
||||
fn cfg_play(p paramsparser.Params) ! {
|
||||
// THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above
|
||||
mut mycfg := RunPod{
|
||||
name: p.get_default('name', 'default')!
|
||||
api_key: p.get_default('api_key', os.getenv('RUNPOD_API_KEY'))!
|
||||
base_url: p.get_default('base_url', 'https://api.runpod.io/')!
|
||||
}
|
||||
set(mycfg)!
|
||||
}
|
||||
|
||||
pub struct EnvironmentVariableInput {
|
||||
pub mut:
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
// new creates a new RunPod client
|
||||
pub fn new(api_key string) !&RunPod {
|
||||
if api_key == '' {
|
||||
return error('API key is required')
|
||||
}
|
||||
return &RunPod{
|
||||
api_key: api_key
|
||||
}
|
||||
}
|
||||
|
||||
// GraphQL query structs
|
||||
struct GqlQuery {
|
||||
query string
|
||||
}
|
||||
|
||||
// GraphQL response wrapper
|
||||
struct GqlResponse[T] {
|
||||
pub mut:
|
||||
data map[string]T
|
||||
errors []map[string]string
|
||||
fn obj_init(obj_ RunPod) !RunPod {
|
||||
// never call get here, only thing we can do here is work on object itself
|
||||
mut obj := obj_
|
||||
return obj
|
||||
}
|
||||
|
||||
@@ -1,200 +1,118 @@
|
||||
module runpod
|
||||
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
import json
|
||||
import x.json2
|
||||
|
||||
// Represents the main structure for interacting with the RunPod API.
|
||||
// Provides utilities to manage HTTP connections and perform GraphQL queries.
|
||||
fn (mut rp RunPod) httpclient() !&httpconnection.HTTPConnection {
|
||||
mut http_conn := httpconnection.new(
|
||||
name: 'runpod_vclient_${rp.name}'
|
||||
url: rp.base_url
|
||||
cache: true
|
||||
retry: 3
|
||||
)!
|
||||
http_conn.default_header.add(.authorization, 'Bearer ${rp.api_key}')
|
||||
return http_conn
|
||||
enum OperationType {
|
||||
query
|
||||
mutation
|
||||
}
|
||||
|
||||
// Retrieves the field name from the `FieldData` struct, either from its attributes or its name.
|
||||
fn get_field_name(field FieldData) string {
|
||||
mut field_name := ''
|
||||
if field.attrs.len > 0 {
|
||||
for attr in field.attrs {
|
||||
attrs := attr.trim_space().split(':')
|
||||
if attrs.len == 2 && attrs[0] == 'json' {
|
||||
field_name = attrs[1].trim_space()
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
field_name = field.name
|
||||
}
|
||||
return field_name
|
||||
struct QueryBuilder {
|
||||
pub mut:
|
||||
operation OperationType
|
||||
fields []Field
|
||||
variables map[string]string
|
||||
}
|
||||
|
||||
// // Constructs JSON-like request fields from a struct.
|
||||
// fn get_request_fields[T](struct_ T) string {
|
||||
// mut body_ := '{ '
|
||||
// mut fields := []string{}
|
||||
struct Field {
|
||||
name string
|
||||
arguments map[string]string
|
||||
sub_fields []Field
|
||||
}
|
||||
|
||||
// $for field in T.fields {
|
||||
// mut string_ := ''
|
||||
// omit := 'omitempty' in field.attrs
|
||||
// mut empty_string := false
|
||||
// $if field.typ is string {
|
||||
// empty_string = struct_.$(field.name) == ''
|
||||
// }
|
||||
// if !omit || !empty_string {
|
||||
// string_ += get_field_name(field)
|
||||
// string_ += ': '
|
||||
@[params]
|
||||
pub struct NewFieldArgs {
|
||||
pub:
|
||||
name string
|
||||
arguments map[string]string
|
||||
sub_fields []Field
|
||||
}
|
||||
|
||||
// $if field.is_enum {
|
||||
// string_ += struct_.$(field.name).to_string()
|
||||
// } $else $if field.typ is string {
|
||||
// item := struct_.$(field.name)
|
||||
// string_ += "\"${item}\""
|
||||
// } $else $if field.is_array {
|
||||
// string_ += '['
|
||||
// for i in struct_.$(field.name) {
|
||||
// string_ += get_request_fields(i)
|
||||
// }
|
||||
// string_ += ']'
|
||||
// } $else $if field.is_struct {
|
||||
// string_ += get_request_fields(struct_.$(field.name))
|
||||
// } $else {
|
||||
// item := struct_.$(field.name)
|
||||
// string_ += '${item}'
|
||||
// }
|
||||
|
||||
// fields << string_
|
||||
// }
|
||||
// }
|
||||
// body_ += fields.join(', ')
|
||||
// body_ += ' }'
|
||||
// return body_
|
||||
// }
|
||||
|
||||
// // Constructs JSON-like response fields for a given struct.
|
||||
// fn get_response_fields[R](struct_ R) string {
|
||||
// mut body_ := '{ '
|
||||
|
||||
// $for field in R.fields {
|
||||
// $if field.is_struct {
|
||||
// // Recursively process nested structs
|
||||
// body_ += '${field.name} '
|
||||
// body_ += get_response_fields(struct_.$(field.name))
|
||||
// } $else {
|
||||
// body_ += get_field_name(field)
|
||||
// body_ += ' '
|
||||
// }
|
||||
// }
|
||||
// body_ += ' }'
|
||||
// return body_
|
||||
// }
|
||||
|
||||
// // Enum representing the type of GraphQL operation.
|
||||
// pub enum QueryType {
|
||||
// query
|
||||
// mutation
|
||||
// }
|
||||
|
||||
// // Struct for building GraphQL queries with request and response models.
|
||||
// @[params]
|
||||
// pub struct BuildQueryArgs[T, R] {
|
||||
// pub:
|
||||
// query_type QueryType // query or mutation
|
||||
// method_name string
|
||||
// request_model T @[required]
|
||||
// response_model R @[required]
|
||||
// }
|
||||
|
||||
// // Builds a GraphQL query or mutation string from provided arguments.
|
||||
// fn build_query[T, R](args BuildQueryArgs[T, R]) string {
|
||||
// mut request_fields := T{}
|
||||
// mut response_fields := R{}
|
||||
|
||||
// if args.request_model {
|
||||
// request_fields = get_request_fields(args.request_model)
|
||||
// }
|
||||
|
||||
// if args.response_model {
|
||||
// response_fields = get_response_fields(args.response_model)
|
||||
// }
|
||||
|
||||
// mut query := ''
|
||||
|
||||
// if args.request_model && args.response_model{
|
||||
// query := '${args.query_type.to_string()} { ${args.method_name}(input: ${request_fields}) ${response_fields} }'
|
||||
// }
|
||||
|
||||
// if args.response_model && !args.request_model{
|
||||
// query := '${args.query_type.to_string()} { ${response_fields} }'
|
||||
// }
|
||||
|
||||
// // Wrap in the final structure
|
||||
// gql := GqlQuery{
|
||||
// query: query
|
||||
// }
|
||||
|
||||
// // Return the final GraphQL query as a JSON string
|
||||
// return json.encode(gql)
|
||||
// }
|
||||
|
||||
// Converts the `QueryType` enum to its string representation.
|
||||
fn (op OperationType) to_string() string {
|
||||
return match op {
|
||||
.query {
|
||||
'query'
|
||||
}
|
||||
.mutation {
|
||||
'mutation'
|
||||
}
|
||||
fn new_field(args NewFieldArgs) Field {
|
||||
return Field{
|
||||
name: args.name
|
||||
arguments: args.arguments
|
||||
sub_fields: args.sub_fields
|
||||
}
|
||||
}
|
||||
|
||||
// Enum representing HTTP methods.
|
||||
enum HTTPMethod {
|
||||
get
|
||||
post
|
||||
put
|
||||
delete
|
||||
fn build_arguments(args map[string]string) string {
|
||||
if args.len == 0 {
|
||||
return ''
|
||||
}
|
||||
|
||||
mut sb := ''
|
||||
sb += '('
|
||||
|
||||
for key, value in args {
|
||||
if value.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sb += '${key}: ${value}, '
|
||||
}
|
||||
|
||||
return sb.trim_right(', ') + ')'
|
||||
}
|
||||
|
||||
// Sends an HTTP request to the RunPod API with the specified method, path, and data.
|
||||
fn (mut rp RunPod) make_request[T](method HTTPMethod, path string, data string) !T {
|
||||
mut request := httpconnection.Request{
|
||||
prefix: path
|
||||
data: data
|
||||
debug: true
|
||||
dataformat: .json
|
||||
fn build_fields(fields []Field) string {
|
||||
mut sb := ' { '
|
||||
for field in fields {
|
||||
sb += field.name
|
||||
if field.arguments.len > 0 {
|
||||
sb += build_arguments(field.arguments)
|
||||
}
|
||||
|
||||
if field.sub_fields.len > 0 {
|
||||
sb += build_fields(field.sub_fields)
|
||||
}
|
||||
|
||||
sb += ' '
|
||||
}
|
||||
|
||||
mut http := rp.httpclient()!
|
||||
mut response := T{}
|
||||
|
||||
match method {
|
||||
.get {
|
||||
request.method = .get
|
||||
response = http.get_json_generic[T](request)!
|
||||
}
|
||||
.post {
|
||||
request.method = .post
|
||||
response = http.post_json_generic[T](request)!
|
||||
}
|
||||
.put {
|
||||
request.method = .put
|
||||
response = http.put_json_generic[T](request)!
|
||||
}
|
||||
.delete {
|
||||
request.method = .delete
|
||||
response = http.delete_json_generic[T](request)!
|
||||
}
|
||||
}
|
||||
|
||||
if response.errors.len > 0 {
|
||||
return error('Error while sending the request due to: ${response.errors[0]['message']}')
|
||||
}
|
||||
|
||||
return response
|
||||
sb += ' } '
|
||||
return sb
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct AddOperationArgs {
|
||||
pub:
|
||||
operation OperationType
|
||||
fields []Field
|
||||
variables map[string]string
|
||||
}
|
||||
|
||||
fn (mut q QueryBuilder) add_operation(args AddOperationArgs) {
|
||||
q.operation = args.operation
|
||||
q.fields = args.fields
|
||||
q.variables = args.variables.clone()
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct BuildQueryArgs {
|
||||
pub:
|
||||
variables map[string]json2.Any
|
||||
}
|
||||
|
||||
fn (q QueryBuilder) build_query(args BuildQueryArgs) string {
|
||||
mut query := ''
|
||||
query += '${q.operation}' + ' myOperation'
|
||||
|
||||
if q.variables.len > 0 {
|
||||
query += build_arguments(q.variables)
|
||||
}
|
||||
|
||||
query += build_fields(q.fields)
|
||||
|
||||
mut q_map := {
|
||||
'query': json2.Any(query)
|
||||
'variables': json2.Any(args.variables)
|
||||
}
|
||||
|
||||
return json2.encode(q_map)
|
||||
}
|
||||
|
||||
fn type_to_map[T](t T) !map[string]json2.Any {
|
||||
encoded_input := json2.encode(t)
|
||||
return json2.raw_decode(encoded_input)!.as_map()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user