ronantakizawa/codereview-bench huggingface dataset:
Repository: moby/moby
File: layer/migration.go (Go)

Code before:
			}
		}()
	}

	// Create new roLayer
	layer := &roLayer{
		parent:         p,
		cacheID:        graphID,
		referenceCount: 1,
		layerStore:     ls,
		references:     map[Layer]struct{}{},
		diffID:         diffID,
		size:           size,
		chainID:        createChainIDFromParent(parent, diffID),
	}

	ls.layerL.Lock()
	defer ls.layerL.Unlock()

	if existingLayer := ls.get(layer.chainID); existingLayer != nil {
		// Set error for cleanup, but do not return
		err = errors.New("layer already exists")
		return existingLayer.getReference(), nil
	}

	tx, err := ls.store.StartTransaction()
	if err != nil {
		return nil, err
	}

	defer func() {
		if err != nil {
			log.G(context.TODO()).Debugf("Cleaning up transaction after failed migration for %s: %v", graphID, err)
			if err := tx.Cancel(); err != nil {
				log.G(context.TODO()).Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
			}
		}
	}()

	tsw, err := tx.TarSplitWriter(false)
	if err != nil {
		return nil, err
	}
	defer tsw.Close()
	tdf, err := os.Open(tarDataFile)
	if err != nil {
		return nil, err
	}
	defer tdf.Close()
	_, err = io.Copy(tsw, tdf)
	if err != nil {

Review comment (suggestion):
There's a few in here that use `:=` with the presumably-shared `cleanupErr` that _won't_ be using the shared variable, right? https://go.dev/play/p/8Weqd7BZJW6

Code after:
			}
		}()
	}

	// Create new roLayer
	layer := &roLayer{
		parent:         p,
		cacheID:        graphID,
		referenceCount: 1,
		layerStore:     ls,
		references:     map[Layer]struct{}{},
		diffID:         diffID,
		size:           size,
		chainID:        createChainIDFromParent(parent, diffID),
	}

	ls.layerL.Lock()
	defer ls.layerL.Unlock()

	if existingLayer := ls.get(layer.chainID); existingLayer != nil {
		// Set error for cleanup, but do not return
		cleanupErr = errors.New("layer already exists")
		return existingLayer.getReference(), nil
	}

	tx, cleanupErr := ls.store.StartTransaction()
	if cleanupErr != nil {
		return nil, cleanupErr
	}

	defer func() {
		if cleanupErr != nil {
			log.G(context.TODO()).Debugf("Cleaning up transaction after failed migration for %s: %v", graphID, cleanupErr)
			if err := tx.Cancel(); err != nil {
				log.G(context.TODO()).Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
			}
		}
	}()

	tsw, cleanupErr := tx.TarSplitWriter(false)
	if cleanupErr != nil {
		return nil, cleanupErr
	}
	defer tsw.Close()
	tdf, cleanupErr := os.Open(tarDataFile)
	if cleanupErr != nil {
		return nil, cleanupErr
	}
	defer tdf.Close()
	_, cleanupErr = io.Copy(tsw, tdf)
	if cleanupErr != nil {
Repository: moby/moby
File: internal/lazyregexp/lazyregexp.go (Go)

Code before:
	return r.re().FindStringSubmatch(s)
}

func (r *Regexp) FindStringSubmatchIndex(s string) []int {
	return r.re().FindStringSubmatchIndex(s)
}

func (r *Regexp) ReplaceAllString(src, repl string) string {
	return r.re().ReplaceAllString(src, repl)
}

func (r *Regexp) FindString(s string) string {
	return r.re().FindString(s)
}

func (r *Regexp) FindAllString(s string, n int) []string {
	return r.re().FindAllString(s, n)
}

func (r *Regexp) MatchString(s string) bool {
	return r.re().MatchString(s)
}

func (r *Regexp) ReplaceAllLiteralString(src, repl string) string {
	return r.re().ReplaceAllLiteralString(src, repl)
}

