package client import ( "context" "encoding/json" "fmt" "net/url" "slices" "github.com/moby/moby/api/types/build" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/image" "github.com/moby/moby/api/types/system" "github.com/moby/moby/api/types/volume" "github.com/moby/moby/client/pkg/versions" ) // DiskUsageOptions holds parameters for [Client.DiskUsage] operations. type DiskUsageOptions struct { // Containers controls whether container disk usage should be computed. Containers bool // Images controls whether image disk usage should be computed. Images bool // BuildCache controls whether build cache disk usage should be computed. BuildCache bool // Volumes controls whether volume disk usage should be computed. Volumes bool // Verbose enables more detailed disk usage information. Verbose bool } // DiskUsageResult is the result of [Client.DiskUsage] operations. type DiskUsageResult struct { // Containers holds container disk usage information. Containers ContainersDiskUsage // Images holds image disk usage information. Images ImagesDiskUsage // BuildCache holds build cache disk usage information. BuildCache BuildCacheDiskUsage // Volumes holds volume disk usage information. Volumes VolumesDiskUsage } // ContainersDiskUsage contains disk usage information for containers. type ContainersDiskUsage struct { // ActiveCount is the number of active containers. ActiveCount int64 // TotalCount is the total number of containers. TotalCount int64 // Reclaimable is the amount of disk space that can be reclaimed. Reclaimable int64 // TotalSize is the total disk space used by all containers. TotalSize int64 // Items holds detailed information about each container. Items []container.Summary } // ImagesDiskUsage contains disk usage information for images. type ImagesDiskUsage struct { // ActiveCount is the number of active images. ActiveCount int64 // TotalCount is the total number of images. TotalCount int64 // Reclaimable is the amount of disk space that can be reclaimed. Reclaimable int64 // TotalSize is the total disk space used by all images. TotalSize int64 // Items holds detailed information about each image. Items []image.Summary } // VolumesDiskUsage contains disk usage information for volumes. type VolumesDiskUsage struct { // ActiveCount is the number of active volumes. ActiveCount int64 // TotalCount is the total number of volumes. TotalCount int64 // Reclaimable is the amount of disk space that can be reclaimed. Reclaimable int64 // TotalSize is the total disk space used by all volumes. TotalSize int64 // Items holds detailed information about each volume. Items []volume.Volume } // BuildCacheDiskUsage contains disk usage information for build cache. type BuildCacheDiskUsage struct { // ActiveCount is the number of active build cache records. ActiveCount int64 // TotalCount is the total number of build cache records. TotalCount int64 // Reclaimable is the amount of disk space that can be reclaimed. Reclaimable int64 // TotalSize is the total disk space used by all build cache records. TotalSize int64 // Items holds detailed information about each build cache record. Items []build.CacheRecord } // DiskUsage requests the current data usage from the daemon. func (cli *Client) DiskUsage(ctx context.Context, options DiskUsageOptions) (DiskUsageResult, error) { query := url.Values{} for _, t := range []struct { flag bool sysObj system.DiskUsageObject }{ {options.Containers, system.ContainerObject}, {options.Images, system.ImageObject}, {options.Volumes, system.VolumeObject}, {options.BuildCache, system.BuildCacheObject}, } { if t.flag { query.Add("type", string(t.sysObj)) } } if options.Verbose { query.Set("verbose", "1") } resp, err := cli.get(ctx, "/system/df", query, nil) defer ensureReaderClosed(resp) if err != nil { return DiskUsageResult{}, err } if versions.LessThan(cli.version, "1.52") { // Generate result from a legacy response. var du legacyDiskUsage if err := json.NewDecoder(resp.Body).Decode(&du); err != nil { return DiskUsageResult{}, fmt.Errorf("retrieving disk usage: %v", err) } return diskUsageResultFromLegacyAPI(&du), nil } var du system.DiskUsage if err := json.NewDecoder(resp.Body).Decode(&du); err != nil { return DiskUsageResult{}, fmt.Errorf("retrieving disk usage: %v", err) } var r DiskUsageResult if idu := du.ImageUsage; idu != nil { r.Images = ImagesDiskUsage{ ActiveCount: idu.ActiveCount, Reclaimable: idu.Reclaimable, TotalCount: idu.TotalCount, TotalSize: idu.TotalSize, } if options.Verbose { r.Images.Items = slices.Clone(idu.Items) } } if cdu := du.ContainerUsage; cdu != nil { r.Containers = ContainersDiskUsage{ ActiveCount: cdu.ActiveCount, Reclaimable: cdu.Reclaimable, TotalCount: cdu.TotalCount, TotalSize: cdu.TotalSize, } if options.Verbose { r.Containers.Items = slices.Clone(cdu.Items) } } if bdu := du.BuildCacheUsage; bdu != nil { r.BuildCache = BuildCacheDiskUsage{ ActiveCount: bdu.ActiveCount, Reclaimable: bdu.Reclaimable, TotalCount: bdu.TotalCount, TotalSize: bdu.TotalSize, } if options.Verbose { r.BuildCache.Items = slices.Clone(bdu.Items) } } if vdu := du.VolumeUsage; vdu != nil { r.Volumes = VolumesDiskUsage{ ActiveCount: vdu.ActiveCount, Reclaimable: vdu.Reclaimable, TotalCount: vdu.TotalCount, TotalSize: vdu.TotalSize, } if options.Verbose { r.Volumes.Items = slices.Clone(vdu.Items) } } return r, nil } // legacyDiskUsage is the response as was used by API < v1.52. type legacyDiskUsage struct { LayersSize int64 `json:"LayersSize,omitempty"` Images []image.Summary `json:"Images,omitzero"` Containers []container.Summary `json:"Containers,omitzero"` Volumes []volume.Volume `json:"Volumes,omitzero"` BuildCache []build.CacheRecord `json:"BuildCache,omitzero"` } func diskUsageResultFromLegacyAPI(du *legacyDiskUsage) DiskUsageResult { return DiskUsageResult{ Images: imageDiskUsageFromLegacyAPI(du), Containers: containerDiskUsageFromLegacyAPI(du), BuildCache: buildCacheDiskUsageFromLegacyAPI(du), Volumes: volumeDiskUsageFromLegacyAPI(du), } } func imageDiskUsageFromLegacyAPI(du *legacyDiskUsage) ImagesDiskUsage { idu := ImagesDiskUsage{ TotalSize: du.LayersSize, TotalCount: int64(len(du.Images)), Items: du.Images, } var used int64 for _, i := range idu.Items { if i.Containers > 0 { idu.ActiveCount++ if i.Size == -1 || i.SharedSize == -1 { continue } used += (i.Size - i.SharedSize) } } if idu.TotalCount > 0 { idu.Reclaimable = idu.TotalSize - used } return idu } func containerDiskUsageFromLegacyAPI(du *legacyDiskUsage) ContainersDiskUsage { cdu := ContainersDiskUsage{ TotalCount: int64(len(du.Containers)), Items: du.Containers, } var used int64 for _, c := range cdu.Items { cdu.TotalSize += c.SizeRw switch c.State { case container.StateRunning, container.StatePaused, container.StateRestarting: cdu.ActiveCount++ used += c.SizeRw case container.StateCreated, container.StateRemoving, container.StateExited, container.StateDead: // not active } } cdu.Reclaimable = cdu.TotalSize - used return cdu } func buildCacheDiskUsageFromLegacyAPI(du *legacyDiskUsage) BuildCacheDiskUsage { bdu := BuildCacheDiskUsage{ TotalCount: int64(len(du.BuildCache)), Items: du.BuildCache, } var used int64 for _, b := range du.BuildCache { if !b.Shared { bdu.TotalSize += b.Size } if b.InUse { bdu.ActiveCount++ if !b.Shared { used += b.Size } } } bdu.Reclaimable = bdu.TotalSize - used return bdu } func volumeDiskUsageFromLegacyAPI(du *legacyDiskUsage) VolumesDiskUsage { vdu := VolumesDiskUsage{ TotalCount: int64(len(du.Volumes)), Items: du.Volumes, } var used int64 for _, v := range vdu.Items { // Ignore volumes with no usage data if v.UsageData != nil { if v.UsageData.RefCount > 0 { vdu.ActiveCount++ used += v.UsageData.Size } if v.UsageData.Size > 0 { vdu.TotalSize += v.UsageData.Size } } } vdu.Reclaimable = vdu.TotalSize - used return vdu }