Browse code

pkg/stringid: move to daemon, and provide copy in client

The stringid package is used in many places; while it's trivial
to implement a similar utility, let's just provide it as a utility
package in the client, removing the daemon-specific logic.

For integration tests, I opted to use the implementation in the
client, as those should not ideally not make assumptions about
the daemon implementation.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/07/24 19:55:07
Showing 67 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+// Package stringid provides helper functions for dealing with string identifiers.
1
+//
2
+// It is similar to the package used by the daemon, but for presentational
3
+// purposes in the client.
4
+package stringid
5
+
6
+import (
7
+	"crypto/rand"
8
+	"encoding/hex"
9
+	"strings"
10
+)
11
+
12
+const (
13
+	shortLen = 12
14
+	fullLen  = 64
15
+)
16
+
17
+// TruncateID returns a shorthand version of a string identifier for presentation.
18
+// For convenience, it accepts both digests ("sha256:xxxx") and IDs without an
19
+// algorithm prefix. It truncates the algorithm (if any) before truncating the
20
+// ID. The length of the truncated ID is currently fixed, but users should make
21
+// no assumptions of this to not change; it is merely a prefix of the ID that
22
+// provides enough uniqueness for common scenarios.
23
+//
24
+// Truncated IDs ("ID-prefixes") usually can be used to uniquely identify an
25
+// object (such as a container or network), but collisions may happen, in
26
+// which case an "ambiguous result" error is produced. In case of a collision,
27
+// the caller should try with a longer prefix or the full-length ID.
28
+func TruncateID(id string) string {
29
+	if i := strings.IndexRune(id, ':'); i >= 0 {
30
+		id = id[i+1:]
31
+	}
32
+	if len(id) > shortLen {
33
+		id = id[:shortLen]
34
+	}
35
+	return id
36
+}
37
+
38
+// GenerateRandomID returns a unique, 64-character ID consisting of a-z, 0-9.
39
+func GenerateRandomID() string {
40
+	b := make([]byte, 32)
41
+	if _, err := rand.Read(b); err != nil {
42
+		panic(err) // This shouldn't happen
43
+	}
44
+	id := hex.EncodeToString(b)
45
+	return id
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package stringid
1
+
2
+import "testing"
3
+
4
+func TestGenerateRandomID(t *testing.T) {
5
+	id := GenerateRandomID()
6
+
7
+	if len(id) != fullLen {
8
+		t.Fatalf("Id returned is incorrect: %s", id)
9
+	}
10
+}
11
+
12
+func TestTruncateID(t *testing.T) {
13
+	tests := []struct {
14
+		doc, id, expected string
15
+	}{
16
+		{
17
+			doc:      "empty ID",
18
+			id:       "",
19
+			expected: "",
20
+		},
21
+		{
22
+			// IDs are expected to be 12 (short) or 64 characters, and not be numeric only,
23
+			// but TruncateID should handle these gracefully.
24
+			doc:      "invalid ID",
25
+			id:       "1234",
26
+			expected: "1234",
27
+		},
28
+		{
29
+			doc:      "full ID",
30
+			id:       "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
31
+			expected: "90435eec5c4e",
32
+		},
33
+		{
34
+			doc:      "digest",
35
+			id:       "sha256:90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
36
+			expected: "90435eec5c4e",
37
+		},
38
+		{
39
+			doc:      "very long ID",
40
+			id:       "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a290435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
41
+			expected: "90435eec5c4e",
42
+		},
43
+	}
44
+
45
+	for _, tc := range tests {
46
+		t.Run(tc.doc, func(t *testing.T) {
47
+			actual := TruncateID(tc.id)
48
+			if actual != tc.expected {
49
+				t.Errorf("expected: %q, got: %q", tc.expected, actual)
50
+			}
51
+		})
52
+	}
53
+}
... ...
@@ -10,7 +10,7 @@ import (
10 10
 	daemonevents "github.com/docker/docker/daemon/events"
11 11
 	buildkit "github.com/docker/docker/daemon/internal/builder-next"
12 12
 	"github.com/docker/docker/daemon/internal/image"
13
-	"github.com/docker/docker/pkg/stringid"
13
+	"github.com/docker/docker/daemon/internal/stringid"
14 14
 	"github.com/moby/moby/api/types/backend"
15 15
 	"github.com/moby/moby/api/types/build"
16 16
 	"github.com/moby/moby/api/types/events"
... ...
@@ -12,8 +12,8 @@ import (
12 12
 	"github.com/containerd/platforms"
13 13
 	"github.com/docker/docker/daemon/builder"
14 14
 	"github.com/docker/docker/daemon/builder/remotecontext"
15
+	"github.com/docker/docker/daemon/internal/stringid"
15 16
 	"github.com/docker/docker/errdefs"
16
-	"github.com/docker/docker/pkg/stringid"
17 17
 	"github.com/moby/buildkit/frontend/dockerfile/instructions"
18 18
 	"github.com/moby/buildkit/frontend/dockerfile/parser"
19 19
 	"github.com/moby/buildkit/frontend/dockerfile/shell"
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	cerrdefs "github.com/containerd/errdefs"
9 9
 	"github.com/containerd/log"
10 10
 	"github.com/docker/docker/daemon/builder"
11
-	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/docker/docker/daemon/internal/stringid"
12 12
 	"github.com/moby/moby/api/types/backend"
13 13
 	"github.com/moby/moby/api/types/container"
14 14
 	"github.com/pkg/errors"
... ...
@@ -14,8 +14,8 @@ import (
14 14
 	"github.com/containerd/platforms"
15 15
 	"github.com/docker/docker/daemon/builder"
16 16
 	"github.com/docker/docker/daemon/internal/image"
17
+	"github.com/docker/docker/daemon/internal/stringid"
17 18
 	networkSettings "github.com/docker/docker/daemon/network"
18
-	"github.com/docker/docker/pkg/stringid"
19 19
 	"github.com/docker/go-connections/nat"
20 20
 	"github.com/moby/go-archive"
21 21
 	"github.com/moby/go-archive/chrootarchive"
... ...
@@ -6,7 +6,7 @@ import (
6 6
 	"testing"
7 7
 
8 8
 	"github.com/docker/docker/daemon"
9
-	"github.com/docker/docker/pkg/stringid"
9
+	"github.com/docker/docker/daemon/internal/stringid"
10 10
 	"github.com/moby/swarmkit/v2/api"
11 11
 )
12 12
 
... ...
@@ -9,7 +9,7 @@ import (
9 9
 	"github.com/containerd/log"
10 10
 	"github.com/docker/docker/daemon/internal/libcontainerd/types"
11 11
 	"github.com/docker/docker/daemon/internal/stream"
12
-	"github.com/docker/docker/pkg/stringid"
12
+	"github.com/docker/docker/daemon/internal/stringid"
13 13
 )
14 14
 
15 15
 // ExecConfig holds the configurations for execs. The Daemon keeps
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	cerrdefs "github.com/containerd/errdefs"
11
-	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/docker/docker/daemon/internal/stringid"
12 12
 	"github.com/google/uuid"
13 13
 	"github.com/moby/moby/api/types/container"
14 14
 	"gotest.tools/v3/assert"
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"github.com/docker/docker/daemon/internal/metrics"
21 21
 	"github.com/docker/docker/daemon/internal/multierror"
22 22
 	"github.com/docker/docker/daemon/internal/sliceutil"
23
+	"github.com/docker/docker/daemon/internal/stringid"
23 24
 	"github.com/docker/docker/daemon/libnetwork"
24 25
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
25 26
 	"github.com/docker/docker/daemon/libnetwork/scope"
... ...
@@ -27,7 +28,6 @@ import (
27 27
 	"github.com/docker/docker/daemon/network"
28 28
 	"github.com/docker/docker/daemon/pkg/opts"
29 29
 	"github.com/docker/docker/errdefs"
30
-	"github.com/docker/docker/pkg/stringid"
31 30
 	"github.com/docker/docker/runconfig"
32 31
 	"github.com/docker/go-connections/nat"
33 32
 	containertypes "github.com/moby/moby/api/types/container"
... ...
@@ -14,13 +14,13 @@ import (
14 14
 	"github.com/containerd/log"
15 15
 	"github.com/docker/docker/daemon/config"
16 16
 	"github.com/docker/docker/daemon/container"
17
+	"github.com/docker/docker/daemon/internal/stringid"
17 18
 	"github.com/docker/docker/daemon/libnetwork"
18 19
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge"
19 20
 	"github.com/docker/docker/daemon/links"
20 21
 	"github.com/docker/docker/daemon/network"
21 22
 	"github.com/docker/docker/errdefs"
22 23
 	"github.com/docker/docker/pkg/process"
23
-	"github.com/docker/docker/pkg/stringid"
24 24
 	"github.com/moby/sys/mount"
25 25
 	"github.com/moby/sys/user"
26 26
 	"github.com/opencontainers/selinux/go-selinux/label"
... ...
@@ -24,10 +24,10 @@ import (
24 24
 	"github.com/docker/docker/daemon/builder"
25 25
 	"github.com/docker/docker/daemon/internal/image"
26 26
 	"github.com/docker/docker/daemon/internal/layer"
27
+	"github.com/docker/docker/daemon/internal/stringid"
27 28
 	"github.com/docker/docker/errdefs"
28 29
 	"github.com/docker/docker/pkg/progress"
29 30
 	"github.com/docker/docker/pkg/streamformatter"
30
-	"github.com/docker/docker/pkg/stringid"
31 31
 	imagespec "github.com/moby/docker-image-spec/specs-go/v1"
32 32
 	"github.com/moby/go-archive"
33 33
 	"github.com/moby/moby/api/types/backend"
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	"github.com/containerd/containerd/v2/core/mount"
8 8
 	"github.com/containerd/log"
9 9
 	"github.com/docker/docker/daemon/container"
10
-	"github.com/docker/docker/pkg/stringid"
10
+	"github.com/docker/docker/daemon/internal/stringid"
11 11
 	"github.com/moby/go-archive"
12 12
 )
13 13
 
... ...
@@ -18,7 +18,7 @@ import (
18 18
 	dimages "github.com/docker/docker/daemon/images"
19 19
 	"github.com/docker/docker/daemon/internal/image"
20 20
 	"github.com/docker/docker/daemon/internal/metrics"
21
-	"github.com/docker/docker/pkg/stringid"
21
+	"github.com/docker/docker/daemon/internal/stringid"
22 22
 	"github.com/moby/moby/api/types/events"
23 23
 	imagetypes "github.com/moby/moby/api/types/image"
24 24
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
... ...
@@ -18,10 +18,10 @@ import (
18 18
 	"github.com/distribution/reference"
19 19
 	"github.com/docker/docker/daemon/internal/distribution"
20 20
 	"github.com/docker/docker/daemon/internal/metrics"
21
+	"github.com/docker/docker/daemon/internal/stringid"
21 22
 	"github.com/docker/docker/errdefs"
22 23
 	"github.com/docker/docker/pkg/progress"
23 24
 	"github.com/docker/docker/pkg/streamformatter"
24
-	"github.com/docker/docker/pkg/stringid"
25 25
 	"github.com/moby/moby/api/types/events"
26 26
 	registrytypes "github.com/moby/moby/api/types/registry"
27 27
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
... ...
@@ -17,9 +17,9 @@ import (
17 17
 	cerrdefs "github.com/containerd/errdefs"
18 18
 	"github.com/containerd/log"
19 19
 	"github.com/distribution/reference"
20
+	"github.com/docker/docker/daemon/internal/stringid"
20 21
 	"github.com/docker/docker/errdefs"
21 22
 	"github.com/docker/docker/pkg/progress"
22
-	"github.com/docker/docker/pkg/stringid"
23 23
 	"github.com/opencontainers/go-digest"
24 24
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
25 25
 )
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"path/filepath"
9 9
 	"testing"
10 10
 
11
-	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/docker/docker/daemon/internal/stringid"
12 12
 	"gotest.tools/v3/assert"
13 13
 )
14 14
 
... ...
@@ -12,7 +12,7 @@ import (
12 12
 
13 13
 	"github.com/docker/docker/daemon/graphdriver"
14 14
 	"github.com/docker/docker/daemon/internal/quota"
15
-	"github.com/docker/docker/pkg/stringid"
15
+	"github.com/docker/docker/daemon/internal/stringid"
16 16
 	"github.com/docker/go-units"
17 17
 	"golang.org/x/sys/unix"
18 18
 	"gotest.tools/v3/assert"
... ...
@@ -10,7 +10,7 @@ import (
10 10
 	"sort"
11 11
 
12 12
 	"github.com/docker/docker/daemon/graphdriver"
13
-	"github.com/docker/docker/pkg/stringid"
13
+	"github.com/docker/docker/daemon/internal/stringid"
14 14
 	"github.com/moby/go-archive"
15 15
 )
16 16
 
... ...
@@ -12,9 +12,9 @@ import (
12 12
 	"github.com/docker/docker/daemon/builder"
13 13
 	"github.com/docker/docker/daemon/internal/image"
14 14
 	"github.com/docker/docker/daemon/internal/layer"
15
+	"github.com/docker/docker/daemon/internal/stringid"
15 16
 	"github.com/docker/docker/pkg/progress"
16 17
 	"github.com/docker/docker/pkg/streamformatter"
17
-	"github.com/docker/docker/pkg/stringid"
18 18
 	"github.com/moby/moby/api/types/backend"
19 19
 	"github.com/moby/moby/api/types/registry"
20 20
 	"github.com/opencontainers/go-digest"
... ...
@@ -10,8 +10,8 @@ import (
10 10
 	"github.com/docker/docker/daemon/container"
11 11
 	"github.com/docker/docker/daemon/internal/image"
12 12
 	"github.com/docker/docker/daemon/internal/metrics"
13
+	"github.com/docker/docker/daemon/internal/stringid"
13 14
 	"github.com/docker/docker/errdefs"
14
-	"github.com/docker/docker/pkg/stringid"
15 15
 	"github.com/moby/moby/api/types/backend"
16 16
 	"github.com/moby/moby/api/types/events"
17 17
 	imagetypes "github.com/moby/moby/api/types/image"
... ...
@@ -7,8 +7,8 @@ import (
7 7
 	"strconv"
8 8
 
9 9
 	"github.com/containerd/log"
10
+	"github.com/docker/docker/daemon/internal/stringid"
10 11
 	"github.com/docker/docker/daemon/libnetwork"
11
-	"github.com/docker/docker/pkg/stringid"
12 12
 	"github.com/moby/buildkit/executor"
13 13
 	"github.com/moby/buildkit/executor/oci"
14 14
 	"github.com/moby/buildkit/executor/resources"
... ...
@@ -22,10 +22,10 @@ import (
22 22
 	"github.com/docker/docker/daemon/internal/image"
23 23
 	"github.com/docker/docker/daemon/internal/layer"
24 24
 	refstore "github.com/docker/docker/daemon/internal/refstore"
25
+	"github.com/docker/docker/daemon/internal/stringid"
25 26
 	"github.com/docker/docker/daemon/pkg/registry"
26 27
 	"github.com/docker/docker/pkg/ioutils"
27 28
 	"github.com/docker/docker/pkg/progress"
28
-	"github.com/docker/docker/pkg/stringid"
29 29
 	"github.com/opencontainers/go-digest"
30 30
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
31 31
 	"github.com/pkg/errors"
... ...
@@ -17,10 +17,10 @@ import (
17 17
 	"github.com/docker/docker/daemon/internal/distribution/metadata"
18 18
 	"github.com/docker/docker/daemon/internal/distribution/xfer"
19 19
 	"github.com/docker/docker/daemon/internal/layer"
20
+	"github.com/docker/docker/daemon/internal/stringid"
20 21
 	"github.com/docker/docker/daemon/pkg/registry"
21 22
 	"github.com/docker/docker/pkg/ioutils"
22 23
 	"github.com/docker/docker/pkg/progress"
23
-	"github.com/docker/docker/pkg/stringid"
24 24
 	apitypes "github.com/moby/moby/api/types"
25 25
 	"github.com/opencontainers/go-digest"
26 26
 	"github.com/pkg/errors"
... ...
@@ -18,9 +18,9 @@ import (
18 18
 	"github.com/docker/docker/daemon/internal/image"
19 19
 	"github.com/docker/docker/daemon/internal/ioutils"
20 20
 	"github.com/docker/docker/daemon/internal/layer"
21
+	"github.com/docker/docker/daemon/internal/stringid"
21 22
 	"github.com/docker/docker/pkg/progress"
22 23
 	"github.com/docker/docker/pkg/streamformatter"
23
-	"github.com/docker/docker/pkg/stringid"
24 24
 	"github.com/moby/go-archive/chrootarchive"
25 25
 	"github.com/moby/go-archive/compression"
26 26
 	"github.com/moby/moby/api/types/events"
... ...
@@ -11,7 +11,7 @@ import (
11 11
 	"syscall"
12 12
 	"testing"
13 13
 
14
-	"github.com/docker/docker/pkg/stringid"
14
+	"github.com/docker/docker/daemon/internal/stringid"
15 15
 	"github.com/opencontainers/go-digest"
16 16
 )
17 17
 
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	"github.com/containerd/log"
13 13
 	"github.com/docker/distribution"
14 14
 	"github.com/docker/docker/daemon/graphdriver"
15
-	"github.com/docker/docker/pkg/stringid"
15
+	"github.com/docker/docker/daemon/internal/stringid"
16 16
 	"github.com/moby/locker"
17 17
 	"github.com/moby/sys/user"
18 18
 	"github.com/opencontainers/go-digest"
... ...
@@ -13,7 +13,7 @@ import (
13 13
 	"github.com/containerd/continuity/driver"
14 14
 	"github.com/docker/docker/daemon/graphdriver"
15 15
 	"github.com/docker/docker/daemon/graphdriver/vfs"
16
-	"github.com/docker/docker/pkg/stringid"
16
+	"github.com/docker/docker/daemon/internal/stringid"
17 17
 	"github.com/moby/go-archive"
18 18
 	"github.com/moby/sys/user"
19 19
 	"github.com/opencontainers/go-digest"
... ...
@@ -2,7 +2,7 @@
2 2
 
3 3
 package layer
4 4
 
5
-import "github.com/docker/docker/pkg/stringid"
5
+import "github.com/docker/docker/daemon/internal/stringid"
6 6
 
7 7
 func (ls *layerStore) mountID(name string) string {
8 8
 	return stringid.GenerateRandomID()
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	"github.com/docker/docker/daemon/graphdriver"
11
-	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/docker/docker/daemon/internal/stringid"
12 12
 )
13 13
 
14 14
 func tarFromFilesInGraph(graph graphdriver.Driver, graphID, parentID string, files ...FileApplier) ([]byte, error) {
15 15
new file mode 100644
... ...
@@ -0,0 +1,70 @@
0
+// Package stringid provides helper functions for dealing with string identifiers
1
+package stringid
2
+
3
+import (
4
+	"crypto/rand"
5
+	"encoding/hex"
6
+	"strings"
7
+)
8
+
9
+const (
10
+	shortLen = 12
11
+	fullLen  = 64
12
+)
13
+
14
+// TruncateID returns a shorthand version of a string identifier for presentation.
15
+// For convenience, it accepts both digests ("sha256:xxxx") and IDs without an
16
+// algorithm prefix. It truncates the algorithm (if any) before truncating the
17
+// ID. The length of the truncated ID is currently fixed, but users should make
18
+// no assumptions of this to not change; it is merely a prefix of the ID that
19
+// provides enough uniqueness for common scenarios.
20
+//
21
+// Truncated IDs ("ID-prefixes") usually can be used to uniquely identify an
22
+// object (such as a container or network), but collisions may happen, in
23
+// which case an "ambiguous result" error is produced. In case of a collision,
24
+// the caller should try with a longer prefix or the full-length ID.
25
+func TruncateID(id string) string {
26
+	if i := strings.IndexRune(id, ':'); i >= 0 {
27
+		id = id[i+1:]
28
+	}
29
+	if len(id) > shortLen {
30
+		id = id[:shortLen]
31
+	}
32
+	return id
33
+}
34
+
35
+// GenerateRandomID returns a unique, 64-character ID consisting of a-z, 0-9.
36
+// It guarantees that the ID, when truncated ([TruncateID]) does not consist
37
+// of numbers only, so that the truncated ID can be used as hostname for
38
+// containers.
39
+func GenerateRandomID() string {
40
+	b := make([]byte, 32)
41
+	for {
42
+		if _, err := rand.Read(b); err != nil {
43
+			panic(err) // This shouldn't happen
44
+		}
45
+		id := hex.EncodeToString(b)
46
+
47
+		// make sure that the truncated ID does not consist of only numeric
48
+		// characters, as it's used as default hostname for containers.
49
+		//
50
+		// See:
51
+		// - https://github.com/moby/moby/issues/3869
52
+		// - https://bugzilla.redhat.com/show_bug.cgi?id=1059122
53
+		if allNum(id[:shortLen]) {
54
+			// all numbers; try again
55
+			continue
56
+		}
57
+		return id
58
+	}
59
+}
60
+
61
+// allNum checks whether id consists of only numbers (0-9).
62
+func allNum(id string) bool {
63
+	for _, c := range []byte(id) {
64
+		if c > '9' || c < '0' {
65
+			return false
66
+		}
67
+	}
68
+	return true
69
+}
0 70
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+package stringid
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestGenerateRandomID(t *testing.T) {
7
+	id := GenerateRandomID()
8
+
9
+	if len(id) != fullLen {
10
+		t.Fatalf("Id returned is incorrect: %s", id)
11
+	}
12
+}
13
+
14
+func TestTruncateID(t *testing.T) {
15
+	tests := []struct {
16
+		doc, id, expected string
17
+	}{
18
+		{
19
+			doc:      "empty ID",
20
+			id:       "",
21
+			expected: "",
22
+		},
23
+		{
24
+			// IDs are expected to be 12 (short) or 64 characters, and not be numeric only,
25
+			// but TruncateID should handle these gracefully.
26
+			doc:      "invalid ID",
27
+			id:       "1234",
28
+			expected: "1234",
29
+		},
30
+		{
31
+			doc:      "full ID",
32
+			id:       "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
33
+			expected: "90435eec5c4e",
34
+		},
35
+		{
36
+			doc:      "digest",
37
+			id:       "sha256:90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
38
+			expected: "90435eec5c4e",
39
+		},
40
+		{
41
+			doc:      "very long ID",
42
+			id:       "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a290435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
43
+			expected: "90435eec5c4e",
44
+		},
45
+	}
46
+
47
+	for _, tc := range tests {
48
+		t.Run(tc.doc, func(t *testing.T) {
49
+			actual := TruncateID(tc.id)
50
+			if actual != tc.expected {
51
+				t.Errorf("expected: %q, got: %q", tc.expected, actual)
52
+			}
53
+		})
54
+	}
55
+}
56
+
57
+func TestAllNum(t *testing.T) {
58
+	tests := []struct {
59
+		doc, id  string
60
+		expected bool
61
+	}{
62
+		{
63
+			doc:      "mixed letters and numbers",
64
+			id:       "4e38e38c8ce0",
65
+			expected: false,
66
+		},
67
+		{
68
+			doc:      "letters only",
69
+			id:       "deadbeefcafe",
70
+			expected: false,
71
+		},
72
+		{
73
+			doc:      "numbers only",
74
+			id:       "012345678912",
75
+			expected: true,
76
+		},
77
+	}
78
+	for _, tc := range tests {
79
+		t.Run(tc.doc, func(t *testing.T) {
80
+			if actual := allNum(tc.id); actual != tc.expected {
81
+				t.Errorf("expected %q to be %t, got %t, ", tc.id, !tc.expected, actual)
82
+			}
83
+		})
84
+	}
85
+}
... ...
@@ -55,6 +55,7 @@ import (
55 55
 
56 56
 	"github.com/containerd/log"
57 57
 	"github.com/docker/docker/daemon/internal/otelutil"
58
+	"github.com/docker/docker/daemon/internal/stringid"
58 59
 	"github.com/docker/docker/daemon/libnetwork/cluster"
59 60
 	"github.com/docker/docker/daemon/libnetwork/config"
60 61
 	"github.com/docker/docker/daemon/libnetwork/datastore"
... ...
@@ -71,7 +72,6 @@ import (
71 71
 	"github.com/docker/docker/daemon/libnetwork/types"
72 72
 	"github.com/docker/docker/pkg/plugingetter"
73 73
 	"github.com/docker/docker/pkg/plugins"
74
-	"github.com/docker/docker/pkg/stringid"
75 74
 	"github.com/moby/locker"
76 75
 	"github.com/pkg/errors"
77 76
 	"go.opentelemetry.io/otel"
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"github.com/containerd/log"
19 19
 	"github.com/docker/docker/daemon/internal/otelutil"
20 20
 	"github.com/docker/docker/daemon/internal/sliceutil"
21
+	"github.com/docker/docker/daemon/internal/stringid"
21 22
 	"github.com/docker/docker/daemon/libnetwork/datastore"
22 23
 	"github.com/docker/docker/daemon/libnetwork/driverapi"
23 24
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge/internal/firewaller"
... ...
@@ -36,7 +37,6 @@ import (
36 36
 	"github.com/docker/docker/daemon/libnetwork/scope"
37 37
 	"github.com/docker/docker/daemon/libnetwork/types"
38 38
 	"github.com/docker/docker/errdefs"
39
-	"github.com/docker/docker/pkg/stringid"
40 39
 	"github.com/pkg/errors"
41 40
 	"github.com/vishvananda/netlink"
42 41
 	"github.com/vishvananda/netns"
... ...
@@ -15,6 +15,7 @@ import (
15 15
 
16 16
 	"github.com/containerd/log"
17 17
 	"github.com/docker/docker/daemon/internal/sliceutil"
18
+	"github.com/docker/docker/daemon/internal/stringid"
18 19
 	"github.com/docker/docker/daemon/libnetwork/datastore"
19 20
 	"github.com/docker/docker/daemon/libnetwork/driverapi"
20 21
 	"github.com/docker/docker/daemon/libnetwork/ipamapi"
... ...
@@ -23,7 +24,6 @@ import (
23 23
 	"github.com/docker/docker/daemon/libnetwork/scope"
24 24
 	"github.com/docker/docker/daemon/libnetwork/types"
25 25
 	"github.com/docker/docker/errdefs"
26
-	"github.com/docker/docker/pkg/stringid"
27 26
 	"go.opentelemetry.io/otel"
28 27
 )
29 28
 
... ...
@@ -17,6 +17,7 @@ import (
17 17
 
18 18
 	"github.com/containerd/log"
19 19
 	"github.com/docker/docker/daemon/internal/sliceutil"
20
+	"github.com/docker/docker/daemon/internal/stringid"
20 21
 	"github.com/docker/docker/daemon/libnetwork/datastore"
21 22
 	"github.com/docker/docker/daemon/libnetwork/driverapi"
22 23
 	"github.com/docker/docker/daemon/libnetwork/internal/netiputil"
... ...
@@ -30,7 +31,6 @@ import (
30 30
 	"github.com/docker/docker/daemon/libnetwork/scope"
31 31
 	"github.com/docker/docker/daemon/libnetwork/types"
32 32
 	"github.com/docker/docker/errdefs"
33
-	"github.com/docker/docker/pkg/stringid"
34 33
 	"go.opentelemetry.io/otel"
35 34
 	"go.opentelemetry.io/otel/attribute"
36 35
 	"go.opentelemetry.io/otel/trace"
... ...
@@ -17,8 +17,8 @@ import (
17 17
 	"time"
18 18
 
19 19
 	"github.com/containerd/log"
20
+	"github.com/docker/docker/daemon/internal/stringid"
20 21
 	"github.com/docker/docker/daemon/libnetwork/types"
21
-	"github.com/docker/docker/pkg/stringid"
22 22
 	"github.com/docker/go-events"
23 23
 	iradix "github.com/hashicorp/go-immutable-radix/v2"
24 24
 	"github.com/hashicorp/memberlist"
... ...
@@ -14,7 +14,7 @@ import (
14 14
 
15 15
 	cerrdefs "github.com/containerd/errdefs"
16 16
 	"github.com/containerd/log"
17
-	"github.com/docker/docker/pkg/stringid"
17
+	"github.com/docker/docker/daemon/internal/stringid"
18 18
 	"github.com/docker/go-events"
19 19
 	"github.com/hashicorp/memberlist"
20 20
 	"gotest.tools/v3/assert"
... ...
@@ -14,8 +14,8 @@ import (
14 14
 
15 15
 	"github.com/containerd/log"
16 16
 	"github.com/docker/docker/daemon/internal/otelutil"
17
+	"github.com/docker/docker/daemon/internal/stringid"
17 18
 	"github.com/docker/docker/daemon/libnetwork/types"
18
-	"github.com/docker/docker/pkg/stringid"
19 19
 	"github.com/moby/sys/reexec"
20 20
 	"github.com/opencontainers/runtime-spec/specs-go"
21 21
 	"go.opentelemetry.io/otel"
... ...
@@ -7,10 +7,10 @@ import (
7 7
 	"fmt"
8 8
 
9 9
 	"github.com/containerd/log"
10
+	"github.com/docker/docker/daemon/internal/stringid"
10 11
 	"github.com/docker/docker/daemon/libnetwork/datastore"
11 12
 	"github.com/docker/docker/daemon/libnetwork/osl"
12 13
 	"github.com/docker/docker/daemon/libnetwork/scope"
13
-	"github.com/docker/docker/pkg/stringid"
14 14
 )
15 15
 
16 16
 const (
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"time"
9 9
 
10 10
 	"github.com/containerd/log"
11
-	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/docker/docker/daemon/internal/stringid"
12 12
 	types "github.com/moby/moby/api/types/backend"
13 13
 )
14 14
 
... ...
@@ -12,9 +12,9 @@ import (
12 12
 
13 13
 	"github.com/coreos/go-systemd/v22/journal"
14 14
 
15
+	"github.com/docker/docker/daemon/internal/stringid"
15 16
 	"github.com/docker/docker/daemon/logger"
16 17
 	"github.com/docker/docker/daemon/logger/loggerutils"
17
-	"github.com/docker/docker/pkg/stringid"
18 18
 )
19 19
 
20 20
 const name = "journald"
... ...
@@ -6,10 +6,10 @@ import (
6 6
 	"os"
7 7
 	"path/filepath"
8 8
 
9
+	"github.com/docker/docker/daemon/internal/stringid"
9 10
 	"github.com/docker/docker/errdefs"
10 11
 	"github.com/docker/docker/pkg/plugingetter"
11 12
 	"github.com/docker/docker/pkg/plugins"
12
-	"github.com/docker/docker/pkg/stringid"
13 13
 	"github.com/moby/moby/api/types/plugins/logdriver"
14 14
 	"github.com/pkg/errors"
15 15
 )
... ...
@@ -7,10 +7,10 @@ import (
7 7
 	cerrdefs "github.com/containerd/errdefs"
8 8
 	"github.com/containerd/log"
9 9
 	"github.com/docker/docker/daemon/container"
10
+	"github.com/docker/docker/daemon/internal/stringid"
10 11
 	"github.com/docker/docker/daemon/names"
11 12
 	"github.com/docker/docker/errdefs"
12 13
 	"github.com/docker/docker/pkg/namesgenerator"
13
-	"github.com/docker/docker/pkg/stringid"
14 14
 	"github.com/pkg/errors"
15 15
 )
16 16
 
... ...
@@ -22,13 +22,13 @@ import (
22 22
 	"github.com/containerd/platforms"
23 23
 	"github.com/distribution/reference"
24 24
 	"github.com/docker/docker/daemon/internal/containerfs"
25
+	"github.com/docker/docker/daemon/internal/stringid"
25 26
 	v2 "github.com/docker/docker/daemon/pkg/plugin/v2"
26 27
 	"github.com/docker/docker/dockerversion"
27 28
 	"github.com/docker/docker/errdefs"
28 29
 	"github.com/docker/docker/pkg/authorization"
29 30
 	"github.com/docker/docker/pkg/pools"
30 31
 	"github.com/docker/docker/pkg/progress"
31
-	"github.com/docker/docker/pkg/stringid"
32 32
 	"github.com/moby/go-archive/chrootarchive"
33 33
 	"github.com/moby/moby/api/types"
34 34
 	"github.com/moby/moby/api/types/backend"
... ...
@@ -14,9 +14,9 @@ import (
14 14
 	"github.com/containerd/log"
15 15
 	"github.com/distribution/reference"
16 16
 	progressutils "github.com/docker/docker/daemon/internal/distribution/utils"
17
+	"github.com/docker/docker/daemon/internal/stringid"
17 18
 	"github.com/docker/docker/pkg/ioutils"
18 19
 	"github.com/docker/docker/pkg/progress"
19
-	"github.com/docker/docker/pkg/stringid"
20 20
 	"github.com/moby/go-archive/chrootarchive"
21 21
 	"github.com/moby/moby/api/types/registry"
22 22
 	"github.com/opencontainers/go-digest"
... ...
@@ -11,10 +11,10 @@ import (
11 11
 	"github.com/containerd/containerd/v2/core/content"
12 12
 	"github.com/containerd/log"
13 13
 	"github.com/docker/docker/daemon/initlayer"
14
+	"github.com/docker/docker/daemon/internal/stringid"
14 15
 	v2 "github.com/docker/docker/daemon/pkg/plugin/v2"
15 16
 	"github.com/docker/docker/errdefs"
16 17
 	"github.com/docker/docker/pkg/plugins"
17
-	"github.com/docker/docker/pkg/stringid"
18 18
 	"github.com/moby/moby/api/types"
19 19
 	"github.com/moby/sys/mount"
20 20
 	"github.com/opencontainers/go-digest"
... ...
@@ -9,8 +9,8 @@ import (
9 9
 	"testing"
10 10
 
11 11
 	"github.com/docker/docker/daemon/internal/containerfs"
12
+	"github.com/docker/docker/daemon/internal/stringid"
12 13
 	v2 "github.com/docker/docker/daemon/pkg/plugin/v2"
13
-	"github.com/docker/docker/pkg/stringid"
14 14
 	"github.com/moby/moby/api/types"
15 15
 	"github.com/moby/moby/api/types/backend"
16 16
 	"github.com/moby/moby/api/types/events"
... ...
@@ -8,8 +8,8 @@ import (
8 8
 	"net/http"
9 9
 
10 10
 	"github.com/docker/docker/daemon/internal/sliceutil"
11
+	"github.com/docker/docker/daemon/internal/stringid"
11 12
 	"github.com/docker/docker/daemon/server/httputils"
12
-	"github.com/docker/docker/pkg/stringid"
13 13
 	"github.com/moby/moby/api/types/backend"
14 14
 	"github.com/moby/moby/api/types/container"
15 15
 	"github.com/moby/moby/api/types/versions"
... ...
@@ -8,9 +8,9 @@ import (
8 8
 
9 9
 	"github.com/containerd/log"
10 10
 	"github.com/docker/docker/daemon/internal/idtools"
11
+	"github.com/docker/docker/daemon/internal/stringid"
11 12
 	"github.com/docker/docker/daemon/volume"
12 13
 	"github.com/docker/docker/daemon/volume/safepath"
13
-	"github.com/docker/docker/pkg/stringid"
14 14
 	mounttypes "github.com/moby/moby/api/types/mount"
15 15
 	"github.com/moby/sys/user"
16 16
 	"github.com/opencontainers/selinux/go-selinux/label"
... ...
@@ -8,12 +8,12 @@ import (
8 8
 	"github.com/containerd/log"
9 9
 	"github.com/docker/docker/daemon/internal/directory"
10 10
 	"github.com/docker/docker/daemon/internal/idtools"
11
+	"github.com/docker/docker/daemon/internal/stringid"
11 12
 	"github.com/docker/docker/daemon/volume"
12 13
 	"github.com/docker/docker/daemon/volume/drivers"
13 14
 	"github.com/docker/docker/daemon/volume/service/opts"
14 15
 	"github.com/docker/docker/errdefs"
15 16
 	"github.com/docker/docker/pkg/plugingetter"
16
-	"github.com/docker/docker/pkg/stringid"
17 17
 	"github.com/moby/moby/api/types/events"
18 18
 	"github.com/moby/moby/api/types/filters"
19 19
 	volumetypes "github.com/moby/moby/api/types/volume"
... ...
@@ -22,7 +22,6 @@ import (
22 22
 	"github.com/docker/docker/daemon/volume"
23 23
 	"github.com/docker/docker/integration-cli/cli"
24 24
 	"github.com/docker/docker/integration-cli/cli/build"
25
-	"github.com/docker/docker/pkg/stringid"
26 25
 	"github.com/docker/docker/testutil"
27 26
 	"github.com/docker/docker/testutil/request"
28 27
 	"github.com/docker/go-connections/nat"
... ...
@@ -30,6 +29,7 @@ import (
30 30
 	"github.com/moby/moby/api/types/mount"
31 31
 	"github.com/moby/moby/api/types/network"
32 32
 	"github.com/moby/moby/client"
33
+	"github.com/moby/moby/client/pkg/stringid"
33 34
 	"gotest.tools/v3/assert"
34 35
 	is "gotest.tools/v3/assert/cmp"
35 36
 	"gotest.tools/v3/poll"
... ...
@@ -19,7 +19,6 @@ import (
19 19
 	"github.com/docker/docker/integration-cli/cli"
20 20
 	"github.com/docker/docker/integration-cli/daemon"
21 21
 	"github.com/docker/docker/pkg/plugins"
22
-	"github.com/docker/docker/pkg/stringid"
23 22
 	"github.com/docker/docker/testutil"
24 23
 	testdaemon "github.com/docker/docker/testutil/daemon"
25 24
 	"github.com/moby/moby/api/types/container"
... ...
@@ -560,7 +559,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverCapabilities(c *test
560 560
 
561 561
 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverOutOfBandDelete(c *testing.T) {
562 562
 	ctx := testutil.GetContext(c)
563
-	driverName := stringid.GenerateRandomID()
563
+	driverName := strings.ReplaceAll(strings.ToLower(c.Name()), "/", "_")
564 564
 	p := newVolumePlugin(c, driverName)
565 565
 	defer p.Close()
566 566
 
... ...
@@ -13,7 +13,7 @@ import (
13 13
 
14 14
 	"github.com/docker/docker/integration-cli/cli"
15 15
 	"github.com/docker/docker/integration-cli/cli/build"
16
-	"github.com/docker/docker/pkg/stringid"
16
+	"github.com/moby/moby/client/pkg/stringid"
17 17
 	"gotest.tools/v3/assert"
18 18
 	is "gotest.tools/v3/assert/cmp"
19 19
 	"gotest.tools/v3/icmd"
... ...
@@ -23,11 +23,11 @@ import (
23 23
 	"github.com/docker/docker/integration-cli/cli"
24 24
 	"github.com/docker/docker/integration-cli/daemon"
25 25
 	"github.com/docker/docker/pkg/plugins"
26
-	"github.com/docker/docker/pkg/stringid"
27 26
 	"github.com/docker/docker/testutil"
28 27
 	testdaemon "github.com/docker/docker/testutil/daemon"
29 28
 	"github.com/moby/moby/api/types/container"
30 29
 	"github.com/moby/moby/api/types/network"
30
+	"github.com/moby/moby/client/pkg/stringid"
31 31
 	"github.com/vishvananda/netlink"
32 32
 	"golang.org/x/sys/unix"
33 33
 	"gotest.tools/v3/assert"
... ...
@@ -10,8 +10,8 @@ import (
10 10
 
11 11
 	"github.com/docker/docker/integration-cli/cli"
12 12
 	"github.com/docker/docker/integration-cli/cli/build"
13
-	"github.com/docker/docker/pkg/stringid"
14 13
 	"github.com/docker/go-units"
14
+	"github.com/moby/moby/client/pkg/stringid"
15 15
 	"gotest.tools/v3/assert"
16 16
 	is "gotest.tools/v3/assert/cmp"
17 17
 	"gotest.tools/v3/icmd"
... ...
@@ -9,7 +9,7 @@ import (
9 9
 
10 10
 	"github.com/docker/docker/integration-cli/cli"
11 11
 	"github.com/docker/docker/integration-cli/cli/build"
12
-	"github.com/docker/docker/pkg/stringid"
12
+	"github.com/moby/moby/client/pkg/stringid"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 	"gotest.tools/v3/icmd"
... ...
@@ -27,12 +27,12 @@ import (
27 27
 	"github.com/docker/docker/integration-cli/cli/build"
28 28
 	"github.com/docker/docker/integration-cli/daemon"
29 29
 	"github.com/docker/docker/internal/testutils/specialimage"
30
-	"github.com/docker/docker/pkg/stringid"
31 30
 	"github.com/docker/docker/testutil"
32 31
 	testdaemon "github.com/docker/docker/testutil/daemon"
33 32
 	"github.com/docker/docker/testutil/fakecontext"
34 33
 	"github.com/docker/go-connections/nat"
35 34
 	"github.com/moby/moby/client"
35
+	"github.com/moby/moby/client/pkg/stringid"
36 36
 	"github.com/moby/sys/mountinfo"
37 37
 	"gotest.tools/v3/assert"
38 38
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -13,8 +13,8 @@ import (
13 13
 	"syscall"
14 14
 	"testing"
15 15
 
16
-	"github.com/docker/docker/pkg/stringid"
17 16
 	"github.com/docker/docker/testutil"
17
+	"github.com/moby/moby/client/pkg/stringid"
18 18
 	"gotest.tools/v3/assert"
19 19
 	is "gotest.tools/v3/assert/cmp"
20 20
 )
... ...
@@ -14,12 +14,12 @@ import (
14 14
 	"github.com/docker/docker/daemon/pkg/oci"
15 15
 	testContainer "github.com/docker/docker/integration/internal/container"
16 16
 	net "github.com/docker/docker/integration/internal/network"
17
-	"github.com/docker/docker/pkg/stringid"
18 17
 	"github.com/docker/docker/testutil"
19 18
 	"github.com/moby/moby/api/types/container"
20 19
 	"github.com/moby/moby/api/types/network"
21 20
 	"github.com/moby/moby/api/types/versions"
22 21
 	"github.com/moby/moby/client"
22
+	"github.com/moby/moby/client/pkg/stringid"
23 23
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
24 24
 	"gotest.tools/v3/assert"
25 25
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"testing"
5 5
 
6 6
 	"github.com/docker/docker/integration/internal/container"
7
-	"github.com/docker/docker/pkg/stringid"
8 7
 	containertypes "github.com/moby/moby/api/types/container"
9 8
 	"github.com/moby/moby/api/types/network"
10 9
 	"gotest.tools/v3/assert"
... ...
@@ -53,7 +52,7 @@ func TestRenameStoppedContainer(t *testing.T) {
53 53
 	assert.NilError(t, err)
54 54
 	assert.Check(t, is.Equal("/"+oldName, inspect.Name))
55 55
 
56
-	newName := "new_name" + stringid.GenerateRandomID()
56
+	newName := "new_name" + cID // using cID as random suffix
57 57
 	err = apiClient.ContainerRename(ctx, oldName, newName)
58 58
 	assert.NilError(t, err)
59 59
 
... ...
@@ -69,7 +68,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) {
69 69
 	oldName := "first_name" + t.Name()
70 70
 	cID := container.Run(ctx, t, apiClient, container.WithName(oldName))
71 71
 
72
-	newName := "new_name" + stringid.GenerateRandomID()
72
+	newName := "new_name" + cID // using cID as random suffix
73 73
 	err := apiClient.ContainerRename(ctx, oldName, newName)
74 74
 	assert.NilError(t, err)
75 75
 
76 76
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+// Package stringid provides helper functions for dealing with string identifiers.
1
+package stringid
2
+
3
+import "github.com/moby/moby/client/pkg/stringid"
4
+
5
+// TruncateID returns a shorthand version of a string identifier for presentation.
6
+//
7
+// Deprecated: use [stringid.TruncateID]. This package will be removed in the next release.
8
+func TruncateID(id string) string {
9
+	return stringid.TruncateID(id)
10
+}
11
+
12
+// GenerateRandomID returns a unique, 64-character ID consisting of a-z, 0-9.
13
+//
14
+// Deprecated: use [stringid.GenerateRandomID]. This package will be removed in the next release.
15
+func GenerateRandomID() string {
16
+	return stringid.GenerateRandomID()
17
+}
0 18
deleted file mode 100644
... ...
@@ -1,70 +0,0 @@
1
-// Package stringid provides helper functions for dealing with string identifiers
2
-package stringid
3
-
4
-import (
5
-	"crypto/rand"
6
-	"encoding/hex"
7
-	"strings"
8
-)
9
-
10
-const (
11
-	shortLen = 12
12
-	fullLen  = 64
13
-)
14
-
15
-// TruncateID returns a shorthand version of a string identifier for presentation.
16
-// For convenience, it accepts both digests ("sha256:xxxx") and IDs without an
17
-// algorithm prefix. It truncates the algorithm (if any) before truncating the
18
-// ID. The length of the truncated ID is currently fixed, but users should make
19
-// no assumptions of this to not change; it is merely a prefix of the ID that
20
-// provides enough uniqueness for common scenarios.
21
-//
22
-// Truncated IDs ("ID-prefixes") usually can be used to uniquely identify an
23
-// object (such as a container or network), but collisions may happen, in
24
-// which case an "ambiguous result" error is produced. In case of a collision,
25
-// the caller should try with a longer prefix or the full-length ID.
26
-func TruncateID(id string) string {
27
-	if i := strings.IndexRune(id, ':'); i >= 0 {
28
-		id = id[i+1:]
29
-	}
30
-	if len(id) > shortLen {
31
-		id = id[:shortLen]
32
-	}
33
-	return id
34
-}
35
-
36
-// GenerateRandomID returns a unique, 64-character ID consisting of a-z, 0-9.
37
-// It guarantees that the ID, when truncated ([TruncateID]) does not consist
38
-// of numbers only, so that the truncated ID can be used as hostname for
39
-// containers.
40
-func GenerateRandomID() string {
41
-	b := make([]byte, 32)
42
-	for {
43
-		if _, err := rand.Read(b); err != nil {
44
-			panic(err) // This shouldn't happen
45
-		}
46
-		id := hex.EncodeToString(b)
47
-
48
-		// make sure that the truncated ID does not consist of only numeric
49
-		// characters, as it's used as default hostname for containers.
50
-		//
51
-		// See:
52
-		// - https://github.com/moby/moby/issues/3869
53
-		// - https://bugzilla.redhat.com/show_bug.cgi?id=1059122
54
-		if allNum(id[:shortLen]) {
55
-			// all numbers; try again
56
-			continue
57
-		}
58
-		return id
59
-	}
60
-}
61
-
62
-// allNum checks whether id consists of only numbers (0-9).
63
-func allNum(id string) bool {
64
-	for _, c := range []byte(id) {
65
-		if c > '9' || c < '0' {
66
-			return false
67
-		}
68
-	}
69
-	return true
70
-}
71 1
deleted file mode 100644
... ...
@@ -1,86 +0,0 @@
1
-package stringid
2
-
3
-import (
4
-	"testing"
5
-)
6
-
7
-func TestGenerateRandomID(t *testing.T) {
8
-	id := GenerateRandomID()
9
-
10
-	if len(id) != fullLen {
11
-		t.Fatalf("Id returned is incorrect: %s", id)
12
-	}
13
-}
14
-
15
-func TestTruncateID(t *testing.T) {
16
-	tests := []struct {
17
-		doc, id, expected string
18
-	}{
19
-		{
20
-			doc:      "empty ID",
21
-			id:       "",
22
-			expected: "",
23
-		},
24
-		{
25
-			// IDs are expected to be 12 (short) or 64 characters, and not be numeric only,
26
-			// but TruncateID should handle these gracefully.
27
-			doc:      "invalid ID",
28
-			id:       "1234",
29
-			expected: "1234",
30
-		},
31
-		{
32
-			doc:      "full ID",
33
-			id:       "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
34
-			expected: "90435eec5c4e",
35
-		},
36
-		{
37
-			doc:      "digest",
38
-			id:       "sha256:90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
39
-			expected: "90435eec5c4e",
40
-		},
41
-		{
42
-			doc:      "very long ID",
43
-			id:       "90435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a290435eec5c4e124e741ef731e118be2fc799a68aba0466ec17717f24ce2ae6a2",
44
-			expected: "90435eec5c4e",
45
-		},
46
-	}
47
-
48
-	for _, tc := range tests {
49
-		t.Run(tc.doc, func(t *testing.T) {
50
-			actual := TruncateID(tc.id)
51
-			if actual != tc.expected {
52
-				t.Errorf("expected: %q, got: %q", tc.expected, actual)
53
-			}
54
-		})
55
-	}
56
-}
57
-
58
-func TestAllNum(t *testing.T) {
59
-	tests := []struct {
60
-		doc, id  string
61
-		expected bool
62
-	}{
63
-		{
64
-			doc:      "mixed letters and numbers",
65
-			id:       "4e38e38c8ce0",
66
-			expected: false,
67
-		},
68
-		{
69
-			doc:      "letters only",
70
-			id:       "deadbeefcafe",
71
-			expected: false,
72
-		},
73
-		{
74
-			doc:      "numbers only",
75
-			id:       "012345678912",
76
-			expected: true,
77
-		},
78
-	}
79
-	for _, tc := range tests {
80
-		t.Run(tc.doc, func(t *testing.T) {
81
-			if actual := allNum(tc.id); actual != tc.expected {
82
-				t.Errorf("expected %q to be %t, got %t, ", tc.id, !tc.expected, actual)
83
-			}
84
-		})
85
-	}
86
-}
... ...
@@ -23,7 +23,6 @@ import (
23 23
 
24 24
 	"github.com/docker/docker/daemon/container"
25 25
 	"github.com/docker/docker/pkg/ioutils"
26
-	"github.com/docker/docker/pkg/stringid"
27 26
 	"github.com/docker/docker/pkg/tailfile"
28 27
 	"github.com/docker/docker/testutil/request"
29 28
 	"github.com/docker/go-connections/sockets"
... ...
@@ -31,6 +30,7 @@ import (
31 31
 	"github.com/moby/moby/api/types/events"
32 32
 	"github.com/moby/moby/api/types/system"
33 33
 	"github.com/moby/moby/client"
34
+	"github.com/moby/moby/client/pkg/stringid"
34 35
 	"github.com/pkg/errors"
35 36
 	"gotest.tools/v3/assert"
36 37
 	"gotest.tools/v3/poll"
37 38
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+// Package stringid provides helper functions for dealing with string identifiers.
1
+//
2
+// It is similar to the package used by the daemon, but for presentational
3
+// purposes in the client.
4
+package stringid
5
+
6
+import (
7
+	"crypto/rand"
8
+	"encoding/hex"
9
+	"strings"
10
+)
11
+
12
+const (
13
+	shortLen = 12
14
+	fullLen  = 64
15
+)
16
+
17
+// TruncateID returns a shorthand version of a string identifier for presentation.
18
+// For convenience, it accepts both digests ("sha256:xxxx") and IDs without an
19
+// algorithm prefix. It truncates the algorithm (if any) before truncating the
20
+// ID. The length of the truncated ID is currently fixed, but users should make
21
+// no assumptions of this to not change; it is merely a prefix of the ID that
22
+// provides enough uniqueness for common scenarios.
23
+//
24
+// Truncated IDs ("ID-prefixes") usually can be used to uniquely identify an
25
+// object (such as a container or network), but collisions may happen, in
26
+// which case an "ambiguous result" error is produced. In case of a collision,
27
+// the caller should try with a longer prefix or the full-length ID.
28
+func TruncateID(id string) string {
29
+	if i := strings.IndexRune(id, ':'); i >= 0 {
30
+		id = id[i+1:]
31
+	}
32
+	if len(id) > shortLen {
33
+		id = id[:shortLen]
34
+	}
35
+	return id
36
+}
37
+
38
+// GenerateRandomID returns a unique, 64-character ID consisting of a-z, 0-9.
39
+func GenerateRandomID() string {
40
+	b := make([]byte, 32)
41
+	if _, err := rand.Read(b); err != nil {
42
+		panic(err) // This shouldn't happen
43
+	}
44
+	id := hex.EncodeToString(b)
45
+	return id
46
+}
... ...
@@ -966,6 +966,7 @@ github.com/moby/moby/api/types/volume
966 966
 # github.com/moby/moby/client v0.0.0 => ./client
967 967
 ## explicit; go 1.23.0
968 968
 github.com/moby/moby/client
969
+github.com/moby/moby/client/pkg/stringid
969 970
 # github.com/moby/patternmatcher v0.6.0
970 971
 ## explicit; go 1.19
971 972
 github.com/moby/patternmatcher