@@ -2,6 +2,7 @@ package main
22
33import (
44 "archive/tar"
5+ "bufio"
56 "encoding/json"
67 "errors"
78 "fmt"
@@ -15,11 +16,12 @@ import (
1516 "golang.org/x/exp/slices"
1617
1718 "github.com/sirupsen/logrus"
19+
20+ "github.com/osbuild/images/pkg/osbuild"
1821)
1922
2023var (
2124 supportedBuildContentTypes = []string {"application/x-tar" }
22- osbuildBinary = "osbuild"
2325)
2426
2527var (
@@ -39,63 +41,96 @@ func (wf *writeFlusher) Write(p []byte) (n int, err error) {
3941 return n , err
4042}
4143
42- func runOsbuild (logger * logrus.Logger , buildDir string , control * controlJSON , output io.Writer ) (string , error ) {
43- flusher , ok := output .(http.Flusher )
44- if ! ok {
45- return "" , fmt .Errorf ("cannot stream the output" )
46- }
47- // stream output over http
48- wf := writeFlusher {w : output , flusher : flusher }
44+ func runOSBuild (logger * logrus.Logger , buildDir string , control * controlJSON , output io.Writer ) (string , error ) {
4945 // note that the "filepath.Clean()" is here for gosec:G304
5046 logfPath := filepath .Clean (filepath .Join (buildDir , "build.log" ))
51- // and also write to our internal log
5247 logf , err := os .Create (logfPath )
5348 if err != nil {
5449 return "" , fmt .Errorf ("cannot create log file: %v" , err )
5550 }
5651 defer logf .Close ()
5752
53+ manifest , err := os .ReadFile (filepath .Join (buildDir , "manifest.json" ))
54+ if err != nil {
55+ return "" , fmt .Errorf ("cannot read manifest file: %w" , err )
56+ }
57+
5858 // use multi writer to get same output for stream and log
59- mw := io .MultiWriter (& wf , logf )
6059 outputDir := filepath .Join (buildDir , "output" )
6160 storeDir := filepath .Join (buildDir , "osbuild-store" )
62- cmd := exec .Command (osbuildBinary )
63- cmd .Stdout = mw
64- cmd .Stderr = mw
65- for _ , exp := range control .Exports {
66- cmd .Args = append (cmd .Args , []string {"--export" , exp }... )
61+
62+ // MonitorFile needs an *os.File:
63+ // MonitorFile -> pipe -> bufio reader -> writeFlusher
64+ rPipe , wPipe , err := os .Pipe ()
65+ if err != nil {
66+ return "" , fmt .Errorf ("cannot create pipe for monitor file: %w" , err )
6767 }
68- cmd .Env = append (cmd .Env , control .Environments ... )
69- cmd .Args = append (cmd .Args , []string {"--output-dir" , outputDir }... )
70- cmd .Args = append (cmd .Args , []string {"--store" , storeDir }... )
71- cmd .Args = append (cmd .Args , "--json" )
72- cmd .Args = append (cmd .Args , filepath .Join (buildDir , "manifest.json" ))
68+ defer rPipe .Close ()
69+ defer wPipe .Close ()
70+
71+ cmd := osbuild .NewOSBuildCmd (manifest , & osbuild.OSBuildOptions {
72+ StoreDir : storeDir ,
73+ OutputDir : outputDir ,
74+ Exports : control .Exports ,
75+ ExtraEnv : control .Environments ,
76+ BuildLog : logf ,
77+ Stdout : os .Stdout ,
78+ Monitor : osbuild .MonitorJSONSeq ,
79+ MonitorFile : wPipe ,
80+ })
7381 if err := cmd .Start (); err != nil {
7482 return "" , err
7583 }
84+ flusher , ok := output .(http.Flusher )
85+ if ! ok {
86+ return "" , fmt .Errorf ("cannot stream the output, output needs to be http.Flusher" )
87+ }
88+
89+ wf := writeFlusher {w : output , flusher : flusher }
90+ reader := bufio .NewReader (rPipe )
91+ wPipe .Close ()
92+ for {
93+ line , err := reader .ReadBytes ('\n' )
94+ if err != nil && err != io .EOF {
95+ return "" , fmt .Errorf ("cannot read bytes from monitor pipe: %w" , err )
96+ }
97+
98+ // handle the case where we have a (final) line without a \n, here we
99+ // get a EOF but still have line content to process before we can exit
100+ // Note that we have not seen this in practise, its for compat with the
101+ // previous bufio.Scanner implementation that had this behavior.
102+ if err == io .EOF && len (line ) == 0 {
103+ break
104+ }
105+
106+ _ , err = wf .Write (line )
107+ if err != nil {
108+ return "" , fmt .Errorf ("cannot write bytes to writeFlusher: %w" , err )
109+ }
110+ }
76111
77112 if err := cmd .Wait (); err != nil {
78113 // we cannot use "http.Error()" here because the http
79114 // header was already set to "201" when we started streaming
80- _ , _ = mw .Write ([]byte (fmt .Sprintf ("cannot run osbuild: %v" , err )))
115+ _ , _ = wf .Write ([]byte (fmt .Sprintf ("cannot run osbuild: %v" , err )))
81116 return "" , err
82117 }
83118
84119 // the result is put into a tar because we get sparse file support for free this way
85120 // #nosec G204
86- cmd = exec .Command (
121+ tarCmd : = exec .Command (
87122 "tar" ,
88123 "--exclude=output/output.tar" ,
89124 "-Scf" ,
90125 filepath .Join (outputDir , "output.tar" ),
91126 "output" ,
92127 )
93- cmd .Dir = buildDir
94- out , err := cmd .CombinedOutput ()
128+ tarCmd .Dir = buildDir
129+ out , err := tarCmd .CombinedOutput ()
95130 if err != nil {
96131 err = fmt .Errorf ("cannot tar output directory: %w, output:\n %s" , err , out )
97132 logger .Errorf ("%v" , err )
98- _ , _ = mw .Write ([]byte (err .Error ()))
133+ _ , _ = wf .Write ([]byte (err .Error ()))
99134 return "" , err
100135 }
101136 if len (out ) > 0 {
@@ -291,12 +326,12 @@ func handleBuild(logger *logrus.Logger, config *Config) http.Handler {
291326
292327 // run osbuild and stream the output to the client
293328 buildResult := newBuildResult (config )
294- _ , err = runOsbuild (logger , buildDir , control , w )
329+ _ , err = runOSBuild (logger , buildDir , control , w )
295330 if werr := buildResult .Mark (err ); werr != nil {
296331 logger .Errorf ("cannot write result file %v" , werr )
297332 }
298333 if err != nil {
299- logger .Errorf ("canot run osbuild: %v" , err )
334+ logger .Errorf ("cannot run osbuild: %v" , err )
300335 return
301336 }
302337 },
0 commit comments