func (r *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string {
	return r.re().ReplaceAllStringFunc(src, repl)
}

func (r *Regexp) SubexpNames() []string {
	return r.re().SubexpNames()
}

var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")

// New creates a new lazy regexp, delaying the compiling work until it is first
// needed. If the code is being run as part of tests, the regexp compiling will
// happen immediately.
func New(str string) *Regexp {
	lr := &Regexp{str: str}
	if inTest {
		// In tests, always compile the regexps early.
		lr.re()
	}
	return lr
}


Review comment (suggestion):
This one was  added for that fix as well, so we could drop it if that one's no longer needed (it's the only place this one was used currently).

Code after:
	return r.re().FindStringSubmatch(s)
}

func (r *Regexp) FindStringSubmatchIndex(s string) []int {
	return r.re().FindStringSubmatchIndex(s)
}

func (r *Regexp) ReplaceAllString(src, repl string) string {
	return r.re().ReplaceAllString(src, repl)
}

func (r *Regexp) FindString(s string) string {
	return r.re().FindString(s)
}

func (r *Regexp) FindAllString(s string, n int) []string {
	return r.re().FindAllString(s, n)
}

func (r *Regexp) MatchString(s string) bool {
	return r.re().MatchString(s)
}

func (r *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string {
	return r.re().ReplaceAllStringFunc(src, repl)
}

func (r *Regexp) SubexpNames() []string {
	return r.re().SubexpNames()
}

var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")

// New creates a new lazy regexp, delaying the compiling work until it is first
// needed. If the code is being run as part of tests, the regexp compiling will
// happen immediately.
func New(str string) *Regexp {
	lr := &Regexp{str: str}
	if inTest {
		// In tests, always compile the regexps early.
		lr.re()
	}
	return lr
}
Repository: moby/moby
File: contrib/dockerd-rootless.sh (Shell)

Code before:
# See the documentation for the further information: https://docs.docker.com/go/rootless/

set -e -x
case "$1" in
	"check" | "install" | "uninstall")
		echo "Did you mean 'dockerd-rootless-setuptool.sh $@' ?"
		exit 1
		;;
esac
if ! [ -w "$XDG_RUNTIME_DIR" ]; then
	echo "XDG_RUNTIME_DIR needs to be set and writable"
	exit 1
fi
if ! [ -d "$HOME" ]; then
	echo "HOME needs to be set and exist."
	exit 1
fi

mount_directory() {
	if [ -z "$_DOCKERD_ROOTLESS_CHILD" ]; then
		echo "mount_directory should be called from the child context. Otherwise data loss is at risk"
		exit 1
	fi

	local directory="$1"
	local mount_options="${2:---bind}"

	# Bind mount directory: this makes this directory visible to
	# Dockerd, even if it is originally a symlink, given Dockerd does
	# not always follow symlinks. Some directories might also be
	# "copied-up", meaning that they will also be writable on the child
	# namespace; this will be the case only if they are provided as
	# --copy-up to the rootlesskit.
	directory_realpath=$(realpath "$directory")
	rm -rf "$directory"
	mkdir -p "$directory"
	mount $mount_options "$directory_realpath" "$directory"
}

rootlesskit=""
for f in docker-rootlesskit rootlesskit; do
	if command -v $f > /dev/null 2>&1; then
		rootlesskit=$f
		break
	fi
done
if [ -z "$rootlesskit" ]; then
	echo "rootlesskit needs to be installed"
	exit 1
fi


Review comment (bug):
Just noticed the use of `local` here, which I think is not part of POSIX, and maybe incorrect for the shebang used (`#!/bin/sh`)

Code after:
# See the documentation for the further information: https://docs.docker.com/go/rootless/

set -e -x
case "$1" in
	"check" | "install" | "uninstall")
		echo "Did you mean 'dockerd-rootless-setuptool.sh $@' ?"
		exit 1
		;;
esac
if ! [ -w "$XDG_RUNTIME_DIR" ]; then
	echo "XDG_RUNTIME_DIR needs to be set and writable"
	exit 1
fi
if ! [ -d "$HOME" ]; then
	echo "HOME needs to be set and exist."
	exit 1
fi

