Compare commits

...
Author SHA1 Message Date
Paweł Gronowski e7c0f5c53c Merge pull request #7357 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.30
2026-10-07 19:28:07 +02:00
Paweł Gronowski 87fb2ebe7e Merge pull request #7359 from vvoland/gha-e2e-master
gha/e2e: Also test against moby master
2026-10-07 19:26:29 +02:00
Paweł Gronowski e8c098c8bf Merge pull request #7358 from nico1510/cloud-plugin-guidance
cmd/docker: Add guidance for unavailable cloud plugins
2026-10-07 19:26:04 +02:00
Nicolas BeckandAllie Sadler 374636ff15 cmd/docker: Add guidance for unavailable cloud plugins
Explain how to install the Offload plugin when --cloud cannot find it,
and link to the cloud flag documentation. Preserve diagnostic details
for invalid or incompatible plugins and limit installation guidance
to Offload.

Use standard default-value handling in the resolution tests to satisfy
the lint complexity limit without changing test coverage.

Co-authored-by: Allie Sadler <102604716+aevesdocker@users.noreply.github.com>
Signed-off-by: Nicolas Beck <nicolas.beck@docker.com>
2026-10-07 16:48:27 +00:00
Paweł Gronowski 862e7e6ad4 gha/e2e: Also test against moby master
Add a "master" engine-version to the e2e matrix to catch regressions
against the upcoming engine release before it reaches an rc.

There is no "master" dind image, so just copy the engine binary from the
moby/moby-bin:master image and put it into the latest dind image.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-10-07 18:25:47 +02:00
Sebastiaan van Stijn 3d2650ce76 Merge pull request #7331 from docker/dependabot/github_actions/codeql-actions-4f351959ed
build(deps): bump the codeql-actions group across 1 directory with 3 updates
2026-10-06 19:31:33 +02:00
Sebastiaan van Stijn e11705385d vendor: github.com/mattn/go-runewidth v0.0.30
- Wrap: measure grapheme clusters, not runes
- Skip grapheme segmentation for text that cannot form clusters
- Keep strings that are not valid UTF-8 on the segmenter - #111
- Take the rune fast path in the Truncate functions too

full diff: https://github.com/mattn/go-runewidth/compare/v0.0.29...v0.0.30

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-10-06 11:07:44 +02:00
dependabot[bot] fd58705f3a build(deps): bump the codeql-actions group across 1 directory with 3 updates
Bumps the codeql-actions group with 3 updates in the / directory: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.38.0 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

Updates `github/codeql-action/autobuild` from 4.38.0 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

Updates `github/codeql-action/analyze` from 4.38.0 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-06 08:46:12 +00:00
Sebastiaan van Stijn 5e6d8e3ab6 Merge pull request #7356 from nicksieger/cloud-resolver-interactive-input
cmd/docker: Allow interactive cloud context resolution
2026-10-05 23:47:38 +02:00
Nick Sieger 8503a98b68 refactor(cmd/docker): split resolveCloudContext to satisfy gocyclo
- extract setResolverStdio for resolver terminal wiring
- extract validateResolvedContext for context/endpoint checks

Signed-off-by: Nick Sieger <nick@nicksieger.com>
2026-10-05 15:29:43 -05:00
Nicolas Beck 488292ca1f cmd/docker: Allow interactive cloud context resolution
Forward stdin and stderr to cloud context providers when both are
terminals, allowing prompts during --cloud resolution. Pass file handles
directly to preserve terminal detection and leave piped or redirected
input available to the requested command.

Use the standard console handles when Windows terminal wrappers hide
their underlying files. Keep stdout reserved for the JSON response.

Signed-off-by: Nicolas Beck <nicolas.beck@docker.com>
2026-10-05 19:59:25 +00:00
Sebastiaan van Stijn 75d1d1a9e6 Merge pull request #7292 from vvoland/umasktest
Revert "e2e/container: Skip umask test until engine support"
2026-10-03 16:55:33 +02:00
Sebastiaan van Stijn cb192aeb41 Merge pull request #7351 from nicksieger/docker-cloud-build-help-parsing
fix(cli): recognize build/bake/builder aliasing before cloud help check
2026-10-03 11:42:29 +02:00
Nick Sieger 6cb63efe6f fix(cli): recognize build/bake/builder aliasing before cloud help check
docker --cloud build rejected every BuildKit-only flag (--secret, --ssh,
--push, --output, ...) because cloudHelpRequest parsed remaining args
against the legacy build command's flag set before processAliases
rewrites build into a buildx invocation. Treat build/bake/builder/image
build the same as plugin commands: only recognize --help/-h immediately
after the resolved command path instead of validating flags.

Fixes #7350

