move endpoint API version constraints to API server

This introduces a `WithMinimumAPIVersion` RouteWrapper to configure the
minimum API version  required for a route. It produces a 400 (Invalid Request)
error when accessing the endpoint on API versions lower than the given version.

Note that technically, it should produce a 404 ("not found") error,
as the endpoint should be considered "non-existing" on such API versions,
but 404 status-codes are used in business logic for various endpoints.

This patch allows removal of corresponding API-version checks from the client,
and other implementation of clients for the API. While the produced error message
is slightly more "technical", these situations should be rare and only happen
when the API version of the client is explicitly overridden, or a client was
implemented with a fixed API version (potentially missing version checks).

Before this patch, these errors were produced by the client:

    DOCKER_API_VERSION=v1.24 docker container prune -f
    docker container prune requires API version 1.25, but the Docker daemon API version is 1.24

With this patch applied, the error is returned by the daemon:

    DOCKER_API_VERSION=v1.24 docker container prune -f
    Error response from daemon: POST /containers/prune requires minimum API version 1.25

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-09-16 11:35:00 +02:00
parent 766c8313cb
commit 20d8342a4b
56 changed files with 53 additions and 239 deletions

View File

@@ -23,10 +23,6 @@ type BuildCachePruneOptions struct {
// BuildCachePrune requests the daemon to delete unused cache data.
func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (*build.CachePruneReport, 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")

View File

@@ -9,16 +9,13 @@ import (
// ConfigCreate creates a new config.
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
var response swarm.ConfigCreateResponse
if err := cli.NewVersionError(ctx, "1.30", "config create"); err != nil {
return response, err
}
resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return swarm.ConfigCreateResponse{}, err
}
var response swarm.ConfigCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@@ -16,17 +16,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestConfigCreateUnsupported(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.29"),
WithHTTPClient(&http.Client{}),
)
assert.NilError(t, err)
_, err = client.ConfigCreate(context.Background(), swarm.ConfigSpec{})
assert.Check(t, is.Error(err, `"config create" requires API version 1.30, but the Docker daemon API version is 1.29`))
}
func TestConfigCreateError(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.30"),

View File

@@ -15,9 +15,6 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
if err != nil {
return swarm.Config{}, nil, err
}
if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil {
return swarm.Config{}, nil, err
}
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@@ -43,17 +43,6 @@ func TestConfigInspectWithEmptyID(t *testing.T) {
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestConfigInspectUnsupported(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.29"),
WithHTTPClient(&http.Client{}),
)
assert.NilError(t, err)
_, _, err = client.ConfigInspectWithRaw(context.Background(), "nothing")
assert.Check(t, is.Error(err, `"config inspect" requires API version 1.30, but the Docker daemon API version is 1.29`))
}
func TestConfigInspectError(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.30"),

View File

@@ -11,9 +11,6 @@ import (
// ConfigList returns the list of configs.
func (cli *Client) ConfigList(ctx context.Context, options ConfigListOptions) ([]swarm.Config, error) {
if err := cli.NewVersionError(ctx, "1.30", "config list"); err != nil {
return nil, err
}
query := url.Values{}
if options.Filters.Len() > 0 {

View File

@@ -17,17 +17,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestConfigListUnsupported(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.29"),
WithHTTPClient(&http.Client{}),
)
assert.NilError(t, err)
_, err = client.ConfigList(context.Background(), ConfigListOptions{})
assert.Check(t, is.Error(err, `"config list" requires API version 1.30, but the Docker daemon API version is 1.29`))
}
func TestConfigListError(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.30"),

View File

@@ -8,9 +8,6 @@ func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil {
return err
}
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
defer ensureReaderClosed(resp)
return err

View File

@@ -14,17 +14,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestConfigRemoveUnsupported(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.29"),
WithHTTPClient(&http.Client{}),
)
assert.NilError(t, err)
err = client.ConfigRemove(context.Background(), "config_id")
assert.Check(t, is.Error(err, `"config remove" requires API version 1.30, but the Docker daemon API version is 1.29`))
}
func TestConfigRemoveError(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.30"),

View File

@@ -13,9 +13,6 @@ func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Ve
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)

View File

@@ -15,17 +15,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestConfigUpdateUnsupported(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.29"),
WithHTTPClient(&http.Client{}),
)
assert.NilError(t, err)
err = client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
assert.Check(t, is.Error(err, `"config update" requires API version 1.30, but the Docker daemon API version is 1.29`))
}
func TestConfigUpdateError(t *testing.T) {
client, err := NewClientWithOpts(
WithVersion("1.30"),

View File

@@ -11,10 +11,6 @@ import (
// ContainersPrune requests the daemon to delete unused data
func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "container prune"); err != nil {
return container.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return container.PruneReport{}, err

View File

@@ -15,10 +15,6 @@ func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedReg
return registry.DistributionInspect{}, objectNotFoundError{object: "distribution", id: imageRef}
}
if err := cli.NewVersionError(ctx, "1.30", "distribution inspect"); err != nil {
return registry.DistributionInspect{}, err
}
var headers http.Header
if encodedRegistryAuth != "" {
headers = http.Header{

View File

@@ -11,13 +11,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestDistributionInspectUnsupported(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.29"), WithHTTPClient(&http.Client{}))
assert.NilError(t, err)
_, err = client.DistributionInspect(context.Background(), "foobar:1.0", "")
assert.Check(t, is.Error(err, `"distribution inspect" requires API version 1.30, but the Docker daemon API version is 1.29`))
}
func TestDistributionInspectWithEmptyID(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
return nil, errors.New("should not make request")

View File

@@ -11,10 +11,6 @@ import (
// ImagesPrune requests the daemon to delete unused data
func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "image prune"); err != nil {
return image.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return image.PruneReport{}, err

View File

@@ -11,10 +11,6 @@ import (
// NetworksPrune requests the daemon to delete unused networks
func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "network prune"); err != nil {
return network.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return network.PruneReport{}, err

View File

@@ -19,9 +19,6 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options Plugi
return nil, err
}
if err := cli.NewVersionError(ctx, "1.26", "plugin upgrade"); err != nil {
return nil, err
}
query := url.Values{}
if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
return nil, fmt.Errorf("invalid remote reference: %w", err)

View File

@@ -9,9 +9,6 @@ import (
// SecretCreate creates a new secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) {
if err := cli.NewVersionError(ctx, "1.25", "secret create"); err != nil {
return swarm.SecretCreateResponse{}, err
}
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@@ -16,13 +16,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestSecretCreateUnsupported(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.24"), WithHTTPClient(&http.Client{}))
assert.NilError(t, err)
_, err = client.SecretCreate(context.Background(), swarm.SecretSpec{})
assert.Check(t, is.Error(err, `"secret create" requires API version 1.25, but the Docker daemon API version is 1.24`))
}
func TestSecretCreateError(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.25"), WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)

View File

@@ -15,9 +15,6 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S
if err != nil {
return swarm.Secret{}, nil, err
}
if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil {
return swarm.Secret{}, nil, err
}
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@@ -17,13 +17,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestSecretInspectUnsupported(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.24"), WithHTTPClient(&http.Client{}))
assert.NilError(t, err)
_, _, err = client.SecretInspectWithRaw(context.Background(), "nothing")
assert.Check(t, is.Error(err, `"secret inspect" requires API version 1.25, but the Docker daemon API version is 1.24`))
}
func TestSecretInspectError(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.25"), WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)

View File

@@ -11,9 +11,6 @@ import (
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) {
if err := cli.NewVersionError(ctx, "1.25", "secret list"); err != nil {
return nil, err
}
query := url.Values{}
if options.Filters.Len() > 0 {

View File

@@ -17,13 +17,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestSecretListUnsupported(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.24"), WithHTTPClient(&http.Client{}))
assert.NilError(t, err)
_, err = client.SecretList(context.Background(), SecretListOptions{})
assert.Check(t, is.Error(err, `"secret list" requires API version 1.25, but the Docker daemon API version is 1.24`))
}
func TestSecretListError(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.25"), WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)

View File

@@ -8,9 +8,6 @@ func (cli *Client) SecretRemove(ctx context.Context, id string) error {
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil {
return err
}
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)
return err

View File

@@ -14,13 +14,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestSecretRemoveUnsupported(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.24"), WithHTTPClient(&http.Client{}))
assert.NilError(t, err)
err = client.SecretRemove(context.Background(), "secret_id")
assert.Check(t, is.Error(err, `"secret remove" requires API version 1.25, but the Docker daemon API version is 1.24`))
}
func TestSecretRemoveError(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.25"), WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)

View File

@@ -13,9 +13,6 @@ func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Ve
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)

View File

@@ -15,13 +15,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestSecretUpdateUnsupported(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.24"), WithHTTPClient(&http.Client{}))
assert.NilError(t, err)
err = client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{})
assert.Check(t, is.Error(err, `"secret update" requires API version 1.25, but the Docker daemon API version is 1.24`))
}
func TestSecretUpdateError(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.25"), WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)

View File

@@ -11,10 +11,6 @@ import (
// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (volume.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "volume prune"); err != nil {
return volume.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return volume.PruneReport{}, err

View File

@@ -15,9 +15,6 @@ func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version sw
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.42", "volume update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", version.String())

View File

@@ -32,7 +32,7 @@ func (br *buildRouter) Routes() []router.Route {
func (br *buildRouter) initRoutes() {
br.routes = []router.Route{
router.NewPostRoute("/build", br.postBuild),
router.NewPostRoute("/build/prune", br.postPrune),
router.NewPostRoute("/build/prune", br.postPrune, router.WithMinimumAPIVersion("1.31")),
router.NewPostRoute("/build/cancel", br.postCancel),
}
}

View File

@@ -54,7 +54,7 @@ func (c *containerRouter) initRoutes() {
router.NewPostRoute("/exec/{name:.*}/resize", c.postContainerExecResize),
router.NewPostRoute("/containers/{name:.*}/rename", c.postContainerRename),
router.NewPostRoute("/containers/{name:.*}/update", c.postContainerUpdate),
router.NewPostRoute("/containers/prune", c.postContainersPrune),
router.NewPostRoute("/containers/prune", c.postContainersPrune, router.WithMinimumAPIVersion("1.25")),
router.NewPostRoute("/commit", c.postCommit),
// PUT
router.NewPutRoute("/containers/{name:.*}/archive", c.putContainersArchive),

View File

@@ -26,6 +26,6 @@ func (dr *distributionRouter) Routes() []router.Route {
func (dr *distributionRouter) initRoutes() {
dr.routes = []router.Route{
// GET
router.NewGetRoute("/distribution/{name:.*}/json", dr.getDistributionInfo),
router.NewGetRoute("/distribution/{name:.*}/json", dr.getDistributionInfo, router.WithMinimumAPIVersion("1.30")),
}
}

View File

@@ -41,7 +41,7 @@ func (ir *imageRouter) initRoutes() {
router.NewPostRoute("/images/create", ir.postImagesCreate),
router.NewPostRoute("/images/{name:.*}/push", ir.postImagesPush),
router.NewPostRoute("/images/{name:.*}/tag", ir.postImagesTag),
router.NewPostRoute("/images/prune", ir.postImagesPrune),
router.NewPostRoute("/images/prune", ir.postImagesPrune, router.WithMinimumAPIVersion("1.25")),
// DELETE
router.NewDeleteRoute("/images/{name:.*}", ir.deleteImages),
}

View File

@@ -1,8 +1,10 @@
package router
import (
"context"
"net/http"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/v2/daemon/server/httputils"
)
@@ -71,3 +73,32 @@ func NewOptionsRoute(path string, handler httputils.APIFunc, opts ...RouteWrappe
func NewHeadRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
return NewRoute(http.MethodHead, path, handler, opts...)
}
// WithMinimumAPIVersion configures the minimum API version required for
// a route. It produces a 400 (Invalid Request) error when accessing the
// endpoint on API versions lower than "minAPIVersion".
//
// Note that technically, it should produce a 404 ("not found") error,
// as the endpoint should be considered "non-existing" on such API versions,
// but 404 status-codes are used in business logic for various endpoints.
func WithMinimumAPIVersion(minAPIVersion string) RouteWrapper {
return func(route Route) Route {
return localRoute{
method: route.Method(),
path: route.Path(),
handler: func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if v := httputils.VersionFromContext(ctx); v != "" && versions.LessThan(v, minAPIVersion) {
return versionError(route.Method() + " " + route.Path() + " requires minimum API version " + minAPIVersion)
}
return route.Handler()(ctx, w, r, vars)
},
}
}
}
type versionError string
func (e versionError) Error() string {
return string(e)
}
func (e versionError) InvalidParameter() {}

View File

@@ -36,7 +36,7 @@ func (n *networkRouter) initRoutes() {
router.NewPostRoute("/networks/create", n.postNetworkCreate),
router.NewPostRoute("/networks/{id:.*}/connect", n.postNetworkConnect),
router.NewPostRoute("/networks/{id:.*}/disconnect", n.postNetworkDisconnect),
router.NewPostRoute("/networks/prune", n.postNetworksPrune),
router.NewPostRoute("/networks/prune", n.postNetworksPrune, router.WithMinimumAPIVersion("1.25")),
// DELETE
router.NewDeleteRoute("/networks/{id:.*}", n.deleteNetwork),
}

View File

@@ -32,7 +32,7 @@ func (pr *pluginRouter) initRoutes() {
router.NewPostRoute("/plugins/{name:.*}/disable", pr.disablePlugin),
router.NewPostRoute("/plugins/pull", pr.pullPlugin),
router.NewPostRoute("/plugins/{name:.*}/push", pr.pushPlugin),
router.NewPostRoute("/plugins/{name:.*}/upgrade", pr.upgradePlugin),
router.NewPostRoute("/plugins/{name:.*}/upgrade", pr.upgradePlugin, router.WithMinimumAPIVersion("1.26")),
router.NewPostRoute("/plugins/{name:.*}/set", pr.setPlugin),
router.NewPostRoute("/plugins/create", pr.createPlugin),
}

View File

@@ -48,16 +48,16 @@ func (sr *swarmRouter) initRoutes() {
router.NewGetRoute("/tasks/{id}", sr.getTask),
router.NewGetRoute("/tasks/{id}/logs", sr.getTaskLogs),
router.NewGetRoute("/secrets", sr.getSecrets),
router.NewPostRoute("/secrets/create", sr.createSecret),
router.NewDeleteRoute("/secrets/{id}", sr.removeSecret),
router.NewGetRoute("/secrets/{id}", sr.getSecret),
router.NewPostRoute("/secrets/{id}/update", sr.updateSecret),
router.NewGetRoute("/secrets", sr.getSecrets, router.WithMinimumAPIVersion("1.25")),
router.NewPostRoute("/secrets/create", sr.createSecret, router.WithMinimumAPIVersion("1.25")),
router.NewDeleteRoute("/secrets/{id}", sr.removeSecret, router.WithMinimumAPIVersion("1.25")),
router.NewGetRoute("/secrets/{id}", sr.getSecret, router.WithMinimumAPIVersion("1.25")),
router.NewPostRoute("/secrets/{id}/update", sr.updateSecret, router.WithMinimumAPIVersion("1.25")),
router.NewGetRoute("/configs", sr.getConfigs),
router.NewPostRoute("/configs/create", sr.createConfig),
router.NewDeleteRoute("/configs/{id}", sr.removeConfig),
router.NewGetRoute("/configs/{id}", sr.getConfig),
router.NewPostRoute("/configs/{id}/update", sr.updateConfig),
router.NewGetRoute("/configs", sr.getConfigs, router.WithMinimumAPIVersion("1.30")),
router.NewPostRoute("/configs/create", sr.createConfig, router.WithMinimumAPIVersion("1.30")),
router.NewDeleteRoute("/configs/{id}", sr.removeConfig, router.WithMinimumAPIVersion("1.30")),
router.NewGetRoute("/configs/{id}", sr.getConfig, router.WithMinimumAPIVersion("1.30")),
router.NewPostRoute("/configs/{id}/update", sr.updateConfig, router.WithMinimumAPIVersion("1.30")),
}
}

View File

@@ -31,9 +31,9 @@ func (v *volumeRouter) initRoutes() {
router.NewGetRoute("/volumes/{name:.*}", v.getVolumeByName),
// POST
router.NewPostRoute("/volumes/create", v.postVolumesCreate),
router.NewPostRoute("/volumes/prune", v.postVolumesPrune),
router.NewPostRoute("/volumes/prune", v.postVolumesPrune, router.WithMinimumAPIVersion("1.25")),
// PUT
router.NewPutRoute("/volumes/{name:.*}", v.putVolumesUpdate),
router.NewPutRoute("/volumes/{name:.*}", v.putVolumesUpdate, router.WithMinimumAPIVersion("1.42")),
// DELETE
router.NewDeleteRoute("/volumes/{name:.*}", v.deleteVolumes),
}

View File

@@ -23,10 +23,6 @@ type BuildCachePruneOptions struct {
// BuildCachePrune requests the daemon to delete unused cache data.
func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (*build.CachePruneReport, 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")

View File

@@ -9,16 +9,13 @@ import (
// ConfigCreate creates a new config.
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
var response swarm.ConfigCreateResponse
if err := cli.NewVersionError(ctx, "1.30", "config create"); err != nil {
return response, err
}
resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return swarm.ConfigCreateResponse{}, err
}
var response swarm.ConfigCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View File

@@ -15,9 +15,6 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
if err != nil {
return swarm.Config{}, nil, err
}
if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil {
return swarm.Config{}, nil, err
}
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@@ -11,9 +11,6 @@ import (
// ConfigList returns the list of configs.
func (cli *Client) ConfigList(ctx context.Context, options ConfigListOptions) ([]swarm.Config, error) {
if err := cli.NewVersionError(ctx, "1.30", "config list"); err != nil {
return nil, err
}
query := url.Values{}
if options.Filters.Len() > 0 {

View File

@@ -8,9 +8,6 @@ func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil {
return err
}
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
defer ensureReaderClosed(resp)
return err

View File

@@ -13,9 +13,6 @@ func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Ve
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)

View File

@@ -11,10 +11,6 @@ import (
// ContainersPrune requests the daemon to delete unused data
func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "container prune"); err != nil {
return container.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return container.PruneReport{}, err

View File

@@ -15,10 +15,6 @@ func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedReg
return registry.DistributionInspect{}, objectNotFoundError{object: "distribution", id: imageRef}
}
if err := cli.NewVersionError(ctx, "1.30", "distribution inspect"); err != nil {
return registry.DistributionInspect{}, err
}
var headers http.Header
if encodedRegistryAuth != "" {
headers = http.Header{

View File

@@ -11,10 +11,6 @@ import (
// ImagesPrune requests the daemon to delete unused data
func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "image prune"); err != nil {
return image.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return image.PruneReport{}, err

View File

@@ -11,10 +11,6 @@ import (
// NetworksPrune requests the daemon to delete unused networks
func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "network prune"); err != nil {
return network.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return network.PruneReport{}, err

View File

@@ -19,9 +19,6 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options Plugi
return nil, err
}
if err := cli.NewVersionError(ctx, "1.26", "plugin upgrade"); err != nil {
return nil, err
}
query := url.Values{}
if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
return nil, fmt.Errorf("invalid remote reference: %w", err)

View File

@@ -9,9 +9,6 @@ import (
// SecretCreate creates a new secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) {
if err := cli.NewVersionError(ctx, "1.25", "secret create"); err != nil {
return swarm.SecretCreateResponse{}, err
}
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@@ -15,9 +15,6 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S
if err != nil {
return swarm.Secret{}, nil, err
}
if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil {
return swarm.Secret{}, nil, err
}
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@@ -11,9 +11,6 @@ import (
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) {
if err := cli.NewVersionError(ctx, "1.25", "secret list"); err != nil {
return nil, err
}
query := url.Values{}
if options.Filters.Len() > 0 {

View File

@@ -8,9 +8,6 @@ func (cli *Client) SecretRemove(ctx context.Context, id string) error {
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil {
return err
}
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)
return err

View File

@@ -13,9 +13,6 @@ func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Ve
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", version.String())
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)

View File

@@ -11,10 +11,6 @@ import (
// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (volume.PruneReport, error) {
if err := cli.NewVersionError(ctx, "1.25", "volume prune"); err != nil {
return volume.PruneReport{}, err
}
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return volume.PruneReport{}, err

View File

@@ -15,9 +15,6 @@ func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version sw
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.42", "volume update"); err != nil {
return err
}
query := url.Values{}
query.Set("version", version.String())