mount_directory() {
	if [ -z "$_DOCKERD_ROOTLESS_CHILD" ]; then
		echo "mount_directory should be called from the child context. Otherwise data loss is at risk" >&2
		exit 1
	fi

	DIRECTORY="$1"
	if [ ! -d "$DIRECTORY" ]; then
		return
	fi

	# Bind mount directory: this makes this directory visible to
	# Dockerd, even if it is originally a symlink, given Dockerd does
	# not always follow symlinks. Some directories might also be
	# "copied-up", meaning that they will also be writable on the child
	# namespace; this will be the case only if they are provided as
	# --copy-up to the rootlesskit.
	DIRECTORY_REALPATH=$(realpath "$DIRECTORY")
	MOUNT_OPTIONS="${2:---bind}"
	rm -rf "$DIRECTORY"
	mkdir -p "$DIRECTORY"
	mount $MOUNT_OPTIONS "$DIRECTORY_REALPATH" "$DIRECTORY"
}

rootlesskit=""
for f in docker-rootlesskit rootlesskit; do
	if command -v $f > /dev/null 2>&1; then
		rootlesskit=$f
		break
	fi
done
if [ -z "$rootlesskit" ]; then
	echo "rootlesskit needs to be installed"
Repository: moby/moby
File: integration/networking/port_mapping_linux_test.go (Go)

Code before:
	const netName = "nat-time-wait"
	network.CreateNoError(ctx, t, c, netName,
		network.WithDriver("bridge"),
		network.WithOption(bridge.BridgeName, netName))
	defer network.RemoveNoError(ctx, t, c, netName)

	ctrOpts := []func(*container.TestContainerConfig){
		container.WithName(sanitizeCtrName(t.Name() + "-server")),
		container.WithExposedPorts("80/tcp"),
		container.WithPortMap(nat.PortMap{"80/tcp": {{HostPort: "1780"}}}),
		container.WithCmd("httpd", "-f"),
		container.WithNetworkMode(netName),
	}

	serverID := container.Run(ctx, t, c, ctrOpts...)
	defer c.ContainerRemove(ctx, serverID, containertypes.RemoveOptions{Force: true})

	httpClient := &http.Client{Timeout: 3 * time.Second}
	resp, err := httpClient.Get("http://127.0.0.1:1780")
	assert.NilError(t, err)
	assert.Check(t, is.Equal(resp.StatusCode, 404))

	assert.NilError(t, c.ContainerRemove(ctx, serverID, containertypes.RemoveOptions{Force: true}))

	serverID = container.Run(ctx, t, c, ctrOpts...)
	defer c.ContainerRemove(ctx, serverID, containertypes.RemoveOptions{Force: true})
}


Review comment (suggestion):
This one doesn't have to be a `defer`;

```suggestion
	assert.NilError(t, c.ContainerRemove(ctx, serverID, containertypes.RemoveOptions{Force: true}))
```

Code after:
	defer c.Close()

	const netName = "nat-time-wait"
	network.CreateNoError(ctx, t, c, netName,
		network.WithDriver("bridge"),
		network.WithOption(bridge.BridgeName, netName))
	defer network.RemoveNoError(ctx, t, c, netName)

	ctrName := sanitizeCtrName(t.Name() + "-server")
	ctrOpts := []func(*container.TestContainerConfig){
		container.WithName(ctrName),
		container.WithExposedPorts("80/tcp"),
		container.WithPortMap(nat.PortMap{"80/tcp": {{HostPort: "1780"}}}),
		container.WithCmd("httpd", "-f"),
		container.WithNetworkMode(netName),
	}

	container.Run(ctx, t, c, ctrOpts...)
	defer c.ContainerRemove(ctx, ctrName, containertypes.RemoveOptions{Force: true})

	// Make an HTTP request to open a TCP connection to the proxy. We don't
	// care about the HTTP response, just that the connection is established.
	// So, check that we receive a 404 to make sure we've a working full-duplex
	// TCP connection.
	httpClient := &http.Client{Timeout: 3 * time.Second}
	resp, err := httpClient.Get("http://127.0.0.1:1780")
	assert.NilError(t, err)
	assert.Check(t, is.Equal(resp.StatusCode, 404))

	// Removing the container will kill the userland proxy, and the connection
	// opened by the previous HTTP request will be properly closed (ie. on both
	// sides). Thus, that connection will transition to the TIME_WAIT state.
	assert.NilError(t, c.ContainerRemove(ctx, ctrName, containertypes.RemoveOptions{Force: true}))

	// Make sure the container can be restarted. [container.Run] checks that
	// the ContainerStart API call doesn't return an error. We don't need to
	// make another TCP connection either, that's out of scope. Hence, we don't
	// need to check anything after this call.
	container.Run(ctx, t, c, ctrOpts...)
}
Repository: moby/moby
File: cmd/docker-proxy/udp_proxy_linux.go (Go)

