| package service | |
| import ( | |
| "context" | |
| "fmt" | |
| "strconv" | |
| "time" | |
| "github.com/containerd/log" | |
| "github.com/docker/docker/api/types/filters" | |
| volumetypes "github.com/docker/docker/api/types/volume" | |
| "github.com/docker/docker/errdefs" | |
| "github.com/docker/docker/internal/directory" | |
| "github.com/docker/docker/volume" | |
| ) | |
| // convertOpts are used to pass options to `volumeToAPI` | |
| type convertOpt interface { | |
| isConvertOpt() | |
| } | |
| type useCachedPath bool | |
| func (useCachedPath) isConvertOpt() {} | |
| type calcSize bool | |
| func (calcSize) isConvertOpt() {} | |
| type pathCacher interface { | |
| CachedPath() string | |
| } | |
| func (s *VolumesService) volumesToAPI(ctx context.Context, volumes []volume.Volume, opts ...convertOpt) []*volumetypes.Volume { | |
| var ( | |
| out = make([]*volumetypes.Volume, 0, len(volumes)) | |
| getSize bool | |
| cachedPath bool | |
| ) | |
| for _, o := range opts { | |
| switch t := o.(type) { | |
| case calcSize: | |
| getSize = bool(t) | |
| case useCachedPath: | |
| cachedPath = bool(t) | |
| } | |
| } | |
| for _, v := range volumes { | |
| select { | |
| case <-ctx.Done(): | |
| return nil | |
| default: | |
| } | |
| apiV := volumeToAPIType(v) | |
| if cachedPath { | |
| if vv, ok := v.(pathCacher); ok { | |
| apiV.Mountpoint = vv.CachedPath() | |
| } | |
| } else { | |
| apiV.Mountpoint = v.Path() | |
| } | |
| if getSize { | |
| p := v.Path() | |
| if apiV.Mountpoint == "" { | |
| apiV.Mountpoint = p | |
| } | |
| sz, err := directory.Size(ctx, p) | |
| if err != nil { | |
| log.G(ctx).WithError(err).WithField("volume", v.Name()).Warnf("Failed to determine size of volume") | |
| sz = -1 | |
| } | |
| apiV.UsageData = &volumetypes.UsageData{Size: sz, RefCount: int64(s.vs.CountReferences(v))} | |
| } | |
| out = append(out, &apiV) | |
| } | |
| return out | |
| } | |
| func volumeToAPIType(v volume.Volume) volumetypes.Volume { | |
| createdAt, _ := v.CreatedAt() | |
| tv := volumetypes.Volume{ | |
| Name: v.Name(), | |
| Driver: v.DriverName(), | |
| CreatedAt: createdAt.Format(time.RFC3339), | |
| } | |
| if v, ok := v.(volume.DetailedVolume); ok { | |
| tv.Labels = v.Labels() | |
| tv.Options = v.Options() | |
| tv.Scope = v.Scope() | |
| } | |
| if cp, ok := v.(pathCacher); ok { | |
| tv.Mountpoint = cp.CachedPath() | |
| } | |
| return tv | |
| } | |
| func filtersToBy(filter filters.Args, acceptedFilters map[string]bool) (By, error) { | |
| if err := filter.Validate(acceptedFilters); err != nil { | |
| return nil, err | |
| } | |
| var bys []By | |
| if drivers := filter.Get("driver"); len(drivers) > 0 { | |
| bys = append(bys, ByDriver(drivers...)) | |
| } | |
| if filter.Contains("name") { | |
| bys = append(bys, CustomFilter(func(v volume.Volume) bool { | |
| return filter.Match("name", v.Name()) | |
| })) | |
| } | |
| bys = append(bys, byLabelFilter(filter)) | |
| if filter.Contains("dangling") { | |
| dangling, err := filter.GetBoolOrDefault("dangling", false) | |
| if err != nil { | |
| return nil, err | |
| } | |
| bys = append(bys, ByReferenced(!dangling)) | |
| } | |
| var by By | |
| switch len(bys) { | |
| case 0: | |
| case 1: | |
| by = bys[0] | |
| default: | |
| by = And(bys...) | |
| } | |
| return by, nil | |
| } | |
| func withPrune(filter filters.Args) error { | |
| all := filter.Get("all") | |
| switch { | |
| case len(all) > 1: | |
| return errdefs.InvalidParameter(fmt.Errorf("invalid filter 'all=%s': only one value is expected", all)) | |
| case len(all) == 1: | |
| ok, err := strconv.ParseBool(all[0]) | |
| if err != nil { | |
| return errdefs.InvalidParameter(fmt.Errorf("invalid filter 'all': %w", err)) | |
| } | |
| if ok { | |
| return nil | |
| } | |
| } | |
| filter.Add("label", AnonymousLabel) | |
| return nil | |
| } |