Signed-off-by: Nick Sieger <nick@nicksieger.com>
2026-10-02 16:52:51 -05:00
Paweł Gronowski e8bb7f7c81 e2e/container: Gate umask test on API support
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-09-04 16:06:32 +02:00
Paweł Gronowski 3ee4c06af6 Revert "e2e/container: Skip umask test until engine support"
This reverts commit 45a3e0bc2a.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-09-04 15:57:17 +02:00
14 changed files with 644 additions and 61 deletions
+3 -3
View File
@@ -68,14 +68,14 @@ jobs:
cache: false
-
name: Initialize CodeQL
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
category: "/language:go"
+1
View File
@@ -37,6 +37,7 @@ jobs:
- alpine
- debian
engine-version:
- master # moby/moby-bin:master
- rc # latest rc
- 29 # latest
- 28 # latest - 1
+80 -14
View File
@@ -7,9 +7,11 @@ import (
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/metadata"
@@ -43,8 +45,11 @@ import (
// The effective config directory is also passed as DOCKER_CONFIG.
// The provider must provision into that context store without changing the saved
// current context or recursively forwarding --cloud.
// It inherits the environment, receives no interactive stdin, and sends progress
// to stderr.
// It inherits the environment and sends progress and prompts to stderr. Stdin
// is forwarded only when both stdin and stderr are terminals with file handles;
// wrapped Windows consoles use the corresponding standard handles. Otherwise
// the provider receives EOF and must not prompt or open a terminal
// separately. Piped and redirected input belongs to the requested command.
// Stdout must contain exactly one JSON object:
//
// {"DOCKER_CONTEXT":"provisioned-context"}
@@ -95,22 +100,31 @@ func processCloud(ctx context.Context, dockerCli *command.DockerCli, cmd *cobra.
// the __resolve-context contract.
const cloudResolverFeature = "cloud-context-resolver"
const cloudPluginHelp = "to use --cloud, install the plugin. See https://docs.docker.com/go/cloud-flag/"
func cloudProvider(dockerCli config.Provider, rootCmd *cobra.Command) (*pluginmanager.Plugin, error) {
provider := dockerCli.ConfigFile().Features["cloud"]
if provider == "" {
provider = "offload"
}
var help string
if provider == "offload" {
help = "\n\n" + cloudPluginHelp
}
plugin, err := pluginmanager.GetPlugin(provider, dockerCli, rootCmd)
if err != nil {
return nil, fmt.Errorf("cloud resolver plugin %q unavailable: %w", provider, err)
if provider == "offload" && errdefs.IsNotFound(err) {
return nil, errors.New(cloudPluginHelp)
}
return nil, fmt.Errorf("plugin %q unavailable: %w%s", provider, err, help)
}
if plugin.Err != nil {
return nil, fmt.Errorf("invalid cloud resolver plugin %q: %w", provider, plugin.Err)
return nil, fmt.Errorf("invalid plugin %q: %w%s", provider, plugin.Err, help)
}
if supported, _ := plugin.Features[cloudResolverFeature].(bool); !supported {
return nil, fmt.Errorf("plugin %q does not support cloud context resolution", provider)
return nil, fmt.Errorf("plugin %q does not support --cloud%s", provider, help)
}
return plugin, nil
@@ -139,7 +153,7 @@ func resolveCloudContext(ctx context.Context, dockerCli *command.DockerCli, root
cmd := exec.CommandContext(ctx, plugin.Path, "--config="+config.Dir(), plugin.Name, "__resolve-context", "--", name) // #nosec G204 -- executable validated through CLI plugin discovery
cmd.Env = append(os.Environ(), config.EnvOverrideConfigDir+"="+config.Dir(), metadata.ReexecEnvvar+"="+os.Args[0])
cmd.Stderr = dockerCli.Err()
setResolverStdio(cmd, dockerCli)
out, err := cmd.Output()
if err != nil {
@@ -160,21 +174,57 @@ func resolveCloudContext(ctx context.Context, dockerCli *command.DockerCli, root
return "", errors.New("cloud resolver must return a non-default DOCKER_CONTEXT")
}
// Do not allow a missing context or endpoint to fall back to the local engine.
meta, err := dockerCli.ContextStore().GetMetadata(response.DockerContext)
if err := validateResolvedContext(dockerCli, response.DockerContext); err != nil {
return "", err
}
return response.DockerContext, nil
}
// setResolverStdio connects the resolver to the terminal when both stdin and
// stderr are terminals, so the plugin can prompt interactively. Otherwise
// stderr is forwarded and stdin is left unset.
func setResolverStdio(cmd *exec.Cmd, dockerCli *command.DockerCli) {
cmd.Stderr = dockerCli.Err()
stdinTerminal := dockerCli.In().IsTerminal()
stderrTerminal := dockerCli.Err().IsTerminal()
stdin, stdinFile := dockerCli.In().File()
stderr, stderrFile := dockerCli.Err().File()
if runtime.GOOS == "windows" {
// term.StdStreams can wrap console handles for terminal emulation,
// preventing File from exposing them to the child process.
if stdinTerminal && !stdinFile {
stdin, stdinFile = os.Stdin, true
}
if stderrTerminal && !stderrFile {
stderr, stderrFile = os.Stderr, true
}
}
if stdinTerminal && stderrTerminal && stdinFile && stderrFile {
// Pass files directly: wrapping them makes os/exec copy through pipes,
// hiding terminal identity and potentially consuming the command's input.
cmd.Stdin = stdin
cmd.Stderr = stderr
}
}
// validateResolvedContext checks that the context exists and has a Docker
// endpoint host, so a missing context or endpoint cannot fall back to the
// local engine.
func validateResolvedContext(dockerCli *command.DockerCli, name string) error {
meta, err := dockerCli.ContextStore().GetMetadata(name)
if err != nil {
return "", fmt.Errorf("loading resolved context %q: %w", response.DockerContext, err)
return fmt.Errorf("loading resolved context %q: %w", name, err)
}
endpoint, err := contextdocker.EndpointFromContext(meta)
if err != nil {
return "", fmt.Errorf("invalid resolved context %q: %w", response.DockerContext, err)
return fmt.Errorf("invalid resolved context %q: %w", name, err)
}
if endpoint.Host == "" {
return "", fmt.Errorf("resolved context %q has no Docker endpoint host", response.DockerContext)
return fmt.Errorf("resolved context %q has no Docker endpoint host", name)
}
return response.DockerContext, nil
return nil
}
// cloudHelpRequest avoids provisioning for help and shell completion.
@@ -199,7 +249,17 @@ func cloudHelpRequest(rootCmd *cobra.Command, args []string) (bool, error) {
if err != nil || cmd == rootCmd || pluginmanager.IsPluginCommand(cmd) {
// Plugin flags are opaque. Only recognize help immediately after the
// plugin name; deeper help is available through "docker help PLUGIN".
return len(args) > 1 && (args[1] == "--help" || args[1] == "-h" || args[1] == "--help=true"), nil
return len(args) > 1 && isHelpFlag(args[1]), nil
}
// "build", "bake", "builder", and "image build" are builtin commands that
// processAliases may still rewrite into a call to the builder plugin
// (buildx) after cloud resolution runs, so their flag set here is only the
// legacy builder's and does not reflect what will actually parse the
// remaining args. Treat them like a plugin command above: only recognize
// help immediately after the resolved command path.
if _, _, _, forwarded := forwardBuilder(builderDefaultPlugin, args, args); forwarded {
return len(remaining) > 0 && isHelpFlag(remaining[0]), nil
}
cmd.InitDefaultHelpFlag()
@@ -226,6 +286,12 @@ func cloudHelpRequest(rootCmd *cobra.Command, args []string) (bool, error) {
return help, nil
}
// isHelpFlag reports whether arg requests help, in any of the forms pflag
// accepts for a boolean flag.
func isHelpFlag(arg string) bool {
return arg == "--help" || arg == "-h" || arg == "--help=true"
}
// cloudPluginArgs only rewrites the global prefix, leaving subcommand arguments
// untouched. Parsing also distinguishes --cloud from another flag's value.
func cloudPluginArgs(cmd *cobra.Command, osArgs []string, contextName string) ([]string, error) {
+169 -29
View File
@@ -2,6 +2,7 @@ package main
import (
"bytes"
"cmp"
"context"
"encoding/json"
"fmt"
@@ -16,6 +17,7 @@ import (
"testing"
"time"
"github.com/creack/pty"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
@@ -62,6 +64,11 @@ func TestCloudHelpRequest(t *testing.T) {
{name: "container help", args: []string{"run", "alpine", "--help"}},
{name: "flag value", args: []string{"run", "--name", "--help", "alpine"}},
{name: "help disabled", args: []string{"run", "--help=false", "alpine"}},
{name: "build help", args: []string{"build", "--help"}, help: true},
{name: "build buildkit-only flag", args: []string{"build", "--secret", "id=s,src=./s", "."}},
{name: "image build buildkit-only flag", args: []string{"image", "build", "--push", "."}},
{name: "builder build buildkit-only flag", args: []string{"builder", "build", "--ssh", "default", "."}},
{name: "bake help", args: []string{"bake", "--help"}, help: true},
} {
t.Run(tc.name, func(t *testing.T) {
dockerCli, err := command.NewDockerCli()
@@ -169,19 +176,21 @@ func TestCloudResolution(t *testing.T) {
assert.NilError(t, err)
for _, tc := range []struct {
name string
args []string
response string
exit int
wantName string
wantErr string
plugin bool
skip bool
provider string
noProvider bool
legacyProvider bool
command []string
visible bool
name string
args []string
response string
exit int
wantName string
wantErr string
plugin bool
skip bool
provider string
noProvider bool
legacyProvider bool
invalidProvider bool
wantHelp bool
command []string
visible bool
}{
{
name: "default target",
@@ -276,28 +285,45 @@ func TestCloudResolution(t *testing.T) {
args: []string{"--cloud"},
noProvider: true,
legacyProvider: true,
wantErr: `plugin "offload" does not support cloud context resolution`,
wantErr: `plugin "offload" does not support --cloud`,
wantHelp: true,
skip: true,
},
{
name: "invalid default provider",
args: []string{"--cloud"},
noProvider: true,
invalidProvider: true,
wantErr: `invalid plugin "offload": plugin metadata does not define a vendor`,
wantHelp: true,
skip: true,
},
{
name: "invalid configured provider",
args: []string{"--cloud"},
invalidProvider: true,
wantErr: `invalid plugin "foobar": plugin metadata does not define a vendor`,
skip: true,
},
{
name: "legacy configured provider",
args: []string{"--cloud"},
legacyProvider: true,
wantErr: `plugin "foobar" does not support cloud context resolution`,
wantErr: `plugin "foobar" does not support --cloud`,
skip: true,
},
{
name: "configured provider unavailable",
args: []string{"--cloud"},
provider: "missing",
wantErr: `cloud resolver plugin "missing" unavailable`,
wantErr: `plugin "missing" unavailable`,
skip: true,
},
{
name: "provider cannot be a path",
args: []string{"--cloud"},
provider: "../foobar",
wantErr: `cloud resolver plugin "../foobar" unavailable`,
wantErr: `plugin "../foobar" unavailable`,
skip: true,
},
{
@@ -370,10 +396,7 @@ func TestCloudResolution(t *testing.T) {
if !tc.noProvider {
installedProvider = "foobar"
cfg := configfile.New(filepath.Join(configDir, config.ConfigFileName))
provider := tc.provider
if provider == "" {
provider = "foobar"
}
provider := cmp.Or(tc.provider, "foobar")
cfg.Features = map[string]string{"cloud": provider}
assert.NilError(t, cfg.Save())
}
@@ -381,12 +404,12 @@ func TestCloudResolution(t *testing.T) {
pluginDir := filepath.Join(configDir, "cli-plugins")
assert.NilError(t, os.MkdirAll(pluginDir, 0o755))
response := tc.response
if response == "" {
response = `{"DOCKER_CONTEXT":"resolved"}`
}
response := cmp.Or(tc.response, `{"DOCKER_CONTEXT":"resolved"}`)
providerMetadata := metadata.Metadata{SchemaVersion: "0.1.0", Vendor: "test"}
if tc.invalidProvider {
providerMetadata.Vendor = ""
}
if !tc.legacyProvider {
providerMetadata.Features = map[string]any{cloudResolverFeature: true}
}
@@ -448,6 +471,7 @@ printf '%s\n' "$@" > "$CLOUD_TEST_ARGS"
if tc.wantErr != "" {
assert.Assert(t, err != nil)
assert.Check(t, is.Contains(stderr.String(), tc.wantErr))
assert.Equal(t, strings.Contains(stderr.String(), cloudPluginHelp), tc.wantHelp, stderr.String())
_, statErr := os.Stat(dispatchFile)
assert.Assert(t, os.IsNotExist(statErr))
assert.Equal(t, stdout.String(), "")
@@ -473,16 +497,132 @@ printf '%s\n' "$@" > "$CLOUD_TEST_ARGS"
} else {
assert.NilError(t, err)
assert.Check(t, is.Contains(stderr.String(), "provisioning"))
name := tc.wantName
if name == "" {
name = "default"
}
name := cmp.Or(tc.wantName, "default")
assert.Equal(t, string(invocation), "--config="+configDir+"\n"+installedProvider+"\n__resolve-context\n--\n"+name+"\n")
}
})
}
}
func TestCloudResolverInput(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("fixture plugins use shell scripts and pseudo-terminals")
}
executable, err := os.Executable()
assert.NilError(t, err)
for _, tc := range []struct {
name string
stdinType string
terminalErr bool
prompt bool
}{
{name: "interactive", stdinType: "terminal", terminalErr: true, prompt: true},
{name: "piped stdin", stdinType: "pipe", terminalErr: true},
{name: "redirected stdin", stdinType: "file", terminalErr: true},
{name: "redirected stderr", stdinType: "terminal"},
{name: "noninteractive", stdinType: "pipe"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CLI_HOOKS", "false")
configDir := t.TempDir()
pluginDir := filepath.Join(configDir, "cli-plugins")
assert.NilError(t, os.MkdirAll(pluginDir, 0o755))
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-offload"), []byte(`#!/bin/sh
if [ "$1" = docker-cli-plugin-metadata ]; then
echo '{"SchemaVersion":"0.1.0","Vendor":"test","Features":{"cloud-context-resolver":true}}'
exit 0
fi
if [ -t 0 ]; then
[ -t 2 ] || exit 1
printf 'Continue? ' >&2
IFS= read -r reply || exit 1
printf '%s\n' "$reply" > "$DOCKER_CONFIG/resolver-input"
elif IFS= read -r unexpected; then
echo 'resolver consumed command input' >&2
exit 1
fi
echo '{"DOCKER_CONTEXT":"resolved"}'
`), 0o755))
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-cloudtest"), []byte(`#!/bin/sh
if [ "$1" = docker-cli-plugin-metadata ]; then
echo '{"SchemaVersion":"0.1.0","Vendor":"test"}'
exit 0
fi
IFS= read -r input || exit 1
printf '%s\n' "$input"
`), 0o755))
contextStore := store.New(filepath.Join(configDir, "contexts"), command.DefaultContextStoreConfig())
assert.NilError(t, contextStore.CreateOrUpdate(store.Metadata{
Name: "resolved",
Endpoints: map[string]any{contextdocker.DockerEndpoint: contextdocker.EndpointMeta{Host: "tcp://127.0.0.1:1"}},
}))
terminal, tty, err := pty.Open()
assert.NilError(t, err)
t.Cleanup(func() {
_ = tty.Close()
_ = terminal.Close()
})
const commandInput = "input for the original command\n"
var stdin *os.File
switch tc.stdinType {
case "terminal":
stdin = tty
input := commandInput
if tc.prompt {
input = "yes\n" + input
}
_, err = terminal.WriteString(input)
assert.NilError(t, err)
case "pipe":
var writer *os.File
stdin, writer, err = os.Pipe()
assert.NilError(t, err)
t.Cleanup(func() { _ = stdin.Close() })
t.Cleanup(func() { _ = writer.Close() })
_, err = writer.WriteString(commandInput)
assert.NilError(t, err)
assert.NilError(t, writer.Close())
case "file":
inputPath := filepath.Join(configDir, "command-input")
assert.NilError(t, os.WriteFile(inputPath, []byte(commandInput), 0o600))
stdin, err = os.Open(inputPath)
assert.NilError(t, err)
t.Cleanup(func() { _ = stdin.Close() })
}
payload, err := json.Marshal([]string{"docker", "--config=" + configDir, "--cloud", "cloudtest"})
assert.NilError(t, err)
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
var stdout, stderr bytes.Buffer
cmd := exec.CommandContext(ctx, executable, "-test.run=^TestCloudCommandProcess$")
cmd.Env = append(os.Environ(), "CLOUD_TEST_COMMAND="+string(payload))
cmd.Stdin = stdin
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.WaitDelay = time.Second
if tc.terminalErr {
cmd.Stderr = tty
}
assert.NilError(t, cmd.Run(), stderr.String())
assert.Equal(t, stdout.String(), commandInput)
reply, err := os.ReadFile(filepath.Join(configDir, "resolver-input"))
if tc.prompt {
assert.NilError(t, err)
assert.Equal(t, string(reply), "yes\n")
} else {
assert.Assert(t, os.IsNotExist(err))
}
})
}
}
// Run startup in a separate process because it installs process-wide signal
// handlers that intentionally outlive an individual command.
func TestCloudCommandProcess(t *testing.T) {
+1
View File
@@ -31,6 +31,7 @@ services:
dockerfile: engine/Dockerfile
args:
ENGINE_VERSION: ${ENGINE_VERSION:-29}
ENGINE_SOURCE: ${ENGINE_SOURCE:-dind}
privileged: true
command: ['--insecure-registry=registry:5000', '--insecure-registry=privateregistry:5001', '--experimental']
environment:
+1 -1
View File
@@ -112,7 +112,7 @@ func TestRunWithCgroupNamespace(t *testing.T) {
func TestRunUmask(t *testing.T) {
environment.SkipIfDaemonNotLinux(t)
t.Skip("FIXME: un-skip once e2e tests v29.8.0")
skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.56"))
testCases := []struct {
name string
+9 -1
View File
@@ -3,8 +3,16 @@
# ENGINE_VERSION is the version of the (docker-in-docker) Docker Engine to
# test against.
ARG ENGINE_VERSION=29
# ENGINE_SOURCE is "dind" or "moby-bin" (moby/moby-bin:master).
ARG ENGINE_SOURCE=dind
FROM docker:${ENGINE_VERSION}-dind
FROM docker:${ENGINE_VERSION}-dind AS engine-dind
# moby/moby-bin only has the daemon binaries.
FROM docker:dind AS engine-moby-bin
COPY --link --from=moby/moby-bin:master / /usr/local/bin/
FROM engine-${ENGINE_SOURCE}
# the openssh-client update is needed for security reasons when using docker:23.0-dind, currently maintained as an lts by mirantis
RUN apk --no-cache add openssl openssh-client openssh-server shadow && \
+10 -1
View File
@@ -1,5 +1,14 @@
ARG ENGINE_VERSION
FROM docker:${ENGINE_VERSION}-dind
# ENGINE_SOURCE is "dind" or "moby-bin" (moby/moby-bin:master).
ARG ENGINE_SOURCE=dind
FROM docker:${ENGINE_VERSION}-dind AS engine-dind
# moby/moby-bin only has the daemon binaries.
FROM docker:dind AS engine-moby-bin
COPY --link --from=moby/moby-bin:master / /usr/local/bin/
FROM engine-${ENGINE_SOURCE}
# Trust the tlsregistry CA so dockerd connects without --insecure-registry.
COPY registry/certs/ca.crt /usr/local/share/ca-certificates/tlsregistry-ca.crt
+5
View File
@@ -42,6 +42,11 @@ setup() {
sh e2e/testdata/registry/certs/gen-certs.sh
fi
# ENGINE_VERSION=master tests against moby/moby-bin:master.
if [ "${ENGINE_VERSION:-}" = "master" ]; then
export ENGINE_SOURCE=moby-bin
fi
COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose up --build -d >&2
# Ensure supporting services exist before running tests. If one fails to start,
+1 -1
View File
@@ -31,7 +31,7 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
github.com/mattn/go-runewidth v0.0.29
github.com/mattn/go-runewidth v0.0.30
github.com/moby/go-archive v0.3.3
github.com/moby/moby/api v1.56.1
github.com/moby/moby/client v0.6.1
+2 -2
View File
@@ -82,8 +82,8 @@ github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLM
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-runewidth v0.0.29 h1:3oGF3R/S2N9DQ3ptftzVIvg2eicmojCzlwBEmqEPDfQ=
github.com/mattn/go-runewidth v0.0.29/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk=
github.com/mattn/go-runewidth v0.0.30/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.3.3 h1:OxxR9paxsluYi+zDUEXTTaIxtkK3viymW+Ka7vRhhME=
+244 -8
View File
@@ -54,10 +54,24 @@ var (
strictWidthLUT [2][0x110000]byte
strictWidthLUTLimit atomic.Int32
strictWidthLUTOnce sync.Once
// joinerBits is the joiner table as a bitmap over
// [joinerBase, 0x10000), the range where the per-rune test has to be
// cheap for the fast paths in StringWidth and Wrap to pay off. It is
// 7.6 KB and init fills it in a few microseconds. Runes above it stay
// on the interval search: they are rare in the text these fast paths
// are for, and the emoji that are common there leave the fast path at
// their first joiner anyway.
joinerBits [(0x10000 - joinerBase) / 8]byte
)
// joinerBase is the lowest rune in the joiner table. CR is the only rune
// below it that can form a multi-rune cluster, by pairing with LF.
const joinerBase = 0x300
func init() {
initStrictWidthLUTLow()
fillJoinerBits()
strictWidthLUTLimit.Store(0x300)
handleEnv()
}
@@ -446,6 +460,60 @@ func (c *Condition) CreateLUT() {
c.lutStrictEmojiNeutral = c.StrictEmojiNeutral
}
// isASCII reports whether s has no byte above 0x7F, in which case every
// grapheme cluster in s is a single byte apart from CRLF.
func isASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] >= 0x80 {
return false
}
}
return true
}
// fillJoinerBits paints joinerBits from the joiner table.
func fillJoinerBits() {
for _, iv := range joiner {
if iv.first >= 0x10000 {
break // the table is sorted, the rest is astral
}
lo, hi := int(iv.first)-joinerBase, int(iv.last)-joinerBase
if hi >= len(joinerBits)*8 {
hi = len(joinerBits)*8 - 1
}
for lo <= hi {
if lo&7 == 0 && lo+7 <= hi {
joinerBits[lo>>3] = 0xFF
lo += 8
continue
}
joinerBits[lo>>3] |= 1 << uint(lo&7)
lo++
}
}
// A byte that is not valid UTF-8 decodes to U+FFFD, and the segmenter
// can gather a run of such bytes into one cluster, which a rune loop
// has no way to see. Marking U+FFFD keeps those strings on the
// segmenter, and the only string it holds back needlessly is one that
// really contains U+FFFD.
i := int(utf8.RuneError) - joinerBase
joinerBits[i>>3] |= 1 << uint(i&7)
}
// isJoiner reports whether r can join with a neighbour into a multi-rune
// grapheme cluster. A string of runes for which it reports false has one
// rune per cluster, so measuring it needs no grapheme segmentation.
func isJoiner(r rune) bool {
if r < joinerBase {
return r == '\r'
}
if r < 0x10000 {
i := r - joinerBase
return joinerBits[i>>3]&(1<<(uint(i)&7)) != 0
}
return inTable(r, joiner)
}
// graphemeWidth returns the width of a single grapheme cluster: the sum of
// the widths of its runes, capped at 2 cells. The cap keeps multi-rune
// sequences that render as a single glyph (ZWJ emoji, flags, Hangul jamo)
@@ -489,6 +557,13 @@ func (c *Condition) StringWidth(s string) (width int) {
return
graphemes:
// Runes first: until one of them can join a cluster, each cluster is a
// single rune and segmenting the string would only find that out the
// expensive way.
if w, ok := c.runeWidthSum(s); ok {
width = w
return
}
width = 0
g := graphemes.FromString(s)
for g.Next() {
@@ -497,12 +572,29 @@ graphemes:
return
}
// runeWidthSum adds up the widths of the runes in s, and reports false
// without a total once it meets a rune that can join a cluster, which is
// where per-rune widths stop being the whole story.
func (c *Condition) runeWidthSum(s string) (int, bool) {
width := 0
for _, r := range s {
if isJoiner(r) {
return 0, false
}
width += c.RuneWidth(r)
}
return width, true
}
// Truncate return string truncated with w cells
func (c *Condition) Truncate(s string, w int, tail string) string {
if c.StringWidth(s) <= w {
return s
}
w -= c.StringWidth(tail)
if pos, ok := c.truncateRunes(s, w); ok {
return s[:pos] + tail
}
var width int
pos := len(s)
g := graphemes.FromString(s)
@@ -517,12 +609,45 @@ func (c *Condition) Truncate(s string, w int, tail string) string {
return s[:pos] + tail
}
// truncateRunes is the loop in Truncate with every rune taken for a whole
// cluster, which holds until a rune can join one. It reports false there
// and leaves the string to the segmenter.
func (c *Condition) truncateRunes(s string, w int) (int, bool) {
width := 0
for i, r := range s {
if isJoiner(r) {
return 0, false
}
cw := c.RuneWidth(r)
if width+cw > w {
return i, true
}
width += cw
}
return len(s), true
}
// endsCluster reports whether the byte at i, which follows a rune that
// cannot join a cluster, also starts one. Cutting there is only safe when
// the rune that follows does not reach back.
func endsCluster(s string, i int) bool {
if i >= len(s) {
return true
}
r, _ := utf8.DecodeRuneInString(s[i:])
return !isJoiner(r)
}
// TruncateLeft cuts w cells from the beginning of the `s`.
func (c *Condition) TruncateLeft(s string, w int, prefix string) string {
if c.StringWidth(s) <= w {
return prefix
}
if pos, pad, ok := c.truncateLeftRunes(s, w); ok {
return prefix + strings.Repeat(" ", pad) + s[pos:]
}
var width int
pos := len(s)
@@ -547,6 +672,32 @@ func (c *Condition) TruncateLeft(s string, w int, prefix string) string {
return prefix + s[pos:]
}
// truncateLeftRunes is the loop in TruncateLeft with every rune taken for a
// whole cluster, returning the cut and the padding that replaces the cell
// the cut lands inside. It reports false at the first rune that can join a
// cluster.
func (c *Condition) truncateLeftRunes(s string, w int) (pos, pad int, ok bool) {
width := 0
for i, r := range s {
if isJoiner(r) {
return 0, 0, false
}
cw := c.RuneWidth(r)
if width+cw > w {
if width >= w {
return i, 0, true
}
end := i + utf8.RuneLen(r)
if !endsCluster(s, end) {
return 0, 0, false
}
return end, width + cw - w, true
}
width += cw
}
return len(s), 0, true
}
// TruncatePrefix cuts the beginning of `s` so the result fits in w cells, with prefix prepended
func (c *Condition) TruncatePrefix(s string, w int, prefix string) string {
if c.StringWidth(prefix) >= w {
@@ -558,6 +709,9 @@ func (c *Condition) TruncatePrefix(s string, w int, prefix string) string {
return s
}
w -= c.StringWidth(prefix)
if pos, ok := c.truncatePrefixRunes(s, sw, w); ok {
return prefix + s[pos:]
}
var width int
var pos int
g := graphemes.FromString(s)
@@ -573,29 +727,111 @@ func (c *Condition) TruncatePrefix(s string, w int, prefix string) string {
return prefix + s[pos:]
}
// Wrap return string wrapped with w cells
func (c *Condition) Wrap(s string, w int) string {
// truncatePrefixRunes is the loop in TruncatePrefix with every rune taken
// for a whole cluster, reporting false at the first rune that can join one.
func (c *Condition) truncatePrefixRunes(s string, sw, w int) (int, bool) {
width := 0
for i, r := range s {
if isJoiner(r) {
return 0, false
}
cw := c.RuneWidth(r)
if sw-(width+cw) <= w {
end := i + utf8.RuneLen(r)
if !endsCluster(s, end) {
return 0, false
}
return end, true
}
width += cw
}
return 0, true
}
// wrapRunes wraps s treating every rune as its own grapheme cluster, and
// reports false without a result once it meets a rune that can join one.
func (c *Condition) wrapRunes(s string, w int) (string, bool) {
width := 0
var out strings.Builder
// max keeps the capacity hint from dividing by zero when w is 0; a
// non-positive width breaks before every rune, as it always has.
out.Grow(len(s) + len(s)/max(w, 1) + 1)
for _, r := range s {
if isJoiner(r) {
return "", false
}
cw := c.RuneWidth(r)
if r == '\n' {
out.WriteRune(r)
width = 0
continue
} else if width+cw > w {
}
if width+cw > w {
out.WriteByte('\n')
width = 0
out.WriteRune(r)
width += cw
continue
}
out.WriteRune(r)
width += cw
}
return out.String(), true
}
// Wrap return string wrapped with w cells
func (c *Condition) Wrap(s string, w int) string {
// ASCII fast path: no grapheme clustering needed for pure ASCII
if isASCII(s) {
width := 0
var out strings.Builder
// max keeps the capacity hint from dividing by zero when w is 0;
// a non-positive width breaks before every cluster, as it always
// has.
out.Grow(len(s) + len(s)/max(w, 1) + 1)
for i := 0; i < len(s); i++ {
b := s[i]
if b == '\n' {
out.WriteByte(b)
width = 0
continue
}
// Same rule as the StringWidth fast path: no ASCII byte is
// wide or ambiguous, so the flags in c cannot change this.
cw := 0
if b >= 0x20 && b != 0x7F {
cw = 1
}
if width+cw > w {
out.WriteByte('\n')
width = 0
}
out.WriteByte(b)
width += cw
}
return out.String()
}
// Runes first, as in StringWidth. Reaching a rune that can join a
// cluster throws the wrapped text away and starts over on the
// segmenter, which is the uncommon case.
if out, ok := c.wrapRunes(s, w); ok {
return out
}
width := 0
var out strings.Builder
out.Grow(len(s) + len(s)/max(w, 1) + 1)
g := graphemes.FromString(s)
for g.Next() {
cluster := g.Value()
// LF and CRLF are each a single cluster
if strings.HasSuffix(cluster, "\n") {
out.WriteString(cluster)
width = 0
continue
}
cw := c.graphemeWidth(cluster)
if width+cw > w {
out.WriteByte('\n')
width = 0
}
out.WriteString(cluster)
width += cw
}
return out.String()
}
+117
View File
@@ -590,3 +590,120 @@ var emoji = table{
{0x1F947, 0x1F9FF}, {0x1FA58, 0x1FA5F}, {0x1FA6E, 0x1FAFF},
{0x1FC00, 0x1FFFD},
}
var joiner = table{
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x0591, 0x05BD},
{0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5},
{0x05C7, 0x05C7}, {0x0600, 0x0605}, {0x0610, 0x061A},
{0x064B, 0x065F}, {0x0670, 0x0670}, {0x06D6, 0x06DD},
{0x06DF, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED},
{0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A},
{0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x07FD, 0x07FD},
{0x0816, 0x0819}, {0x081B, 0x0823}, {0x0825, 0x0827},
{0x0829, 0x082D}, {0x0859, 0x085B}, {0x0890, 0x0891},
{0x0897, 0x089F}, {0x08CA, 0x0903}, {0x093A, 0x093C},
{0x093E, 0x094F}, {0x0951, 0x0957}, {0x0962, 0x0963},
{0x0981, 0x0983}, {0x09BC, 0x09BC}, {0x09BE, 0x09C4},
{0x09C7, 0x09C8}, {0x09CB, 0x09CD}, {0x09D7, 0x09D7},
{0x09E2, 0x09E3}, {0x09FE, 0x09FE}, {0x0A01, 0x0A03},
{0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42}, {0x0A47, 0x0A48},
{0x0A4B, 0x0A4D}, {0x0A51, 0x0A51}, {0x0A70, 0x0A71},
{0x0A75, 0x0A75}, {0x0A81, 0x0A83}, {0x0ABC, 0x0ABC},
{0x0ABE, 0x0AC5}, {0x0AC7, 0x0AC9}, {0x0ACB, 0x0ACD},
{0x0AE2, 0x0AE3}, {0x0AFA, 0x0AFF}, {0x0B01, 0x0B03},
{0x0B3C, 0x0B3C}, {0x0B3E, 0x0B44}, {0x0B47, 0x0B48},
{0x0B4B, 0x0B4D}, {0x0B55, 0x0B57}, {0x0B62, 0x0B63},
{0x0B82, 0x0B82}, {0x0BBE, 0x0BC2}, {0x0BC6, 0x0BC8},
{0x0BCA, 0x0BCD}, {0x0BD7, 0x0BD7}, {0x0C00, 0x0C04},
{0x0C3C, 0x0C3C}, {0x0C3E, 0x0C44}, {0x0C46, 0x0C48},
{0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0C62, 0x0C63},
{0x0C81, 0x0C83}, {0x0CBC, 0x0CBC}, {0x0CBE, 0x0CC4},
{0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD}, {0x0CD5, 0x0CD6},
{0x0CE2, 0x0CE3}, {0x0CF3, 0x0CF3}, {0x0D00, 0x0D03},
{0x0D3B, 0x0D3C}, {0x0D3E, 0x0D44}, {0x0D46, 0x0D48},
{0x0D4A, 0x0D4E}, {0x0D57, 0x0D57}, {0x0D62, 0x0D63},
{0x0D81, 0x0D83}, {0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4},
{0x0DD6, 0x0DD6}, {0x0DD8, 0x0DDF}, {0x0DF2, 0x0DF3},
{0x0E31, 0x0E31}, {0x0E33, 0x0E3A}, {0x0E47, 0x0E4E},
{0x0EB1, 0x0EB1}, {0x0EB3, 0x0EBC}, {0x0EC8, 0x0ECE},
{0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37},
{0x0F39, 0x0F39}, {0x0F3E, 0x0F3F}, {0x0F71, 0x0F84},
{0x0F86, 0x0F87}, {0x0F8D, 0x0F97}, {0x0F99, 0x0FBC},
{0x0FC6, 0x0FC6}, {0x102D, 0x1037}, {0x1039, 0x103E},
{0x1056, 0x1059}, {0x105E, 0x1060}, {0x1071, 0x1074},
{0x1082, 0x1082}, {0x1084, 0x1086}, {0x108D, 0x108D},
{0x109D, 0x109D}, {0x1100, 0x11FF}, {0x135D, 0x135F},
{0x1712, 0x1715}, {0x1732, 0x1734}, {0x1752, 0x1753},
{0x1772, 0x1773}, {0x17B4, 0x17D3}, {0x17DD, 0x17DD},
{0x180B, 0x180D}, {0x180F, 0x180F}, {0x1885, 0x1886},
{0x18A9, 0x18A9}, {0x1920, 0x192B}, {0x1930, 0x193B},
{0x1A17, 0x1A1B}, {0x1A55, 0x1A5E}, {0x1A60, 0x1A60},
{0x1A62, 0x1A62}, {0x1A65, 0x1A7C}, {0x1A7F, 0x1A7F},
{0x1AB0, 0x1ADD}, {0x1AE0, 0x1AEB}, {0x1B00, 0x1B04},
{0x1B34, 0x1B44}, {0x1B6B, 0x1B73}, {0x1B80, 0x1B82},
{0x1BA1, 0x1BAD}, {0x1BE6, 0x1BF3}, {0x1C24, 0x1C37},
{0x1CD0, 0x1CD2}, {0x1CD4, 0x1CE8}, {0x1CED, 0x1CED},
{0x1CF4, 0x1CF4}, {0x1CF7, 0x1CF9}, {0x1DC0, 0x1DFF},
{0x200C, 0x200D}, {0x20D0, 0x20F0}, {0x2CEF, 0x2CF1},
{0x2D7F, 0x2D7F}, {0x2DE0, 0x2DFF}, {0x302A, 0x302F},
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA802, 0xA802},
{0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA823, 0xA827},
{0xA82C, 0xA82C}, {0xA880, 0xA881}, {0xA8B4, 0xA8C5},
{0xA8E0, 0xA8F1}, {0xA8FF, 0xA8FF}, {0xA926, 0xA92D},
{0xA947, 0xA953}, {0xA960, 0xA97C}, {0xA980, 0xA983},
{0xA9B3, 0xA9C0}, {0xA9E5, 0xA9E5}, {0xAA29, 0xAA36},
{0xAA43, 0xAA43}, {0xAA4C, 0xAA4D}, {0xAA7C, 0xAA7C},
{0xAAB0, 0xAAB0}, {0xAAB2, 0xAAB4}, {0xAAB7, 0xAAB8},
{0xAABE, 0xAABF}, {0xAAC1, 0xAAC1}, {0xAAEB, 0xAAEF},
{0xAAF5, 0xAAF6}, {0xABE3, 0xABEA}, {0xABEC, 0xABED},
{0xAC00, 0xD7A3}, {0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB},
{0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE2F},
{0xFF9E, 0xFF9F}, {0x101FD, 0x101FD}, {0x102E0, 0x102E0},
{0x10376, 0x1037A}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06},
{0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F},
{0x10AE5, 0x10AE6}, {0x10D24, 0x10D27}, {0x10D69, 0x10D6D},
{0x10EAB, 0x10EAC}, {0x10EFA, 0x10EFF}, {0x10F46, 0x10F50},
{0x10F82, 0x10F85}, {0x11000, 0x11002}, {0x11038, 0x11046},
{0x11070, 0x11070}, {0x11073, 0x11074}, {0x1107F, 0x11082},
{0x110B0, 0x110BA}, {0x110BD, 0x110BD}, {0x110C2, 0x110C2},
{0x110CD, 0x110CD}, {0x11100, 0x11102}, {0x11127, 0x11134},
{0x11145, 0x11146}, {0x11173, 0x11173}, {0x11180, 0x11182},
{0x111B3, 0x111C0}, {0x111C2, 0x111C3}, {0x111C9, 0x111CC},
{0x111CE, 0x111CF}, {0x1122C, 0x11237}, {0x1123E, 0x1123E},
{0x11241, 0x11241}, {0x112DF, 0x112EA}, {0x11300, 0x11303},
{0x1133B, 0x1133C}, {0x1133E, 0x11344}, {0x11347, 0x11348},
{0x1134B, 0x1134D}, {0x11357, 0x11357}, {0x11362, 0x11363},
{0x11366, 0x1136C}, {0x11370, 0x11374}, {0x113B8, 0x113C0},
{0x113C2, 0x113C2}, {0x113C5, 0x113C5}, {0x113C7, 0x113CA},
{0x113CC, 0x113D2}, {0x113E1, 0x113E2}, {0x11435, 0x11446},
{0x1145E, 0x1145E}, {0x114B0, 0x114C3}, {0x115AF, 0x115B5},
{0x115B8, 0x115C0}, {0x115DC, 0x115DD}, {0x11630, 0x11640},
{0x116AB, 0x116B7}, {0x1171D, 0x1171F}, {0x11722, 0x1172B},
{0x1182C, 0x1183A}, {0x11930, 0x11935}, {0x11937, 0x11938},
{0x1193B, 0x11943}, {0x119D1, 0x119D7}, {0x119DA, 0x119E0},
{0x119E4, 0x119E4}, {0x11A01, 0x11A0A}, {0x11A33, 0x11A3E},
{0x11A47, 0x11A47}, {0x11A51, 0x11A5B}, {0x11A84, 0x11A99},
{0x11B60, 0x11B67}, {0x11C2F, 0x11C36}, {0x11C38, 0x11C3F},
{0x11C92, 0x11CA7}, {0x11CA9, 0x11CB6}, {0x11D31, 0x11D36},
{0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D}, {0x11D3F, 0x11D47},
{0x11D8A, 0x11D8E}, {0x11D90, 0x11D91}, {0x11D93, 0x11D97},
{0x11EF3, 0x11EF6}, {0x11F00, 0x11F03}, {0x11F34, 0x11F3A},
{0x11F3E, 0x11F42}, {0x11F5A, 0x11F5A}, {0x13440, 0x13440},
{0x13447, 0x13455}, {0x1611E, 0x1612F}, {0x16AF0, 0x16AF4},
{0x16B30, 0x16B36}, {0x16D63, 0x16D63}, {0x16D67, 0x16D6A},
{0x16F4F, 0x16F4F}, {0x16F51, 0x16F87}, {0x16F8F, 0x16F92},
{0x16FE4, 0x16FE4}, {0x16FF0, 0x16FF1}, {0x1BC9D, 0x1BC9E},
{0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1D165, 0x1D169},
{0x1D16D, 0x1D172}, {0x1D17B, 0x1D182}, {0x1D185, 0x1D18B},
{0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0x1DA00, 0x1DA36},
{0x1DA3B, 0x1DA6C}, {0x1DA75, 0x1DA75}, {0x1DA84, 0x1DA84},
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
{0x1E026, 0x1E02A}, {0x1E08F, 0x1E08F}, {0x1E130, 0x1E136},
{0x1E2AE, 0x1E2AE}, {0x1E2EC, 0x1E2EF}, {0x1E4EC, 0x1E4EF},
{0x1E5EE, 0x1E5EF}, {0x1E6E3, 0x1E6E3}, {0x1E6E6, 0x1E6E6},
{0x1E6EE, 0x1E6EF}, {0x1E6F5, 0x1E6F5}, {0x1E8D0, 0x1E8D6},
{0x1E944, 0x1E94A}, {0x1F1E6, 0x1F1FF}, {0x1F3FB, 0x1F3FF},
{0xE0020, 0xE007F}, {0xE0100, 0xE01EF},
}
+1 -1
View File
@@ -155,7 +155,7 @@ github.com/klauspost/compress/internal/le
github.com/klauspost/compress/internal/snapref
github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd/internal/xxhash
# github.com/mattn/go-runewidth v0.0.29
# github.com/mattn/go-runewidth v0.0.30
## explicit; go 1.23
github.com/mattn/go-runewidth
# github.com/moby/docker-image-spec v1.3.1