Code before:
				// proxied port on the container), ignore it
				// and continue until UDPConnTrackTimeout
				// expires:
				goto again
			}
			return
		}
		for i := 0; i != read; {
			written, _, err := proxy.listener.WriteMsgUDP(readBuf[i:read], oob, clientAddr)
			if err != nil {
				return
			}
			i += written
		}
	}
}

// Run starts forwarding the traffic using UDP.
func (proxy *UDPProxy) Run() {
	readBuf := make([]byte, UDPBufSize)
	oob := ipv4.NewControlMessage(ipv4.FlagDst)
	if proxy.ipVer == ip6 {
		oob = ipv6.NewControlMessage(ipv6.FlagDst)
	}

	for {
		read, _, _, from, err := proxy.listener.ReadMsgUDP(readBuf, oob)
		if err != nil {
			// NOTE: Apparently ReadFrom doesn't return
			// ECONNREFUSED like Read do (see comment in
			// UDPProxy.replyLoop)
			if !isClosedError(err) {
				log.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
			}
			break
		}

		fromKey := newConnTrackKey(from)
		proxy.connTrackLock.Lock()
		proxyConn, hit := proxy.connTrackTable[*fromKey]
		if !hit {
			proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
			if err != nil {
				log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
				proxy.connTrackLock.Unlock()
				continue
			}
			proxy.connTrackTable[*fromKey] = proxyConn

			daddr, err := readDestFromCmsg(oob, proxy.ipVer)
			if err != nil {

Review comment (suggestion):
Perhaps to prevent calling `NewControlMessage` twice;

```suggestion
	var oob []byte
	if proxy.ipVer == ip6 {
		oob = ipv6.NewControlMessage(ipv6.FlagDst)
	} else {
		oob = ipv4.NewControlMessage(ipv4.FlagDst)
	}
```


Code after:
				// proxied port on the container), ignore it
				// and continue until UDPConnTrackTimeout
				// expires:
				goto again
			}
			return
		}
		for i := 0; i != read; {
			written, _, err := proxy.listener.WriteMsgUDP(readBuf[i:read], oob, clientAddr)
			if err != nil {
				return
			}
			i += written
		}
	}
}

