mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

Fixes: https://github.com/containers/podman/issues/19947 Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
94 lines
2.4 KiB
Go
94 lines
2.4 KiB
Go
package containers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/containers/podman/v4/pkg/bindings"
|
|
"github.com/containers/podman/v4/pkg/bindings/images"
|
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
|
"github.com/containers/storage/pkg/regexp"
|
|
)
|
|
|
|
var iidRegex = regexp.Delayed(`^[0-9a-f]{12}`)
|
|
|
|
// Commit creates a container image from a container. The container is defined by nameOrID. Use
|
|
// the CommitOptions for finer grain control on characteristics of the resulting image.
|
|
func Commit(ctx context.Context, nameOrID string, options *CommitOptions) (entities.IDResponse, error) {
|
|
if options == nil {
|
|
options = new(CommitOptions)
|
|
}
|
|
id := entities.IDResponse{}
|
|
conn, err := bindings.GetClient(ctx)
|
|
if err != nil {
|
|
return id, err
|
|
}
|
|
params, err := options.ToParams()
|
|
if err != nil {
|
|
return entities.IDResponse{}, err
|
|
}
|
|
params.Set("container", nameOrID)
|
|
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/commit", params, nil)
|
|
if err != nil {
|
|
return id, err
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
if !response.IsSuccess() {
|
|
return id, response.Process(err)
|
|
}
|
|
|
|
if !options.GetStream() {
|
|
return id, response.Process(&id)
|
|
}
|
|
stderr := os.Stderr
|
|
body := response.Body.(io.Reader)
|
|
dec := json.NewDecoder(body)
|
|
for {
|
|
var s images.BuildResponse
|
|
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 id, nil
|
|
default:
|
|
// non-blocking select
|
|
}
|
|
|
|
if err := dec.Decode(&s); err != nil {
|
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
|
return id, fmt.Errorf("server probably quit: %w", err)
|
|
}
|
|
// EOF means the stream is over in which case we need
|
|
// to have read the id.
|
|
if errors.Is(err, io.EOF) && id.ID != "" {
|
|
break
|
|
}
|
|
return id, fmt.Errorf("decoding stream: %w", err)
|
|
}
|
|
|
|
switch {
|
|
case s.Stream != "":
|
|
raw := []byte(s.Stream)
|
|
stderr.Write(raw)
|
|
if iidRegex.Match(raw) {
|
|
id.ID = strings.TrimSuffix(s.Stream, "\n")
|
|
return id, nil
|
|
}
|
|
case s.Error != nil:
|
|
// If there's an error, return directly. The stream
|
|
// will be closed on return.
|
|
return id, errors.New(s.Error.Message)
|
|
default:
|
|
return id, errors.New("failed to parse build results stream, unexpected input")
|
|
}
|
|
}
|
|
return id, response.Process(&id)
|
|
}
|