Skip to content

Commit 900677a

Browse files
committed
feat(create): add --dry-run flag to preview matching instance types
1 parent db8beb0 commit 900677a

2 files changed

Lines changed: 36 additions & 13 deletions

File tree

pkg/cmd/gpucreate/gpucreate.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ func NewCmdGPUCreate(t *terminal.Terminal, gpuCreateStore GPUCreateStore) *cobra
179179
var detached bool
180180
var timeout int
181181
var startupScript string
182+
var dryRun bool
182183
var filters searchFilterFlags
183184

184185
cmd := &cobra.Command{
@@ -203,6 +204,10 @@ func NewCmdGPUCreate(t *terminal.Terminal, gpuCreateStore GPUCreateStore) *cobra
203204

204205
// If no types provided, use search filters (or defaults) to find suitable GPUs
205206
if len(types) == 0 {
207+
if dryRun {
208+
return runDryRun(t, gpuCreateStore, &filters)
209+
}
210+
206211
types, err = getFilteredInstanceTypes(gpuCreateStore, &filters)
207212
if err != nil {
208213
return breverrors.WrapAndTrace(err)
@@ -256,6 +261,7 @@ func NewCmdGPUCreate(t *terminal.Terminal, gpuCreateStore GPUCreateStore) *cobra
256261
cmd.Flags().BoolVarP(&detached, "detached", "d", false, "Don't wait for instances to be ready")
257262
cmd.Flags().IntVar(&timeout, "timeout", 300, "Timeout in seconds for each instance to become ready")
258263
cmd.Flags().StringVarP(&startupScript, "startup-script", "s", "", "Startup script to run on instance (string or @filepath)")
264+
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Show matching instance types without creating anything")
259265

260266
// Search filter flags (same as brev search, used when --type is not specified)
261267
cmd.Flags().StringVarP(&filters.gpuName, "gpu-name", "g", "", "Filter by GPU name (e.g., A100, H100)")
@@ -312,22 +318,17 @@ func parseStartupScript(value string) (string, error) {
312318
return value, nil
313319
}
314320

315-
// getFilteredInstanceTypes fetches GPU instance types using user-provided filters
316-
// merged with defaults. When a filter flag is not set, the default value is used.
317-
func getFilteredInstanceTypes(s GPUCreateStore, filters *searchFilterFlags) ([]InstanceSpec, error) {
321+
// searchInstances fetches and filters GPU instances using user-provided filters merged with defaults
322+
func searchInstances(s GPUCreateStore, filters *searchFilterFlags) ([]gpusearch.GPUInstanceInfo, float64, error) {
318323
response, err := s.GetInstanceTypes()
319324
if err != nil {
320-
return nil, breverrors.WrapAndTrace(err)
325+
return nil, 0, breverrors.WrapAndTrace(err)
321326
}
322327

323328
if response == nil || len(response.Items) == 0 {
324-
return nil, nil
329+
return nil, 0, nil
325330
}
326331

327-
// Merge user-provided filters with defaults
328-
gpuName := filters.gpuName
329-
provider := filters.provider
330-
minVRAM := filters.minVRAM
331332
minTotalVRAM := orDefault(filters.minTotalVRAM, defaultMinTotalVRAM)
332333
minCapability := orDefault(filters.minCapability, defaultMinCapability)
333334
minDisk := orDefault(filters.minDisk, defaultMinDisk)
@@ -341,10 +342,21 @@ func getFilteredInstanceTypes(s GPUCreateStore, filters *searchFilterFlags) ([]I
341342
}
342343

343344
instances := gpusearch.ProcessInstances(response.Items)
344-
filtered := gpusearch.FilterInstances(instances, gpuName, provider, minVRAM, minTotalVRAM, minCapability, minDisk, maxBootTime,
345-
filters.stoppable, filters.rebootable, filters.flexPorts)
345+
filtered := gpusearch.FilterInstances(instances, filters.gpuName, filters.provider, filters.minVRAM,
346+
minTotalVRAM, minCapability, minDisk, maxBootTime, filters.stoppable, filters.rebootable, filters.flexPorts)
346347
gpusearch.SortInstances(filtered, sortBy, filters.descending)
347348

349+
return filtered, minDisk, nil
350+
}
351+
352+
// getFilteredInstanceTypes fetches GPU instance types using user-provided filters
353+
// merged with defaults. When a filter flag is not set, the default value is used.
354+
func getFilteredInstanceTypes(s GPUCreateStore, filters *searchFilterFlags) ([]InstanceSpec, error) {
355+
filtered, minDisk, err := searchInstances(s, filters)
356+
if err != nil {
357+
return nil, breverrors.WrapAndTrace(err)
358+
}
359+
348360
var specs []InstanceSpec
349361
for _, inst := range filtered {
350362
diskGB := inst.DiskMin
@@ -357,6 +369,17 @@ func getFilteredInstanceTypes(s GPUCreateStore, filters *searchFilterFlags) ([]I
357369
return specs, nil
358370
}
359371

372+
// runDryRun shows the instance types that would be used without creating anything
373+
func runDryRun(t *terminal.Terminal, s GPUCreateStore, filters *searchFilterFlags) error {
374+
filtered, _, err := searchInstances(s, filters)
375+
if err != nil {
376+
return breverrors.WrapAndTrace(err)
377+
}
378+
379+
piped := isStdoutPiped()
380+
return gpusearch.DisplayResults(t, filtered, false, piped)
381+
}
382+
360383
// orDefault returns val if it's non-zero, otherwise returns def
361384
func orDefault(val, def float64) float64 {
362385
if val > 0 {

pkg/cmd/gpusearch/gpusearch.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, provider
271271
SortInstances(filtered, sortBy, descending)
272272

273273
// Display results
274-
return displayResults(t, filtered, jsonOutput, piped)
274+
return DisplayResults(t, filtered, jsonOutput, piped)
275275
}
276276

277277
// validateSortOption returns an error if sortBy is not a valid option
@@ -313,7 +313,7 @@ func setTargetDisks(instances []GPUInstanceInfo, minDisk float64) {
313313
}
314314

315315
// displayResults renders the GPU instances in the appropriate format
316-
func displayResults(t *terminal.Terminal, instances []GPUInstanceInfo, jsonOutput, piped bool) error {
316+
func DisplayResults(t *terminal.Terminal, instances []GPUInstanceInfo, jsonOutput, piped bool) error {
317317
if jsonOutput {
318318
return displayGPUJSON(instances)
319319
}

0 commit comments

Comments
 (0)