Skip to content

Commit d934ec9

Browse files
committed
refactor(search): make --min-ram and --arch shared flags
Move --min-ram and --arch from CPU-dedicated flags to shared flags so they are available on all search subcommands (gpu, cpu, and parent).
1 parent e90f76a commit d934ec9

3 files changed

Lines changed: 47 additions & 71 deletions

File tree

pkg/cmd/gpucreate/gpucreate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ func searchInstances(s GPUCreateStore, filters *searchFilterFlags) ([]gpusearch.
314314
}
315315

316316
instances := gpusearch.ProcessInstances(response.Items)
317-
filtered := gpusearch.FilterInstances(instances, filters.gpuName, filters.provider, filters.minVRAM,
318-
minTotalVRAM, minCapability, minDisk, 0, maxBootTime, filters.stoppable, filters.rebootable, filters.flexPorts, true)
317+
filtered := gpusearch.FilterInstances(instances, filters.gpuName, filters.provider, "", filters.minVRAM,
318+
minTotalVRAM, minCapability, 0, minDisk, 0, maxBootTime, filters.stoppable, filters.rebootable, filters.flexPorts, true)
319319
gpusearch.SortInstances(filtered, sortBy, filters.descending)
320320

321321
return filtered, minDisk, nil

pkg/cmd/gpusearch/gpusearch.go

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ Features column shows instance capabilities:
154154
// sharedFlags holds flags shared between gpu and cpu subcommands
155155
type sharedFlags struct {
156156
provider string
157+
arch string
157158
minVCPU int
159+
minRAM float64
158160
minDisk float64
159161
maxBootTime int
160162
stoppable bool
@@ -168,7 +170,9 @@ type sharedFlags struct {
168170
// addSharedFlags adds common flags to a command
169171
func addSharedFlags(cmd *cobra.Command, f *sharedFlags) {
170172
cmd.Flags().StringVarP(&f.provider, "provider", "p", "", "Filter by provider/cloud (case-insensitive, partial match)")
173+
cmd.Flags().StringVar(&f.arch, "arch", "", "Filter by architecture (e.g., x86_64, arm64)")
171174
cmd.Flags().IntVar(&f.minVCPU, "min-vcpu", 0, "Minimum number of vCPUs")
175+
cmd.Flags().Float64Var(&f.minRAM, "min-ram", 0, "Minimum RAM in GB")
172176
cmd.Flags().Float64Var(&f.minDisk, "min-disk", 0, "Minimum disk size in GB")
173177
cmd.Flags().IntVar(&f.maxBootTime, "max-boot-time", 0, "Maximum boot time in minutes")
174178
cmd.Flags().BoolVar(&f.stoppable, "stoppable", false, "Only show instances that can be stopped and restarted")
@@ -199,7 +203,7 @@ func NewCmdGPUSearch(t *terminal.Terminal, store GPUSearchStore) *cobra.Command
199203
Example: gpuExample,
200204
RunE: func(cmd *cobra.Command, args []string) error {
201205
// Default behavior: GPU search
202-
return RunGPUSearch(t, store, gpuName, shared.provider, minVRAM, minTotalVRAM, minCapability, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide)
206+
return RunGPUSearch(t, store, gpuName, shared.provider, shared.arch, minVRAM, minTotalVRAM, minCapability, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide)
203207
},
204208
}
205209

@@ -233,7 +237,7 @@ func newCmdGPUSubcommand(t *terminal.Terminal, store GPUSearchStore) *cobra.Comm
233237
Short: "Search GPU instance types",
234238
Example: gpuExample,
235239
RunE: func(cmd *cobra.Command, args []string) error {
236-
return RunGPUSearch(t, store, gpuName, shared.provider, minVRAM, minTotalVRAM, minCapability, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide)
240+
return RunGPUSearch(t, store, gpuName, shared.provider, shared.arch, minVRAM, minTotalVRAM, minCapability, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide)
237241
},
238242
}
239243

@@ -249,8 +253,6 @@ func newCmdGPUSubcommand(t *terminal.Terminal, store GPUSearchStore) *cobra.Comm
249253

250254
// newCmdCPUSubcommand creates the 'cpu' subcommand
251255
func newCmdCPUSubcommand(t *terminal.Terminal, store GPUSearchStore) *cobra.Command {
252-
var minRAM float64
253-
var arch string
254256
var shared sharedFlags
255257

256258
cmd := &cobra.Command{
@@ -259,12 +261,10 @@ func newCmdCPUSubcommand(t *terminal.Terminal, store GPUSearchStore) *cobra.Comm
259261
Short: "Search CPU-only instance types",
260262
Example: cpuExample,
261263
RunE: func(cmd *cobra.Command, args []string) error {
262-
return RunCPUSearch(t, store, shared.provider, arch, minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput)
264+
return RunCPUSearch(t, store, shared.provider, shared.arch, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput)
263265
},
264266
}
265267

266-
cmd.Flags().Float64Var(&minRAM, "min-ram", 0, "Minimum RAM in GB")
267-
cmd.Flags().StringVar(&arch, "arch", "", "Filter by architecture (e.g., x86_64, arm64)")
268268
addSharedFlags(cmd, &shared)
269269

270270
return cmd
@@ -303,7 +303,7 @@ func IsStdoutPiped() bool {
303303
}
304304

305305
// RunGPUSearch executes the GPU search with filters and sorting
306-
func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, provider string, minVRAM, minTotalVRAM, minCapability, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool, sortBy string, descending, jsonOutput, wide bool) error {
306+
func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, provider, arch string, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool, sortBy string, descending, jsonOutput, wide bool) error {
307307
if err := validateSortOption(sortBy); err != nil {
308308
return err
309309
}
@@ -322,7 +322,7 @@ func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, provider
322322
instances := ProcessInstances(response.Items)
323323

324324
// Filter to GPU-only instances
325-
filtered := FilterInstances(instances, gpuName, provider, minVRAM, minTotalVRAM, minCapability, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts, false)
325+
filtered := FilterInstances(instances, gpuName, provider, arch, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts, false)
326326

327327
if len(filtered) == 0 {
328328
return displayEmptyResults(t, "No GPU instances match the specified filters", jsonOutput, piped)
@@ -792,13 +792,15 @@ func ProcessInstances(items []InstanceType) []GPUInstanceInfo {
792792
return instances
793793
}
794794

795-
// FilterOptions holds all filter criteria for GPU instances
795+
// FilterOptions holds all filter criteria for instances
796796
type FilterOptions struct {
797797
GPUName string
798798
Provider string
799+
Arch string
799800
MinVRAM float64
800801
MinTotalVRAM float64
801802
MinCapability float64
803+
MinRAM float64
802804
MinDisk float64
803805
MinVCPU int
804806
MaxBootTime int // in minutes
@@ -821,6 +823,10 @@ func (f *FilterOptions) matchesStringFilters(inst GPUInstanceInfo) bool {
821823
if f.Provider != "" && !strings.Contains(strings.ToLower(inst.Provider), strings.ToLower(f.Provider)) {
822824
return false
823825
}
826+
// Filter by architecture (case-insensitive partial match)
827+
if f.Arch != "" && !strings.Contains(strings.ToLower(inst.Arch), strings.ToLower(f.Arch)) {
828+
return false
829+
}
824830
return true
825831
}
826832

@@ -829,6 +835,9 @@ func (f *FilterOptions) matchesNumericFilters(inst GPUInstanceInfo) bool {
829835
if f.MinVCPU > 0 && inst.VCPUs < f.MinVCPU {
830836
return false
831837
}
838+
if f.MinRAM > 0 && inst.RAMInGB < f.MinRAM {
839+
return false
840+
}
832841
if f.MinVRAM > 0 && inst.VRAMPerGPU < f.MinVRAM {
833842
return false
834843
}
@@ -870,13 +879,15 @@ func (f *FilterOptions) matchesFilter(inst GPUInstanceInfo) bool {
870879
}
871880

872881
// FilterInstances applies all filters to the instance list. When gpuOnly is true, CPU-only instances are excluded.
873-
func FilterInstances(instances []GPUInstanceInfo, gpuName, provider string, minVRAM, minTotalVRAM, minCapability, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts, gpuOnly bool) []GPUInstanceInfo {
882+
func FilterInstances(instances []GPUInstanceInfo, gpuName, provider, arch string, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts, gpuOnly bool) []GPUInstanceInfo {
874883
opts := &FilterOptions{
875884
GPUName: gpuName,
876885
Provider: provider,
886+
Arch: arch,
877887
MinVRAM: minVRAM,
878888
MinTotalVRAM: minTotalVRAM,
879889
MinCapability: minCapability,
890+
MinRAM: minRAM,
880891
MinDisk: minDisk,
881892
MinVCPU: minVCPU,
882893
MaxBootTime: maxBootTime,
@@ -897,51 +908,16 @@ func FilterInstances(instances []GPUInstanceInfo, gpuName, provider string, minV
897908
return filtered
898909
}
899910

900-
// FilterCPUInstances filters to CPU-only instances and applies CPU-specific filters
911+
// FilterCPUInstances filters to CPU-only instances using shared filter logic
901912
func FilterCPUInstances(instances []GPUInstanceInfo, provider, arch string, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool) []GPUInstanceInfo {
902-
var filtered []GPUInstanceInfo
913+
// Filter out GPU instances first, then apply shared filters
914+
var cpuOnly []GPUInstanceInfo
903915
for _, inst := range instances {
904-
// CPU-only: skip GPU instances
905-
if inst.Manufacturer != "cpu" {
906-
continue
916+
if inst.Manufacturer == "cpu" {
917+
cpuOnly = append(cpuOnly, inst)
907918
}
908-
// Provider filter
909-
if provider != "" && !strings.Contains(strings.ToLower(inst.Provider), strings.ToLower(provider)) {
910-
continue
911-
}
912-
// Arch filter
913-
if arch != "" && !strings.Contains(strings.ToLower(inst.Arch), strings.ToLower(arch)) {
914-
continue
915-
}
916-
// Min vCPU filter
917-
if minVCPU > 0 && inst.VCPUs < minVCPU {
918-
continue
919-
}
920-
// Min RAM filter
921-
if minRAM > 0 && inst.RAMInGB < minRAM {
922-
continue
923-
}
924-
// Min disk filter
925-
if minDisk > 0 && inst.DiskMax < minDisk {
926-
continue
927-
}
928-
// Max boot time filter
929-
if maxBootTime > 0 && (inst.BootTime == 0 || inst.BootTime > maxBootTime*60) {
930-
continue
931-
}
932-
// Feature filters
933-
if stoppable && !inst.Stoppable {
934-
continue
935-
}
936-
if rebootable && !inst.Rebootable {
937-
continue
938-
}
939-
if flexPorts && !inst.FlexPorts {
940-
continue
941-
}
942-
filtered = append(filtered, inst)
943919
}
944-
return filtered
920+
return FilterInstances(cpuOnly, "", provider, arch, 0, 0, 0, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts, false)
945921
}
946922

947923
// SortInstances sorts the instance list by the specified column

pkg/cmd/gpusearch/gpusearch_test.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,19 @@ func TestFilterInstancesByGPUName(t *testing.T) {
168168
instances := ProcessInstances(response.Items)
169169

170170
// Filter by A10G
171-
filtered := FilterInstances(instances, "A10G", "", 0, 0, 0, 0, 0, 0, false, false, false, true)
171+
filtered := FilterInstances(instances, "A10G", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true)
172172
assert.Len(t, filtered, 2, "Should have 2 A10G instances")
173173

174174
// Filter by V100
175-
filtered = FilterInstances(instances, "V100", "", 0, 0, 0, 0, 0, 0, false, false, false, true)
175+
filtered = FilterInstances(instances, "V100", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true)
176176
assert.Len(t, filtered, 2, "Should have 2 V100 instances")
177177

178178
// Filter by lowercase (case-insensitive)
179-
filtered = FilterInstances(instances, "v100", "", 0, 0, 0, 0, 0, 0, false, false, false, true)
179+
filtered = FilterInstances(instances, "v100", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true)
180180
assert.Len(t, filtered, 2, "Should have 2 V100 instances (case-insensitive)")
181181

182182
// Filter by partial match
183-
filtered = FilterInstances(instances, "A1", "", 0, 0, 0, 0, 0, 0, false, false, false, true)
183+
filtered = FilterInstances(instances, "A1", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true)
184184
assert.Len(t, filtered, 3, "Should have 3 instances matching 'A1' (A10G and A100)")
185185
}
186186

@@ -189,11 +189,11 @@ func TestFilterInstancesByMinVRAM(t *testing.T) {
189189
instances := ProcessInstances(response.Items)
190190

191191
// Filter by min VRAM 24GB
192-
filtered := FilterInstances(instances, "", "", 24, 0, 0, 0, 0, 0, false, false, false, true)
192+
filtered := FilterInstances(instances, "", "", "", 24, 0, 0, 0, 0, 0, 0, false, false, false, true)
193193
assert.Len(t, filtered, 4, "Should have 4 instances with >= 24GB VRAM")
194194

195195
// Filter by min VRAM 40GB
196-
filtered = FilterInstances(instances, "", "", 40, 0, 0, 0, 0, 0, false, false, false, true)
196+
filtered = FilterInstances(instances, "", "", "", 40, 0, 0, 0, 0, 0, 0, false, false, false, true)
197197
assert.Len(t, filtered, 1, "Should have 1 instance with >= 40GB VRAM")
198198
assert.Equal(t, "A100", filtered[0].GPUName)
199199
}
@@ -203,11 +203,11 @@ func TestFilterInstancesByMinTotalVRAM(t *testing.T) {
203203
instances := ProcessInstances(response.Items)
204204

205205
// Filter by min total VRAM 60GB
206-
filtered := FilterInstances(instances, "", "", 0, 60, 0, 0, 0, 0, false, false, false, true)
206+
filtered := FilterInstances(instances, "", "", "", 0, 60, 0, 0, 0, 0, 0, false, false, false, true)
207207
assert.Len(t, filtered, 2, "Should have 2 instances with >= 60GB total VRAM")
208208

209209
// Filter by min total VRAM 300GB
210-
filtered = FilterInstances(instances, "", "", 0, 300, 0, 0, 0, 0, false, false, false, true)
210+
filtered = FilterInstances(instances, "", "", "", 0, 300, 0, 0, 0, 0, 0, false, false, false, true)
211211
assert.Len(t, filtered, 1, "Should have 1 instance with >= 300GB total VRAM")
212212
assert.Equal(t, "p4d.24xlarge", filtered[0].Type)
213213
}
@@ -217,11 +217,11 @@ func TestFilterInstancesByMinCapability(t *testing.T) {
217217
instances := ProcessInstances(response.Items)
218218

219219
// Filter by capability >= 8.0
220-
filtered := FilterInstances(instances, "", "", 0, 0, 8.0, 0, 0, 0, false, false, false, true)
220+
filtered := FilterInstances(instances, "", "", "", 0, 0, 8.0, 0, 0, 0, 0, false, false, false, true)
221221
assert.Len(t, filtered, 4, "Should have 4 instances with capability >= 8.0")
222222

223223
// Filter by capability >= 8.5
224-
filtered = FilterInstances(instances, "", "", 0, 0, 8.5, 0, 0, 0, false, false, false, true)
224+
filtered = FilterInstances(instances, "", "", "", 0, 0, 8.5, 0, 0, 0, 0, false, false, false, true)
225225
assert.Len(t, filtered, 3, "Should have 3 instances with capability >= 8.5")
226226
}
227227

@@ -230,11 +230,11 @@ func TestFilterInstancesCombined(t *testing.T) {
230230
instances := ProcessInstances(response.Items)
231231

232232
// Filter by GPU name and min VRAM
233-
filtered := FilterInstances(instances, "A10G", "", 24, 0, 0, 0, 0, 0, false, false, false, true)
233+
filtered := FilterInstances(instances, "A10G", "", "", 24, 0, 0, 0, 0, 0, 0, false, false, false, true)
234234
assert.Len(t, filtered, 2, "Should have 2 A10G instances with >= 24GB VRAM")
235235

236236
// Filter by GPU name, min VRAM, and capability
237-
filtered = FilterInstances(instances, "", "", 24, 0, 8.5, 0, 0, 0, false, false, false, true)
237+
filtered = FilterInstances(instances, "", "", "", 24, 0, 8.5, 0, 0, 0, 0, false, false, false, true)
238238
assert.Len(t, filtered, 3, "Should have 3 instances with >= 24GB VRAM and capability >= 8.5")
239239
}
240240

@@ -336,7 +336,7 @@ func TestEmptyInstanceTypes(t *testing.T) {
336336

337337
assert.Len(t, instances, 0, "Should have 0 instances")
338338

339-
filtered := FilterInstances(instances, "A100", "", 0, 0, 0, 0, 0, 0, false, false, false, true)
339+
filtered := FilterInstances(instances, "A100", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true)
340340
assert.Len(t, filtered, 0, "Filtered should also be empty")
341341
}
342342

@@ -395,12 +395,12 @@ func TestNonGPUInstancesFilteredByDefault(t *testing.T) {
395395
instances := ProcessInstances(response.Items)
396396

397397
// gpuOnly=true should filter out CPU instances
398-
filtered := FilterInstances(instances, "", "", 0, 0, 0, 0, 0, 0, false, false, false, true)
398+
filtered := FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true)
399399
assert.Len(t, filtered, 1, "gpuOnly should exclude CPU instances")
400400
assert.Equal(t, "g5.xlarge", filtered[0].Type)
401401

402402
// gpuOnly=false should keep CPU instances
403-
filtered = FilterInstances(instances, "", "", 0, 0, 0, 0, 0, 0, false, false, false, false)
403+
filtered = FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, false)
404404
assert.Len(t, filtered, 2, "Without gpuOnly, both CPU and GPU instances pass")
405405
}
406406

@@ -464,7 +464,7 @@ func TestFilterByMaxBootTimeExcludesUnknown(t *testing.T) {
464464
assert.Len(t, instances, 3, "Should have 3 instances before filtering")
465465

466466
// Filter by max boot time of 10 minutes - should exclude unknown and slow-boot
467-
filtered := FilterInstances(instances, "", "", 0, 0, 0, 0, 0, 10, false, false, false, true)
467+
filtered := FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 10, false, false, false, true)
468468
assert.Len(t, filtered, 1, "Should have 1 instance with boot time <= 10 minutes")
469469
assert.Equal(t, "fast-boot", filtered[0].Type, "Only fast-boot should match")
470470

@@ -475,7 +475,7 @@ func TestFilterByMaxBootTimeExcludesUnknown(t *testing.T) {
475475
}
476476

477477
// Without filter, all instances should be included
478-
noFilter := FilterInstances(instances, "", "", 0, 0, 0, 0, 0, 0, false, false, false, true)
478+
noFilter := FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true)
479479
assert.Len(t, noFilter, 3, "Without filter, all 3 instances should be included")
480480
}
481481

0 commit comments

Comments
 (0)