Repository: moby/moby
File: daemon/server/router/container/container_routes.go (Go)

Code before:
	"github.com/moby/moby/api/types/mount"
	"github.com/moby/moby/api/types/network"
	"github.com/moby/moby/api/types/versions"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"go.opentelemetry.io/otel"
	"golang.org/x/net/websocket"
)

func (c *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	if err := httputils.CheckForJSON(r); err != nil {
		return err
	}

	// FIXME(thaJeztah): change this to unmarshal just [container.Config]:
	// The commit endpoint accepts a [container.Config], but the decoder uses a
	// [container.CreateRequest], which is a superset, and also contains
	// [container.HostConfig] and [network.NetworkConfig]. Those structs
	// are discarded here, but decoder.DecodeConfig also performs validation,
	// so a request containing those additional fields would result in a
	// validation error.
	config, _, _, err := c.decoder.DecodeConfig(r.Body)
	if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty.
		return err
	}

	ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag"))
	if err != nil {
		return errdefs.InvalidParameter(err)
	}

	imgID, err := c.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{
		Pause:   httputils.BoolValueOrDefault(r, "pause", true), // TODO(dnephin): remove pause arg, and always pause in backend
		Tag:     ref,
		Author:  r.Form.Get("author"),
		Comment: r.Form.Get("comment"),
		Config:  config,
		Changes: r.Form["changes"],
	})
	if err != nil {
		return err
	}

	return httputils.WriteJSON(w, http.StatusCreated, &container.CommitResponse{ID: imgID})
}

func (c *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

Review comment (bug):
This one had some additional logic for validation:

https://github.com/moby/moby/blob/b48637310530e17c16ef7e59a1802e6cf76e7874/runconfig/config.go#L20-L78

Looks like most of them were related to `HostConfig` (which isn't present in the commit request though), but there are some related to `Config` like:

- https://github.com/moby/moby/blob/b48637310530e17c16ef7e59a1802e6cf76e7874/runconfig/config.go#L74-L76 (making sure Volumes map isn't nil, but perhaps this one isn't needed anymore)
- https://github.com/moby/moby/blob/afd6487b2e1e8be17856e7fe1eac09ac08c3d2f1/runconfig/hostconfig_unix.go#L20-L22
- https://github.com/moby/moby/blob/afd6487b2e1e8be17856e7fe1eac09ac08c3d2f1/runconfig/hostconfig.go#L39-L41 (for container-mode networking only)

But I assume these would fail at later point of the commit anyway, so perhaps it's not worth to bother with them?

Code after:
	"github.com/moby/moby/api/types/mount"
	"github.com/moby/moby/api/types/network"
	"github.com/moby/moby/api/types/versions"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"go.opentelemetry.io/otel"
	"golang.org/x/net/websocket"
)

// commitRequest may contain an optional [container.Config].
type commitRequest struct {
	*container.Config
}

// decodeCommitRequest decodes the request body and returns a [container.Config]
// if present, or nil otherwise. It returns an error when failing to decode
// due to invalid JSON, or if the request contains multiple JSON documents.
//
// The client posts a bare [*container.Config] (see [Client.ContainerCommit]
// and [container.CommitOptions]), but it may be empty / nil, in which case
// it must be ignored, and no overrides to be applied.
//
// [Client.ContainerCommit]: https://github.com/moby/moby/blob/c4afa7715715a1020e50b19ad60728c4479fb0a5/client/container_commit.go#L52
// [container.CommitOptions]: https://github.com/moby/moby/blob/c4afa7715715a1020e50b19ad60728c4479fb0a5/api/types/container/options.go#L30
func decodeCommitRequest(r *http.Request) (*container.Config, error) {
	var w commitRequest
	if err := httputils.ReadJSON(r, &w); err != nil {
		return nil, err
	}
	return w.Config, nil
}

func (c *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	config, err := decodeCommitRequest(r)
	if err != nil {
		return err
	}

	ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag"))
	if err != nil {
		return errdefs.InvalidParameter(err)
	}

	imgID, err := c.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{
		Pause:   httputils.BoolValueOrDefault(r, "pause", true), // TODO(dnephin): remove pause arg, and always pause in backend
		Tag:     ref,
		Author:  r.Form.Get("author"),