// Run starts forwarding the traffic using UDP.
func (proxy *UDPProxy) Run() {
	readBuf := make([]byte, UDPBufSize)
	var oob []byte
	if proxy.ipVer == ip4 {
		oob = ipv4.NewControlMessage(ipv4.FlagDst)
	} else {
		oob = ipv6.NewControlMessage(ipv6.FlagDst)
	}

	for {
		read, _, _, from, err := proxy.listener.ReadMsgUDP(readBuf, oob)
		if err != nil {
			// The frontend listener socket might be closed by the signal
			// handler. In that case, don't log anything - it's not an error.
			if !errors.Is(err, net.ErrClosed) {
				log.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
			}
			break
		}

		fromKey := newConnTrackKey(from)
		proxy.connTrackLock.Lock()
		proxyConn, hit := proxy.connTrackTable[*fromKey]
		if !hit {
			proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
			if err != nil {
				log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
				proxy.connTrackLock.Unlock()
				continue
			}
			proxy.connTrackTable[*fromKey] = proxyConn

			daddr, err := readDestFromCmsg(oob, proxy.ipVer)
Repository: moby/moby
File: image/tarexport/save.go (Go)

Code before:
	arch, err := l.TarStream()
	if err != nil {
		return distribution.Descriptor{}, err
	}
	defer arch.Close()

	digester := digest.Canonical.Digester()
	digestedArch := io.TeeReader(arch, digester.Hash())

	tarSize, err := ioutils.CopyCtx(ctx, tarFile, digestedArch)
	if err != nil {
		return distribution.Descriptor{}, err
	}

	tarDigest := digester.Digest()
	if lDgst != tarDigest {
		log.G(context.TODO()).WithFields(log.Fields{
			"layerDigest":  lDgst,
			"actualDigest": tarDigest,
		}).Warn("layer digest doesn't match its tar archive digest")

		lDgst = digester.Digest()
		layerPath = filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded())
	}

	for _, fname := range []string{outDir, layerPath} {
		// todo: maybe save layer created timestamp?
		if err := system.Chtimes(fname, ts, ts); err != nil {
			return distribution.Descriptor{}, errors.Wrap(err, "could not set layer timestamp")
		}
	}

	var desc distribution.Descriptor
	if fs, ok := l.(distribution.Describable); ok {
		desc = fs.Descriptor()
	}

	if desc.Digest == "" {
		desc.Digest = tarDigest
		desc.Size = tarSize
	}
	if desc.MediaType == "" {
		desc.MediaType = ocispec.MediaTypeImageLayer
	}
	s.savedLayers[lDiffID] = desc

	return desc, nil
}

func (s *saveSession) saveConfig(legacyImg image.V1Image, outDir string, createdTime *time.Time) error {
	imageConfig, err := json.Marshal(legacyImg)

Review comment (suggestion):
This TODO was added back in 2015 by @tonistiigi in https://github.com/moby/moby/commit/01ba0a935b9227583ec05a96e85273cc2276bb93#diff-9ac2cf7826d0cfc8316ca97dceae47447f431bc510fd4a4622ebeb53cc74d08aR295

I wonder if it's still clear what it's about, or if we should just drop it 🤔 

Code after:
	if err != nil {
		return distribution.Descriptor{}, err
	}
	defer arch.Close()

	digester := digest.Canonical.Digester()
	digestedArch := io.TeeReader(arch, digester.Hash())

	tarSize, err := ioutils.CopyCtx(ctx, tarFile, digestedArch)
	if err != nil {
		return distribution.Descriptor{}, err
	}

	tarDigest := digester.Digest()
	if lDgst != tarDigest {
		log.G(context.TODO()).WithFields(log.Fields{
			"layerDigest":  lDgst,
			"actualDigest": tarDigest,
		}).Warn("layer digest doesn't match its tar archive digest")

		lDgst = digester.Digest()
		layerPath = filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded())
	}

	for _, fname := range []string{outDir, layerPath} {
		// todo: maybe save layer created timestamp?
		if err := system.Chtimes(fname, ts, ts); err != nil {
			return distribution.Descriptor{}, errors.Wrap(err, "could not set layer timestamp")
		}
	}

	var desc distribution.Descriptor
	if fs, ok := l.(distribution.Describable); ok {
		desc = fs.Descriptor()
	}

	if desc.Digest == "" {
		desc.Digest = tarDigest
		desc.Size = tarSize
	}
	if desc.MediaType == "" {
		desc.MediaType = ocispec.MediaTypeImageLayer
	}
	s.savedLayers[lDiffID] = desc

	return desc, nil
}

