diff options
author | Valentin Rothberg <rothberg@redhat.com> | 2021-07-26 11:55:33 +0200 |
---|---|---|
committer | Valentin Rothberg <rothberg@redhat.com> | 2021-07-28 14:46:15 +0200 |
commit | 4df6e31ccbad8dd7800e413a0377fa0d1a0774ce (patch) | |
tree | 4b81a37fa61e156d871d98f6874d2afe0ba1ab19 /pkg/bindings/images/build.go | |
parent | a5de8314188d7376f645d8ac6c6f7a6f685b6a45 (diff) | |
download | podman-4df6e31ccbad8dd7800e413a0377fa0d1a0774ce.tar.gz podman-4df6e31ccbad8dd7800e413a0377fa0d1a0774ce.tar.bz2 podman-4df6e31ccbad8dd7800e413a0377fa0d1a0774ce.zip |
remote build: fix streaming and error handling
Address a number of issues in the streaming logic in remote build, most
importantly an error in using buffered channels on the server side.
The pattern below does not guarantee that the channel is entirely read
before the context fires.
for {
select {
case <- bufferedChannel:
...
case <- ctx.Done():
...
}
}
Fixes: #10154
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
Diffstat (limited to 'pkg/bindings/images/build.go')
-rw-r--r-- | pkg/bindings/images/build.go | 38 |
1 files changed, 23 insertions, 15 deletions
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 142204f27..a35f461a7 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -391,42 +391,50 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO dec := json.NewDecoder(body) var id string - var mErr error for { var s struct { Stream string `json:"stream,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 { + // 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(): - return &entities.BuildReport{ID: id}, mErr + return &entities.BuildReport{ID: id}, nil default: // 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 { case s.Stream != "": - stdout.Write([]byte(s.Stream)) - if iidRegex.Match([]byte(s.Stream)) { + raw := []byte(s.Stream) + stdout.Write(raw) + if iidRegex.Match(raw) { id = strings.TrimSuffix(s.Stream, "\n") } 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: 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) { |