@@ -85,11 +85,17 @@ func (e *CommandExecutor) Execute(ctx context.Context, req *ExecuteRequest) *Com
8585
8686 // 3. Load existing state if any
8787 var state []byte
88+ logger .Info ("Looking for existing state" , slog .String ("unit_id" , unitID ))
8889 if meta , err := e .unitRepo .Get (ctx , unitID ); err == nil && meta != nil {
90+ logger .Info ("Unit found, downloading state..." )
8991 if stateData , err := e .store .Download (ctx , unitID ); err == nil {
9092 state = stateData
9193 logger .Info ("Loaded existing state" , slog .Int ("size" , len (state )))
94+ } else {
95+ logger .Warn ("Failed to download state" , slog .String ("error" , err .Error ()))
9296 }
97+ } else {
98+ logger .Info ("No existing unit/state found" , slog .String ("error" , fmt .Sprintf ("%v" , err )))
9399 }
94100
95101 // 4. Get terraform version from options or use default
@@ -140,7 +146,12 @@ func (e *CommandExecutor) Execute(ctx context.Context, req *ExecuteRequest) *Com
140146 if metadata ["AWS_REGION" ] == "" {
141147 metadata ["AWS_REGION" ] = "us-east-1"
142148 }
143- // Don't log that credentials are present - security best practice
149+ // Log that credentials are configured (not the values)
150+ logger .Info ("AWS credentials configured for sandbox" ,
151+ slog .String ("region" , metadata ["AWS_REGION" ]),
152+ slog .Int ("key_length" , len (awsKey )))
153+ } else {
154+ logger .Warn ("AWS_ACCESS_KEY_ID not set - AWS resources will fail" )
144155 }
145156
146157 // 8. Execute based on action
@@ -151,6 +162,8 @@ func (e *CommandExecutor) Execute(ctx context.Context, req *ExecuteRequest) *Com
151162 result = e .executeApply (ctx , logger , req , runID , unitID , archive , state , tfVersion , engine , workingDir , metadata , totalStart , false )
152163 case "destroy" :
153164 result = e .executeApply (ctx , logger , req , runID , unitID , archive , state , tfVersion , engine , workingDir , metadata , totalStart , true )
165+ case "benchmark" :
166+ result = e .executeBenchmark (ctx , logger , req , runID , unitID , archive , tfVersion , engine , workingDir , metadata , totalStart )
154167 default :
155168 result .Error = fmt .Sprintf ("Unknown action: %s" , req .Command .Action )
156169 }
@@ -303,12 +316,21 @@ func (e *CommandExecutor) executeApply(
303316 }
304317
305318 // Save the new state
319+ logger .Info ("Apply result received" ,
320+ slog .Int ("state_size" , len (applyResult .State )),
321+ slog .Int ("logs_size" , len (applyResult .Logs )),
322+ slog .Bool ("is_destroy" , isDestroy ))
323+
306324 if len (applyResult .State ) > 0 && ! isDestroy {
307325 if err := e .saveState (ctx , unitID , applyResult .State ); err != nil {
308326 logger .Warn ("Failed to save state" , slog .String ("error" , err .Error ()))
309327 } else {
310- logger .Info ("State saved" , slog .Int ("size" , len (applyResult .State )))
328+ logger .Info ("State saved successfully" ,
329+ slog .String ("unit_id" , unitID ),
330+ slog .Int ("size" , len (applyResult .State )))
311331 }
332+ } else if ! isDestroy {
333+ logger .Warn ("No state returned from apply - state will not persist!" )
312334 }
313335
314336 // For destroy, clean up the state
@@ -339,6 +361,125 @@ func (e *CommandExecutor) executeApply(
339361 return result
340362}
341363
364+ // executeBenchmark runs apply followed by destroy in a single flow
365+ // This keeps state in the sandbox and ensures resources are cleaned up
366+ func (e * CommandExecutor ) executeBenchmark (
367+ ctx context.Context ,
368+ logger * slog.Logger ,
369+ req * ExecuteRequest ,
370+ runID , unitID string ,
371+ archive []byte ,
372+ tfVersion , engine , workingDir string ,
373+ metadata map [string ]string ,
374+ totalStart time.Time ,
375+ ) * CommandResult {
376+ result := & CommandResult {
377+ Command : req .Command ,
378+ Success : false ,
379+ }
380+
381+ if e .sandbox == nil {
382+ result .Error = "Sandbox provider not configured"
383+ result .Timing .Total = time .Since (totalStart )
384+ return result
385+ }
386+
387+ // Generate a config version ID for the sandbox
388+ configVersionID := fmt .Sprintf ("cv-%s" , uuid .New ().String ()[:8 ])
389+
390+ logger .Info ("Starting benchmark: apply + destroy cycle" ,
391+ slog .String ("run_id" , runID ),
392+ slog .String ("engine" , engine ),
393+ slog .String ("version" , tfVersion ))
394+
395+ var allLogs strings.Builder
396+
397+ // Phase 1: Apply
398+ applyStart := time .Now ()
399+ applyReq := & sandbox.ApplyRequest {
400+ RunID : runID ,
401+ PlanID : uuid .New ().String (),
402+ OrgID : "github-benchmark" ,
403+ UnitID : unitID ,
404+ ConfigurationVersionID : configVersionID ,
405+ IsDestroy : false ,
406+ TerraformVersion : tfVersion ,
407+ Engine : engine ,
408+ WorkingDirectory : workingDir ,
409+ ConfigArchive : archive ,
410+ State : nil , // Fresh apply
411+ Metadata : metadata ,
412+ }
413+
414+ applyResult , err := e .sandbox .ExecuteApply (ctx , applyReq )
415+ result .Timing .Apply = time .Since (applyStart )
416+
417+ if err != nil {
418+ result .Error = fmt .Sprintf ("Apply phase failed: %v" , err )
419+ result .Timing .Total = time .Since (totalStart )
420+ logger .Error ("Benchmark apply failed" , slog .String ("error" , err .Error ()))
421+ return result
422+ }
423+
424+ allLogs .WriteString ("=== APPLY PHASE ===\n " )
425+ allLogs .WriteString (applyResult .Logs )
426+ allLogs .WriteString ("\n \n " )
427+
428+ logger .Info ("Benchmark apply completed" ,
429+ slog .Duration ("duration" , result .Timing .Apply ),
430+ slog .Int ("state_size" , len (applyResult .State )))
431+
432+ // Phase 2: Destroy (using state from apply)
433+ destroyStart := time .Now ()
434+ destroyReq := & sandbox.ApplyRequest {
435+ RunID : runID + "-destroy" ,
436+ PlanID : uuid .New ().String (),
437+ OrgID : "github-benchmark" ,
438+ UnitID : unitID ,
439+ ConfigurationVersionID : configVersionID ,
440+ IsDestroy : true ,
441+ TerraformVersion : tfVersion ,
442+ Engine : engine ,
443+ WorkingDirectory : workingDir ,
444+ ConfigArchive : archive ,
445+ State : applyResult .State , // Use state from apply
446+ Metadata : metadata ,
447+ }
448+
449+ destroyResult , err := e .sandbox .ExecuteApply (ctx , destroyReq )
450+ result .Timing .Destroy = time .Since (destroyStart )
451+
452+ if err != nil {
453+ result .Error = fmt .Sprintf ("Destroy phase failed (resources may be orphaned!): %v" , err )
454+ result .Timing .Total = time .Since (totalStart )
455+ logger .Error ("Benchmark destroy failed" , slog .String ("error" , err .Error ()))
456+ return result
457+ }
458+
459+ allLogs .WriteString ("=== DESTROY PHASE ===\n " )
460+ allLogs .WriteString (destroyResult .Logs )
461+
462+ logger .Info ("Benchmark destroy completed" ,
463+ slog .Duration ("duration" , result .Timing .Destroy ))
464+
465+ // Success!
466+ result .Success = true
467+ result .Output = allLogs .String ()
468+ result .Summary = fmt .Sprintf ("Apply: %.2fs | Destroy: %.2fs | Total: %.2fs" ,
469+ result .Timing .Apply .Seconds (),
470+ result .Timing .Destroy .Seconds (),
471+ time .Since (totalStart ).Seconds ())
472+
473+ result .Timing .Total = time .Since (totalStart )
474+
475+ logger .Info ("Benchmark completed successfully" ,
476+ slog .Duration ("apply" , result .Timing .Apply ),
477+ slog .Duration ("destroy" , result .Timing .Destroy ),
478+ slog .Duration ("total" , result .Timing .Total ))
479+
480+ return result
481+ }
482+
342483func (e * CommandExecutor ) saveState (ctx context.Context , unitID string , state []byte ) error {
343484 // Check if unit exists, create if not
344485 if _ , err := e .unitRepo .Get (ctx , unitID ); err != nil {
0 commit comments