package formatter import ( "fmt" "sort" "strings" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/docker/go-units" ) const ( defaultBuildCacheTableFormat = "table {{.ID}}\t{{.Type}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}\t{{.Description}}" cacheIDHeader = "CACHE ID" cacheTypeHeader = "CACHE TYPE" parentHeader = "PARENT" lastUsedSinceHeader = "LAST USED" usageCountHeader = "USAGE" inUseHeader = "IN USE" sharedHeader = "SHARED" ) // NewBuildCacheFormat returns a Format for rendering using a Context func NewBuildCacheFormat(source string, quiet bool) Format { switch source { case TableFormatKey: if quiet { return DefaultQuietFormat } return Format(defaultBuildCacheTableFormat) case RawFormatKey: if quiet { return `build_cache_id: {{.ID}}` } format := `build_cache_id: {{.ID}} parent_id: {{.Parent}} build_cache_type: {{.CacheType}} description: {{.Description}} created_at: {{.CreatedAt}} created_since: {{.CreatedSince}} last_used_at: {{.LastUsedAt}} last_used_since: {{.LastUsedSince}} usage_count: {{.UsageCount}} in_use: {{.InUse}} shared: {{.Shared}} ` return Format(format) } return Format(source) } func buildCacheSort(buildCache []*types.BuildCache) { sort.Slice(buildCache, func(i, j int) bool { lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt switch { case lui == nil && luj == nil: return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0 case lui == nil: return true case luj == nil: return false case lui.Equal(*luj): return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0 default: return lui.Before(*luj) } }) } // BuildCacheWrite renders the context for a list of containers func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error { render := func(format func(subContext SubContext) error) error { buildCacheSort(buildCaches) for _, bc := range buildCaches { err := format(&buildCacheContext{trunc: ctx.Trunc, v: bc}) if err != nil { return err } } return nil } return ctx.Write(newBuildCacheContext(), render) } type buildCacheContext struct { HeaderContext trunc bool v *types.BuildCache } func newBuildCacheContext() *buildCacheContext { buildCacheCtx := buildCacheContext{} buildCacheCtx.Header = SubHeaderContext{ "ID": cacheIDHeader, "Parent": parentHeader, "CacheType": cacheTypeHeader, "Size": SizeHeader, "CreatedSince": CreatedSinceHeader, "LastUsedSince": lastUsedSinceHeader, "UsageCount": usageCountHeader, "InUse": inUseHeader, "Shared": sharedHeader, "Description": DescriptionHeader, } return &buildCacheCtx } func (c *buildCacheContext) MarshalJSON() ([]byte, error) { return MarshalJSON(c) } func (c *buildCacheContext) ID() string { id := c.v.ID if c.trunc { id = stringid.TruncateID(c.v.ID) } if c.v.InUse { return id + "*" } return id } func (c *buildCacheContext) Parent() string { var parent string if len(c.v.Parents) > 0 { parent = strings.Join(c.v.Parents, ", ") } else { parent = c.v.Parent //nolint:staticcheck // Ignore SA1019: Field was deprecated in API v1.42, but kept for backward compatibility } if c.trunc { return stringid.TruncateID(parent) } return parent } func (c *buildCacheContext) CacheType() string { return c.v.Type } func (c *buildCacheContext) Description() string { return c.v.Description } func (c *buildCacheContext) Size() string { return units.HumanSizeWithPrecision(float64(c.v.Size), 3) } func (c *buildCacheContext) CreatedAt() string { return c.v.CreatedAt.String() } func (c *buildCacheContext) CreatedSince() string { return units.HumanDuration(time.Now().UTC().Sub(c.v.CreatedAt)) + " ago" } func (c *buildCacheContext) LastUsedAt() string { if c.v.LastUsedAt == nil { return "" } return c.v.LastUsedAt.String() } func (c *buildCacheContext) LastUsedSince() string { if c.v.LastUsedAt == nil { return "" } return units.HumanDuration(time.Now().UTC().Sub(*c.v.LastUsedAt)) + " ago" } func (c *buildCacheContext) UsageCount() string { return fmt.Sprintf("%d", c.v.UsageCount) } func (c *buildCacheContext) InUse() string { return fmt.Sprintf("%t", c.v.InUse) } func (c *buildCacheContext) Shared() string { return fmt.Sprintf("%t", c.v.Shared) }