func (s *saveSession) saveConfig(legacyImg image.V1Image, outDir string, createdTime *time.Time) error {
	imageConfig, err := json.Marshal(legacyImg)
	if err != nil {
Repository: moby/moby
File: api/types/types.go (Go)

Code before:
	//
	// Deprecated: deprecated in API v1.42 and up, as it was deprecated in BuildKit; use Parents instead.
	Parent string `json:"Parent,omitempty"`
	// Parents is the list of parent build cache record IDs.
	Parents []string `json:" Parents,omitempty"`
	// Type is the cache record type.
	Type string
	// Description is a description of the build-step that produced the build cache.
	Description string
	// InUse indicates if the build cache is in use.
	InUse bool
	// Shared indicates if the build cache is shared.
	Shared bool
	// Size is the amount of disk space used by the build cache (in bytes).
	Size int64
	// CreatedAt is the date and time at which the build cache was created.
	CreatedAt time.Time
	// LastUsedAt is the date and time at which the build cache was last used.
	LastUsedAt *time.Time
	UsageCount int
}

// BuildCachePruneOptions hold parameters to prune the build cache
type BuildCachePruneOptions struct {
	All           bool
	ReservedSpace int64
	MaxUsedSpace  int64
	MinFreeSpace  int64
	Filters       filters.Args
}


Review comment (suggestion):
We probably should keep it as `Deprecated` for at least one version.

Code after:
	//
	// Deprecated: deprecated in API v1.42 and up, as it was deprecated in BuildKit; use Parents instead.
	Parent string `json:"Parent,omitempty"`
	// Parents is the list of parent build cache record IDs.
	Parents []string `json:" Parents,omitempty"`
	// Type is the cache record type.
	Type string
	// Description is a description of the build-step that produced the build cache.
	Description string
	// InUse indicates if the build cache is in use.
	InUse bool
	// Shared indicates if the build cache is shared.
	Shared bool
	// Size is the amount of disk space used by the build cache (in bytes).
	Size int64
	// CreatedAt is the date and time at which the build cache was created.
	CreatedAt time.Time
	// LastUsedAt is the date and time at which the build cache was last used.
	LastUsedAt *time.Time
	UsageCount int
}

// BuildCachePruneOptions hold parameters to prune the build cache
type BuildCachePruneOptions struct {
	All           bool
	ReservedSpace int64
	MaxUsedSpace  int64
	MinFreeSpace  int64
	Filters       filters.Args

	KeepStorage int64 // Deprecated: deprecated in API 1.48.
}
Repository: moby/moby
File: client/build_prune.go (Go)

Code before:
package client // import "github.com/docker/docker/client"

import (
	"context"
	"encoding/json"
	"net/url"
	"strconv"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/pkg/errors"
)

// BuildCachePrune requests the daemon to delete unused cache data
func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
	if err := cli.NewVersionError(ctx, "1.31", "build prune"); err != nil {
		return nil, err
	}

	query := url.Values{}
	if opts.All {
		query.Set("all", "1")
	}

	if opts.ReservedSpace != 0 {
		query.Set("reserved-space", strconv.Itoa(int(opts.ReservedSpace)))
	}
	if opts.MaxUsedSpace != 0 {
		query.Set("max-used-space", strconv.Itoa(int(opts.MaxUsedSpace)))
	}
	if opts.MinFreeSpace != 0 {
		query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace)))
	}
	f, err := filters.ToJSON(opts.Filters)
	if err != nil {
		return nil, errors.Wrap(err, "prune could not marshal filters option")
	}
	query.Set("filters", f)

	serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
	defer ensureReaderClosed(serverResp)

	if err != nil {
		return nil, err
	}

	report := types.BuildCachePruneReport{}
	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
		return nil, errors.Wrap(err, "error retrieving disk usage")

Review comment (suggestion):
We're still keeping `KeepStorage` as deprecated, so we should still set it.

Code after:
package client // import "github.com/docker/docker/client"

import (
	"context"
	"encoding/json"
	"net/url"
	"strconv"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/pkg/errors"
)

// BuildCachePrune requests the daemon to delete unused cache data
func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
	if err := cli.NewVersionError(ctx, "1.31", "build prune"); err != nil {
		return nil, err
	}

	query := url.Values{}
	if opts.All {
		query.Set("all", "1")
	}

	if opts.KeepStorage != 0 {
		query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage)))
	}
	if opts.ReservedSpace != 0 {
		query.Set("reserved-space", strconv.Itoa(int(opts.ReservedSpace)))
	}
	if opts.MaxUsedSpace != 0 {
		query.Set("max-used-space", strconv.Itoa(int(opts.MaxUsedSpace)))
	}
	if opts.MinFreeSpace != 0 {
		query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace)))
	}
	f, err := filters.ToJSON(opts.Filters)
	if err != nil {
		return nil, errors.Wrap(err, "prune could not marshal filters option")
	}
	query.Set("filters", f)

	serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
	defer ensureReaderClosed(serverResp)

	if err != nil {
		return nil, err
	}