2018-10-23 11:05:44 -04:00
|
|
|
package image
|
2017-02-12 14:22:01 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2018-10-23 11:05:44 -04:00
|
|
|
"github.com/docker/cli/cli/command/formatter"
|
2017-02-12 14:22:01 -05:00
|
|
|
"github.com/docker/docker/api/types/image"
|
|
|
|
"github.com/docker/docker/pkg/stringid"
|
|
|
|
units "github.com/docker/go-units"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultHistoryTableFormat = "table {{.ID}}\t{{.CreatedSince}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}"
|
|
|
|
nonHumanHistoryTableFormat = "table {{.ID}}\t{{.CreatedAt}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}"
|
|
|
|
|
|
|
|
historyIDHeader = "IMAGE"
|
|
|
|
createdByHeader = "CREATED BY"
|
|
|
|
commentHeader = "COMMENT"
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewHistoryFormat returns a format for rendering an HistoryContext
|
2018-10-23 11:05:44 -04:00
|
|
|
func NewHistoryFormat(source string, quiet bool, human bool) formatter.Format {
|
2023-11-20 07:54:53 -05:00
|
|
|
if source == formatter.TableFormatKey {
|
2017-02-12 14:22:01 -05:00
|
|
|
switch {
|
|
|
|
case quiet:
|
2018-10-23 11:05:44 -04:00
|
|
|
return formatter.DefaultQuietFormat
|
2017-02-12 14:22:01 -05:00
|
|
|
case !human:
|
|
|
|
return nonHumanHistoryTableFormat
|
|
|
|
default:
|
|
|
|
return defaultHistoryTableFormat
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-23 11:05:44 -04:00
|
|
|
return formatter.Format(source)
|
2017-02-12 14:22:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// HistoryWrite writes the context
|
2018-10-23 11:05:44 -04:00
|
|
|
func HistoryWrite(ctx formatter.Context, human bool, histories []image.HistoryResponseItem) error {
|
|
|
|
render := func(format func(subContext formatter.SubContext) error) error {
|
2017-02-12 14:22:01 -05:00
|
|
|
for _, history := range histories {
|
|
|
|
historyCtx := &historyContext{trunc: ctx.Trunc, h: history, human: human}
|
|
|
|
if err := format(historyCtx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
historyCtx := &historyContext{}
|
2018-10-23 11:05:44 -04:00
|
|
|
historyCtx.Header = formatter.SubHeaderContext{
|
2017-02-12 14:22:01 -05:00
|
|
|
"ID": historyIDHeader,
|
2018-10-23 11:05:44 -04:00
|
|
|
"CreatedSince": formatter.CreatedSinceHeader,
|
|
|
|
"CreatedAt": formatter.CreatedAtHeader,
|
2017-02-12 14:22:01 -05:00
|
|
|
"CreatedBy": createdByHeader,
|
2018-10-23 11:05:44 -04:00
|
|
|
"Size": formatter.SizeHeader,
|
2017-02-12 14:22:01 -05:00
|
|
|
"Comment": commentHeader,
|
|
|
|
}
|
|
|
|
return ctx.Write(historyCtx, render)
|
|
|
|
}
|
|
|
|
|
|
|
|
type historyContext struct {
|
2018-10-23 11:05:44 -04:00
|
|
|
formatter.HeaderContext
|
2017-02-12 14:22:01 -05:00
|
|
|
trunc bool
|
|
|
|
human bool
|
|
|
|
h image.HistoryResponseItem
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *historyContext) MarshalJSON() ([]byte, error) {
|
2018-10-23 11:05:44 -04:00
|
|
|
return formatter.MarshalJSON(c)
|
2017-02-12 14:22:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *historyContext) ID() string {
|
|
|
|
if c.trunc {
|
|
|
|
return stringid.TruncateID(c.h.ID)
|
|
|
|
}
|
|
|
|
return c.h.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *historyContext) CreatedAt() string {
|
2017-08-12 14:38:01 -04:00
|
|
|
return time.Unix(c.h.Created, 0).Format(time.RFC3339)
|
2017-02-12 14:22:01 -05:00
|
|
|
}
|
|
|
|
|
2022-09-16 10:50:16 -04:00
|
|
|
// epoch is the time before which created-at dates are not displayed with human units.
|
|
|
|
var epoch = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
|
|
|
|
|
2017-02-12 14:22:01 -05:00
|
|
|
func (c *historyContext) CreatedSince() string {
|
2017-08-12 14:38:01 -04:00
|
|
|
if !c.human {
|
|
|
|
return c.CreatedAt()
|
|
|
|
}
|
2022-09-16 10:50:16 -04:00
|
|
|
if c.h.Created <= epoch {
|
2022-09-16 16:29:17 -04:00
|
|
|
return "N/A"
|
2022-09-16 10:50:16 -04:00
|
|
|
}
|
2017-06-09 16:41:53 -04:00
|
|
|
created := units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0)))
|
2017-02-12 14:22:01 -05:00
|
|
|
return created + " ago"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *historyContext) CreatedBy() string {
|
linting: address assorted issues found by gocritic
internal/test/builders/config.go:36:15: captLocal: `ID' should not be capitalized (gocritic)
func ConfigID(ID string) func(config *swarm.Config) {
^
internal/test/builders/secret.go:45:15: captLocal: `ID' should not be capitalized (gocritic)
func SecretID(ID string) func(secret *swarm.Secret) {
^
internal/test/builders/service.go:21:16: captLocal: `ID' should not be capitalized (gocritic)
func ServiceID(ID string) func(*swarm.Service) {
^
cli/command/image/formatter_history.go:100:15: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(c.h.CreatedBy, "\t", " ", -1)` (gocritic)
createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1)
^
e2e/image/push_test.go:246:34: badCall: suspicious Join on 1 argument (gocritic)
assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
^
e2e/image/push_test.go:313:34: badCall: suspicious Join on 1 argument (gocritic)
assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
^
cli/config/configfile/file_test.go:185:2: assignOp: replace `c.GetAllCallCount = c.GetAllCallCount + 1` with `c.GetAllCallCount++` (gocritic)
c.GetAllCallCount = c.GetAllCallCount + 1
^
cli/command/context/inspect_test.go:20:58: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(si.MetadataPath, `\`, `\\`, -1)` (gocritic)
expected = strings.Replace(expected, "<METADATA_PATH>", strings.Replace(si.MetadataPath, `\`, `\\`, -1), 1)
^
cli/command/context/inspect_test.go:21:53: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(si.TLSPath, `\`, `\\`, -1)` (gocritic)
expected = strings.Replace(expected, "<TLS_PATH>", strings.Replace(si.TLSPath, `\`, `\\`, -1), 1)
^
cli/command/container/formatter_stats.go:119:46: captLocal: `Stats' should not be capitalized (gocritic)
func statsFormatWrite(ctx formatter.Context, Stats []StatsEntry, osType string, trunc bool) error {
^
cli/command/container/stats_helpers.go:209:4: assignOp: replace `blkRead = blkRead + bioEntry.Value` with `blkRead += bioEntry.Value` (gocritic)
blkRead = blkRead + bioEntry.Value
^
cli/command/container/stats_helpers.go:211:4: assignOp: replace `blkWrite = blkWrite + bioEntry.Value` with `blkWrite += bioEntry.Value` (gocritic)
blkWrite = blkWrite + bioEntry.Value
^
cli/command/registry/formatter_search.go:67:10: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(c.s.Description, "\n", " ", -1)` (gocritic)
desc := strings.Replace(c.s.Description, "\n", " ", -1)
^
cli/command/registry/formatter_search.go:68:9: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(desc, "\r", " ", -1)` (gocritic)
desc = strings.Replace(desc, "\r", " ", -1)
^
cli/command/service/list_test.go:164:5: assignOp: replace `tc.doc = tc.doc + " with quiet"` with `tc.doc += " with quiet"` (gocritic)
tc.doc = tc.doc + " with quiet"
^
cli/command/service/progress/progress.go:274:11: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(errMsg, "\n", " ", -1)` (gocritic)
errMsg = strings.Replace(errMsg, "\n", " ", -1)
^
cli/manifest/store/store.go:153:9: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(fileName, "/", "_", -1)` (gocritic)
return strings.Replace(fileName, "/", "_", -1)
^
cli/manifest/store/store.go:152:14: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(ref, ":", "-", -1)` (gocritic)
fileName := strings.Replace(ref, ":", "-", -1)
^
cli/command/plugin/formatter.go:79:10: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(c.p.Config.Description, "\n", "", -1)` (gocritic)
desc := strings.Replace(c.p.Config.Description, "\n", "", -1)
^
cli/command/plugin/formatter.go:80:9: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(desc, "\r", "", -1)` (gocritic)
desc = strings.Replace(desc, "\r", "", -1)
^
cli/compose/convert/service.go:642:23: captLocal: `DNS' should not be capitalized (gocritic)
func convertDNSConfig(DNS []string, DNSSearch []string) *swarm.DNSConfig {
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-11-20 07:30:05 -05:00
|
|
|
createdBy := strings.ReplaceAll(c.h.CreatedBy, "\t", " ")
|
2017-02-12 14:22:01 -05:00
|
|
|
if c.trunc {
|
2018-10-23 11:05:44 -04:00
|
|
|
return formatter.Ellipsis(createdBy, 45)
|
2017-02-12 14:22:01 -05:00
|
|
|
}
|
|
|
|
return createdBy
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *historyContext) Size() string {
|
|
|
|
if c.human {
|
2017-05-02 16:22:13 -04:00
|
|
|
return units.HumanSizeWithPrecision(float64(c.h.Size), 3)
|
2017-02-12 14:22:01 -05:00
|
|
|
}
|
2017-05-02 16:22:13 -04:00
|
|
|
return strconv.FormatInt(c.h.Size, 10)
|
2017-02-12 14:22:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *historyContext) Comment() string {
|
|
|
|
return c.h.Comment
|
|
|
|
}
|