cli/command/formatter: add TrunateID utility
We were depending on pkg/stringid to truncate IDs for presentation. While traditionally, we used a fixed length for "truncated" IDs, this is not a strict requirement (any ID-prefix should work, but conflicts may happen on shorter IDs). This patch adds a local `TruncateID()` utility in the formatter package; it's currently using the same implementation and length as the `stringid.TruncateID` function, but may diverge in future. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@ -5,7 +5,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -176,7 +175,7 @@ func (c *statsContext) Name() string {
|
||||
|
||||
func (c *statsContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.s.ID)
|
||||
return formatter.TruncateID(c.s.ID)
|
||||
}
|
||||
return c.s.ID
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -115,7 +114,7 @@ func (c *buildCacheContext) MarshalJSON() ([]byte, error) {
|
||||
func (c *buildCacheContext) ID() string {
|
||||
id := c.v.ID
|
||||
if c.trunc {
|
||||
id = stringid.TruncateID(c.v.ID)
|
||||
id = TruncateID(c.v.ID)
|
||||
}
|
||||
if c.v.InUse {
|
||||
return id + "*"
|
||||
@ -131,7 +130,7 @@ func (c *buildCacheContext) Parent() string {
|
||||
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 TruncateID(parent)
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
@ -135,7 +134,7 @@ func (c *ContainerContext) MarshalJSON() ([]byte, error) {
|
||||
// option being set, the full or truncated ID is returned.
|
||||
func (c *ContainerContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.c.ID)
|
||||
return TruncateID(c.c.ID)
|
||||
}
|
||||
return c.c.ID
|
||||
}
|
||||
@ -172,7 +171,7 @@ func (c *ContainerContext) Image() string {
|
||||
return "<no image>"
|
||||
}
|
||||
if c.trunc {
|
||||
if trunc := stringid.TruncateID(c.c.ImageID); trunc == stringid.TruncateID(c.c.Image) {
|
||||
if trunc := TruncateID(c.c.ImageID); trunc == TruncateID(c.c.Image) {
|
||||
return trunc
|
||||
}
|
||||
// truncate digest if no-trunc option was not selected
|
||||
|
||||
@ -34,7 +34,7 @@ func TestContainerPsContext(t *testing.T) {
|
||||
{
|
||||
container: container.Summary{ID: containerID},
|
||||
trunc: true,
|
||||
expValue: stringid.TruncateID(containerID),
|
||||
expValue: TruncateID(containerID),
|
||||
call: ctx.ID,
|
||||
},
|
||||
{
|
||||
|
||||
@ -27,6 +27,25 @@ func charWidth(r rune) int {
|
||||
}
|
||||
}
|
||||
|
||||
const shortLen = 12
|
||||
|
||||
// TruncateID returns a shorthand version of a string identifier for presentation,
|
||||
// after trimming digest algorithm prefix (if any).
|
||||
//
|
||||
// This function is a copy of [stringid.TruncateID] for presentation / formatting
|
||||
// purposes.
|
||||
//
|
||||
// [stringid.TruncateID]: https://github.com/moby/moby/blob/v28.3.2/pkg/stringid/stringid.go#L19
|
||||
func TruncateID(id string) string {
|
||||
if i := strings.IndexRune(id, ':'); i >= 0 {
|
||||
id = id[i+1:]
|
||||
}
|
||||
if len(id) > shortLen {
|
||||
id = id[:shortLen]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).
|
||||
// For maxDisplayWidth of 1 and lower, no ellipsis is appended.
|
||||
// For maxDisplayWidth of 1, first char of string will return even if its width > 1.
|
||||
|
||||
@ -7,6 +7,49 @@ import (
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestTruncateID(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc, id, expected string
|
||||
}{
|
||||
{
|
||||
doc: "empty ID",
|
||||
id: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
// IDs are expected to be 12 (short) or 64 characters, and not be numeric only,
|
||||
// but TruncateID should handle these gracefully.
|
||||
doc: "invalid ID",
|
||||
id: "1234",
|
||||
expected: "1234",
|
||||
},
|
||||
{
|
||||
doc: "full ID",
|
||||
id: "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
|
||||
expected: "90435eec5c4e",
|
||||
},
|
||||
{
|
||||
doc: "digest",
|
||||
id: "sha256:90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
|
||||
expected: "90435eec5c4e",
|
||||
},
|
||||
{
|
||||
doc: "very long ID",
|
||||
id: "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a290435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
|
||||
expected: "90435eec5c4e",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
actual := TruncateID(tc.id)
|
||||
if actual != tc.expected {
|
||||
t.Errorf("expected: %q, got: %q", tc.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEllipsis(t *testing.T) {
|
||||
testcases := []struct {
|
||||
source string
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -216,7 +215,7 @@ func (c *imageContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (c *imageContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.i.ID)
|
||||
return TruncateID(c.i.ID)
|
||||
}
|
||||
return c.i.ID
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ func TestImageContext(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{ID: imageID}, trunc: true},
|
||||
expValue: stringid.TruncateID(imageID),
|
||||
expValue: TruncateID(imageID),
|
||||
call: ctx.ID,
|
||||
},
|
||||
{
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -72,7 +71,7 @@ func (c *historyContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (c *historyContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.h.ID)
|
||||
return formatter.TruncateID(c.h.ID)
|
||||
}
|
||||
return c.h.ID
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ func TestHistoryContext_ID(t *testing.T) {
|
||||
historyContext{
|
||||
h: image.HistoryResponseItem{ID: id},
|
||||
trunc: true,
|
||||
}, stringid.TruncateID(id), ctx.ID,
|
||||
}, formatter.TruncateID(id), ctx.ID,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -12,10 +12,10 @@ import (
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/internal/tui"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@ -222,7 +222,7 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
|
||||
Align: alignLeft,
|
||||
Width: 12,
|
||||
DetailsValue: func(d *imageDetails) string {
|
||||
return stringid.TruncateID(d.ID)
|
||||
return formatter.TruncateID(d.ID)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -73,7 +72,7 @@ func (c *networkContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (c *networkContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.n.ID)
|
||||
return formatter.TruncateID(c.n.ID)
|
||||
}
|
||||
return c.n.ID
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ func TestNetworkContext(t *testing.T) {
|
||||
{networkContext{
|
||||
n: network.Summary{ID: networkID},
|
||||
trunc: true,
|
||||
}, stringid.TruncateID(networkID), ctx.ID},
|
||||
}, formatter.TruncateID(networkID), ctx.ID},
|
||||
{networkContext{
|
||||
n: network.Summary{Name: "network_name"},
|
||||
}, "network_name", ctx.Name},
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -66,7 +65,7 @@ func (c *pluginContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (c *pluginContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.p.ID)
|
||||
return formatter.TruncateID(c.p.ID)
|
||||
}
|
||||
return c.p.ID
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ func TestPluginContext(t *testing.T) {
|
||||
{pluginContext{
|
||||
p: types.Plugin{ID: pluginID},
|
||||
trunc: true,
|
||||
}, stringid.TruncateID(pluginID), ctx.ID},
|
||||
}, formatter.TruncateID(pluginID), ctx.ID},
|
||||
{pluginContext{
|
||||
p: types.Plugin{Name: "plugin_name"},
|
||||
}, "plugin_name", ctx.Name},
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/pkg/errors"
|
||||
@ -645,7 +644,7 @@ func (c *serviceContext) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (c *serviceContext) ID() string {
|
||||
return stringid.TruncateID(c.service.ID)
|
||||
return formatter.TruncateID(c.service.ID)
|
||||
}
|
||||
|
||||
func (c *serviceContext) Name() string {
|
||||
|
||||
@ -13,13 +13,13 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/internal/logdetails"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@ -220,7 +220,7 @@ func (f *taskFormatter) format(ctx context.Context, logCtx logContext) (string,
|
||||
if f.opts.noTrunc {
|
||||
taskName += "." + task.ID
|
||||
} else {
|
||||
taskName += "." + stringid.TruncateID(task.ID)
|
||||
taskName += "." + formatter.TruncateID(task.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,12 +11,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -505,7 +505,7 @@ func (u *globalProgressUpdater) writeTaskProgress(task swarm.Task, nodeCount int
|
||||
|
||||
if task.Status.Err != "" {
|
||||
u.progressOut.WriteProgress(progress.Progress{
|
||||
ID: stringid.TruncateID(task.NodeID),
|
||||
ID: formatter.TruncateID(task.NodeID),
|
||||
Action: truncError(task.Status.Err),
|
||||
})
|
||||
return
|
||||
@ -513,7 +513,7 @@ func (u *globalProgressUpdater) writeTaskProgress(task swarm.Task, nodeCount int
|
||||
|
||||
if !terminalState(task.DesiredState) && !terminalState(task.Status.State) {
|
||||
u.progressOut.WriteProgress(progress.Progress{
|
||||
ID: stringid.TruncateID(task.NodeID),
|
||||
ID: formatter.TruncateID(task.NodeID),
|
||||
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
|
||||
Current: numberedStates[task.Status.State],
|
||||
Total: maxProgress,
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -79,7 +78,7 @@ func (c *taskContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (c *taskContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.task.ID)
|
||||
return formatter.TruncateID(c.task.ID)
|
||||
}
|
||||
return c.task.ID
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -119,7 +118,7 @@ func (c *signerInfoContext) Keys() string {
|
||||
truncatedKeys := []string{}
|
||||
if c.trunc {
|
||||
for _, keyID := range c.s.Keys {
|
||||
truncatedKeys = append(truncatedKeys, stringid.TruncateID(keyID))
|
||||
truncatedKeys = append(truncatedKeys, formatter.TruncateID(keyID))
|
||||
}
|
||||
return strings.Join(truncatedKeys, ", ")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user