11package steamcmd
22
33import (
4+ "bufio"
5+ "bytes"
46 "context"
57 "fmt"
8+ "io"
69 "os"
710 "os/exec"
811 "path/filepath"
912 "strings"
13+ "sync"
1014
1115 "k8s.io/klog/v2"
1216)
@@ -103,10 +107,7 @@ func (c *Client) ApplyUpdate(ctx context.Context) error {
103107 return fmt .Errorf ("failed to create update script: %w" , err )
104108 }
105109
106- cmd := exec .CommandContext (ctx , c .steamCMDPath + "/steamcmd.sh" , "+runscript" , scriptPath )
107- output , err := cmd .CombinedOutput ()
108-
109- klog .V (2 ).Infof ("SteamCMD update output: %s" , string (output ))
110+ output , err := c .runSteamCMD (ctx , scriptPath , "update" )
110111
111112 // Check for 0x6 error state and attempt recovery
112113 if c .hasState0x6Error (output ) {
@@ -117,9 +118,7 @@ func (c *Client) ApplyUpdate(ctx context.Context) error {
117118
118119 // Retry update after clearing steamapps
119120 klog .Info ("Retrying update after clearing steamapps..." )
120- cmd = exec .CommandContext (ctx , c .steamCMDPath + "/steamcmd.sh" , "+runscript" , scriptPath )
121- output , err = cmd .CombinedOutput ()
122- klog .V (2 ).Infof ("SteamCMD retry output: %s" , string (output ))
121+ output , err = c .runSteamCMD (ctx , scriptPath , "update-retry" )
123122
124123 if err != nil {
125124 return fmt .Errorf ("steamcmd update failed after 0x6 recovery: %w, output: %s" , err , string (output ))
@@ -144,10 +143,7 @@ func (c *Client) ValidateUpdate(ctx context.Context) error {
144143 return fmt .Errorf ("failed to create validate script: %w" , err )
145144 }
146145
147- cmd := exec .CommandContext (ctx , c .steamCMDPath + "/steamcmd.sh" , "+runscript" , scriptPath )
148- output , err := cmd .CombinedOutput ()
149-
150- klog .V (2 ).Infof ("SteamCMD validation output: %s" , string (output ))
146+ output , err := c .runSteamCMD (ctx , scriptPath , "validate" )
151147
152148 if err != nil {
153149 return fmt .Errorf ("validation failed: %w, output: %s" , err , string (output ))
@@ -320,6 +316,77 @@ quit
320316 return "" , fmt .Errorf ("buildid not found in app_info output" )
321317}
322318
319+ // runSteamCMD executes steamcmd scripts while streaming progress output.
320+ func (c * Client ) runSteamCMD (ctx context.Context , scriptPath , stage string ) ([]byte , error ) {
321+ cmd := exec .CommandContext (ctx , c .steamCMDPath + "/steamcmd.sh" , "+runscript" , scriptPath )
322+
323+ stdout , err := cmd .StdoutPipe ()
324+ if err != nil {
325+ return nil , fmt .Errorf ("failed to attach steamcmd stdout: %w" , err )
326+ }
327+
328+ stderr , err := cmd .StderrPipe ()
329+ if err != nil {
330+ return nil , fmt .Errorf ("failed to attach steamcmd stderr: %w" , err )
331+ }
332+
333+ if err := cmd .Start (); err != nil {
334+ return nil , fmt .Errorf ("failed to start steamcmd: %w" , err )
335+ }
336+
337+ var combined bytes.Buffer
338+ var combinedMu sync.Mutex
339+ var wg sync.WaitGroup
340+ logStream := func (r io.Reader , stream string ) {
341+ defer wg .Done ()
342+ scanner := bufio .NewScanner (r )
343+ for scanner .Scan () {
344+ line := scanner .Text ()
345+ trimmed := strings .TrimSpace (line )
346+ combinedMu .Lock ()
347+ combined .WriteString (line )
348+ combined .WriteByte ('\n' )
349+ combinedMu .Unlock ()
350+
351+ if trimmed == "" {
352+ continue
353+ }
354+
355+ if c .shouldLogProgress (trimmed ) {
356+ klog .Infof ("[%s:%s] %s" , stage , stream , trimmed )
357+ } else {
358+ klog .V (4 ).Infof ("[%s:%s] %s" , stage , stream , trimmed )
359+ }
360+ }
361+
362+ if err := scanner .Err (); err != nil {
363+ klog .Warningf ("[%s:%s] error while reading steamcmd output: %v" , stage , stream , err )
364+ }
365+ }
366+
367+ wg .Add (2 )
368+ go logStream (stdout , "stdout" )
369+ go logStream (stderr , "stderr" )
370+
371+ cmdErr := cmd .Wait ()
372+ wg .Wait ()
373+
374+ return combined .Bytes (), cmdErr
375+ }
376+
377+ // shouldLogProgress decides whether a steamcmd line should be surfaced at info level.
378+ func (c * Client ) shouldLogProgress (line string ) bool {
379+ lower := strings .ToLower (line )
380+ return strings .Contains (lower , "update state" ) ||
381+ strings .Contains (lower , "progress" ) ||
382+ strings .Contains (lower , "downloading" ) ||
383+ strings .Contains (lower , "install" ) ||
384+ strings .Contains (lower , "validat" ) ||
385+ strings .Contains (lower , "success" ) ||
386+ strings .Contains (lower , "error" ) ||
387+ strings .Contains (lower , "app_update" )
388+ }
389+
323390// parseUpdateStatus parses SteamCMD output to determine if an update is needed
324391// DEPRECATED: This method triggers actual downloads. Use getInstalledBuildID/getLatestBuildID instead.
325392func (c * Client ) parseUpdateStatus (output []byte ) bool {
0 commit comments