diff --git a/api/server/router/volume/volume_routes.go b/api/server/router/volume/volume_routes.go index 6fd5097c8b..04c6b0d654 100644 --- a/api/server/router/volume/volume_routes.go +++ b/api/server/router/volume/volume_routes.go @@ -162,11 +162,16 @@ func (v *volumeRouter) deleteVolumes(ctx context.Context, w http.ResponseWriter, version := httputils.VersionFromContext(ctx) err := v.backend.Remove(ctx, vars["name"], opts.WithPurgeOnError(force)) - if err != nil { - if errdefs.IsNotFound(err) && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() { - err := v.cluster.RemoveVolume(vars["name"], force) - if err != nil { - return err + // when a removal is forced, if the volume does not exist, no error will be + // returned. this means that to ensure forcing works on swarm volumes as + // well, we should always also force remove against the cluster. + if err != nil || force { + if versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() { + if errdefs.IsNotFound(err) || force { + err := v.cluster.RemoveVolume(vars["name"], force) + if err != nil { + return err + } } } else { return err diff --git a/api/server/router/volume/volume_routes_test.go b/api/server/router/volume/volume_routes_test.go index c80ff189a4..9bc50f3392 100644 --- a/api/server/router/volume/volume_routes_test.go +++ b/api/server/router/volume/volume_routes_test.go @@ -574,6 +574,7 @@ func TestVolumeRemoveSwarmForce(t *testing.T) { assert.NilError(t, err) assert.Equal(t, len(b.volumes), 0) + assert.Equal(t, len(c.volumes), 0) } type fakeVolumeBackend struct { @@ -616,9 +617,16 @@ func (b *fakeVolumeBackend) Create(_ context.Context, name, driverName string, _ return v, nil } -func (b *fakeVolumeBackend) Remove(_ context.Context, name string, _ ...opts.RemoveOption) error { +func (b *fakeVolumeBackend) Remove(_ context.Context, name string, o ...opts.RemoveOption) error { + removeOpts := &opts.RemoveConfig{} + for _, opt := range o { + opt(removeOpts) + } + if v, ok := b.volumes[name]; !ok { - return errdefs.NotFound(fmt.Errorf("volume %s not found", name)) + if !removeOpts.PurgeOnError { + return errdefs.NotFound(fmt.Errorf("volume %s not found", name)) + } } else if v.Name == "inuse" { return errdefs.Conflict(fmt.Errorf("volume in use")) }