mirror of
https://github.com/docker/cli.git
synced 2026-11-06 13:10:41 -04:00
Merge tag 'v29.9.0' into 7417-29.x
v29.9.0 # -----BEGIN PGP SIGNATURE----- # # iK0EABYKAFUWIQQX90wwlHYgZQhz61ZuYvmhLGnMZgUCasf+dhsUgAAAAAAEAA5t # YW51MiwyLjUrMS4xMiwwLDMbHHBhd2VsLmdyb25vd3NraUBkb2NrZXIuY29tAAoJ # EG5i+aEsacxm3EYBAKRhamCCxqMBniTJ9GtLRVn7G3gbweSztHdAzhCo70C6AP41 # K+a4eqECp+bjqTK1ldegnmdyAPZ8g8AoPMpJgSQ2CA== # =N5Qp # -----END PGP SIGNATURE----- # gpg: Signature made Thu Oct 8 22:35:02 2026 CEST # gpg: using EDDSA key 17F74C30947620650873EB566E62F9A12C69CC66 # gpg: issuer "pawel.gronowski@docker.com" # gpg: Good signature from "Paweł Gronowski <pawel.gronowski@docker.com>" [ultimate]
This commit is contained in:
@@ -64,18 +64,18 @@ jobs:
|
||||
name: Update Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: "1.26.8"
|
||||
go-version: "1.26.9"
|
||||
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"
|
||||
|
||||
@@ -37,6 +37,7 @@ jobs:
|
||||
- alpine
|
||||
- debian
|
||||
engine-version:
|
||||
- master # moby/moby-bin:master
|
||||
- rc # latest rc
|
||||
- 29 # latest
|
||||
- 28 # latest - 1
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: "1.26.8"
|
||||
go-version: "1.26.9"
|
||||
cache: false
|
||||
-
|
||||
name: Test
|
||||
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: "1.26.8"
|
||||
go-version: "1.26.9"
|
||||
cache: false
|
||||
-
|
||||
name: Run gocompat check
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ run:
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
#
|
||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||
go: "1.26.8"
|
||||
go: "1.26.9"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
ARG GO_VERSION=1.26.9
|
||||
|
||||
# XX_VERSION specifies the version of the xx utility to use.
|
||||
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.26
|
||||
|
||||
package metadata
|
||||
|
||||
const (
|
||||
@@ -35,4 +38,7 @@ type Metadata struct {
|
||||
URL string `json:",omitempty"`
|
||||
// Hidden hides the plugin in completion and help message output.
|
||||
Hidden bool `json:",omitempty"`
|
||||
// Features declares optional contracts supported by the plugin, keyed by
|
||||
// feature name.
|
||||
Features map[string]any `json:",omitempty"`
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ type DockerCli struct {
|
||||
serverInfo ServerInfo
|
||||
contextStore store.Store
|
||||
currentContext string
|
||||
contextResolver func(*DockerCli) (string, error)
|
||||
init sync.Once
|
||||
initErr error
|
||||
dockerEndpoint docker.Endpoint
|
||||
@@ -208,6 +209,8 @@ func (cli *DockerCli) HooksEnabled() bool {
|
||||
|
||||
// Initialize the dockerCli runs initialization that must happen after command
|
||||
// line flags are parsed.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) error {
|
||||
for _, o := range ops {
|
||||
if err := o(cli); err != nil {
|
||||
@@ -257,6 +260,16 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
},
|
||||
}
|
||||
|
||||
if cli.contextResolver != nil {
|
||||
contextName, err := cli.contextResolver(cli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if contextName != "" {
|
||||
cli.currentContext = contextName
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(krissetto): pass ctx to the funcs instead of using this
|
||||
if cli.enableGlobalMeter {
|
||||
cli.createGlobalMeterProvider(cli.baseCtx)
|
||||
|
||||
@@ -84,6 +84,19 @@ func WithDefaultContextStoreConfig() CLIOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextResolver overrides context selection during [DockerCli.Initialize].
|
||||
// The resolver runs after configuration and the context store are loaded, but
|
||||
// before telemetry and endpoint initialization.
|
||||
// An empty result preserves normal context selection; an error aborts
|
||||
// initialization.
|
||||
// The resolver must not initialize the API client.
|
||||
func WithContextResolver(resolve func(*DockerCli) (string, error)) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.contextResolver = resolve
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAPIClient configures the cli to use the given API client.
|
||||
func WithAPIClient(c client.APIClient) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
|
||||
@@ -325,6 +325,64 @@ func TestInitializeShouldAlwaysCreateTheContextStore(t *testing.T) {
|
||||
assert.Check(t, cli.ContextStore() != nil)
|
||||
}
|
||||
|
||||
func TestInitializeContextResolver(t *testing.T) {
|
||||
resolverErr := errors.New("resolver failed")
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
context string
|
||||
err error
|
||||
}{
|
||||
{name: "resolved context", context: "resolved"},
|
||||
{name: "normal selection"},
|
||||
{name: "resolver failure", err: resolverErr},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
originalConfigDir := config.Dir()
|
||||
t.Cleanup(func() { config.SetDir(originalConfigDir) })
|
||||
config.SetDir(t.TempDir())
|
||||
|
||||
configDir := t.TempDir()
|
||||
cfg := configfile.New(filepath.Join(configDir, config.ConfigFileName))
|
||||
cfg.Features = map[string]string{"cloud": "foobar"}
|
||||
assert.NilError(t, cfg.Save())
|
||||
|
||||
cli, err := NewDockerCli()
|
||||
assert.NilError(t, err)
|
||||
|
||||
var loadedConfig *configfile.ConfigFile
|
||||
calls := 0
|
||||
|
||||
err = cli.Initialize(&flags.ClientOptions{ConfigDir: configDir, Context: "original"},
|
||||
WithContextResolver(func(cli *DockerCli) (string, error) {
|
||||
calls++
|
||||
assert.Equal(t, config.Dir(), configDir)
|
||||
loadedConfig = cli.ConfigFile()
|
||||
assert.Equal(t, loadedConfig.Features["cloud"], "foobar")
|
||||
assert.Assert(t, cli.ContextStore() != nil)
|
||||
assert.Assert(t, cli.client == nil)
|
||||
return tc.context, tc.err
|
||||
}),
|
||||
)
|
||||
assert.Equal(t, calls, 1)
|
||||
assert.Assert(t, cli.ConfigFile() == loadedConfig)
|
||||
assert.Assert(t, cli.client == nil)
|
||||
|
||||
if tc.err != nil {
|
||||
assert.ErrorIs(t, err, tc.err)
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
|
||||
wantContext := tc.context
|
||||
if wantContext == "" {
|
||||
wantContext = "original"
|
||||
}
|
||||
assert.Equal(t, cli.CurrentContext(), wantContext)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHooksEnabled(t *testing.T) {
|
||||
t.Run("disabled by default", func(t *testing.T) {
|
||||
// Make sure we don't depend on any existing ~/.docker/config.json
|
||||
|
||||
+10
-10
@@ -5,13 +5,13 @@ go 1.26.0
|
||||
require (
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli v29.7.2+incompatible
|
||||
github.com/docker/cli v29.8.2+incompatible
|
||||
github.com/docker/cli-docs-tool v0.11.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/go-connections v0.8.1
|
||||
github.com/docker/go-connections v0.8.2
|
||||
github.com/fvbommel/sortorder v1.2.0
|
||||
github.com/moby/moby/api v1.55.0
|
||||
github.com/moby/moby/client v0.5.1
|
||||
github.com/moby/moby/api v1.56.1
|
||||
github.com/moby/moby/client v0.6.2
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/sirupsen/logrus v1.10.2
|
||||
github.com/spf13/cobra v1.10.2
|
||||
@@ -22,7 +22,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
@@ -66,11 +66,11 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.46.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.56.0 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.57.0 // indirect
|
||||
golang.org/x/net v0.60.0 // indirect
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/term v0.46.0 // indirect
|
||||
golang.org/x/text v0.42.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
|
||||
+20
-21
@@ -1,5 +1,5 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 h1:m2xcjRABtG288vnlV3U/PKh/HnPURxZp50RKNUwBxM8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4/go.mod h1:MTUE7ZuZO89FAtAwozGg6AWD83pcdefnEq5gF82MB8s=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
@@ -42,8 +42,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/cli v29.7.2+incompatible h1:dlkwallR8XqfeVnA2ELEhdwvb4lsSwuB4IgsG8Q9cLY=
|
||||
github.com/docker/cli v29.7.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v29.8.2+incompatible h1:2zgdFuoFst2T80oS42vhKGxkd0JRo305eIEKTsxR7pQ=
|
||||
github.com/docker/cli v29.8.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.11.0 h1:7d8QARFb7QEobizqxmEM7fOteZEHwH/zWgHQtHZEcfE=
|
||||
github.com/docker/cli-docs-tool v0.11.0/go.mod h1:ma8BKiisUo8D6W05XEYIh3oa1UbgrZhi1nowyKFJa8Q=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
@@ -54,8 +54,8 @@ github.com/docker/docker-credential-helpers v0.9.9/go.mod h1:v1S+hepowrQXITkEfw6
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M=
|
||||
github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-connections v0.8.2 h1:9d9DEokx5W7qlr0shkFXIxEA7+TTD+llwzcykSlxMNc=
|
||||
github.com/docker/go-connections v0.8.2/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||
github.com/docker/go-metrics v0.1.0 h1:r76KPNpstz+IvQKSWpYegSkkyzex0V3A1ZGVx6bhGlY=
|
||||
github.com/docker/go-metrics v0.1.0/go.mod h1:PciI3sONtB051kXALN1JoIlpcu54E1FuPh+4DuqEzyw=
|
||||
@@ -133,10 +133,10 @@ github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KH
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
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/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
||||
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.5.1 h1:tYNaJno4c0HXz12y5BiqEDy0rVTYkWzI26lGvnTMiJw=
|
||||
github.com/moby/moby/client v0.5.1/go.mod h1:odLstlZ6uSnfvAgVxMpvgmb8SUdd+siH2T0GBuxVAlM=
|
||||
github.com/moby/moby/api v1.56.1 h1:PpWkvVPB7Fr/No8w+TfyJ/I6rWZ2YPZhT3JorIg+06c=
|
||||
github.com/moby/moby/api v1.56.1/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk=
|
||||
github.com/moby/moby/client v0.6.2 h1:ekSPlMccrZGcw6Scb8T9D39qE0mB+XupU/yS0zNw9N4=
|
||||
github.com/moby/moby/client v0.6.2/go.mod h1:DGcnO7qmPJrqobx/ggQgQ/AUk+2wgVdQv7uxB0/mEsc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
|
||||
@@ -236,27 +236,26 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
|
||||
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
|
||||
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
contextdocker "github.com/docker/cli/cli/context/docker"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// processCloud resolves the global --cloud[=NAME] option after configuration is
|
||||
// loaded, but before telemetry and endpoint initialization.
|
||||
// The option appears in help and completion only when the selected provider is
|
||||
// installed, valid, and declares support for the resolver contract.
|
||||
// The provider defaults to the offload plugin; features.cloud in the effective
|
||||
// Docker config.json can override the plugin name, not its executable path.
|
||||
// Explicit use also requires the provider's capability declaration.
|
||||
// Discovery for visibility does not provision a context or connect to a daemon.
|
||||
//
|
||||
// # Provider contract
|
||||
//
|
||||
// The provider must include "Features": {"cloud-context-resolver": true} in its
|
||||
// docker-cli-plugin-metadata response to declare support for this contract.
|
||||
// Older plugins without this declaration leave the flag hidden and cannot be
|
||||
// used for resolution.
|
||||
// The CLI uses normal plugin discovery and invokes:
|
||||
//
|
||||
// docker-<provider> --config=<dir> <provider> __resolve-context -- <name>
|
||||
//
|
||||
// Bare --cloud and --cloud= pass default as the target name.
|
||||
// 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 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"}
|
||||
//
|
||||
// The returned context must exist, must not be the virtual default context, and
|
||||
// must have a Docker endpoint with a nonempty host.
|
||||
// Resolver failures abort command execution without falling back to another
|
||||
// context.
|
||||
// Cancellation terminates the resolver process; provisioning subprocesses and
|
||||
// resource cleanup remain the provider's responsibility.
|
||||
// Downstream plugins receive --context=<resolved-name> instead of the global
|
||||
// --cloud option.
|
||||
// The provider implementation ships separately and must support this operation
|
||||
// before enabling the integration.
|
||||
func processCloud(ctx context.Context, dockerCli *command.DockerCli, cmd *cobra.Command, args, osArgs []string) ([]string, error) {
|
||||
if !cmd.Flags().Changed("cloud") {
|
||||
return osArgs, nil
|
||||
}
|
||||
if cmd.Flags().Changed("context") || cmd.Flags().Changed("host") {
|
||||
return osArgs, errors.New("conflicting options: cannot specify --cloud together with --context or --host")
|
||||
}
|
||||
|
||||
help, err := cloudHelpRequest(cmd, args)
|
||||
if err != nil {
|
||||
return osArgs, err
|
||||
}
|
||||
|
||||
var contextName string
|
||||
if !help {
|
||||
name, _ := cmd.Flags().GetString("cloud")
|
||||
if name == "" {
|
||||
name = "default"
|
||||
}
|
||||
|
||||
contextName, err = resolveCloudContext(ctx, dockerCli, cmd, name)
|
||||
if err != nil {
|
||||
return osArgs, fmt.Errorf("--cloud: %w", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("context", contextName); err != nil {
|
||||
return osArgs, err
|
||||
}
|
||||
}
|
||||
|
||||
return cloudPluginArgs(cmd, osArgs, contextName)
|
||||
}
|
||||
|
||||
// cloudResolverFeature is the plugin metadata feature that declares support for
|
||||
// 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 {
|
||||
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 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%s", provider, help)
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
// updateCloudFlagVisibility runs only for help and completion, avoiding plugin
|
||||
// discovery on ordinary invocations that do not use --cloud.
|
||||
func updateCloudFlagVisibility(dockerCli config.Provider, rootCmd *cobra.Command) {
|
||||
flag := rootCmd.Flags().Lookup("cloud")
|
||||
if flag == nil {
|
||||
return
|
||||
}
|
||||
_, err := cloudProvider(dockerCli, rootCmd)
|
||||
flag.Hidden = err != nil
|
||||
}
|
||||
|
||||
func resolveCloudContext(ctx context.Context, dockerCli *command.DockerCli, rootCmd *cobra.Command, name string) (string, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plugin, err := cloudProvider(dockerCli, rootCmd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
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])
|
||||
setResolverStdio(cmd, dockerCli)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return "", ctx.Err()
|
||||
}
|
||||
return "", fmt.Errorf("cloud resolver failed: %w", err)
|
||||
}
|
||||
|
||||
var response struct {
|
||||
DockerContext string `json:"DOCKER_CONTEXT"`
|
||||
}
|
||||
if err := json.Unmarshal(out, &response); err != nil {
|
||||
return "", fmt.Errorf("invalid cloud resolver response: %w", err)
|
||||
}
|
||||
response.DockerContext = strings.TrimSpace(response.DockerContext)
|
||||
if response.DockerContext == "" || response.DockerContext == command.DefaultContextName {
|
||||
return "", errors.New("cloud resolver must return a non-default DOCKER_CONTEXT")
|
||||
}
|
||||
|
||||
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", name, err)
|
||||
}
|
||||
|
||||
endpoint, err := contextdocker.EndpointFromContext(meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid resolved context %q: %w", name, err)
|
||||
}
|
||||
if endpoint.Host == "" {
|
||||
return fmt.Errorf("resolved context %q has no Docker endpoint host", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cloudHelpRequest avoids provisioning for help and shell completion.
|
||||
// Parse known command flags rather than scanning argv: --help may be an option
|
||||
// value or an argument to a container, not a request for Docker help.
|
||||
// Unknown flags and missing flag values return a usage error, so a mistyped
|
||||
// invocation fails without provisioning or falling back to the local engine.
|
||||
// Flag values are validated only by the command's own parse, after resolution.
|
||||
func cloudHelpRequest(rootCmd *cobra.Command, args []string) (bool, error) {
|
||||
help, _ := rootCmd.PersistentFlags().GetBool("help")
|
||||
version, _ := rootCmd.Flags().GetBool("version")
|
||||
if help || version || len(args) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "help", "completion", cobra.ShellCompRequestCmd, cobra.ShellCompNoDescRequestCmd:
|
||||
return true, nil
|
||||
}
|
||||
|
||||
cmd, remaining, err := rootCmd.Find(args)
|
||||
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 && 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()
|
||||
flags := cmd.Flags()
|
||||
flags.AddFlagSet(cmd.PersistentFlags())
|
||||
flags.AddFlagSet(cmd.InheritedFlags())
|
||||
|
||||
err = flags.ParseAll(remaining, func(flag *pflag.Flag, value string) error {
|
||||
if flag.Name == "help" {
|
||||
var err error
|
||||
if help, err = strconv.ParseBool(value); err != nil {
|
||||
return fmt.Errorf("invalid argument %q for \"--help\" flag: %w", value, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
// Format the usage error directly. The root command's FlagErrorFunc
|
||||
// first checks whether the daemon supports the command, which would
|
||||
// connect to the engine selected before --cloud is resolved.
|
||||
return false, cli.FlagErrorFunc(cmd, err)
|
||||
}
|
||||
|
||||
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) {
|
||||
flags := pflag.NewFlagSet(cmd.Name(), pflag.ContinueOnError)
|
||||
flags.SetInterspersed(false)
|
||||
flags.AddFlagSet(cmd.Flags())
|
||||
flags.AddFlagSet(cmd.PersistentFlags())
|
||||
|
||||
result := []string{osArgs[0]}
|
||||
if contextName != "" {
|
||||
result = append(result, "--context="+contextName)
|
||||
}
|
||||
|
||||
if err := flags.ParseAll(osArgs[1:], func(flag *pflag.Flag, value string) error {
|
||||
if flag.Name != "cloud" {
|
||||
result = append(result, "--"+flag.Name+"="+value)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return osArgs, err
|
||||
}
|
||||
|
||||
if flags.ArgsLenAtDash() >= 0 {
|
||||
result = append(result, "--")
|
||||
}
|
||||
|
||||
return append(result, flags.Args()...), nil
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"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"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
contextdocker "github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestCloudFlagHidden(t *testing.T) {
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
assert.NilError(t, err)
|
||||
|
||||
tcmd := newDockerCommand(dockerCli)
|
||||
tcmd.SetArgs([]string{"--cloud=team", "--help"})
|
||||
cmd, _, err := tcmd.HandleGlobalFlags()
|
||||
assert.NilError(t, err)
|
||||
|
||||
name, err := cmd.Flags().GetString("cloud")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, name, "team")
|
||||
assert.Assert(t, cmd.Flags().Lookup("cloud").Hidden)
|
||||
assert.Assert(t, !strings.Contains(cmd.UsageString(), "--cloud"))
|
||||
}
|
||||
|
||||
func TestCloudHelpRequest(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
help bool
|
||||
}{
|
||||
{name: "no command", help: true},
|
||||
{name: "global help", args: []string{"--help"}, help: true},
|
||||
{name: "global version", args: []string{"--version"}, help: true},
|
||||
{name: "help command", args: []string{"help", "run"}, help: true},
|
||||
{name: "completion request", args: []string{"__complete", "run", ""}, help: true},
|
||||
{name: "completion script", args: []string{"completion", "fish"}, help: true},
|
||||
{name: "builtin help", args: []string{"run", "--help"}, help: true},
|
||||
{name: "boolean help value", args: []string{"run", "--help=1"}, help: true},
|
||||
{name: "nested help", args: []string{"container", "run", "--help"}, help: true},
|
||||
{name: "plugin help", args: []string{"compose", "--help"}, help: true},
|
||||
{name: "command execution", args: []string{"ps"}},
|
||||
{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()
|
||||
assert.NilError(t, err)
|
||||
tcmd := newDockerCommand(dockerCli)
|
||||
tcmd.SetArgs(append([]string{"--cloud"}, tc.args...))
|
||||
cmd, args, err := tcmd.HandleGlobalFlags()
|
||||
assert.NilError(t, err)
|
||||
|
||||
help, err := cloudHelpRequest(cmd, args)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, help, tc.help)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Flag errors are reported before cloud resolution, so they must not run the
|
||||
// command's daemon feature checks against the engine selected before it.
|
||||
func TestCloudFlagErrorSkipsDaemon(t *testing.T) {
|
||||
originalConfig := config.Dir()
|
||||
t.Cleanup(func() { config.SetDir(originalConfig) })
|
||||
config.SetDir(t.TempDir())
|
||||
|
||||
var pings atomic.Int32
|
||||
daemon := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
pings.Add(1)
|
||||
w.Header().Set("Docker-Experimental", "false")
|
||||
}))
|
||||
defer daemon.Close()
|
||||
t.Setenv("DOCKER_HOST", "tcp://"+daemon.Listener.Addr().String())
|
||||
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
assert.NilError(t, err)
|
||||
tcmd := newDockerCommand(dockerCli)
|
||||
tcmd.SetArgs([]string{"--cloud", "checkpoint", "ls", "--unknown"})
|
||||
cmd, args, err := tcmd.HandleGlobalFlags()
|
||||
assert.NilError(t, err)
|
||||
assert.NilError(t, tcmd.Initialize())
|
||||
|
||||
_, err = cloudHelpRequest(cmd, args)
|
||||
assert.Check(t, is.ErrorContains(err, "unknown flag: --unknown"))
|
||||
assert.Check(t, is.Equal(pings.Load(), int32(0)))
|
||||
}
|
||||
|
||||
func TestCloudPluginArgs(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
context string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "global flag only",
|
||||
args: []string{"docker", "--cloud=foo", "compose", "up", "--cloud=child"},
|
||||
context: "cloud-foo",
|
||||
want: []string{"docker", "--context=cloud-foo", "compose", "up", "--cloud=child"},
|
||||
},
|
||||
{
|
||||
name: "flag value is not cloud flag",
|
||||
args: []string{"docker", "--config", "--cloud", "--cloud", "-D", "compose", "up"},
|
||||
context: "cloud-default",
|
||||
want: []string{"docker", "--context=cloud-default", "--config=--cloud", "--debug=true", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "delimiter preserved",
|
||||
args: []string{"docker", "--cloud=foo", "--", "compose", "--", "--cloud"},
|
||||
context: "cloud-foo",
|
||||
want: []string{"docker", "--context=cloud-foo", "--", "compose", "--", "--cloud"},
|
||||
},
|
||||
{
|
||||
name: "repeated cloud flags",
|
||||
args: []string{"docker", "--cloud=foo", "--cloud=bar", "compose", "up"},
|
||||
context: "cloud-bar",
|
||||
want: []string{"docker", "--context=cloud-bar", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "help without resolution",
|
||||
args: []string{"docker", "--cloud", "compose", "--help"},
|
||||
want: []string{"docker", "compose", "--help"},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
assert.NilError(t, err)
|
||||
tcmd := newDockerCommand(dockerCli)
|
||||
tcmd.SetArgs(tc.args[1:])
|
||||
cmd, _, err := tcmd.HandleGlobalFlags()
|
||||
assert.NilError(t, err)
|
||||
|
||||
got, err := cloudPluginArgs(cmd, tc.args, tc.context)
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, got, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Exercise the real startup and subprocess boundary without a daemon or cloud
|
||||
// service, including failures that must never dispatch the requested command.
|
||||
func TestCloudResolution(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("fixture plugins use shell scripts")
|
||||
}
|
||||
|
||||
executable, err := os.Executable()
|
||||
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
|
||||
invalidProvider bool
|
||||
wantHelp bool
|
||||
command []string
|
||||
visible bool
|
||||
}{
|
||||
{
|
||||
name: "default target",
|
||||
args: []string{"--cloud"},
|
||||
wantName: "default",
|
||||
},
|
||||
{
|
||||
name: "empty target",
|
||||
args: []string{"--cloud="},
|
||||
wantName: "default",
|
||||
},
|
||||
{
|
||||
name: "named target",
|
||||
args: []string{"--cloud=team"},
|
||||
wantName: "team",
|
||||
},
|
||||
{
|
||||
name: "plugin dispatch",
|
||||
args: []string{"--cloud=team"},
|
||||
wantName: "team",
|
||||
plugin: true,
|
||||
},
|
||||
{
|
||||
name: "nonzero exit",
|
||||
args: []string{"--cloud"},
|
||||
exit: 1,
|
||||
wantErr: "cloud resolver failed",
|
||||
},
|
||||
{
|
||||
name: "malformed JSON",
|
||||
args: []string{"--cloud"},
|
||||
response: "not JSON",
|
||||
wantErr: "invalid cloud resolver response",
|
||||
},
|
||||
{
|
||||
name: "multiple responses",
|
||||
args: []string{"--cloud"},
|
||||
response: `{} {}`,
|
||||
wantErr: "invalid cloud resolver response",
|
||||
},
|
||||
{
|
||||
name: "missing field",
|
||||
args: []string{"--cloud"},
|
||||
response: `{}`,
|
||||
wantErr: "must return a non-default DOCKER_CONTEXT",
|
||||
},
|
||||
{
|
||||
name: "local context",
|
||||
args: []string{"--cloud"},
|
||||
response: `{"DOCKER_CONTEXT":"default"}`,
|
||||
wantErr: "must return a non-default DOCKER_CONTEXT",
|
||||
},
|
||||
{
|
||||
name: "missing context",
|
||||
args: []string{"--cloud"},
|
||||
response: `{"DOCKER_CONTEXT":"missing"}`,
|
||||
wantErr: `loading resolved context "missing"`,
|
||||
},
|
||||
{
|
||||
name: "missing endpoint host",
|
||||
args: []string{"--cloud"},
|
||||
response: `{"DOCKER_CONTEXT":"empty-host"}`,
|
||||
wantErr: "has no Docker endpoint host",
|
||||
},
|
||||
{
|
||||
name: "explicit context conflict",
|
||||
args: []string{"--cloud", "--context=other"},
|
||||
wantErr: "conflicting options",
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "explicit host conflict",
|
||||
args: []string{"--cloud", "--host=tcp://localhost:2375"},
|
||||
wantErr: "conflicting options",
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "invalid flag before help",
|
||||
args: []string{"--cloud"},
|
||||
command: []string{"run", "--unknown", "--help"},
|
||||
wantErr: "unknown flag: --unknown",
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "default provider",
|
||||
args: []string{"--cloud"},
|
||||
noProvider: true,
|
||||
wantName: "default",
|
||||
},
|
||||
{
|
||||
name: "legacy default provider",
|
||||
args: []string{"--cloud"},
|
||||
noProvider: true,
|
||||
legacyProvider: true,
|
||||
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`,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "configured provider unavailable",
|
||||
args: []string{"--cloud"},
|
||||
provider: "missing",
|
||||
wantErr: `plugin "missing" unavailable`,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "provider cannot be a path",
|
||||
args: []string{"--cloud"},
|
||||
provider: "../foobar",
|
||||
wantErr: `plugin "../foobar" unavailable`,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "no cloud option",
|
||||
noProvider: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "help",
|
||||
args: []string{"--cloud", "--help"},
|
||||
noProvider: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "version",
|
||||
args: []string{"--cloud", "--version"},
|
||||
noProvider: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "help exposes supported provider",
|
||||
command: []string{"--help"},
|
||||
noProvider: true,
|
||||
visible: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "help hides legacy provider",
|
||||
command: []string{"--help"},
|
||||
noProvider: true,
|
||||
legacyProvider: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "help hides missing provider",
|
||||
command: []string{"--help"},
|
||||
provider: "missing",
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "completion exposes supported provider",
|
||||
command: []string{"__complete", "--cl"},
|
||||
noProvider: true,
|
||||
visible: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "completion hides legacy provider",
|
||||
command: []string{"__complete", "--cl"},
|
||||
noProvider: true,
|
||||
legacyProvider: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "completion hides missing provider",
|
||||
command: []string{"__complete", "--cl"},
|
||||
provider: "missing",
|
||||
skip: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Explicit --cloud must override inherited endpoint selection.
|
||||
t.Setenv("DOCKER_CONTEXT", "inherited")
|
||||
t.Setenv("DOCKER_HOST", "tcp://127.0.0.1:1")
|
||||
t.Setenv("DOCKER_CLI_HOOKS", "false")
|
||||
t.Setenv("DOCKER_CONFIG", t.TempDir())
|
||||
|
||||
configDir := t.TempDir()
|
||||
installedProvider := "offload"
|
||||
if !tc.noProvider {
|
||||
installedProvider = "foobar"
|
||||
cfg := configfile.New(filepath.Join(configDir, config.ConfigFileName))
|
||||
provider := cmp.Or(tc.provider, "foobar")
|
||||
cfg.Features = map[string]string{"cloud": provider}
|
||||
assert.NilError(t, cfg.Save())
|
||||
}
|
||||
|
||||
pluginDir := filepath.Join(configDir, "cli-plugins")
|
||||
assert.NilError(t, os.MkdirAll(pluginDir, 0o755))
|
||||
|
||||
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}
|
||||
}
|
||||
pluginMetadata, err := json.Marshal(providerMetadata)
|
||||
assert.NilError(t, err)
|
||||
|
||||
script := fmt.Sprintf(`#!/bin/sh
|
||||
if [ "$1" = docker-cli-plugin-metadata ]; then
|
||||
echo '%s'
|
||||
exit 0
|
||||
fi
|
||||
printf '%%s\n' "$@" >> "$DOCKER_CONFIG/resolver-args"
|
||||
echo provisioning >&2
|
||||
printf '%%s\n' '%s'
|
||||
exit %d
|
||||
`, pluginMetadata, response, tc.exit)
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-"+installedProvider), []byte(script), 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
|
||||
printf '%s\n' "$@" > "$CLOUD_TEST_ARGS"
|
||||
`), 0o755))
|
||||
|
||||
dispatchFile := filepath.Join(configDir, "dispatch-args")
|
||||
t.Setenv("CLOUD_TEST_ARGS", dispatchFile)
|
||||
|
||||
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"}},
|
||||
}))
|
||||
assert.NilError(t, contextStore.CreateOrUpdate(store.Metadata{
|
||||
Name: "empty-host",
|
||||
Endpoints: map[string]any{contextdocker.DockerEndpoint: contextdocker.EndpointMeta{}},
|
||||
}))
|
||||
|
||||
args := append([]string{"docker", "--config=" + configDir}, tc.args...)
|
||||
switch {
|
||||
case tc.command != nil:
|
||||
args = append(args, tc.command...)
|
||||
case tc.plugin || tc.wantErr != "":
|
||||
args = append(args, "cloudtest", "up", "--cloud=child")
|
||||
default:
|
||||
args = append(args, "context", "show")
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(args)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := exec.CommandContext(t.Context(), executable, "-test.run=^TestCloudCommandProcess$")
|
||||
cmd.Env = append(os.Environ(), "CLOUD_TEST_COMMAND="+string(payload))
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err = cmd.Run()
|
||||
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(), "")
|
||||
} else {
|
||||
assert.NilError(t, err, stderr.String())
|
||||
if tc.command != nil {
|
||||
assert.Equal(t, strings.Contains(stdout.String(), "--cloud"), tc.visible, stdout.String())
|
||||
}
|
||||
if !tc.skip {
|
||||
if tc.plugin {
|
||||
args, err := os.ReadFile(dispatchFile)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(args), "--context=resolved\n--config="+configDir+"\ncloudtest\nup\n--cloud=child\n")
|
||||
} else {
|
||||
assert.Equal(t, stdout.String(), "resolved\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invocation, err := os.ReadFile(filepath.Join(configDir, "resolver-args"))
|
||||
if tc.skip {
|
||||
assert.Assert(t, os.IsNotExist(err))
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Contains(stderr.String(), "provisioning"))
|
||||
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) {
|
||||
payload := os.Getenv("CLOUD_TEST_COMMAND")
|
||||
if payload == "" {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NilError(t, json.Unmarshal([]byte(payload), &os.Args))
|
||||
|
||||
err := dockerMain(context.Background())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
if err == nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
os.Exit(getExitCode(err))
|
||||
}
|
||||
|
||||
func TestCloudResolutionCancellation(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("fixture plugin uses a shell script")
|
||||
}
|
||||
|
||||
originalConfig := config.Dir()
|
||||
t.Cleanup(func() { config.SetDir(originalConfig) })
|
||||
config.SetDir(t.TempDir())
|
||||
|
||||
cfg := configfile.New(filepath.Join(config.Dir(), config.ConfigFileName))
|
||||
cfg.Features = map[string]string{"cloud": "foobar"}
|
||||
assert.NilError(t, cfg.Save())
|
||||
|
||||
pluginDir := filepath.Join(config.Dir(), "cli-plugins")
|
||||
assert.NilError(t, os.MkdirAll(pluginDir, 0o755))
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-foobar"), []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
|
||||
exec sleep 30
|
||||
`), 0o755))
|
||||
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
assert.NilError(t, err)
|
||||
tcmd := newDockerCommand(dockerCli)
|
||||
tcmd.SetArgs(strings.Fields("--cloud context show"))
|
||||
cmd, _, err := tcmd.HandleGlobalFlags()
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err = resolveCloudContext(ctx, dockerCli, cmd, "default")
|
||||
assert.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
}
|
||||
+17
-1
@@ -178,6 +178,9 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand {
|
||||
_ = cmd.RegisterFlagCompletionFunc("log-level", completeLogLevels)
|
||||
|
||||
cmd.Flags().BoolP("version", "v", false, "Print version information and quit")
|
||||
cmd.Flags().String("cloud", "", "Use a cloud context, provisioning it if necessary (optionally --cloud=NAME)")
|
||||
cmd.Flags().Lookup("cloud").NoOptDefVal = "default"
|
||||
cmd.Flags().Lookup("cloud").Hidden = true
|
||||
setFlagErrorFunc(dockerCli, cmd)
|
||||
|
||||
setupHelpCommand(dockerCli, cmd, helpCmd)
|
||||
@@ -250,6 +253,7 @@ func setHelpFunc(dockerCli command.Cli, cmd *cobra.Command) {
|
||||
ccmd.Println(err)
|
||||
return
|
||||
}
|
||||
updateCloudFlagVisibility(dockerCli, ccmd.Root())
|
||||
|
||||
if len(args) >= 1 {
|
||||
err := tryRunPluginHelp(dockerCli, ccmd, args)
|
||||
@@ -508,7 +512,18 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tcmd.Initialize(command.WithEnableGlobalMeterProvider(), command.WithEnableGlobalTracerProvider()); err != nil {
|
||||
if err := tcmd.Initialize(
|
||||
command.WithEnableGlobalMeterProvider(),
|
||||
command.WithEnableGlobalTracerProvider(),
|
||||
command.WithContextResolver(func(cli *command.DockerCli) (string, error) {
|
||||
var err error
|
||||
os.Args, err = processCloud(ctx, cli, cmd, args, os.Args)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cmd.Flags().GetString("context")
|
||||
}),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -538,6 +553,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
|
||||
if err := pluginmanager.AddPluginCommandStubs(dockerCli, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
updateCloudFlagVisibility(dockerCli, cmd)
|
||||
}
|
||||
|
||||
var subCommand *cobra.Command
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
ARG GO_VERSION=1.26.9
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
ARG GO_VERSION=1.26.9
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
ARG GO_VERSION=1.26.9
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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 && \
|
||||
|
||||
Vendored
+10
-1
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
+9
-9
@@ -15,14 +15,14 @@ require (
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containerd/errdefs/pkg v0.3.0
|
||||
github.com/containerd/log v0.1.0
|
||||
github.com/containerd/log v0.2.0
|
||||
github.com/containerd/platforms v1.0.0-rc.5
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli-docs-tool v0.11.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.9.9
|
||||
github.com/docker/go-connections v0.8.1
|
||||
github.com/docker/go-connections v0.8.2
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fvbommel/sortorder v1.2.0
|
||||
github.com/go-jose/go-jose/v4 v4.1.5
|
||||
@@ -31,10 +31,10 @@ 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.31
|
||||
github.com/moby/go-archive v0.3.3
|
||||
github.com/moby/moby/api v1.56.0
|
||||
github.com/moby/moby/client v0.6.0
|
||||
github.com/moby/moby/api v1.56.1
|
||||
github.com/moby/moby/client v0.6.2
|
||||
github.com/moby/patternmatcher v0.6.1
|
||||
github.com/moby/swarmkit/v2 v2.1.2
|
||||
github.com/moby/sys/atomicwriter v0.1.0
|
||||
@@ -70,8 +70,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0 // indirect; see https://github.com/microsoft/hcsshim/pull/2545
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -87,7 +87,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.19.2 // indirect
|
||||
github.com/klauspost/compress v1.20.1 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/user v0.4.1 // indirect
|
||||
github.com/moby/sys/userns v0.2.1 // indirect
|
||||
@@ -102,7 +102,7 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
|
||||
golang.org/x/mod v0.41.0 // indirect
|
||||
golang.org/x/net v0.59.0 // indirect
|
||||
golang.org/x/net v0.60.0 // indirect
|
||||
golang.org/x/time v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
|
||||
+18
-19
@@ -2,10 +2,10 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 h1:m2xcjRABtG288vnlV3U/PKh/HnPURxZp50RKNUwBxM8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4/go.mod h1:MTUE7ZuZO89FAtAwozGg6AWD83pcdefnEq5gF82MB8s=
|
||||
github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0 h1:71VU8kW/LCxJkx41+YYys1EgQO+AUnPa1TrxTSyYUCU=
|
||||
github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0/go.mod h1:ma0QpxizD4fyyzJ1C7MIblymTD2Nxz5Mho/3CepwIZg=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
@@ -18,8 +18,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc=
|
||||
github.com/containerd/log v0.2.0/go.mod h1:/M7L7CXKcPTfNC74XzaK+5H5KbO5+4lJVpuVI6vRLoM=
|
||||
github.com/containerd/platforms v1.0.0-rc.5 h1:vXd569rDrz8LeMXzAnBsy6LADV5YtsD8oyaRarxdmSU=
|
||||
github.com/containerd/platforms v1.0.0-rc.5/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
@@ -37,8 +37,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker-credential-helpers v0.9.9 h1:BkydjIgZ46JnDbqyM2p2fc63KMw6y+KHL3Em/2AGJ7w=
|
||||
github.com/docker/docker-credential-helpers v0.9.9/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M=
|
||||
github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-connections v0.8.2 h1:9d9DEokx5W7qlr0shkFXIxEA7+TTD+llwzcykSlxMNc=
|
||||
github.com/docker/go-connections v0.8.2/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-events v0.1.0 h1:J8VX4H7Ta8mmBhv/K/24x1kosKOrEEnYmGjcdFHTOW0=
|
||||
github.com/docker/go-events v0.1.0/go.mod h1:jwVwMgySJX4PiukIIRrind1wPGU8QhPa8aTEPkreUj4=
|
||||
github.com/docker/go-metrics v0.1.0 h1:r76KPNpstz+IvQKSWpYegSkkyzex0V3A1ZGVx6bhGlY=
|
||||
@@ -78,20 +78,20 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
|
||||
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
|
||||
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.31 h1:1oCV4ReTlosDBc6rYRw5cL8S/4y8iv5oRl4SJ5IBgwo=
|
||||
github.com/mattn/go-runewidth v0.0.31/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=
|
||||
github.com/moby/go-archive v0.3.3/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
||||
github.com/moby/moby/api v1.56.0 h1:GQzua3NA599ASSIICx0iFgiJeO9YkdDARvQsm23ZZuQ=
|
||||
github.com/moby/moby/api v1.56.0/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk=
|
||||
github.com/moby/moby/client v0.6.0 h1:AJjEB21QPbXSXjDsZorFBoDZPhMrfbpaPLgSMAW9Bgs=
|
||||
github.com/moby/moby/client v0.6.0/go.mod h1:OCo00wNRyA3m4lmJ228W3JbyCN4ZNNYjpOXiJydBdcQ=
|
||||
github.com/moby/moby/api v1.56.1 h1:PpWkvVPB7Fr/No8w+TfyJ/I6rWZ2YPZhT3JorIg+06c=
|
||||
github.com/moby/moby/api v1.56.1/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk=
|
||||
github.com/moby/moby/client v0.6.2 h1:ekSPlMccrZGcw6Scb8T9D39qE0mB+XupU/yS0zNw9N4=
|
||||
github.com/moby/moby/client v0.6.2/go.mod h1:DGcnO7qmPJrqobx/ggQgQ/AUk+2wgVdQv7uxB0/mEsc=
|
||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/swarmkit/v2 v2.1.2 h1:1WDZAI6HVYNKdCG4zlXnTAPyLsLwuhRGWlHoOUf5Z6I=
|
||||
@@ -193,8 +193,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
|
||||
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -203,7 +203,6 @@ golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ func (csiState csiEntryState) Handle(b byte) (s state, e error) {
|
||||
|
||||
func (csiState csiEntryState) Transition(s state) error {
|
||||
csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
|
||||
csiState.baseState.Transition(s)
|
||||
_ = csiState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case csiState.parser.ground:
|
||||
@@ -34,9 +34,9 @@ func (csiState csiEntryState) Transition(s state) error {
|
||||
case csiState.parser.csiParam:
|
||||
switch {
|
||||
case sliceContains(csiParams, csiState.parser.context.currentChar):
|
||||
csiState.parser.collectParam()
|
||||
_ = csiState.parser.collectParam()
|
||||
case sliceContains(intermeds, csiState.parser.context.currentChar):
|
||||
csiState.parser.collectInter()
|
||||
_ = csiState.parser.collectInter()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -16,8 +16,7 @@ func (csiState csiParamState) Handle(b byte) (s state, e error) {
|
||||
case sliceContains(alphabetics, b):
|
||||
return csiState.parser.ground, nil
|
||||
case sliceContains(csiCollectables, b):
|
||||
csiState.parser.collectParam()
|
||||
return csiState, nil
|
||||
return csiState, csiState.parser.collectParam()
|
||||
case sliceContains(executors, b):
|
||||
return csiState, csiState.parser.execute()
|
||||
}
|
||||
@@ -27,7 +26,7 @@ func (csiState csiParamState) Handle(b byte) (s state, e error) {
|
||||
|
||||
func (csiState csiParamState) Transition(s state) error {
|
||||
csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
|
||||
csiState.baseState.Transition(s)
|
||||
_ = csiState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case csiState.parser.ground:
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
|
||||
|
||||
func (escState escapeIntermediateState) Transition(s state) error {
|
||||
escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
|
||||
escState.baseState.Transition(s)
|
||||
_ = escState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case escState.parser.ground:
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ func (escState escapeState) Handle(b byte) (s state, e error) {
|
||||
|
||||
func (escState escapeState) Transition(s state) error {
|
||||
escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
|
||||
escState.baseState.Transition(s)
|
||||
_ = escState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case escState.parser.ground:
|
||||
|
||||
+5
-8
@@ -113,16 +113,13 @@ func (ap *AnsiParser) handle(b byte) error {
|
||||
|
||||
if newState == nil {
|
||||
ap.logf("WARNING: newState is nil")
|
||||
return errors.New("New state of 'nil' is invalid.")
|
||||
return errors.New("new state of 'nil' is invalid")
|
||||
}
|
||||
|
||||
if newState != ap.currState {
|
||||
if err := ap.changeState(newState); err != nil {
|
||||
return err
|
||||
}
|
||||
if newState == ap.currState {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return ap.changeState(newState)
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) changeState(newState state) error {
|
||||
@@ -136,7 +133,7 @@ func (ap *AnsiParser) changeState(newState state) error {
|
||||
|
||||
// Perform transition action
|
||||
if err := ap.currState.Transition(newState); err != nil {
|
||||
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
|
||||
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+5
-9
@@ -4,9 +4,9 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func parseParams(bytes []byte) ([]string, error) {
|
||||
paramBuff := make([]byte, 0, 0)
|
||||
params := []string{}
|
||||
func parseParams(bytes []byte) []string {
|
||||
var paramBuff []byte
|
||||
var params []string
|
||||
|
||||
for _, v := range bytes {
|
||||
if v == ';' {
|
||||
@@ -14,7 +14,7 @@ func parseParams(bytes []byte) ([]string, error) {
|
||||
// Completed parameter, append it to the list
|
||||
s := string(paramBuff)
|
||||
params = append(params, s)
|
||||
paramBuff = make([]byte, 0, 0)
|
||||
paramBuff = paramBuff[:0]
|
||||
}
|
||||
} else {
|
||||
paramBuff = append(paramBuff, v)
|
||||
@@ -27,11 +27,7 @@ func parseParams(bytes []byte) ([]string, error) {
|
||||
params = append(params, s)
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func parseCmd(context ansiContext) (string, error) {
|
||||
return string(context.currentChar), nil
|
||||
return params
|
||||
}
|
||||
|
||||
func getInt(params []string, dflt int) int {
|
||||
|
||||
+7
-9
@@ -15,10 +15,9 @@ func (ap *AnsiParser) collectInter() error {
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) escDispatch() error {
|
||||
cmd, _ := parseCmd(*ap.context)
|
||||
intermeds := ap.context.interBuffer
|
||||
cmd := string(ap.context.currentChar)
|
||||
ap.logf("escDispatch currentChar: %#x", ap.context.currentChar)
|
||||
ap.logf("escDispatch: %v(%v)", cmd, intermeds)
|
||||
ap.logf("escDispatch: %s(%q)", cmd, string(ap.context.interBuffer))
|
||||
|
||||
switch cmd {
|
||||
case "D": // IND
|
||||
@@ -31,14 +30,14 @@ func (ap *AnsiParser) escDispatch() error {
|
||||
return err
|
||||
case "M": // RI
|
||||
return ap.eventHandler.RI()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) csiDispatch() error {
|
||||
cmd, _ := parseCmd(*ap.context)
|
||||
params, _ := parseParams(ap.context.paramBuffer)
|
||||
cmd := string(ap.context.currentChar)
|
||||
params := parseParams(ap.context.paramBuffer)
|
||||
ap.logf("Parsed params: %v with length: %d", params, len(params))
|
||||
|
||||
ap.logf("csiDispatch: %v(%v)", cmd, params)
|
||||
@@ -109,9 +108,8 @@ func (ap *AnsiParser) print() error {
|
||||
return ap.eventHandler.Print(ap.context.currentChar)
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) clear() error {
|
||||
func (ap *AnsiParser) clear() {
|
||||
ap.context = &ansiContext{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) execute() error {
|
||||
|
||||
-2
@@ -1,7 +1,5 @@
|
||||
package ansiterm
|
||||
|
||||
type stateID int
|
||||
|
||||
type state interface {
|
||||
Enter() error
|
||||
Exit() error
|
||||
|
||||
-10
@@ -1,9 +1,5 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func sliceContains(bytes []byte, b byte) bool {
|
||||
for _, v := range bytes {
|
||||
if v == b {
|
||||
@@ -13,9 +9,3 @@ func sliceContains(bytes []byte, b byte) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func convertBytesToInteger(bytes []byte) int {
|
||||
s := string(bytes)
|
||||
i, _ := strconv.Atoi(s)
|
||||
return i
|
||||
}
|
||||
|
||||
+1
-98
@@ -5,12 +5,9 @@ package winterm
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Azure/go-ansiterm"
|
||||
windows "golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Windows keyboard constants
|
||||
@@ -55,100 +52,6 @@ const (
|
||||
ENHANCED_KEY = 0x0100
|
||||
)
|
||||
|
||||
type ansiCommand struct {
|
||||
CommandBytes []byte
|
||||
Command string
|
||||
Parameters []string
|
||||
IsSpecial bool
|
||||
}
|
||||
|
||||
func newAnsiCommand(command []byte) *ansiCommand {
|
||||
|
||||
if isCharacterSelectionCmdChar(command[1]) {
|
||||
// Is Character Set Selection commands
|
||||
return &ansiCommand{
|
||||
CommandBytes: command,
|
||||
Command: string(command),
|
||||
IsSpecial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// last char is command character
|
||||
lastCharIndex := len(command) - 1
|
||||
|
||||
ac := &ansiCommand{
|
||||
CommandBytes: command,
|
||||
Command: string(command[lastCharIndex]),
|
||||
IsSpecial: false,
|
||||
}
|
||||
|
||||
// more than a single escape
|
||||
if lastCharIndex != 0 {
|
||||
start := 1
|
||||
// skip if double char escape sequence
|
||||
if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
|
||||
start++
|
||||
}
|
||||
// convert this to GetNextParam method
|
||||
ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
|
||||
}
|
||||
|
||||
return ac
|
||||
}
|
||||
|
||||
func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
|
||||
if index < 0 || index >= len(ac.Parameters) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return int16(param)
|
||||
}
|
||||
|
||||
func (ac *ansiCommand) String() string {
|
||||
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
||||
bytesToHex(ac.CommandBytes),
|
||||
ac.Command,
|
||||
strings.Join(ac.Parameters, "\",\""))
|
||||
}
|
||||
|
||||
// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
|
||||
// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
|
||||
func isAnsiCommandChar(b byte) bool {
|
||||
switch {
|
||||
case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
|
||||
return true
|
||||
case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
|
||||
// non-CSI escape sequence terminator
|
||||
return true
|
||||
case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
|
||||
// String escape sequence terminator
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isXtermOscSequence(command []byte, current byte) bool {
|
||||
return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
|
||||
}
|
||||
|
||||
func isCharacterSelectionCmdChar(b byte) bool {
|
||||
return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
|
||||
}
|
||||
|
||||
// bytesToHex converts a slice of bytes to a human-readable string.
|
||||
func bytesToHex(b []byte) string {
|
||||
hex := make([]string, len(b))
|
||||
for i, ch := range b {
|
||||
hex[i] = fmt.Sprintf("%X", ch)
|
||||
}
|
||||
return strings.Join(hex, "")
|
||||
}
|
||||
|
||||
// ensureInRange adjusts the passed value, if necessary, to ensure it is within
|
||||
// the passed min / max range.
|
||||
func ensureInRange(n int16, min int16, max int16) int16 {
|
||||
|
||||
+153
-127
@@ -1,9 +1,15 @@
|
||||
version: "2"
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt # files are gofmt'ed
|
||||
linters:
|
||||
enable:
|
||||
# style
|
||||
- containedctx # struct contains a context
|
||||
- dupl # duplicate code
|
||||
- errname # erorrs are named correctly
|
||||
- dupword # duplicate words in comments
|
||||
- errname # errors are named correctly
|
||||
- intrange # integer ranges in for loop
|
||||
- nolintlint # "//nolint" directives are properly explained
|
||||
- revive # golint replacement
|
||||
- unconvert # unnecessary conversions
|
||||
@@ -13,135 +19,155 @@ linters:
|
||||
- contextcheck # function uses a non-inherited context
|
||||
- errorlint # errors not wrapped for 1.13
|
||||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- gofmt # files are gofmt'ed
|
||||
- gocritic # various code quality checks
|
||||
- gosec # security
|
||||
- mirror # avoid allocations
|
||||
- nilerr # returns nil even with non-nil error
|
||||
- thelper # test helpers without t.Helper()
|
||||
- unparam # unused function params
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- pkg/etw/sample
|
||||
|
||||
exclude-rules:
|
||||
# err is very often shadowed in nested scopes
|
||||
- linters:
|
||||
- govet
|
||||
text: '^shadow: declaration of "err" shadows declaration'
|
||||
|
||||
# ignore long lines for skip autogen directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^line-length-limit: "
|
||||
source: "^//(go:generate|sys) "
|
||||
|
||||
#TODO: remove after upgrading to go1.18
|
||||
# ignore comment spacing for nolint and sys directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^comment-spacings: no space between comment delimiter and comment text"
|
||||
source: "//(cspell:|nolint:|sys |todo)"
|
||||
|
||||
# not on go 1.18 yet, so no any
|
||||
- linters:
|
||||
- revive
|
||||
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
|
||||
|
||||
# allow unjustified ignores of error checks in defer statements
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errcheck` should provide explanation"
|
||||
source: '^\s*defer '
|
||||
|
||||
# allow unjustified ignores of error lints for io.EOF
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errorlint` should provide explanation"
|
||||
source: '[=|!]= io.EOF'
|
||||
|
||||
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
# struct order is often for Win32 compat
|
||||
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
||||
- fieldalignment
|
||||
nolintlint:
|
||||
require-explanation: true
|
||||
require-specific: true
|
||||
revive:
|
||||
# revive is more configurable than static check, so likely the preferred alternative to static-check
|
||||
# (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997)
|
||||
enable-all-rules:
|
||||
true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
exclusions:
|
||||
paths:
|
||||
- pkg/etw/sample
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
# rules with required arguments
|
||||
- name: argument-limit
|
||||
disabled: true
|
||||
- name: banned-characters
|
||||
disabled: true
|
||||
- name: cognitive-complexity
|
||||
disabled: true
|
||||
- name: cyclomatic
|
||||
disabled: true
|
||||
- name: file-header
|
||||
disabled: true
|
||||
- name: function-length
|
||||
disabled: true
|
||||
- name: function-result-limit
|
||||
disabled: true
|
||||
- name: max-public-structs
|
||||
disabled: true
|
||||
# geneally annoying rules
|
||||
- name: add-constant # complains about any and all strings and integers
|
||||
disabled: true
|
||||
- name: confusing-naming # we frequently use "Foo()" and "foo()" together
|
||||
disabled: true
|
||||
- name: flag-parameter # excessive, and a common idiom we use
|
||||
disabled: true
|
||||
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
|
||||
disabled: true
|
||||
# general config
|
||||
- name: line-length-limit
|
||||
arguments:
|
||||
- 140
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- []
|
||||
- - CID
|
||||
- CRI
|
||||
- CTRD
|
||||
- DACL
|
||||
- DLL
|
||||
- DOS
|
||||
- ETW
|
||||
- FSCTL
|
||||
- GCS
|
||||
- GMSA
|
||||
- HCS
|
||||
- HV
|
||||
- IO
|
||||
- LCOW
|
||||
- LDAP
|
||||
- LPAC
|
||||
- LTSC
|
||||
- MMIO
|
||||
- NT
|
||||
- OCI
|
||||
- PMEM
|
||||
- PWSH
|
||||
- RX
|
||||
- SACl
|
||||
- SID
|
||||
- SMB
|
||||
- TX
|
||||
- VHD
|
||||
- VHDX
|
||||
- VMID
|
||||
- VPCI
|
||||
- WCOW
|
||||
- WIM
|
||||
# err is very often shadowed in nested scopes
|
||||
- linters:
|
||||
- govet
|
||||
text: '^shadow: declaration of "err" shadows declaration'
|
||||
|
||||
# ignore long lines for skip autogen directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^line-length-limit: "
|
||||
source: "^//(go:generate|sys) "
|
||||
|
||||
# ignore comment spacing for sys directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^comment-spacings: no space between comment delimiter and comment text"
|
||||
source: "//(cspell:|sys )"
|
||||
|
||||
# allow unjustified ignores of error checks in defer statements
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errcheck` should provide explanation"
|
||||
source: '^\s*defer '
|
||||
|
||||
# allow unjustified ignores of error lints for io.EOF
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errorlint` should provide explanation"
|
||||
source: '[=|!]= io.EOF'
|
||||
|
||||
- linters:
|
||||
- gosec
|
||||
text: "^G115: integer overflow conversion"
|
||||
|
||||
|
||||
settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
gocritic:
|
||||
disable-all: true
|
||||
enabled-checks:
|
||||
- nilValReturn
|
||||
- sprintfQuotedString
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
# struct order is often for Win32 compat
|
||||
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
||||
- fieldalignment
|
||||
nolintlint:
|
||||
require-explanation: true
|
||||
require-specific: true
|
||||
revive:
|
||||
# revive is more configurable than static check, so likely the preferred alternative to static-check
|
||||
# (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997)
|
||||
enable-all-rules:
|
||||
true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
rules:
|
||||
# rules with required arguments
|
||||
- name: argument-limit
|
||||
disabled: true
|
||||
- name: banned-characters
|
||||
disabled: true
|
||||
- name: cognitive-complexity
|
||||
disabled: true
|
||||
- name: cyclomatic
|
||||
disabled: true
|
||||
# covered by "exhaustive" linter
|
||||
- name: enforce-switch-style
|
||||
disabled: true
|
||||
- name: file-header
|
||||
disabled: true
|
||||
- name: function-length
|
||||
disabled: true
|
||||
- name: function-result-limit
|
||||
disabled: true
|
||||
- name: max-public-structs
|
||||
disabled: true
|
||||
# generally annoying rules
|
||||
- name: add-constant # complains about any and all strings and integers
|
||||
disabled: true
|
||||
- name: confusing-naming # we frequently use "Foo()" and "foo()" together
|
||||
disabled: true
|
||||
- name: flag-parameter # excessive, and a common idiom we use
|
||||
disabled: true
|
||||
- name: multiline-if-init # too opinionated; see https://github.com/microsoft/go-winio/pull/325#issuecomment-5822487436
|
||||
disabled: true
|
||||
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
|
||||
disabled: true
|
||||
# general config
|
||||
- name: line-length-limit
|
||||
arguments:
|
||||
- 140
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- []
|
||||
- - CID
|
||||
- CRI
|
||||
- CTRD
|
||||
- DACL
|
||||
- DLL
|
||||
- DOS
|
||||
- ETW
|
||||
- FSCTL
|
||||
- GCS
|
||||
- GMSA
|
||||
- HCS
|
||||
- HV
|
||||
- IO
|
||||
- LCOW
|
||||
- LDAP
|
||||
- LPAC
|
||||
- LTSC
|
||||
- MMIO
|
||||
- NT
|
||||
- OCI
|
||||
- PMEM
|
||||
- PWSH
|
||||
- RX
|
||||
- SACl
|
||||
- SID
|
||||
- SMB
|
||||
- TX
|
||||
- VHD
|
||||
- VHDX
|
||||
- VMID
|
||||
- VPCI
|
||||
- WCOW
|
||||
- WIM
|
||||
staticcheck:
|
||||
# Enable all options, with some exceptions.
|
||||
# For defaults, see https://golangci-lint.run/usage/linters/#staticcheck
|
||||
checks:
|
||||
- all
|
||||
- -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008
|
||||
- -ST1003 # Poorly chosen identifier; https://staticcheck.dev/docs/checks/#ST1003
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
@@ -36,7 +35,7 @@ const (
|
||||
StreamSparseAttributes = uint32(8)
|
||||
)
|
||||
|
||||
//nolint:revive // var-naming: ALL_CAPS
|
||||
//nolint:revive,nolintlint // var-naming: ALL_CAPS
|
||||
const (
|
||||
WRITE_DAC = windows.WRITE_DAC
|
||||
WRITE_OWNER = windows.WRITE_OWNER
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write([]byte(ea.Name))
|
||||
_, err = buf.WriteString(ea.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+9
-5
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
@@ -235,12 +234,17 @@ func (f *win32File) Read(b []byte) (int, error) {
|
||||
runtime.KeepAlive(b)
|
||||
|
||||
// Handle EOF conditions.
|
||||
if err == nil && n == 0 && len(b) != 0 {
|
||||
return 0, io.EOF
|
||||
} else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
|
||||
switch err {
|
||||
case windows.ERROR_BROKEN_PIPE: //nolint:errorlint // err is Errno
|
||||
return 0, io.EOF
|
||||
case nil:
|
||||
if n == 0 && len(b) != 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
default:
|
||||
return n, err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write writes to a file handle.
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
|
||||
+1
-1
@@ -243,7 +243,7 @@ const (
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
|
||||
func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) {
|
||||
b := stringbuffer.NewWString()
|
||||
//TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n?
|
||||
// TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n?
|
||||
for {
|
||||
n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags))
|
||||
if err != nil {
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
// TODO: worth exporting and using in mkwinsyscall?
|
||||
|
||||
// Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate
|
||||
// MinWStringCap is the buffer size in the pool, chosen somewhat arbitrarily to accommodate
|
||||
// large path strings:
|
||||
// MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310.
|
||||
const MinWStringCap = 310
|
||||
@@ -16,7 +16,7 @@ const MinWStringCap = 310
|
||||
// is copied to heap and then referenced via pointer in the interface header that sync.Pool
|
||||
// stores.
|
||||
var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
b := make([]uint16, MinWStringCap)
|
||||
return &b
|
||||
},
|
||||
|
||||
+21
-19
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@@ -179,14 +179,15 @@ func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n, err := f.win32File.Read(b)
|
||||
if err == io.EOF { //nolint:errorlint
|
||||
switch err {
|
||||
case io.EOF: //nolint:errorlint // error is not wrapped.
|
||||
// If this was the result of a zero-byte read, then
|
||||
// it is possible that the read was due to a zero-size
|
||||
// message. Since we are simulating CloseWrite with a
|
||||
// zero-byte message, ensure that all future Read() calls
|
||||
// also return EOF.
|
||||
f.readEOF = true
|
||||
} else if err == windows.ERROR_MORE_DATA { //nolint:errorlint // err is Errno
|
||||
case windows.ERROR_MORE_DATA:
|
||||
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
||||
// and the message still has more bytes. Treat this as a success, since
|
||||
// this package presents all named pipes as byte streams.
|
||||
@@ -316,8 +317,9 @@ type win32PipeListener struct {
|
||||
path string
|
||||
config PipeConfig
|
||||
acceptCh chan (chan acceptResponse)
|
||||
closeCh chan int
|
||||
doneCh chan int
|
||||
closeOnce sync.Once
|
||||
closeCh chan struct{} // closed (never sent on) to broadcast listener shutdown
|
||||
doneCh chan struct{}
|
||||
}
|
||||
|
||||
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (windows.Handle, error) {
|
||||
@@ -344,7 +346,7 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (wi
|
||||
// The security descriptor is only needed for the first pipe.
|
||||
if first {
|
||||
if sd != nil {
|
||||
//todo: does `sdb` need to be allocated on the heap, or can go allocate it?
|
||||
// todo: does `sdb` need to be allocated on the heap, or can go allocate it?
|
||||
l := uint32(len(sd))
|
||||
sdb, err := windows.LocalAlloc(0, l)
|
||||
if err != nil {
|
||||
@@ -445,13 +447,14 @@ func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
||||
p = nil
|
||||
}
|
||||
case <-l.closeCh:
|
||||
// Abort the connect request by closing the handle.
|
||||
p.Close()
|
||||
// Abort the connect request by closing the handle. Listener closure is
|
||||
// authoritative: ConnectNamedPipe may race the handle close and report a
|
||||
// connection or error (e.g. ERROR_NO_DATA) instead of ErrFileClosed, and
|
||||
// that result must not be surfaced or cause listenerRoutine to retry.
|
||||
_ = p.Close()
|
||||
p = nil
|
||||
err = <-ch
|
||||
if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno
|
||||
err = ErrPipeListenerClosed
|
||||
}
|
||||
<-ch
|
||||
err = ErrPipeListenerClosed
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
@@ -530,8 +533,8 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||
path: path,
|
||||
config: *c,
|
||||
acceptCh: make(chan (chan acceptResponse)),
|
||||
closeCh: make(chan int),
|
||||
doneCh: make(chan int),
|
||||
closeCh: make(chan struct{}),
|
||||
doneCh: make(chan struct{}),
|
||||
}
|
||||
go l.listenerRoutine()
|
||||
return l, nil
|
||||
@@ -573,11 +576,10 @@ func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (l *win32PipeListener) Close() error {
|
||||
select {
|
||||
case l.closeCh <- 1:
|
||||
<-l.doneCh
|
||||
case <-l.doneCh:
|
||||
}
|
||||
l.closeOnce.Do(func() {
|
||||
close(l.closeCh)
|
||||
})
|
||||
<-l.doneCh
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment
|
||||
//go:generate go tool stringer -type=Variant -trimprefix=Variant -linecomment
|
||||
|
||||
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
||||
// how the entirety of the rest of the GUID is interpreted.
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package guid
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package guid
|
||||
|
||||
|
||||
+3
-2
@@ -20,8 +20,9 @@ const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture"
|
||||
var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33}
|
||||
|
||||
func (i Variant) String() string {
|
||||
if i >= Variant(len(_Variant_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_Variant_index)-1 {
|
||||
return "Variant(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Variant_name[_Variant_index[i]:_Variant_index[i+1]]
|
||||
return _Variant_name[_Variant_index[idx]:_Variant_index[idx+1]]
|
||||
}
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
|
||||
+55
-6
@@ -1,11 +1,11 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
@@ -15,6 +15,9 @@ import (
|
||||
const (
|
||||
reparseTagMountPoint = 0xA0000003
|
||||
reparseTagSymlink = 0xA000000C
|
||||
reparseTagLxSymlink = 0xA000001D // WSL/MSYS2 native symlinks
|
||||
|
||||
lxSymlinkVersion = 2 // LX symlink format version
|
||||
)
|
||||
|
||||
type reparseDataBuffer struct {
|
||||
@@ -31,6 +34,7 @@ type reparseDataBuffer struct {
|
||||
type ReparsePoint struct {
|
||||
Target string
|
||||
IsMountPoint bool
|
||||
IsLxSymlink bool // True if this is an LX symlink (WSL/MSYS2 native)
|
||||
}
|
||||
|
||||
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
||||
@@ -51,14 +55,19 @@ func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
||||
}
|
||||
|
||||
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||
isMountPoint := false
|
||||
switch tag {
|
||||
case reparseTagMountPoint:
|
||||
isMountPoint = true
|
||||
return decodeWindowsReparsePointData(b, true)
|
||||
case reparseTagSymlink:
|
||||
return decodeWindowsReparsePointData(b, false)
|
||||
case reparseTagLxSymlink:
|
||||
return decodeLxReparsePointData(b)
|
||||
default:
|
||||
return nil, &UnsupportedReparsePointError{tag}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeWindowsReparsePointData(b []byte, isMountPoint bool) (*ReparsePoint, error) {
|
||||
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
|
||||
if !isMountPoint {
|
||||
nameOffset += 4
|
||||
@@ -69,16 +78,56 @@ func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||
return &ReparsePoint{Target: string(utf16.Decode(name)), IsMountPoint: isMountPoint, IsLxSymlink: false}, nil
|
||||
}
|
||||
|
||||
func decodeLxReparsePointData(b []byte) (*ReparsePoint, error) {
|
||||
// LX symlinks store the target as UTF-8 after a 4-byte version field
|
||||
if len(b) < 4 {
|
||||
return nil, errors.New("LX symlink buffer too short")
|
||||
}
|
||||
targetBytes := b[4:]
|
||||
for i, c := range targetBytes {
|
||||
if c == 0 {
|
||||
targetBytes = targetBytes[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
target := string(targetBytes)
|
||||
return &ReparsePoint{Target: target, IsMountPoint: false, IsLxSymlink: true}, nil
|
||||
}
|
||||
|
||||
func isDriveLetter(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||
// mount point.
|
||||
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink,
|
||||
// mount point, or LX symlink.
|
||||
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||
if rp == nil {
|
||||
return nil
|
||||
}
|
||||
if rp.IsLxSymlink {
|
||||
return encodeLxReparsePoint(rp)
|
||||
}
|
||||
return encodeWindowsReparsePoint(rp)
|
||||
}
|
||||
|
||||
func encodeLxReparsePoint(rp *ReparsePoint) []byte {
|
||||
// LX symlink: 4-byte version + UTF-8 target
|
||||
targetBytes := []byte(rp.Target)
|
||||
dataLength := 4 + len(targetBytes)
|
||||
|
||||
var b bytes.Buffer
|
||||
_ = binary.Write(&b, binary.LittleEndian, uint32(reparseTagLxSymlink))
|
||||
_ = binary.Write(&b, binary.LittleEndian, uint16(dataLength))
|
||||
_ = binary.Write(&b, binary.LittleEndian, uint16(0))
|
||||
_ = binary.Write(&b, binary.LittleEndian, uint32(lxSymlinkVersion))
|
||||
_, _ = b.Write(targetBytes)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func encodeWindowsReparsePoint(rp *ReparsePoint) []byte {
|
||||
// Generate an NT path and determine if this is a relative path.
|
||||
var ntTarget string
|
||||
relative := false
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
+35
-15
@@ -1,30 +1,50 @@
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- exportloopref # Checks for pointers to enclosing loop variables
|
||||
- gofmt
|
||||
- goimports
|
||||
- copyloopvar
|
||||
- dupword
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
|
||||
- unconvert
|
||||
- unused
|
||||
- vet
|
||||
- dupword # Checks for duplicate words in the source code
|
||||
disable:
|
||||
- errcheck
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
skip-dirs:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test # e2e scripts
|
||||
|
||||
+61
-9
@@ -40,8 +40,11 @@ package log
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
lslog "github.com/sirupsen/logrus/hooks/slog"
|
||||
)
|
||||
|
||||
// G is a shorthand for [GetLogger].
|
||||
@@ -108,10 +111,17 @@ const (
|
||||
PanicLevel Level = logrus.PanicLevel
|
||||
)
|
||||
|
||||
// SetLevel sets log level globally. It returns an error if the given
|
||||
// level is not supported.
|
||||
// levelValue is a log level accepted by [SetLevel].
|
||||
type levelValue interface {
|
||||
string | Level | slog.Level | lslog.Level | lslog.SlogLevel
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level.
|
||||
//
|
||||
// level can be one of:
|
||||
// The level may be specified as a string, a [Level], a [slog.Level], an
|
||||
// [lslog.Level], or an [lslog.SlogLevel].
|
||||
//
|
||||
// String levels are parsed using [logrus.ParseLevel] and may be one of:
|
||||
//
|
||||
// - "trace" ([TraceLevel])
|
||||
// - "debug" ([DebugLevel])
|
||||
@@ -120,10 +130,34 @@ const (
|
||||
// - "error" ([ErrorLevel])
|
||||
// - "fatal" ([FatalLevel])
|
||||
// - "panic" ([PanicLevel])
|
||||
func SetLevel(level string) error {
|
||||
lvl, err := logrus.ParseLevel(level)
|
||||
if err != nil {
|
||||
return err
|
||||
//
|
||||
// slog levels are mapped to Logrus levels using the mapping defined by
|
||||
// [lslog.SlogLevel]. [lslog.Level] values are accepted directly as their
|
||||
// underlying Logrus level.
|
||||
//
|
||||
// SetLevel returns an error if a string level is not supported.
|
||||
func SetLevel[T levelValue](level T) error {
|
||||
var lvl Level
|
||||
|
||||
switch l := any(level).(type) {
|
||||
case string:
|
||||
var err error
|
||||
lvl, err = logrus.ParseLevel(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case Level:
|
||||
lvl = l
|
||||
|
||||
case slog.Level:
|
||||
lvl = lslog.SlogLevel(l).Level()
|
||||
|
||||
case lslog.Level:
|
||||
lvl = Level(l)
|
||||
|
||||
case lslog.SlogLevel:
|
||||
lvl = l.Level()
|
||||
}
|
||||
|
||||
L.Logger.SetLevel(lvl)
|
||||
@@ -149,21 +183,39 @@ const (
|
||||
|
||||
// SetFormat sets the log output format ([TextFormat] or [JSONFormat]).
|
||||
func SetFormat(format OutputFormat) error {
|
||||
if slogOut != nil {
|
||||
var handler slog.Handler
|
||||
switch format {
|
||||
case TextFormat:
|
||||
handler = slog.NewTextHandler(slogOut, &slog.HandlerOptions{Level: loggerLevel{}})
|
||||
case JSONFormat:
|
||||
handler = slog.NewJSONHandler(slogOut, &slog.HandlerOptions{Level: loggerLevel{}})
|
||||
default:
|
||||
return fmt.Errorf("unknown log format: %s", format)
|
||||
}
|
||||
slog.SetDefault(slog.New(handler))
|
||||
|
||||
// Keep logrus formatting and output disabled, as both are handled by slog.
|
||||
L.Logger.SetFormatter(discardFormatter{})
|
||||
L.Logger.SetOutput(io.Discard)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case TextFormat:
|
||||
L.Logger.SetFormatter(&logrus.TextFormatter{
|
||||
TimestampFormat: RFC3339NanoFixed,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
return nil
|
||||
case JSONFormat:
|
||||
L.Logger.SetFormatter(&logrus.JSONFormatter{
|
||||
TimestampFormat: RFC3339NanoFixed,
|
||||
})
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown log format: %s", format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithLogger returns a new context with the provided logger. Use in
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
lslog "github.com/sirupsen/logrus/hooks/slog"
|
||||
)
|
||||
|
||||
// slogOut is used to set the slog logger when setting output format.
|
||||
var slogOut io.Writer
|
||||
|
||||
// slogOnce guards UseSlog so repeated calls do not stack up hooks or
|
||||
// reset slogOut to the discard writer installed on the first call.
|
||||
var slogOnce sync.Once
|
||||
|
||||
func UseSlog() {
|
||||
slogOnce.Do(func() {
|
||||
L.Logger.SetNoLock()
|
||||
L.Logger.AddHook(slogHook{})
|
||||
slogOut = L.Logger.Out
|
||||
|
||||
// Disable logrus formatting and output, as both are handled by slog.
|
||||
L.Logger.SetFormatter(discardFormatter{})
|
||||
L.Logger.SetOutput(io.Discard)
|
||||
})
|
||||
}
|
||||
|
||||
type slogHook struct{}
|
||||
|
||||
func (slogHook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
func (slogHook) Fire(entry *logrus.Entry) error {
|
||||
return lslog.NewHook(slog.Default(), nil).Fire(entry)
|
||||
}
|
||||
|
||||
// loggerLevel exposes the current Logrus logger level as a slog.Leveler.
|
||||
type loggerLevel struct{}
|
||||
|
||||
func (loggerLevel) Level() slog.Level {
|
||||
return lslog.Level(L.Logger.GetLevel()).Level()
|
||||
}
|
||||
|
||||
type discardFormatter struct{}
|
||||
|
||||
func (discardFormatter) Format(*logrus.Entry) ([]byte, error) { return nil, nil }
|
||||
+5
-1
@@ -9,13 +9,17 @@ import (
|
||||
// NewTCPSocket creates a TCP socket listener with the specified address and
|
||||
// the specified tls configuration. If TLSConfig is set, will encapsulate the
|
||||
// TCP listener inside a TLS one.
|
||||
// If no ALPN protocols are configured, it defaults to HTTP/1.1.
|
||||
func NewTCPSocket(addr string, tlsConfig *tls.Config) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig != nil {
|
||||
tlsConfig.NextProtos = []string{"http/1.1"}
|
||||
if len(tlsConfig.NextProtos) == 0 {
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
l = tls.NewListener(l, tlsConfig)
|
||||
}
|
||||
return l, nil
|
||||
|
||||
+2
@@ -6,7 +6,9 @@ This package provides various compression algorithms.
|
||||
* [S2](https://github.com/klauspost/compress/tree/master/s2#s2-compression) is a high performance replacement for Snappy.
|
||||
* Optimized [deflate](https://godoc.org/github.com/klauspost/compress/flate) packages which can be used as a dropin replacement for [gzip](https://godoc.org/github.com/klauspost/compress/gzip), [zip](https://godoc.org/github.com/klauspost/compress/zip) and [zlib](https://godoc.org/github.com/klauspost/compress/zlib).
|
||||
* [snappy](https://github.com/klauspost/compress/tree/master/snappy) is a drop-in replacement for `github.com/golang/snappy` offering better compression and concurrent streams.
|
||||
* [lzw](https://github.com/klauspost/compress/tree/master/lzw) is a drop-in replacement for `compress/lzw` with 1.4-4x faster decompression and 1.1-2.7x faster compression, depending on the data.
|
||||
* [huff0](https://github.com/klauspost/compress/tree/master/huff0) and [FSE](https://github.com/klauspost/compress/tree/master/fse) implementations for raw entropy encoding.
|
||||
* [Xpress](https://github.com/klauspost/compress/tree/master/xpress) decompression of the Microsoft XPRESS (MS-XCA) plain LZ77 and LZ77+Huffman formats (the LZ77+Huffman variant is the one used in WIM images and Windows Compact OS / WOF data).
|
||||
* [gzhttp](https://github.com/klauspost/compress/tree/master/gzhttp) Provides client and server wrappers for handling gzipped/zstd HTTP requests efficiently.
|
||||
* [pgzip](https://github.com/klauspost/pgzip) is a separate package that provides a very fast parallel gzip implementation.
|
||||
|
||||
|
||||
+63
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/bits"
|
||||
|
||||
"github.com/klauspost/compress/internal/le"
|
||||
)
|
||||
@@ -210,6 +211,68 @@ func (b *bitReaderShifted) remaining() uint {
|
||||
return b.off*8 + uint(64-b.bitsRead)
|
||||
}
|
||||
|
||||
// canUseAsm reports whether the reader has a full 8-byte window ahead of
|
||||
// its read pointer, which the Decompress4X asm loops need to take over.
|
||||
func (b *bitReaderShifted) canUseAsm() bool {
|
||||
return b.off >= 8
|
||||
}
|
||||
|
||||
// prepareForAsm establishes the invariant the Decompress4X asm loops rely
|
||||
// on: value == load64(in[off:off+8]) << bitsRead with bitsRead <= 7, so
|
||||
// that a full group of symbols can never shift their sentinel bit out of
|
||||
// the container. init leaves bitsRead == 8 when the final byte of the
|
||||
// stream is exactly 0x01; that whole byte is consumed, so the same position
|
||||
// is the window one byte lower with nothing consumed. Requires canUseAsm.
|
||||
func (b *bitReaderShifted) prepareForAsm() {
|
||||
if b.bitsRead >= 8 {
|
||||
b.off--
|
||||
b.value = le.Load64(b.in, b.off)
|
||||
b.bitsRead -= 8
|
||||
}
|
||||
}
|
||||
|
||||
// restoreFromAsm converts the state left behind by the Decompress4X asm
|
||||
// loops back to the invariant the Go code relies on: value holds the 8
|
||||
// bytes at in[off:off+8] shifted left by bitsRead, with the low bitsRead
|
||||
// bits zero.
|
||||
//
|
||||
// The asm keeps a sentinel bit in value just below the unread bits, so its
|
||||
// trailing zero count is the number of consumed bits. The sentinel is ORed
|
||||
// over the lowest bit of the window, which the loop never consumes before
|
||||
// re-reading memory, so the window is re-read here too rather than taken
|
||||
// from value. The asm reports off as the signed distance from the start of
|
||||
// the stream, and it can be negative: a reload always reads a whole 8-byte
|
||||
// window, so a stream that is nearly drained ends with up to 7 bytes of the
|
||||
// previous stream (or the jump table) below its start inside the window.
|
||||
// Those bytes sit below the stream's own bits and count as consumed.
|
||||
// Anything further below, or more bits consumed than the stream holds, is
|
||||
// corruption.
|
||||
func (b *bitReaderShifted) restoreFromAsm() error {
|
||||
off := int(b.off)
|
||||
consumed := uint(bits.TrailingZeros64(b.value))
|
||||
if off < 0 {
|
||||
if off < -7 {
|
||||
return errors.New("corruption detected: stream underrun")
|
||||
}
|
||||
consumed += uint(-off) * 8
|
||||
if consumed > 64 {
|
||||
return errors.New("corruption detected: stream underrun")
|
||||
}
|
||||
off = 0
|
||||
}
|
||||
if off+8 > len(b.in) {
|
||||
return errors.New("corruption detected: stream overrun")
|
||||
}
|
||||
b.off = uint(off)
|
||||
b.bitsRead = uint8(consumed)
|
||||
if consumed >= 64 {
|
||||
b.value = 0
|
||||
} else {
|
||||
b.value = le.Load64(b.in, b.off) << consumed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// close the bitstream and returns an error if out-of-buffer reads occurred.
|
||||
func (b *bitReaderShifted) close() error {
|
||||
// Release reference.
|
||||
|
||||
+87
-8
@@ -8,36 +8,99 @@ import (
|
||||
)
|
||||
|
||||
// decompress4x_main_loop_amd64 is an x86 assembler implementation
|
||||
// of Decompress4X when tablelog > 8.
|
||||
// of Decompress4X when tablelog > 8, decoding fastSymbols symbols per
|
||||
// stream between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_main_loop_amd64(ctx *decompress4xContext)
|
||||
|
||||
// decompress4x_8b_main_loop_amd64 is an x86 assembler implementation
|
||||
// of Decompress4X when tablelog <= 8 which decodes 4 entries
|
||||
// per loop.
|
||||
// of Decompress4X when tablelog <= 8, decoding fast8bSymbols symbols
|
||||
// per stream between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_8b_main_loop_amd64(ctx *decompress4xContext)
|
||||
|
||||
// decompress4x_4b_main_loop_amd64 is an x86 assembler implementation
|
||||
// of Decompress4X when tablelog <= 4, decoding fast4bSymbols symbols
|
||||
// per stream between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_4b_main_loop_amd64(ctx *decompress4xContext)
|
||||
|
||||
// decompress4x_4b_main_loop_bmi2 is the BMI2 twin of decompress4x_4b_main_loop_amd64.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_4b_main_loop_bmi2(ctx *decompress4xContext)
|
||||
|
||||
// decompress4x_main_loop_bmi2 is the BMI2 twin of decompress4x_main_loop_amd64.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_main_loop_bmi2(ctx *decompress4xContext)
|
||||
|
||||
// decompress4x_8b_main_loop_bmi2 is the BMI2 twin of decompress4x_8b_main_loop_amd64.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_8b_main_loop_bmi2(ctx *decompress4xContext)
|
||||
|
||||
// decompress1x_main_loop_amd64 is an x86 assembler implementation
|
||||
// of Decompress1X when tablelog > 8.
|
||||
// of Decompress1X when tablelog > 8, decoding fastSymbols symbols
|
||||
// between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_main_loop_amd64(ctx *decompress1xContext)
|
||||
|
||||
// decompress1x_main_loop_bmi2 is an x86 with BMI2 assembler implementation
|
||||
// of Decompress1X when tablelog > 8.
|
||||
// decompress1x_8b_main_loop_amd64 is an x86 assembler implementation
|
||||
// of Decompress1X when tablelog <= 8, decoding fast8bSymbols symbols
|
||||
// between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_8b_main_loop_amd64(ctx *decompress1xContext)
|
||||
|
||||
// decompress1x_4b_main_loop_amd64 is an x86 assembler implementation
|
||||
// of Decompress1X when tablelog <= 4, decoding fast4bSymbols symbols
|
||||
// between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_4b_main_loop_amd64(ctx *decompress1xContext)
|
||||
|
||||
// decompress1x_main_loop_bmi2 is the BMI2 twin of decompress1x_main_loop_amd64.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_main_loop_bmi2(ctx *decompress1xContext)
|
||||
|
||||
// decompress1x_8b_main_loop_bmi2 is the BMI2 twin of decompress1x_8b_main_loop_amd64.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_8b_main_loop_bmi2(ctx *decompress1xContext)
|
||||
|
||||
// decompress1x_4b_main_loop_bmi2 is the BMI2 twin of decompress1x_4b_main_loop_amd64.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_4b_main_loop_bmi2(ctx *decompress1xContext)
|
||||
|
||||
func decompress4x_main_loop_asm(ctx *decompress4xContext) {
|
||||
decompress4x_main_loop_amd64(ctx)
|
||||
if cpuinfo.HasBMI2() {
|
||||
decompress4x_main_loop_bmi2(ctx)
|
||||
} else {
|
||||
decompress4x_main_loop_amd64(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func decompress4x_8b_main_loop_asm(ctx *decompress4xContext) {
|
||||
decompress4x_8b_main_loop_amd64(ctx)
|
||||
if cpuinfo.HasBMI2() {
|
||||
decompress4x_8b_main_loop_bmi2(ctx)
|
||||
} else {
|
||||
decompress4x_8b_main_loop_amd64(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func decompress4x_4b_main_loop_asm(ctx *decompress4xContext) {
|
||||
if cpuinfo.HasBMI2() {
|
||||
decompress4x_4b_main_loop_bmi2(ctx)
|
||||
} else {
|
||||
decompress4x_4b_main_loop_amd64(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func decompress1x_main_loop_asm(ctx *decompress1xContext) {
|
||||
@@ -47,3 +110,19 @@ func decompress1x_main_loop_asm(ctx *decompress1xContext) {
|
||||
decompress1x_main_loop_amd64(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func decompress1x_8b_main_loop_asm(ctx *decompress1xContext) {
|
||||
if cpuinfo.HasBMI2() {
|
||||
decompress1x_8b_main_loop_bmi2(ctx)
|
||||
} else {
|
||||
decompress1x_8b_main_loop_amd64(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func decompress1x_4b_main_loop_asm(ctx *decompress1xContext) {
|
||||
if cpuinfo.HasBMI2() {
|
||||
decompress1x_4b_main_loop_bmi2(ctx)
|
||||
} else {
|
||||
decompress1x_4b_main_loop_amd64(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
+3261
-705
File diff suppressed because it is too large
Load Diff
+39
-4
@@ -6,24 +6,47 @@
|
||||
package huff0
|
||||
|
||||
// decompress4x_main_loop_arm64 is an arm64 assembler implementation
|
||||
// of Decompress4X when tablelog > 8.
|
||||
// of Decompress4X when tablelog > 8, decoding fastSymbols symbols per
|
||||
// stream between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_main_loop_arm64(ctx *decompress4xContext)
|
||||
|
||||
// decompress4x_8b_main_loop_arm64 is an arm64 assembler implementation
|
||||
// of Decompress4X when tablelog <= 8 which decodes 4 entries
|
||||
// per loop.
|
||||
// of Decompress4X when tablelog <= 8, decoding fast8bSymbols symbols
|
||||
// per stream between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_8b_main_loop_arm64(ctx *decompress4xContext)
|
||||
|
||||
// decompress4x_4b_main_loop_arm64 is an arm64 assembler implementation
|
||||
// of Decompress4X when tablelog <= 4, decoding fast4bSymbols symbols
|
||||
// per stream between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress4x_4b_main_loop_arm64(ctx *decompress4xContext)
|
||||
|
||||
// decompress1x_main_loop_arm64 is an arm64 assembler implementation
|
||||
// of Decompress1X when tablelog > 8.
|
||||
// of Decompress1X when tablelog > 8, decoding fastSymbols symbols
|
||||
// between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_main_loop_arm64(ctx *decompress1xContext)
|
||||
|
||||
// decompress1x_8b_main_loop_arm64 is an arm64 assembler implementation
|
||||
// of Decompress1X when tablelog <= 8, decoding fast8bSymbols symbols
|
||||
// between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_8b_main_loop_arm64(ctx *decompress1xContext)
|
||||
|
||||
// decompress1x_4b_main_loop_arm64 is an arm64 assembler implementation
|
||||
// of Decompress1X when tablelog <= 4, decoding fast4bSymbols symbols
|
||||
// between bit container reloads.
|
||||
//
|
||||
//go:noescape
|
||||
func decompress1x_4b_main_loop_arm64(ctx *decompress1xContext)
|
||||
|
||||
func decompress4x_main_loop_asm(ctx *decompress4xContext) {
|
||||
decompress4x_main_loop_arm64(ctx)
|
||||
}
|
||||
@@ -32,6 +55,18 @@ func decompress4x_8b_main_loop_asm(ctx *decompress4xContext) {
|
||||
decompress4x_8b_main_loop_arm64(ctx)
|
||||
}
|
||||
|
||||
func decompress4x_4b_main_loop_asm(ctx *decompress4xContext) {
|
||||
decompress4x_4b_main_loop_arm64(ctx)
|
||||
}
|
||||
|
||||
func decompress1x_main_loop_asm(ctx *decompress1xContext) {
|
||||
decompress1x_main_loop_arm64(ctx)
|
||||
}
|
||||
|
||||
func decompress1x_8b_main_loop_asm(ctx *decompress1xContext) {
|
||||
decompress1x_8b_main_loop_arm64(ctx)
|
||||
}
|
||||
|
||||
func decompress1x_4b_main_loop_asm(ctx *decompress1xContext) {
|
||||
decompress1x_4b_main_loop_arm64(ctx)
|
||||
}
|
||||
|
||||
+1631
-825
File diff suppressed because it is too large
Load Diff
+79
-19
@@ -14,6 +14,10 @@ import (
|
||||
// fallback8BitSize is the size where using Go version is faster.
|
||||
const fallback8BitSize = 800
|
||||
|
||||
// decompress4xContext is the argument block of the Decompress4X asm loops.
|
||||
// Go fills every field but decoded and inner; the asm advances ip, and on
|
||||
// return leaves each bit reader in the form bitReaderShifted.restoreFromAsm
|
||||
// expects.
|
||||
type decompress4xContext struct {
|
||||
pbr *[4]bitReaderShifted
|
||||
peekBits uint8
|
||||
@@ -21,9 +25,20 @@ type decompress4xContext struct {
|
||||
dstEvery int
|
||||
tbl *dEntrySingle
|
||||
decoded int
|
||||
limit *byte
|
||||
limit *byte // stream 0's output pointer must stay below this
|
||||
ilowest *byte // start of the input block; no read goes below it
|
||||
ip [4]*byte // each stream's 8-byte input window, advanced by the asm
|
||||
inner *byte // scratch for the asm: the current inner-loop limit
|
||||
}
|
||||
|
||||
// Symbols decoded per stream between reloads by the 4X asm loops; must
|
||||
// match the constants of the same names in _generate/gen.go.
|
||||
const (
|
||||
fastSymbols = 5 // tablelog 9..11
|
||||
fast8bSymbols = 7 // tablelog 5..8
|
||||
fast4bSymbols = 14 // tablelog <= 4
|
||||
)
|
||||
|
||||
// Decompress4X will decompress a 4X encoded stream.
|
||||
// The length of the supplied input must match the end of a block exactly.
|
||||
// The *capacity* of the dst slice must match the destination size of
|
||||
@@ -72,23 +87,55 @@ func (d *Decoder) Decompress4X(dst, src []byte) ([]byte, error) {
|
||||
|
||||
var decoded int
|
||||
|
||||
if len(out) > 4*4 && !(br[0].off < 4 || br[1].off < 4 || br[2].off < 4 || br[3].off < 4) {
|
||||
nSyms := fastSymbols
|
||||
if d.actualTableLog <= 4 {
|
||||
nSyms = fast4bSymbols
|
||||
} else if use8BitTables {
|
||||
nSyms = fast8bSymbols
|
||||
}
|
||||
// The asm writes nSyms bytes per stream per iteration and only re-checks
|
||||
// its bounds between batches of iterations (the batch size is derived
|
||||
// from limit in _generate/gen.go), so stream 0 must stop early enough
|
||||
// that stream 3 (which may be up to 3 bytes shorter than dstEvery)
|
||||
// never writes past the end of out. Every stream needs a full 8-byte
|
||||
// window ahead of its read pointer to enter the loop.
|
||||
if limit := dstEvery - nSyms - 2; limit > 0 && br[0].canUseAsm() && br[1].canUseAsm() && br[2].canUseAsm() && br[3].canUseAsm() {
|
||||
for i := range br {
|
||||
br[i].prepareForAsm()
|
||||
}
|
||||
ctx := decompress4xContext{
|
||||
pbr: &br,
|
||||
peekBits: uint8((64 - d.actualTableLog) & 63), // see: bitReaderShifted.peekBitsFast()
|
||||
out: &out[0],
|
||||
dstEvery: dstEvery,
|
||||
tbl: &single[0],
|
||||
limit: &out[dstEvery-4], // Always stop decoding when first buffer gets here to avoid writing OOB on last.
|
||||
limit: &out[limit],
|
||||
// The 6-byte jump table sits below stream 0 inside src, so the
|
||||
// lowest window may reach into it; restoreFromAsm accounts for
|
||||
// bytes below a stream's start. Bounding by src rather than by
|
||||
// stream 0 keeps the asm running about six bytes longer, which
|
||||
// matters for streams with very short codes.
|
||||
ilowest: &src[0],
|
||||
}
|
||||
if use8BitTables {
|
||||
for i := range br {
|
||||
ctx.ip[i] = &br[i].in[br[i].off]
|
||||
}
|
||||
switch nSyms {
|
||||
case fast4bSymbols:
|
||||
decompress4x_4b_main_loop_asm(&ctx)
|
||||
case fast8bSymbols:
|
||||
decompress4x_8b_main_loop_asm(&ctx)
|
||||
} else {
|
||||
default:
|
||||
decompress4x_main_loop_asm(&ctx)
|
||||
}
|
||||
|
||||
decoded = ctx.decoded
|
||||
out = out[decoded/4:]
|
||||
for i := range br {
|
||||
if err := br[i].restoreFromAsm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decode remaining.
|
||||
@@ -128,18 +175,20 @@ func (d *Decoder) Decompress4X(dst, src []byte) ([]byte, error) {
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// decompress1xContext is the argument block of the Decompress1X asm loops.
|
||||
// Go fills every field but decoded; the asm advances ip, and on return
|
||||
// leaves the bit reader in the form bitReaderShifted.restoreFromAsm expects.
|
||||
type decompress1xContext struct {
|
||||
pbr *bitReaderShifted
|
||||
peekBits uint8
|
||||
out *byte
|
||||
outCap int
|
||||
outCap int // no write reaches out[outCap]
|
||||
tbl *dEntrySingle
|
||||
decoded int
|
||||
ilowest *byte // start of the stream; no read goes below it
|
||||
ip *byte // the 8-byte input window, advanced by the asm
|
||||
}
|
||||
|
||||
// Error reported by asm implementations
|
||||
const error_max_decoded_size_exeeded = -1
|
||||
|
||||
// Decompress1X will decompress a 1X encoded stream.
|
||||
// The cap of the output buffer will be the maximum decompressed size.
|
||||
// The length of the supplied input must match the end of a block exactly.
|
||||
@@ -158,25 +207,36 @@ func (d *Decoder) Decompress1X(dst, src []byte) ([]byte, error) {
|
||||
const tlSize = 1 << tableLogMax
|
||||
const tlMask = tlSize - 1
|
||||
|
||||
if maxDecodedSize >= 4 {
|
||||
// The asm decodes whole batches of symbols and only re-checks its
|
||||
// bounds between them, so it needs at least one batch of room (the
|
||||
// output bound rounds nSyms up to 16, see _generate/gen.go) and a full
|
||||
// 8-byte window ahead of the read pointer to enter the loop. It also
|
||||
// needs at most 7 bits consumed on entry so that a batch cannot run
|
||||
// the container dry, which prepareForAsm establishes.
|
||||
decoded := 0
|
||||
if maxDecodedSize >= 16 && br.canUseAsm() {
|
||||
br.prepareForAsm()
|
||||
ctx := decompress1xContext{
|
||||
pbr: &br,
|
||||
out: &dst[0],
|
||||
outCap: maxDecodedSize,
|
||||
peekBits: uint8((64 - d.actualTableLog) & 63), // see: bitReaderShifted.peekBitsFast()
|
||||
tbl: &d.dt.single[0],
|
||||
ilowest: &src[0],
|
||||
ip: &br.in[br.off],
|
||||
}
|
||||
|
||||
decompress1x_main_loop_asm(&ctx)
|
||||
if ctx.decoded == error_max_decoded_size_exeeded {
|
||||
return nil, ErrMaxDecodedSizeExceeded
|
||||
if d.actualTableLog <= 4 {
|
||||
decompress1x_4b_main_loop_asm(&ctx)
|
||||
} else if d.actualTableLog <= 8 {
|
||||
decompress1x_8b_main_loop_asm(&ctx)
|
||||
} else {
|
||||
decompress1x_main_loop_asm(&ctx)
|
||||
}
|
||||
|
||||
dst = dst[:ctx.decoded]
|
||||
decoded = ctx.decoded
|
||||
}
|
||||
dst = dst[:decoded]
|
||||
|
||||
// br < 8, so uint8 is fine
|
||||
bitsLeft := uint8(br.off)*8 + 64 - br.bitsRead
|
||||
bitsLeft := br.remaining()
|
||||
for bitsLeft > 0 {
|
||||
br.fill()
|
||||
if len(dst) >= maxDecodedSize {
|
||||
@@ -186,7 +246,7 @@ func (d *Decoder) Decompress1X(dst, src []byte) ([]byte, error) {
|
||||
v := d.dt.single[br.peekBitsFast(d.actualTableLog)&tlMask]
|
||||
nBits := uint8(v.entry)
|
||||
br.advance(nBits)
|
||||
bitsLeft -= nBits
|
||||
bitsLeft -= uint(nBits)
|
||||
dst = append(dst, uint8(v.entry>>8))
|
||||
}
|
||||
return dst, br.close()
|
||||
|
||||
+3
@@ -240,9 +240,11 @@ func (b *blockDec) decodeBuf(hist *history) error {
|
||||
b.dst[i] = v
|
||||
}
|
||||
hist.appendKeep(b.dst)
|
||||
hist.decoders.consumeSyncLen(len(b.dst))
|
||||
return nil
|
||||
case blockTypeRaw:
|
||||
hist.appendKeep(b.data)
|
||||
hist.decoders.consumeSyncLen(len(b.data))
|
||||
return nil
|
||||
case blockTypeCompressed:
|
||||
saved := b.dst
|
||||
@@ -487,6 +489,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
|
||||
}
|
||||
if hist.decoders.nSeqs == 0 {
|
||||
b.dst = append(b.dst, hist.decoders.literals...)
|
||||
hist.decoders.consumeSyncLen(len(hist.decoders.literals))
|
||||
return nil
|
||||
}
|
||||
before := len(hist.decoders.out)
|
||||
|
||||
+81
-44
@@ -7,8 +7,11 @@ package zstd
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"maps"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/klauspost/compress/zstd/internal/xxhash"
|
||||
)
|
||||
@@ -20,7 +23,8 @@ import (
|
||||
// A decoder can safely be re-used even if the previous stream failed.
|
||||
// To release the resources, you must call the Close() function on a decoder.
|
||||
type Decoder struct {
|
||||
o decoderOptions
|
||||
// publishedOptions contains the current immutable decoder options.
|
||||
publishedOptions atomic.Pointer[decoderOptions]
|
||||
|
||||
// Unreferenced decoders, ready for use.
|
||||
decoders chan *blockDec
|
||||
@@ -84,9 +88,10 @@ var (
|
||||
func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) {
|
||||
initPredefined()
|
||||
var d Decoder
|
||||
d.o.setDefault()
|
||||
for _, o := range opts {
|
||||
err := o(&d.o)
|
||||
var o decoderOptions
|
||||
o.setDefault()
|
||||
for _, opt := range opts {
|
||||
err := opt(&o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,17 +104,19 @@ func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) {
|
||||
}
|
||||
|
||||
// Initialize dict map if needed.
|
||||
if d.o.dicts == nil {
|
||||
d.o.dicts = make(map[uint32]*dict)
|
||||
if o.dicts == nil {
|
||||
o.dicts = make(map[uint32]*dict)
|
||||
}
|
||||
|
||||
// Create decoders
|
||||
d.decoders = make(chan *blockDec, d.o.concurrent)
|
||||
for i := 0; i < d.o.concurrent; i++ {
|
||||
dec := newBlockDec(d.o.lowMem)
|
||||
dec.localFrame = newFrameDec(d.o)
|
||||
d.decoders = make(chan *blockDec, o.concurrent)
|
||||
for i := 0; i < o.concurrent; i++ {
|
||||
dec := newBlockDec(o.lowMem)
|
||||
dec.localFrame = newFrameDec(o)
|
||||
dec.localFrame.o.dicts = nil
|
||||
d.decoders <- dec
|
||||
}
|
||||
d.publishedOptions.Store(&o)
|
||||
|
||||
if r == nil {
|
||||
return &d, nil
|
||||
@@ -171,6 +178,9 @@ func (d *Decoder) Reset(r io.Reader) error {
|
||||
d.drainOutput()
|
||||
|
||||
d.syncStream.br.r = nil
|
||||
if d.frame != nil {
|
||||
d.frame.o.dicts = nil
|
||||
}
|
||||
if r == nil {
|
||||
d.current.err = ErrDecoderNilInput
|
||||
if len(d.current.b) > 0 {
|
||||
@@ -180,8 +190,10 @@ func (d *Decoder) Reset(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
o := *d.publishedOptions.Load()
|
||||
|
||||
// If bytes buffer and < 5MB, do sync decoding anyway.
|
||||
if bb, ok := r.(byter); ok && bb.Len() < d.o.decodeBufsBelow && !d.o.limitToCap {
|
||||
if bb, ok := r.(byter); ok && bb.Len() < o.decodeBufsBelow && !o.limitToCap {
|
||||
bb2 := bb
|
||||
if debugDecoder {
|
||||
println("*bytes.Buffer detected, doing sync decode, len:", bb.Len())
|
||||
@@ -193,18 +205,30 @@ func (d *Decoder) Reset(r io.Reader) error {
|
||||
}
|
||||
|
||||
dst, err := d.DecodeAll(b, dst)
|
||||
if err == nil {
|
||||
err = io.EOF
|
||||
if !errors.Is(err, ErrDecoderSizeExceeded) {
|
||||
if err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
// Save output buffer
|
||||
d.syncStream.dstBuf = dst
|
||||
d.current.b = dst
|
||||
d.current.err = err
|
||||
d.current.flushed = true
|
||||
if debugDecoder {
|
||||
println("sync decode to", len(dst), "bytes, err:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Save output buffer
|
||||
d.syncStream.dstBuf = dst
|
||||
d.current.b = dst
|
||||
d.current.err = err
|
||||
d.current.flushed = true
|
||||
// The output does not fit WithDecoderMaxMemory, which caps the total
|
||||
// decoded size when decoding in memory. A Reader follows the
|
||||
// streaming contract, where the option caps the window instead, so
|
||||
// decode this input as a stream after all; the shortcut has not
|
||||
// consumed the reader. Drop the oversized buffer rather than keep it
|
||||
// for reuse.
|
||||
if debugDecoder {
|
||||
println("sync decode to", len(dst), "bytes, err:", err)
|
||||
println("sync decode exceeded max memory after", len(dst), "bytes; streaming instead")
|
||||
}
|
||||
return nil
|
||||
d.syncStream.dstBuf = nil
|
||||
}
|
||||
// Remove current block.
|
||||
d.stashDecoder()
|
||||
@@ -217,14 +241,16 @@ func (d *Decoder) Reset(r io.Reader) error {
|
||||
// Ensure no-one else is still running...
|
||||
d.streamWg.Wait()
|
||||
if d.frame == nil {
|
||||
d.frame = newFrameDec(d.o)
|
||||
d.frame = newFrameDec(o)
|
||||
} else {
|
||||
d.frame.setOptions(o)
|
||||
}
|
||||
|
||||
if d.o.concurrent == 1 {
|
||||
if o.concurrent == 1 {
|
||||
return d.startSyncDecoder(r)
|
||||
}
|
||||
|
||||
d.current.output = make(chan decodeOutput, d.o.concurrent)
|
||||
d.current.output = make(chan decodeOutput, o.concurrent)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
d.current.cancel = cancel
|
||||
d.streamWg.Add(1)
|
||||
@@ -238,13 +264,21 @@ func (d *Decoder) Reset(r io.Reader) error {
|
||||
// Options are applied on top of the existing options.
|
||||
// Some options cannot be changed on reset and will return an error.
|
||||
func (d *Decoder) ResetWithOptions(r io.Reader, opts ...DOption) error {
|
||||
d.o.resetOpt = true
|
||||
defer func() { d.o.resetOpt = false }()
|
||||
if d.current.err == ErrDecoderClosed {
|
||||
return d.current.err
|
||||
}
|
||||
next := *d.publishedOptions.Load()
|
||||
next.dicts = maps.Clone(next.dicts)
|
||||
next.resetOpt = true
|
||||
for _, o := range opts {
|
||||
if err := o(&d.o); err != nil {
|
||||
if err := o(&next); err != nil {
|
||||
next.resetOpt = false
|
||||
d.publishedOptions.Store(&next)
|
||||
return err
|
||||
}
|
||||
}
|
||||
next.resetOpt = false
|
||||
d.publishedOptions.Store(&next)
|
||||
return d.Reset(r)
|
||||
}
|
||||
|
||||
@@ -324,6 +358,8 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
|
||||
// Grab a block decoder and frame decoder.
|
||||
block := <-d.decoders
|
||||
frame := block.localFrame
|
||||
o := *d.publishedOptions.Load()
|
||||
frame.setOptions(o)
|
||||
initialSize := len(dst)
|
||||
defer func() {
|
||||
if debugDecoder {
|
||||
@@ -335,6 +371,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
|
||||
frame.history.decoders.br.in = nil
|
||||
frame.history.decoders.br.cursor = 0
|
||||
}
|
||||
frame.o.dicts = nil
|
||||
d.decoders <- block
|
||||
}()
|
||||
frame.bBuf = input
|
||||
@@ -354,20 +391,20 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
|
||||
if err = d.setDict(frame); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if frame.WindowSize > d.o.maxWindowSize {
|
||||
if frame.WindowSize > frame.o.maxWindowSize {
|
||||
if debugDecoder {
|
||||
println("window size exceeded:", frame.WindowSize, ">", d.o.maxWindowSize)
|
||||
println("window size exceeded:", frame.WindowSize, ">", frame.o.maxWindowSize)
|
||||
}
|
||||
return dst, ErrWindowSizeExceeded
|
||||
}
|
||||
if frame.FrameContentSize != fcsUnknown {
|
||||
if frame.FrameContentSize > d.o.maxDecodedSize-uint64(len(dst)-initialSize) {
|
||||
if frame.FrameContentSize > frame.o.maxDecodedSize-uint64(len(dst)-initialSize) {
|
||||
if debugDecoder {
|
||||
println("decoder size exceeded; fcs:", frame.FrameContentSize, "> mcs:", d.o.maxDecodedSize-uint64(len(dst)-initialSize), "len:", len(dst))
|
||||
println("decoder size exceeded; fcs:", frame.FrameContentSize, "> mcs:", frame.o.maxDecodedSize-uint64(len(dst)-initialSize), "len:", len(dst))
|
||||
}
|
||||
return dst, ErrDecoderSizeExceeded
|
||||
}
|
||||
if d.o.limitToCap && frame.FrameContentSize > uint64(cap(dst)-len(dst)) {
|
||||
if frame.o.limitToCap && frame.FrameContentSize > uint64(cap(dst)-len(dst)) {
|
||||
if debugDecoder {
|
||||
println("decoder size exceeded; fcs:", frame.FrameContentSize, "> (cap-len)", cap(dst)-len(dst))
|
||||
}
|
||||
@@ -380,14 +417,14 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if cap(dst) == 0 && !d.o.limitToCap {
|
||||
if cap(dst) == 0 && !frame.o.limitToCap {
|
||||
// Allocate len(input) * 2 by default if nothing is provided
|
||||
// and we didn't get frame content size.
|
||||
size := min(
|
||||
// Cap to 1 MB.
|
||||
len(input)*2, 1<<20)
|
||||
if uint64(size) > d.o.maxDecodedSize {
|
||||
size = int(d.o.maxDecodedSize)
|
||||
if uint64(size) > frame.o.maxDecodedSize {
|
||||
size = int(frame.o.maxDecodedSize)
|
||||
}
|
||||
dst = make([]byte, 0, size)
|
||||
}
|
||||
@@ -396,7 +433,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
|
||||
if err != nil {
|
||||
return dst, err
|
||||
}
|
||||
if uint64(len(dst)-initialSize) > d.o.maxDecodedSize {
|
||||
if uint64(len(dst)-initialSize) > frame.o.maxDecodedSize {
|
||||
return dst, ErrDecoderSizeExceeded
|
||||
}
|
||||
if len(frame.bBuf) == 0 {
|
||||
@@ -459,7 +496,7 @@ func (d *Decoder) nextBlock(blocking bool) (ok bool) {
|
||||
println("got", len(d.current.b), "bytes, error:", d.current.err, "data crc:", tmp)
|
||||
}
|
||||
|
||||
if d.o.ignoreChecksum {
|
||||
if d.frame.o.ignoreChecksum {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -497,7 +534,7 @@ func (d *Decoder) nextBlockSync() (ok bool) {
|
||||
if d.current.err != nil {
|
||||
return false
|
||||
}
|
||||
if d.frame.WindowSize > d.o.maxDecodedSize || d.frame.WindowSize > d.o.maxWindowSize {
|
||||
if d.frame.WindowSize > d.frame.o.maxDecodedSize || d.frame.WindowSize > d.frame.o.maxWindowSize {
|
||||
d.current.err = ErrDecoderSizeExceeded
|
||||
return false
|
||||
}
|
||||
@@ -546,11 +583,11 @@ func (d *Decoder) nextBlockSync() (ok bool) {
|
||||
|
||||
// Update/Check CRC
|
||||
if d.frame.HasCheckSum {
|
||||
if !d.o.ignoreChecksum {
|
||||
if !d.frame.o.ignoreChecksum {
|
||||
d.frame.crc.Write(d.current.b)
|
||||
}
|
||||
if d.current.d.Last {
|
||||
if !d.o.ignoreChecksum {
|
||||
if !d.frame.o.ignoreChecksum {
|
||||
d.current.err = d.frame.checkCRC()
|
||||
} else {
|
||||
d.current.err = d.frame.consumeCRC()
|
||||
@@ -656,8 +693,8 @@ func (d *Decoder) startStreamDecoder(ctx context.Context, r io.Reader, output ch
|
||||
defer d.streamWg.Done()
|
||||
br := readerWrapper{r: r}
|
||||
|
||||
var seqDecode = make(chan *blockDec, d.o.concurrent)
|
||||
var seqExecute = make(chan *blockDec, d.o.concurrent)
|
||||
var seqDecode = make(chan *blockDec, d.frame.o.concurrent)
|
||||
var seqExecute = make(chan *blockDec, d.frame.o.concurrent)
|
||||
|
||||
// Async 1: Decode sequences...
|
||||
go func() {
|
||||
@@ -858,9 +895,9 @@ decodeStream:
|
||||
if err == nil {
|
||||
err = d.setDict(frame)
|
||||
}
|
||||
if err == nil && d.frame.WindowSize > d.o.maxWindowSize {
|
||||
if err == nil && d.frame.WindowSize > d.frame.o.maxWindowSize {
|
||||
if debugDecoder {
|
||||
println("decoder size exceeded, fws:", d.frame.WindowSize, "> mws:", d.o.maxWindowSize)
|
||||
println("decoder size exceeded, fws:", d.frame.WindowSize, "> mws:", d.frame.o.maxWindowSize)
|
||||
}
|
||||
|
||||
err = ErrDecoderSizeExceeded
|
||||
@@ -940,7 +977,7 @@ decodeStream:
|
||||
}
|
||||
|
||||
func (d *Decoder) setDict(frame *frameDec) (err error) {
|
||||
dict, ok := d.o.dicts[frame.DictionaryID]
|
||||
dict, ok := frame.o.dicts[frame.DictionaryID]
|
||||
if ok {
|
||||
if debugDecoder {
|
||||
println("setting dict", frame.DictionaryID)
|
||||
|
||||
+7
-4
@@ -49,13 +49,16 @@ const (
|
||||
)
|
||||
|
||||
func newFrameDec(o decoderOptions) *frameDec {
|
||||
d := frameDec{}
|
||||
d.setOptions(o)
|
||||
return &d
|
||||
}
|
||||
|
||||
func (d *frameDec) setOptions(o decoderOptions) {
|
||||
if o.maxWindowSize > o.maxDecodedSize {
|
||||
o.maxWindowSize = o.maxDecodedSize
|
||||
}
|
||||
d := frameDec{
|
||||
o: o,
|
||||
}
|
||||
return &d
|
||||
d.o = o
|
||||
}
|
||||
|
||||
// reset will read the frame header and prepare for block decoding.
|
||||
|
||||
+13
-22
@@ -1,7 +1,7 @@
|
||||
// Code generated by command: go run gen_fse.go -out ../fse_decoder.s -arch amd64,arm64 -pkg=zstd. DO NOT EDIT.
|
||||
// EXPERIMENTAL arm64 output lowered from an amd64 avo program.
|
||||
|
||||
//go:build arm64 && !appengine && !noasm && gc && !noasm
|
||||
//go:build arm64 && (!appengine && !noasm && gc && !noasm)
|
||||
|
||||
// func buildDtable_asm(s *fseDecoder, ctx *buildDtableAsmContext) int
|
||||
TEXT ·buildDtable_asm(SB), $0-24
|
||||
@@ -26,11 +26,10 @@ TEXT ·buildDtable_asm(SB), $0-24
|
||||
JMP init_main_loop_condition
|
||||
|
||||
init_main_loop:
|
||||
ADD R8<<1, R1, R15
|
||||
MOVH (R15), R9
|
||||
AND $0xffff, R9, R15
|
||||
MOVH (R1)(R8<<1), R9
|
||||
MOVD $-1, R16
|
||||
AND $0xffff, R16, R16
|
||||
AND $0xffff, R9, R15
|
||||
CMP R16, R15
|
||||
BNE do_not_update_high_threshold
|
||||
ADD R7<<3, R5, R15
|
||||
@@ -39,8 +38,7 @@ init_main_loop:
|
||||
MOVD $0x0000000000000001, R9
|
||||
|
||||
do_not_update_high_threshold:
|
||||
ADD R8<<1, R3, R15
|
||||
MOVH R9, (R15)
|
||||
MOVH R9, (R3)(R8<<1)
|
||||
ADD $1, R8, R8
|
||||
|
||||
init_main_loop_condition:
|
||||
@@ -49,12 +47,10 @@ init_main_loop_condition:
|
||||
|
||||
// Spread symbols
|
||||
// Calculate table step
|
||||
MOVD R0, R8
|
||||
LSR $0x01, R8, R8
|
||||
MOVD R0, R9
|
||||
LSR $0x03, R9, R9
|
||||
ADD R9, R8, R8
|
||||
ADD $3, R8, R8
|
||||
LSR $0x01, R0, R8
|
||||
LSR $0x03, R0, R9
|
||||
ADD R9, R8, R8
|
||||
ADD $3, R8, R8
|
||||
|
||||
// Fill add bits values
|
||||
SUB $1, R0, R9
|
||||
@@ -64,8 +60,7 @@ init_main_loop_condition:
|
||||
|
||||
spread_main_loop:
|
||||
MOVD $0, R12
|
||||
ADD R11<<1, R1, R15
|
||||
MOVH (R15), R13
|
||||
MOVH (R1)(R11<<1), R13
|
||||
JMP spread_inner_loop_condition
|
||||
|
||||
spread_inner_loop:
|
||||
@@ -102,11 +97,9 @@ spread_check_ok:
|
||||
build_table_main_table:
|
||||
ADD R6<<3, R5, R15
|
||||
MOVBU 1(R15), R1
|
||||
ADD R1<<1, R3, R15
|
||||
MOVHU (R15), R7
|
||||
MOVHU (R3)(R1<<1), R7
|
||||
ADD $1, R7, R8
|
||||
ADD R1<<1, R3, R15
|
||||
MOVH R8, (R15)
|
||||
MOVH R8, (R3)(R1<<1)
|
||||
MOVD R7, R8
|
||||
CLZ R8, R16
|
||||
MOVD $63, R8
|
||||
@@ -129,12 +122,10 @@ build_table_main_table:
|
||||
RET
|
||||
|
||||
build_table_check1_ok:
|
||||
AND $0xff, R1, R15
|
||||
AND $0xff, R1, R16
|
||||
TST R16, R15
|
||||
TST $0xff, R1
|
||||
BNE build_table_check2_ok
|
||||
AND $0xffff, R7, R15
|
||||
AND $0xffff, R6, R16
|
||||
AND $0xffff, R7, R15
|
||||
CMP R16, R15
|
||||
BNE build_table_check2_ok
|
||||
MOVD ctx+8(FP), R0
|
||||
|
||||
+12
@@ -52,6 +52,11 @@ func (d *Digest) Reset() {
|
||||
d.n = 0
|
||||
}
|
||||
|
||||
// maxAsmSize bounds the input handed to a single writeBlocks call. Assembly is
|
||||
// never preemptible, so an unbounded call holds every P in stop-the-world for
|
||||
// its duration. Whole 32-byte blocks, so Write can feed it in pieces.
|
||||
const maxAsmSize = 128 << 10 // 4096 whole 32-byte blocks
|
||||
|
||||
// Size always returns 8 bytes.
|
||||
func (d *Digest) Size() int { return 8 }
|
||||
|
||||
@@ -83,6 +88,13 @@ func (d *Digest) Write(b []byte) (n int, err error) {
|
||||
d.n = 0
|
||||
}
|
||||
|
||||
for len(b) >= maxAsmSize {
|
||||
// Assembly is not preemptible, so hashing a large buffer in one call
|
||||
// blocks every stop-the-world for the whole call. Feed it in chunks.
|
||||
writeBlocks(d, b[:maxAsmSize])
|
||||
b = b[maxAsmSize:]
|
||||
}
|
||||
|
||||
if len(b) >= 32 {
|
||||
// One or more full blocks left.
|
||||
nw := writeBlocks(d, b)
|
||||
|
||||
+5
-1
@@ -175,7 +175,11 @@ finalize:
|
||||
RET
|
||||
|
||||
// func writeBlocks(d *Digest, b []byte) int
|
||||
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
||||
//
|
||||
// Deliberately not NOSPLIT: the stack check is the preemption point that lets a
|
||||
// stop-the-world proceed between chunks. The frame must be >= abi.StackSmall
|
||||
// (128), or the assembler marks this leaf NOSPLIT anyway and drops that check.
|
||||
TEXT ·writeBlocks(SB), $128-40
|
||||
// Load fixed primes needed for round.
|
||||
MOVQ ·primes+0(SB), prime1
|
||||
MOVQ ·primes+8(SB), prime2
|
||||
|
||||
+5
-1
@@ -163,7 +163,11 @@ finalize:
|
||||
RET
|
||||
|
||||
// func writeBlocks(s *Digest, b []byte) int
|
||||
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
||||
//
|
||||
// Deliberately not NOSPLIT: the stack check is the preemption point that lets a
|
||||
// stop-the-world proceed between chunks. The frame must be >= abi.StackSmall
|
||||
// (128), or the assembler marks this leaf NOSPLIT anyway and drops that check.
|
||||
TEXT ·writeBlocks(SB), $128-40
|
||||
LDP ·primes+0(SB), (prime1, prime2)
|
||||
|
||||
// Load state. Assume v[1-4] are stored contiguously.
|
||||
|
||||
+23
@@ -99,6 +99,29 @@ func (s *sequenceDecs) initialize(br *bitReader, hist *history, out []byte) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
// consumeSyncLen accounts for n bytes of frame output produced outside
|
||||
// decodeSync. maxSyncLen bounds how much the frame may still produce and
|
||||
// decodeSyncSimple compares it with the output buffer's slack to decide
|
||||
// whether the extended 16-byte copies are safe. Blocks with sequences
|
||||
// subtracted their output; raw blocks, RLE blocks and compressed blocks
|
||||
// holding only literals did not, so a frame that opened with a raw block
|
||||
// overstated its remaining size for every block after it and ran the
|
||||
// bounds-exact copies for the rest of the frame.
|
||||
//
|
||||
// Zero means "no bound" and selects the conservative paths, so a block that
|
||||
// meets or exceeds the bound lands there; the frame-size checks in
|
||||
// runDecoder reject the excess afterwards.
|
||||
func (s *sequenceDecs) consumeSyncLen(n int) {
|
||||
if s.maxSyncLen == 0 {
|
||||
return
|
||||
}
|
||||
if uint64(n) >= s.maxSyncLen {
|
||||
s.maxSyncLen = 0
|
||||
return
|
||||
}
|
||||
s.maxSyncLen -= uint64(n)
|
||||
}
|
||||
|
||||
func (s *sequenceDecs) freeDecoders() {
|
||||
if f := s.litLengths.fse; f != nil && !f.preDefined {
|
||||
fseDecoderPool.Put(f)
|
||||
|
||||
+1062
-1023
File diff suppressed because it is too large
Load Diff
+736
-899
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -83,9 +83,10 @@ const errorOverread = 6
|
||||
// When the output and literal buffers have compressedBlockOverAlloc (16)
|
||||
// bytes of slack past their logical use, the assembly may use extended
|
||||
// memory copies that read and write in 16-byte blocks, overrunning the end
|
||||
// of a literal run or match by up to 15 bytes. Otherwise it must use the
|
||||
// bounds-exact ("safe") copies. This mirrors the analogous, always-dynamic
|
||||
// selection in executeSimple below.
|
||||
// of a literal run or match by up to 15 bytes; an empty literal run still
|
||||
// copies one block, reading and writing the full 16 bytes of slack past
|
||||
// its position. Otherwise it must use the bounds-exact ("safe") copies.
|
||||
// This mirrors the analogous, always-dynamic selection in executeSimple below.
|
||||
//
|
||||
// The unsafe copies were disabled in #644 (2022) as a mitigation for a
|
||||
// crash, but that crash's root cause — an unguarded bitReader overread that
|
||||
|
||||
+5
@@ -15,6 +15,11 @@ Usage
|
||||
runewidth.StringWidth("つのだ☆HIRO") == 12
|
||||
```
|
||||
|
||||
NOTE: Latin letters that Unicode gives ambiguous East Asian width, such as
|
||||
`ü`, `é` and `ß`, are measured as one cell even when `EastAsianWidth` is
|
||||
set, as terminals draw them in CJK locales too. Other ambiguous characters
|
||||
such as `±`, `×` and `○` still take two cells there.
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
+342
-28
@@ -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()
|
||||
}
|
||||
@@ -347,9 +361,11 @@ var private = table{
|
||||
|
||||
var nonprint = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
||||
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
||||
{0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
|
||||
{0x061C, 0x061C}, {0x070F, 0x070F}, {0x180B, 0x180E},
|
||||
{0x200B, 0x200F}, {0x2028, 0x202E}, {0x2060, 0x2064},
|
||||
{0x2066, 0x206F}, {0xD800, 0xDFFF}, {0xFEFF, 0xFEFF},
|
||||
{0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF}, {0x13430, 0x1343F},
|
||||
{0x1BCA0, 0x1BCA3}, {0x1D173, 0x1D17A}, {0xE0001, 0xE0001},
|
||||
}
|
||||
|
||||
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
|
||||
@@ -446,19 +462,131 @@ 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)
|
||||
}
|
||||
|
||||
// clusterText returns s to segment in place of s. When s ends in a lead
|
||||
// byte short of the bytes it promises, the segmenter gives up there and
|
||||
// glues everything from it onto the cluster before, where the two-cell cap
|
||||
// hides it, although the same bytes followed by anything else form
|
||||
// clusters of their own. Such a string gets NULs after it, enough for any
|
||||
// lead byte to be decoded; as controls they are clusters of their own, and
|
||||
// the loops over the clusters stop at len(s) before reaching them.
|
||||
func clusterText(s string) string {
|
||||
if endsShort(s) {
|
||||
return s + "\x00\x00\x00"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// endsShort reports whether one of the last three bytes of s is a lead
|
||||
// byte that needs more bytes than are left, which is where the segmenter
|
||||
// stops decoding. It only looks at the length the lead byte announces, not
|
||||
// at whether the bytes after it could continue it, as the segmenter does.
|
||||
func endsShort(s string) bool {
|
||||
for i := max(len(s)-3, 0); i < len(s); i++ {
|
||||
b, left := s[i], len(s)-i
|
||||
if b >= 0xC2 && b < 0xE0 && left < 2 ||
|
||||
b >= 0xE0 && b < 0xF0 && left < 3 ||
|
||||
b >= 0xF0 && b < 0xF8 && left < 4 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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)
|
||||
// from being counted wider than the two cells terminals give them.
|
||||
// the widths of its runes, with the part from the first rune that starts a
|
||||
// single glyph (an emoji, a regional indicator, a Hangul jamo or syllable)
|
||||
// capped at 2 cells. The cap keeps multi-rune sequences that render as one
|
||||
// glyph (ZWJ emoji, skin tones, flags, Hangul jamo) from being counted
|
||||
// wider than the two cells terminals give them. What comes before it, such
|
||||
// as a Prepend rune, and a cluster with no such rune, like a letter with a
|
||||
// skin tone modifier after it, are shown rune by rune and summed.
|
||||
func (c *Condition) graphemeWidth(cluster string) int {
|
||||
width := 0
|
||||
if r, size := utf8.DecodeRuneInString(cluster); size == len(cluster) {
|
||||
return c.RuneWidth(r) // one rune, never over the cap
|
||||
}
|
||||
width, glyph := 0, -1
|
||||
for _, r := range cluster {
|
||||
width += c.RuneWidth(r)
|
||||
w := c.RuneWidth(r)
|
||||
if glyph < 0 && r >= 0x1100 && startsGlyph(r) {
|
||||
glyph = 0
|
||||
}
|
||||
if glyph >= 0 {
|
||||
glyph += w
|
||||
} else {
|
||||
width += w
|
||||
}
|
||||
}
|
||||
if width > 2 {
|
||||
width = 2
|
||||
return width + min(max(glyph, 0), 2)
|
||||
}
|
||||
|
||||
// startsGlyph reports whether r begins a sequence that a terminal draws as
|
||||
// a single glyph however many runes it has.
|
||||
func startsGlyph(r rune) bool {
|
||||
switch {
|
||||
case 0x1100 <= r && r <= 0x11FF, // Hangul jamo
|
||||
0xA960 <= r && r <= 0xA97F, // Hangul jamo extended-A
|
||||
0xAC00 <= r && r <= 0xD7FF, // Hangul syllables, jamo extended-B
|
||||
0x1F1E6 <= r && r <= 0x1F1FF: // regional indicators
|
||||
return true
|
||||
case r < 0x203C, 0x3299 < r && r < 0x1F004:
|
||||
// Outside the emoji table: spares CJK text the search.
|
||||
return false
|
||||
}
|
||||
return width
|
||||
return inTable(r, emoji)
|
||||
}
|
||||
|
||||
// StringWidth return width as you can see
|
||||
@@ -468,6 +596,11 @@ func (c *Condition) StringWidth(s string) (width int) {
|
||||
if b < 0x20 || b == 0x7F {
|
||||
return 0
|
||||
}
|
||||
if b >= 0x80 {
|
||||
// Not UTF-8 on its own: measure it as the U+FFFD every other
|
||||
// path decodes it to, which is ambiguous width.
|
||||
return c.RuneWidth(utf8.RuneError)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if len(s) > 0 && len(s) <= utf8.UTFMax {
|
||||
@@ -489,24 +622,48 @@ 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() {
|
||||
g := graphemes.FromString(clusterText(s))
|
||||
for g.Next() && g.Start() < len(s) {
|
||||
width += c.graphemeWidth(g.Value())
|
||||
}
|
||||
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)
|
||||
for g.Next() {
|
||||
g := graphemes.FromString(clusterText(s))
|
||||
for g.Next() && g.Start() < len(s) {
|
||||
chWidth := c.graphemeWidth(g.Value())
|
||||
if width+chWidth > w {
|
||||
pos = g.Start()
|
||||
@@ -517,17 +674,50 @@ 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)
|
||||
|
||||
g := graphemes.FromString(s)
|
||||
for g.Next() {
|
||||
g := graphemes.FromString(clusterText(s))
|
||||
for g.Next() && g.Start() < len(s) {
|
||||
chWidth := c.graphemeWidth(g.Value())
|
||||
|
||||
if width+chWidth > w {
|
||||
@@ -547,6 +737,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,10 +774,13 @@ 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)
|
||||
for g.Next() {
|
||||
g := graphemes.FromString(clusterText(s))
|
||||
for g.Next() && g.Start() < len(s) {
|
||||
chWidth := c.graphemeWidth(g.Value())
|
||||
if sw-(width+chWidth) <= w {
|
||||
pos = g.End()
|
||||
@@ -573,29 +792,119 @@ 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
|
||||
}
|
||||
// CRLF is one cluster that ends the line, as on the
|
||||
// segmenter, so it is never broken before.
|
||||
if b == '\r' && i+1 < len(s) && s[i+1] == '\n' {
|
||||
out.WriteString("\r\n")
|
||||
width = 0
|
||||
i++
|
||||
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(clusterText(s))
|
||||
for g.Next() && g.Start() < len(s) {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -626,8 +935,13 @@ func RuneWidth(r rune) int {
|
||||
}
|
||||
|
||||
// IsAmbiguousWidth returns whether is ambiguous width or not.
|
||||
//
|
||||
// NOTE: The Latin letters of U+0080..U+017F that Unicode gives ambiguous
|
||||
// width (ü, é, ß, æ, ø and so on) are reported as ambiguous here, but
|
||||
// RuneWidth measures them as one cell even with EastAsianWidth, since
|
||||
// terminals draw them narrow in CJK locales too.
|
||||
func IsAmbiguousWidth(r rune) bool {
|
||||
return inTable(r, private) || inTable(r, ambiguous)
|
||||
return inTable(r, private) || inTable(r, ambiguous) || inTable(r, ambiguousLatin)
|
||||
}
|
||||
|
||||
// IsCombiningWidth returns whether is combining width or not.
|
||||
|
||||
+35
-8
@@ -14,9 +14,11 @@ func mblen(charset string) int {
|
||||
return 6
|
||||
case "jis":
|
||||
return 8
|
||||
case "euctw", "gb18030":
|
||||
return 4
|
||||
case "eucjp":
|
||||
return 3
|
||||
case "euckr", "euccn", "sjis", "cp932", "cp51932", "cp936", "cp949", "cp950", "big5", "gbk", "gb2312":
|
||||
case "euckr", "euccn", "sjis", "shiftjis", "cp932", "cp51932", "cp936", "cp949", "cp950", "big5", "big5hkscs", "gbk", "gb2312":
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
@@ -45,9 +47,35 @@ func localeCharset(locale string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// isCJKLanguage reports whether the language code at the start of locale is
|
||||
// Japanese, Korean or Chinese.
|
||||
func isCJKLanguage(locale string) bool {
|
||||
n := 0
|
||||
for n < len(locale) && locale[n] >= 'a' && locale[n] <= 'z' {
|
||||
n++
|
||||
}
|
||||
switch locale[:n] {
|
||||
case "ja", "ko", "zh":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// normalizeCharset drops the hyphens and underscores that charset names are
|
||||
// spelled with or without, so that "EUC-JP" and "eucjp" compare equal.
|
||||
func normalizeCharset(charset string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r == '-' || r == '_' {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, charset)
|
||||
}
|
||||
|
||||
func isEastAsian(locale string) bool {
|
||||
charset := strings.ToLower(locale)
|
||||
if cs := localeCharset(locale); cs != "" {
|
||||
cs := localeCharset(locale)
|
||||
if cs != "" {
|
||||
charset = strings.ToLower(cs)
|
||||
}
|
||||
|
||||
@@ -61,14 +89,13 @@ func isEastAsian(locale string) bool {
|
||||
break
|
||||
}
|
||||
}
|
||||
max := mblen(charset)
|
||||
if max > 1 && (charset[0] != 'u' ||
|
||||
strings.HasPrefix(locale, "ja") ||
|
||||
strings.HasPrefix(locale, "ko") ||
|
||||
strings.HasPrefix(locale, "zh")) {
|
||||
cjk := isCJKLanguage(locale)
|
||||
if cs == "" && cjk {
|
||||
// A CJK locale without a charset, such as "ja_JP" on macOS.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
max := mblen(normalizeCharset(charset))
|
||||
return max > 1 && (charset[0] != 'u' || cjk)
|
||||
}
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
|
||||
+175
-52
@@ -173,65 +173,71 @@ var doublewidth = table{
|
||||
|
||||
var ambiguous = table{
|
||||
{0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
|
||||
{0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4},
|
||||
{0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
|
||||
{0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
|
||||
{0x00AD, 0x00AE}, {0x00B0, 0x00B4}, {0x00B6, 0x00B9},
|
||||
{0x00BC, 0x00BF}, {0x00D7, 0x00D7}, {0x00F7, 0x00F7},
|
||||
{0x01CE, 0x01CE}, {0x01D0, 0x01D0}, {0x01D2, 0x01D2},
|
||||
{0x01D4, 0x01D4}, {0x01D6, 0x01D6}, {0x01D8, 0x01D8},
|
||||
{0x01DA, 0x01DA}, {0x01DC, 0x01DC}, {0x0251, 0x0251},
|
||||
{0x0261, 0x0261}, {0x02C4, 0x02C4}, {0x02C7, 0x02C7},
|
||||
{0x02C9, 0x02CB}, {0x02CD, 0x02CD}, {0x02D0, 0x02D0},
|
||||
{0x02D8, 0x02DB}, {0x02DD, 0x02DD}, {0x02DF, 0x02DF},
|
||||
{0x0300, 0x036F}, {0x0391, 0x03A1}, {0x03A3, 0x03A9},
|
||||
{0x03B1, 0x03C1}, {0x03C3, 0x03C9}, {0x0401, 0x0401},
|
||||
{0x0410, 0x044F}, {0x0451, 0x0451}, {0x2010, 0x2010},
|
||||
{0x2013, 0x2016}, {0x2018, 0x2019}, {0x201C, 0x201D},
|
||||
{0x2020, 0x2022}, {0x2024, 0x2027}, {0x2030, 0x2030},
|
||||
{0x2032, 0x2033}, {0x2035, 0x2035}, {0x203B, 0x203B},
|
||||
{0x203E, 0x203E}, {0x2074, 0x2074}, {0x207F, 0x207F},
|
||||
{0x2081, 0x2084}, {0x20AC, 0x20AC}, {0x2103, 0x2103},
|
||||
{0x2105, 0x2105}, {0x2109, 0x2109}, {0x2113, 0x2113},
|
||||
{0x2116, 0x2116}, {0x2121, 0x2122}, {0x2126, 0x2126},
|
||||
{0x212B, 0x212B}, {0x2153, 0x2154}, {0x215B, 0x215E},
|
||||
{0x2160, 0x216B}, {0x2170, 0x2179}, {0x2189, 0x2189},
|
||||
{0x2190, 0x2199}, {0x21B8, 0x21B9}, {0x21D2, 0x21D2},
|
||||
{0x21D4, 0x21D4}, {0x21E7, 0x21E7}, {0x2200, 0x2200},
|
||||
{0x2202, 0x2203}, {0x2207, 0x2208}, {0x220B, 0x220B},
|
||||
{0x220F, 0x220F}, {0x2211, 0x2211}, {0x2215, 0x2215},
|
||||
{0x221A, 0x221A}, {0x221D, 0x2220}, {0x2223, 0x2223},
|
||||
{0x2225, 0x2225}, {0x2227, 0x222C}, {0x222E, 0x222E},
|
||||
{0x2234, 0x2237}, {0x223C, 0x223D}, {0x2248, 0x2248},
|
||||
{0x224C, 0x224C}, {0x2252, 0x2252}, {0x2260, 0x2261},
|
||||
{0x2264, 0x2267}, {0x226A, 0x226B}, {0x226E, 0x226F},
|
||||
{0x2282, 0x2283}, {0x2286, 0x2287}, {0x2295, 0x2295},
|
||||
{0x2299, 0x2299}, {0x22A5, 0x22A5}, {0x22BF, 0x22BF},
|
||||
{0x2312, 0x2312}, {0x2460, 0x24E9}, {0x24EB, 0x254B},
|
||||
{0x2550, 0x2573}, {0x2580, 0x258F}, {0x2592, 0x2595},
|
||||
{0x25A0, 0x25A1}, {0x25A3, 0x25A9}, {0x25B2, 0x25B3},
|
||||
{0x25B6, 0x25B7}, {0x25BC, 0x25BD}, {0x25C0, 0x25C1},
|
||||
{0x25C6, 0x25C8}, {0x25CB, 0x25CB}, {0x25CE, 0x25D1},
|
||||
{0x25E2, 0x25E5}, {0x25EF, 0x25EF}, {0x2605, 0x2606},
|
||||
{0x2609, 0x2609}, {0x260E, 0x260F}, {0x261C, 0x261C},
|
||||
{0x261E, 0x261E}, {0x2640, 0x2640}, {0x2642, 0x2642},
|
||||
{0x2660, 0x2661}, {0x2663, 0x2665}, {0x2667, 0x266A},
|
||||
{0x266C, 0x266D}, {0x266F, 0x266F}, {0x269E, 0x269F},
|
||||
{0x26BF, 0x26BF}, {0x26C6, 0x26CD}, {0x26CF, 0x26D3},
|
||||
{0x26D5, 0x26E1}, {0x26E3, 0x26E3}, {0x26E8, 0x26E9},
|
||||
{0x26EB, 0x26F1}, {0x26F4, 0x26F4}, {0x26F6, 0x26F9},
|
||||
{0x26FB, 0x26FC}, {0x26FE, 0x26FF}, {0x273D, 0x273D},
|
||||
{0x2776, 0x277F}, {0x2B56, 0x2B59}, {0x3248, 0x324F},
|
||||
{0xE000, 0xF8FF}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A},
|
||||
{0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D},
|
||||
{0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xF0000, 0xFFFFD},
|
||||
{0x100000, 0x10FFFD},
|
||||
}
|
||||
|
||||
var ambiguousLatin = table{
|
||||
{0x00AA, 0x00AA}, {0x00BA, 0x00BA}, {0x00C6, 0x00C6},
|
||||
{0x00D0, 0x00D0}, {0x00D8, 0x00D8}, {0x00DE, 0x00E1},
|
||||
{0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
|
||||
{0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
|
||||
{0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F8, 0x00FA},
|
||||
{0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
|
||||
{0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
|
||||
{0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
|
||||
{0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
|
||||
{0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
|
||||
{0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
|
||||
{0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
|
||||
{0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
|
||||
{0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
|
||||
{0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
|
||||
{0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
|
||||
{0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F},
|
||||
{0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1},
|
||||
{0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F},
|
||||
{0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016},
|
||||
{0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022},
|
||||
{0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033},
|
||||
{0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E},
|
||||
{0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084},
|
||||
{0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105},
|
||||
{0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116},
|
||||
{0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B},
|
||||
{0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B},
|
||||
{0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199},
|
||||
{0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4},
|
||||
{0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203},
|
||||
{0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F},
|
||||
{0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A},
|
||||
{0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225},
|
||||
{0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237},
|
||||
{0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C},
|
||||
{0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
|
||||
{0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283},
|
||||
{0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299},
|
||||
{0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312},
|
||||
{0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573},
|
||||
{0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1},
|
||||
{0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7},
|
||||
{0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8},
|
||||
{0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5},
|
||||
{0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609},
|
||||
{0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E},
|
||||
{0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661},
|
||||
{0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D},
|
||||
{0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF},
|
||||
{0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1},
|
||||
{0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1},
|
||||
{0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC},
|
||||
{0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F},
|
||||
{0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF},
|
||||
{0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A}, {0x1F110, 0x1F12D},
|
||||
{0x1F130, 0x1F169}, {0x1F170, 0x1F18D}, {0x1F18F, 0x1F190},
|
||||
{0x1F19B, 0x1F1AC}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
|
||||
{0x0166, 0x0167}, {0x016B, 0x016B},
|
||||
}
|
||||
|
||||
var narrow = table{
|
||||
{0x0020, 0x007E}, {0x00A2, 0x00A3}, {0x00A5, 0x00A6},
|
||||
{0x00AC, 0x00AC}, {0x00AF, 0x00AF}, {0x27E6, 0x27ED},
|
||||
@@ -590,3 +596,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
@@ -21,7 +21,7 @@ func Encode(authConfig registry.AuthConfig) (string, error) {
|
||||
//
|
||||
// FIXME(thaJeztah): find exactly what code-paths are impacted by this.
|
||||
// if authConfig == (AuthConfig{}) { return "", nil }
|
||||
buf, err := json.Marshal(authConfig)
|
||||
buf, err := json.Marshal(authConfig) // #nosec G117 -- intentionally marshaling auth.
|
||||
if err != nil {
|
||||
return "", errInvalidParameter{err}
|
||||
}
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package build
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// DiskUsage represents system data usage for build cache resources.
|
||||
//
|
||||
// swagger:model DiskUsage
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package common
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ErrorResponse Represents an error.
|
||||
// Example: {"message":"Something went wrong."}
|
||||
//
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package common
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// IDResponse Response to an API call that returns just an Id
|
||||
//
|
||||
// swagger:model IDResponse
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ChangeType Kind of change
|
||||
//
|
||||
// Can be one of:
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// CreateResponse ContainerCreateResponse
|
||||
//
|
||||
// # OK response to ContainerCreate operation
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// DiskUsage represents system data usage information for container resources.
|
||||
//
|
||||
// swagger:model DiskUsage
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// FilesystemChange Change in the container's filesystem.
|
||||
//
|
||||
// swagger:model FilesystemChange
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// TopResponse ContainerTopResponse
|
||||
//
|
||||
// Container "top" response.
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// UpdateResponse ContainerUpdateResponse
|
||||
//
|
||||
// Response for a successful container-update.
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// WaitExitError container waiting error, if any
|
||||
//
|
||||
// swagger:model WaitExitError
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package container
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// WaitResponse ContainerWaitResponse
|
||||
//
|
||||
// # OK response to ContainerWait operation
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package image
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// DeleteResponse delete response
|
||||
//
|
||||
// swagger:model DeleteResponse
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package image
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// DiskUsage represents system data usage for image resources.
|
||||
//
|
||||
// swagger:model DiskUsage
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package image
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// HistoryResponseItem HistoryResponseItem
|
||||
//
|
||||
// individual image layer information in response to ImageHistory operation
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package network
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ConfigReference The config-only network source to provide the configuration for
|
||||
// this network.
|
||||
//
|
||||
|
||||
-3
@@ -2,9 +2,6 @@
|
||||
|
||||
package network
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ConnectRequest NetworkConnectRequest represents the data to be used to connect a container to a network.
|
||||
//
|
||||
// swagger:model ConnectRequest
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user