mirror of
https://github.com/containers/podman.git
synced 2025-06-20 17:13:43 +08:00
Merge pull request #11067 from vrothberg/fix-10154-2
remote build: fix streaming and error handling
This commit is contained in:
@ -393,16 +393,16 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer auth.RemoveAuthfile(authfile)
|
defer auth.RemoveAuthfile(authfile)
|
||||||
|
|
||||||
// Channels all mux'ed in select{} below to follow API build protocol
|
// Channels all mux'ed in select{} below to follow API build protocol
|
||||||
stdout := channel.NewWriter(make(chan []byte, 1))
|
stdout := channel.NewWriter(make(chan []byte))
|
||||||
defer stdout.Close()
|
defer stdout.Close()
|
||||||
|
|
||||||
auxout := channel.NewWriter(make(chan []byte, 1))
|
auxout := channel.NewWriter(make(chan []byte))
|
||||||
defer auxout.Close()
|
defer auxout.Close()
|
||||||
|
|
||||||
stderr := channel.NewWriter(make(chan []byte, 1))
|
stderr := channel.NewWriter(make(chan []byte))
|
||||||
defer stderr.Close()
|
defer stderr.Close()
|
||||||
|
|
||||||
reporter := channel.NewWriter(make(chan []byte, 1))
|
reporter := channel.NewWriter(make(chan []byte))
|
||||||
defer reporter.Close()
|
defer reporter.Close()
|
||||||
|
|
||||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
@ -529,7 +529,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
enc := json.NewEncoder(body)
|
enc := json.NewEncoder(body)
|
||||||
enc.SetEscapeHTML(true)
|
enc.SetEscapeHTML(true)
|
||||||
loop:
|
|
||||||
for {
|
for {
|
||||||
m := struct {
|
m := struct {
|
||||||
Stream string `json:"stream,omitempty"`
|
Stream string `json:"stream,omitempty"`
|
||||||
@ -543,13 +543,13 @@ loop:
|
|||||||
stderr.Write([]byte(err.Error()))
|
stderr.Write([]byte(err.Error()))
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
case e := <-auxout.Chan():
|
case e := <-reporter.Chan():
|
||||||
m.Stream = string(e)
|
m.Stream = string(e)
|
||||||
if err := enc.Encode(m); err != nil {
|
if err := enc.Encode(m); err != nil {
|
||||||
stderr.Write([]byte(err.Error()))
|
stderr.Write([]byte(err.Error()))
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
case e := <-reporter.Chan():
|
case e := <-auxout.Chan():
|
||||||
m.Stream = string(e)
|
m.Stream = string(e)
|
||||||
if err := enc.Encode(m); err != nil {
|
if err := enc.Encode(m); err != nil {
|
||||||
stderr.Write([]byte(err.Error()))
|
stderr.Write([]byte(err.Error()))
|
||||||
@ -561,8 +561,8 @@ loop:
|
|||||||
logrus.Warnf("Failed to json encode error %v", err)
|
logrus.Warnf("Failed to json encode error %v", err)
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
|
return
|
||||||
case <-runCtx.Done():
|
case <-runCtx.Done():
|
||||||
flush()
|
|
||||||
if success {
|
if success {
|
||||||
if !utils.IsLibpodRequest(r) {
|
if !utils.IsLibpodRequest(r) {
|
||||||
m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
|
m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
|
||||||
@ -579,7 +579,8 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break loop
|
flush()
|
||||||
|
return
|
||||||
case <-r.Context().Done():
|
case <-r.Context().Done():
|
||||||
cancel()
|
cancel()
|
||||||
logrus.Infof("Client disconnect reported for build %q / %q.", registry, query.Dockerfile)
|
logrus.Infof("Client disconnect reported for build %q / %q.", registry, query.Dockerfile)
|
||||||
|
@ -327,7 +327,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
|
|||||||
uri := fmt.Sprintf("http://d/v%d.%d.%d/libpod"+endpoint, params...)
|
uri := fmt.Sprintf("http://d/v%d.%d.%d/libpod"+endpoint, params...)
|
||||||
logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri)
|
logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri)
|
||||||
|
|
||||||
req, err := http.NewRequest(httpMethod, uri, httpBody)
|
req, err := http.NewRequestWithContext(context.WithValue(context.Background(), clientKey, c), httpMethod, uri, httpBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -337,7 +337,6 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
|
|||||||
for key, val := range header {
|
for key, val := range header {
|
||||||
req.Header.Set(key, val)
|
req.Header.Set(key, val)
|
||||||
}
|
}
|
||||||
req = req.WithContext(context.WithValue(context.Background(), clientKey, c))
|
|
||||||
// Give the Do three chances in the case of a comm/service hiccup
|
// Give the Do three chances in the case of a comm/service hiccup
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
response, err = c.Client.Do(req) // nolint
|
response, err = c.Client.Do(req) // nolint
|
||||||
|
@ -391,42 +391,50 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
|
|||||||
dec := json.NewDecoder(body)
|
dec := json.NewDecoder(body)
|
||||||
|
|
||||||
var id string
|
var id string
|
||||||
var mErr error
|
|
||||||
for {
|
for {
|
||||||
var s struct {
|
var s struct {
|
||||||
Stream string `json:"stream,omitempty"`
|
Stream string `json:"stream,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
if err := dec.Decode(&s); err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
if mErr == nil && id == "" {
|
|
||||||
mErr = errors.New("stream dropped, unexpected failure")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s.Error = err.Error() + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
// FIXME(vrothberg): it seems we always hit the EOF case below,
|
||||||
|
// even when the server quit but it seems desirable to
|
||||||
|
// distinguish a proper build from a transient EOF.
|
||||||
case <-response.Request.Context().Done():
|
case <-response.Request.Context().Done():
|
||||||
return &entities.BuildReport{ID: id}, mErr
|
return &entities.BuildReport{ID: id}, nil
|
||||||
default:
|
default:
|
||||||
// non-blocking select
|
// non-blocking select
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := dec.Decode(&s); err != nil {
|
||||||
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return nil, errors.Wrap(err, "server probably quit")
|
||||||
|
}
|
||||||
|
// EOF means the stream is over in which case we need
|
||||||
|
// to have read the id.
|
||||||
|
if errors.Is(err, io.EOF) && id != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return &entities.BuildReport{ID: id}, errors.Wrap(err, "decoding stream")
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case s.Stream != "":
|
case s.Stream != "":
|
||||||
stdout.Write([]byte(s.Stream))
|
raw := []byte(s.Stream)
|
||||||
if iidRegex.Match([]byte(s.Stream)) {
|
stdout.Write(raw)
|
||||||
|
if iidRegex.Match(raw) {
|
||||||
id = strings.TrimSuffix(s.Stream, "\n")
|
id = strings.TrimSuffix(s.Stream, "\n")
|
||||||
}
|
}
|
||||||
case s.Error != "":
|
case s.Error != "":
|
||||||
mErr = errors.New(s.Error)
|
// If there's an error, return directly. The stream
|
||||||
|
// will be closed on return.
|
||||||
|
return &entities.BuildReport{ID: id}, errors.New(s.Error)
|
||||||
default:
|
default:
|
||||||
return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input")
|
return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &entities.BuildReport{ID: id}, mErr
|
return &entities.BuildReport{ID: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nTar(excludes []string, sources ...string) (io.ReadCloser, error) {
|
func nTar(excludes []string, sources ...string) (io.ReadCloser, error) {
|
||||||
|
@ -33,6 +33,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
|
|||||||
r, err := NewLibpodImageRuntime(facts.FlagSet, facts)
|
r, err := NewLibpodImageRuntime(facts.FlagSet, facts)
|
||||||
return r, err
|
return r, err
|
||||||
case entities.TunnelMode:
|
case entities.TunnelMode:
|
||||||
|
// TODO: look at me!
|
||||||
ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
|
ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
|
||||||
return &tunnel.ImageEngine{ClientCtx: ctx}, err
|
return &tunnel.ImageEngine{ClientCtx: ctx}, err
|
||||||
}
|
}
|
||||||
|
@ -749,16 +749,9 @@ RUN echo $random_string
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
run_podman 125 build -t build_test --pull-never $tmpdir
|
run_podman 125 build -t build_test --pull-never $tmpdir
|
||||||
# FIXME: this is just ridiculous. Even after #10030 and #10034, Ubuntu
|
|
||||||
# remote *STILL* flakes this test! It fails with the correct exit status,
|
|
||||||
# but the error output is 'Error: stream dropped, unexpected failure'
|
|
||||||
# Let's just stop checking on podman-remote. As long as it exits 125,
|
|
||||||
# we're happy.
|
|
||||||
if ! is_remote; then
|
|
||||||
is "$output" \
|
is "$output" \
|
||||||
".*Error: error creating build container: quay.io/libpod/nosuchimage:nosuchtag: image not known" \
|
".*Error: error creating build container: quay.io/libpod/nosuchimage:nosuchtag: image not known" \
|
||||||
"--pull-never fails with expected error message"
|
"--pull-never fails with expected error message"
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "podman build --logfile test" {
|
@test "podman build --logfile test" {
|
||||||
|
Reference in New Issue
Block a user