mirror of
https://github.com/docker/cli.git
synced 2026-11-07 13:32:59 -04:00
Compare commits
157
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c0f1b261a | ||
|
|
e863f84b15 | ||
|
|
e969249cf5 | ||
|
|
bad50f6bbe | ||
|
|
89e99a7ee4 | ||
|
|
e63c04ae28 | ||
|
|
c3a866740e | ||
|
|
dfe5bf3073 | ||
|
|
85af295d30 | ||
|
|
de692998a4 | ||
|
|
873db34f4b | ||
|
|
66da404208 | ||
|
|
a0f7df60ba | ||
|
|
8fa64e724a | ||
|
|
e2aefc6e1a | ||
|
|
26a4786788 | ||
|
|
8f1203743d | ||
|
|
32a838bf17 | ||
|
|
cbb0dabae0 | ||
|
|
d216e46795 | ||
|
|
1c6c7ffcc8 | ||
|
|
11626f30d0 | ||
|
|
7817ee1c5c | ||
|
|
6b88c47537 | ||
|
|
2b3688febb | ||
|
|
28f460abf3 | ||
|
|
1f6f73a7be | ||
|
|
f9ad558f5d | ||
|
|
dd422f424c | ||
|
|
965590b378 | ||
|
|
191aab9fe8 | ||
|
|
682373a887 | ||
|
|
949b412ae3 | ||
|
|
57393ea18e | ||
|
|
84e55d46d7 | ||
|
|
18958e4c02 | ||
|
|
ab54f1f097 | ||
|
|
d278c1ec68 | ||
|
|
fb2fe0eac7 | ||
|
|
920919bd15 | ||
|
|
beafae2696 | ||
|
|
b9456fd725 | ||
|
|
7a75f75b96 | ||
|
|
9762c1688f | ||
|
|
b2f5d905c7 | ||
|
|
77ebef3df8 | ||
|
|
6d15cedd97 | ||
|
|
82b833e57a | ||
|
|
997674f459 | ||
|
|
81e4a109a3 | ||
|
|
e7ece565b2 | ||
|
|
4214721b3b | ||
|
|
281291299e | ||
|
|
796f9633b4 | ||
|
|
29b22240bc | ||
|
|
19a353f2ea | ||
|
|
3bc38f9c19 | ||
|
|
0ccbdf86a0 | ||
|
|
fbafb4965c | ||
|
|
276829e744 | ||
|
|
cc8c87844d | ||
|
|
e512522843 | ||
|
|
8c8a77521a | ||
|
|
bf8066ca18 | ||
|
|
d35139c89f | ||
|
|
b32aa42608 | ||
|
|
8ff6f243a2 | ||
|
|
b9312b9d88 | ||
|
|
ccccec73b0 | ||
|
|
dd8edb6863 | ||
|
|
66786b775a | ||
|
|
47e9dbf7b0 | ||
|
|
abf2f34c74 | ||
|
|
dd50814004 | ||
|
|
6d274590f2 | ||
|
|
6998c79cfd | ||
|
|
c7bdacad70 | ||
|
|
1faf23e25f | ||
|
|
b8dd18fe2c | ||
|
|
a3539bd5c9 | ||
|
|
adfb114576 | ||
|
|
4e09976ddd | ||
|
|
69c9ccd358 | ||
|
|
1600418944 | ||
|
|
1fab379bcc | ||
|
|
df9a0f430f | ||
|
|
fa65e9a3a0 | ||
|
|
dc66c2a8d0 | ||
|
|
ae317ab44a | ||
|
|
3f2ad8bf05 | ||
|
|
6f8369014a | ||
|
|
e08aa72569 | ||
|
|
f522fffe52 | ||
|
|
5e1e84ee5d | ||
|
|
00bf2d970d | ||
|
|
4a93a41589 | ||
|
|
e300e529d5 | ||
|
|
72f6e50c62 | ||
|
|
52a2671034 | ||
|
|
1d02640576 | ||
|
|
4d74583397 | ||
|
|
de4ddc4fae | ||
|
|
ccac941dd9 | ||
|
|
e6beb40c03 | ||
|
|
dfc520dcc2 | ||
|
|
a3a2bb6118 | ||
|
|
c11295afc4 | ||
|
|
010e125d8b | ||
|
|
5cdf229230 | ||
|
|
66f6797582 | ||
|
|
cb8870a537 | ||
|
|
529f66d2f9 | ||
|
|
4d6ff7d646 | ||
|
|
b997c5b7aa | ||
|
|
1822903c1d | ||
|
|
37013cb262 | ||
|
|
61ee187552 | ||
|
|
433b13a249 | ||
|
|
5c8bfb2844 | ||
|
|
528a2ada2e | ||
|
|
de8f4032ad | ||
|
|
e4078058de | ||
|
|
8da8b00555 | ||
|
|
067d069f0c | ||
|
|
02fbbf3eb0 | ||
|
|
890976a13b | ||
|
|
23c66ccd61 | ||
|
|
3e686a8b59 | ||
|
|
f415da838b | ||
|
|
45c2317672 | ||
|
|
8308969eda | ||
|
|
cf809998ab | ||
|
|
730c880b35 | ||
|
|
e63e4fe498 | ||
|
|
e7df01fe6e | ||
|
|
2e51107855 | ||
|
|
949bf401dd | ||
|
|
9db9802127 | ||
|
|
63445d6b70 | ||
|
|
fdb4ee3e75 | ||
|
|
4ae247756e | ||
|
|
e7c0f5c53c | ||
|
|
87fb2ebe7e | ||
|
|
e8c098c8bf | ||
|
|
374636ff15 | ||
|
|
862e7e6ad4 | ||
|
|
3d2650ce76 | ||
|
|
e11705385d | ||
|
|
fd58705f3a | ||
|
|
5e6d8e3ab6 | ||
|
|
8503a98b68 | ||
|
|
488292ca1f | ||
|
|
75d1d1a9e6 | ||
|
|
cb192aeb41 | ||
|
|
6cb63efe6f | ||
|
|
e8bb7f7c81 | ||
|
|
3ee4c06af6 |
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
// https://docs.renovatebot.com/modules/manager/github-actions/
|
||||
// https://docs.renovatebot.com/presets-helpers/
|
||||
"helpers:pinGitHubActionDigestsToSemver",
|
||||
":gitSignOff"
|
||||
],
|
||||
"gomod": {
|
||||
"managerFilePatterns": ["/(^|/)vendor\\.mod$/"]
|
||||
},
|
||||
"postUpdateOptions": ["gomodTidy"],
|
||||
"minimumReleaseAge": "3 days",
|
||||
"internalChecksFilter": "strict",
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["gomod"],
|
||||
"matchFileNames": ["vendor.mod"],
|
||||
"semanticCommits": "disabled",
|
||||
"commitMessagePrefix": "vendor:"
|
||||
},
|
||||
{
|
||||
"matchManagers": ["gomod"],
|
||||
"matchFileNames": ["cmd/docker-trust/go.mod"],
|
||||
// The trust module does not vendor dependencies; vendor/ belongs to the root module.
|
||||
"postUpdateOptions": ["gomodSkipVendor"],
|
||||
"semanticCommits": "disabled",
|
||||
"commitMessagePrefix": "cmd/docker-trust:"
|
||||
},
|
||||
{
|
||||
"matchManagers": ["gomod"],
|
||||
"matchFileNames": ["vendor.mod"],
|
||||
"groupName": "golang.org/x packages",
|
||||
"groupSlug": "golang-x",
|
||||
"matchDepNames": ["/^golang\\.org\\/x\\//"]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"minimumReleaseAge": "7 days",
|
||||
"addLabels": ["area/ci", "status/2-code-review"]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"groupName": "docker GitHub Actions",
|
||||
"groupSlug": "docker-actions",
|
||||
"matchDepNames": ["/^docker\\//"]
|
||||
},
|
||||
{
|
||||
// Keep all the google.golang.org/genproto modules at the same version.
|
||||
"matchManagers": ["gomod"],
|
||||
"matchFileNames": ["vendor.mod"],
|
||||
"groupName": "google.golang.org/genproto packages",
|
||||
"groupSlug": "google-genproto",
|
||||
"matchDepNames": ["/^google\\.golang\\.org\\/genproto(\\/|$)/"],
|
||||
"schedule": ["every month"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -65,10 +65,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
||||
uses: docker/bake-action@59cb1203cd42bcefd40a1b6d73b589e48c86040e # v7.5.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
set: |
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: /tmp/out/*
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
|
||||
bin-image:
|
||||
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/cli' }}
|
||||
uses: docker/github-builder/.github/workflows/bake.yml@58cb9f5b71b1836d6f690c1e95effdeb9b98cb8a # v1.17.0
|
||||
uses: docker/github-builder/.github/workflows/bake.yml@cbc9d42f8c89a55623392aadd03600965b80e963 # v1.18.1
|
||||
permissions:
|
||||
contents: read # same as global permission
|
||||
id-token: write # for signing attestation(s) and authenticating to Docker Hub with GitHub OIDC Token
|
||||
@@ -156,10 +156,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
||||
uses: docker/bake-action@59cb1203cd42bcefd40a1b6d73b589e48c86040e # v7.5.0
|
||||
with:
|
||||
targets: plugins-cross
|
||||
set: |
|
||||
|
||||
@@ -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.27.3"
|
||||
cache: false
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/autobuild@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
category: "/language:go"
|
||||
|
||||
@@ -26,6 +26,9 @@ on:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # needed for CodeCov; https://github.com/codecov/codecov-action/blob/v7.1.1/README.md#using-oidc
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -37,6 +40,7 @@ jobs:
|
||||
- alpine
|
||||
- debian
|
||||
engine-version:
|
||||
- master # moby/moby-bin:master
|
||||
- rc # latest rc
|
||||
- 29 # latest
|
||||
- 28 # latest - 1
|
||||
@@ -65,7 +69,7 @@ jobs:
|
||||
docker info
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
-
|
||||
name: Run ${{ matrix.target }}
|
||||
run: |
|
||||
@@ -79,4 +83,4 @@ jobs:
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
with:
|
||||
files: ./build/coverage/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
use_oidc: true
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload context
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: pr-review-context
|
||||
path: context/
|
||||
|
||||
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
review:
|
||||
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@3654ec391b070e247de2e2e3c1ff2519b6378b3a # v2.0.9
|
||||
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@aaa657c53cc69ed15683cd8a985d86663a0e1594 # v2.1.0
|
||||
permissions:
|
||||
contents: read # Read repository files and PR diffs
|
||||
pull-requests: write # Post review comments
|
||||
|
||||
@@ -26,14 +26,17 @@ on:
|
||||
|
||||
jobs:
|
||||
ctn:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # needed for CodeCov; https://github.com/codecov/codecov-action/blob/v7.1.1/README.md#using-oidc
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
||||
uses: docker/bake-action@59cb1203cd42bcefd40a1b6d73b589e48c86040e # v7.5.0
|
||||
with:
|
||||
targets: test-coverage
|
||||
-
|
||||
@@ -41,9 +44,12 @@ jobs:
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
with:
|
||||
files: ./build/coverage/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
use_oidc: true
|
||||
|
||||
host:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # needed for CodeCov; https://github.com/codecov/codecov-action/blob/v7.1.1/README.md#using-oidc
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
GOPATH: ${{ github.workspace }}
|
||||
@@ -53,9 +59,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
|
||||
- macos-15-intel # macOS 15 on Intel
|
||||
- macos-15 # macOS 15 on arm64 (Apple Silicon M1)
|
||||
- macos-26 # macOS 26 on arm64 (Apple Silicon M1)
|
||||
- macos-26-intel # macOS 26 on Intel
|
||||
# - windows-2022 # FIXME: some tests are failing on the Windows runner, as well as on Appveyor since June 24, 2018: https://ci.appveyor.com/project/docker/cli/history
|
||||
steps:
|
||||
-
|
||||
@@ -68,7 +74,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: "1.26.8"
|
||||
go-version: "1.27.3"
|
||||
cache: false
|
||||
-
|
||||
name: Test
|
||||
@@ -86,4 +92,4 @@ jobs:
|
||||
with:
|
||||
files: /tmp/coverage.txt
|
||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
use_oidc: true
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Run
|
||||
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
||||
uses: docker/bake-action@59cb1203cd42bcefd40a1b6d73b589e48c86040e # v7.5.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -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.27.3"
|
||||
cache: false
|
||||
-
|
||||
name: Run gocompat check
|
||||
|
||||
+1
-11
@@ -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.27.3"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
@@ -201,11 +201,6 @@ linters:
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
# TODO: make sure all packages have a description. Currently, there's 67 packages without.
|
||||
- text: "package-comments: should have a package comment"
|
||||
linters:
|
||||
- revive
|
||||
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
@@ -227,11 +222,6 @@ linters:
|
||||
linters:
|
||||
- staticcheck
|
||||
|
||||
# TODO(thaJeztah): remove once https://github.com/leighmcculloch/gocheckcompilerdirectives/issues/7 is fixed.
|
||||
- text: "compiler directive unrecognized: //go:fix"
|
||||
linters:
|
||||
- gocheckcompilerdirectives
|
||||
|
||||
# Log a warning if an exclusion rule is unused.
|
||||
# Default: false
|
||||
warn-unused: true
|
||||
|
||||
+9
-7
@@ -5,14 +5,16 @@ ARG BASE_VARIANT=alpine
|
||||
# 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
|
||||
# that's also available as alpine image variant for the Golang version used.
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
ARG ALPINE_VERSION=3.24
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
# BASE_DEBIAN_DISTRO sets the version of the debian base image to use,
|
||||
ARG BASE_DEBIAN_DISTRO=trixie
|
||||
|
||||
ARG GO_VERSION=1.27.3
|
||||
|
||||
# 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.
|
||||
ARG XX_VERSION=1.9.0
|
||||
ARG XX_VERSION=1.10.0
|
||||
|
||||
# GOVERSIONINFO_VERSION is the version of GoVersionInfo to install.
|
||||
# It must be a valid tag from https://github.com/josephspurrier/goversioninfo
|
||||
@@ -25,12 +27,12 @@ ARG GOTESTSUM_VERSION=v1.13.0
|
||||
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
|
||||
# It must be a tag in the docker.io/docker/buildx-bin image repository
|
||||
# on Docker Hub.
|
||||
ARG BUILDX_VERSION=0.34.1
|
||||
ARG BUILDX_VERSION=0.38.1
|
||||
|
||||
# COMPOSE_VERSION is the version of compose to install in the dev container.
|
||||
# It must be a tag in the docker.io/docker/compose-bin image repository
|
||||
# on Docker Hub.
|
||||
ARG COMPOSE_VERSION=v5.1.3
|
||||
ARG COMPOSE_VERSION=v5.7.0
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
@@ -53,7 +55,7 @@ WORKDIR /go/src/github.com/docker/cli
|
||||
|
||||
FROM build-base-debian AS build-debian
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-apt-get install --no-install-recommends -y libc6-dev libgcc-12-dev pkgconf
|
||||
RUN xx-apt-get install --no-install-recommends -y libc6-dev libgcc-14-dev pkgconf
|
||||
|
||||
FROM build-base-${BASE_VARIANT} AS goversioninfo
|
||||
ARG GOVERSIONINFO_VERSION
|
||||
|
||||
@@ -30,7 +30,11 @@ package hooks
|
||||
type ResponseType int
|
||||
|
||||
const (
|
||||
// NextSteps renders suggestions under a shared "What's next:" header.
|
||||
NextSteps ResponseType = 0
|
||||
// GenericMessage renders before NextSteps, without a header or indentation.
|
||||
// CLI versions that do not recognize this type ignore the response.
|
||||
GenericMessage ResponseType = 1
|
||||
)
|
||||
|
||||
// Request is the type representing the information
|
||||
|
||||
@@ -7,17 +7,42 @@ const (
|
||||
indent = " "
|
||||
)
|
||||
|
||||
// PrintNextSteps renders list of [NextSteps] messages and writes them
|
||||
// to out. It is a no-op if messages is empty.
|
||||
func PrintNextSteps(out io.Writer, messages []string) {
|
||||
if len(messages) == 0 {
|
||||
return
|
||||
// PrintMessages groups messages by type and writes them to out. Generic messages
|
||||
// appear first, without a header or indentation, followed by next steps under a
|
||||
// shared "What's next:" header. Order within each type is preserved.
|
||||
// Messages with no lines or an unknown type produce no output.
|
||||
func PrintMessages(out io.Writer, messages []Response, evaluate func(Response) []string) {
|
||||
grouped := make(map[ResponseType][]string)
|
||||
for _, message := range messages {
|
||||
lines := evaluate(message)
|
||||
grouped[message.Type] = append(grouped[message.Type], lines...)
|
||||
}
|
||||
for _, responseType := range []ResponseType{GenericMessage, NextSteps} {
|
||||
lines := grouped[responseType]
|
||||
if len(lines) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _ = io.WriteString(out, whatsNext)
|
||||
for _, msg := range messages {
|
||||
_, _ = io.WriteString(out, indent)
|
||||
_, _ = io.WriteString(out, msg)
|
||||
_, _ = io.WriteString(out, "\n")
|
||||
header, prefix := "\n", ""
|
||||
if responseType == NextSteps {
|
||||
header, prefix = whatsNext, indent
|
||||
}
|
||||
|
||||
_, _ = io.WriteString(out, header)
|
||||
for _, line := range lines {
|
||||
_, _ = io.WriteString(out, prefix)
|
||||
_, _ = io.WriteString(out, line)
|
||||
_, _ = io.WriteString(out, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintNextSteps renders list of [NextSteps] messages and writes them
|
||||
// to out. It is a no-op if messages is empty.
|
||||
//
|
||||
// Deprecated: use [PrintMessages] instead.
|
||||
func PrintNextSteps(out io.Writer, messages []string) {
|
||||
PrintMessages(out, []Response{{Type: NextSteps}}, func(Response) []string {
|
||||
return messages
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,85 @@ import (
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestPrintHookMessages(t *testing.T) {
|
||||
func TestPrintMessages(t *testing.T) {
|
||||
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
|
||||
|
||||
for _, tc := range []struct {
|
||||
doc string
|
||||
messages []hooks.Response
|
||||
expected string
|
||||
}{
|
||||
{doc: "no messages"},
|
||||
{
|
||||
doc: "no lines",
|
||||
messages: []hooks.Response{
|
||||
{Type: hooks.GenericMessage},
|
||||
{Type: hooks.NextSteps},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "generic message",
|
||||
messages: []hooks.Response{
|
||||
{Type: hooks.GenericMessage, Template: "Session active"},
|
||||
},
|
||||
expected: "\nSession active\n",
|
||||
},
|
||||
{
|
||||
doc: "preserve message formatting",
|
||||
messages: []hooks.Response{
|
||||
{Type: hooks.GenericMessage, Template: "Session active\n\n Details"},
|
||||
},
|
||||
expected: "\nSession active\n\n Details\n",
|
||||
},
|
||||
{
|
||||
doc: "next steps",
|
||||
messages: []hooks.Response{
|
||||
{Type: hooks.NextSteps, Template: "Try another command"},
|
||||
},
|
||||
expected: header + " Try another command\n",
|
||||
},
|
||||
{
|
||||
doc: "group interleaved types and preserve order within each type",
|
||||
messages: []hooks.Response{
|
||||
{Type: hooks.NextSteps, Template: "First suggestion"},
|
||||
{Type: hooks.GenericMessage, Template: "Session active"},
|
||||
{Type: hooks.NextSteps, Template: "Second suggestion\nThird suggestion"},
|
||||
{Type: hooks.GenericMessage, Template: "Another status"},
|
||||
},
|
||||
expected: "\nSession active\nAnother status\n" + header + " First suggestion\n Second suggestion\n Third suggestion\n",
|
||||
},
|
||||
{
|
||||
doc: "unknown type",
|
||||
messages: []hooks.Response{
|
||||
{Type: hooks.ResponseType(99), Template: "Ignored"},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "unknown type does not affect known messages",
|
||||
messages: []hooks.Response{
|
||||
{Type: hooks.ResponseType(99), Template: "Ignored"},
|
||||
{Type: hooks.GenericMessage, Template: "Session active"},
|
||||
{Type: hooks.NextSteps, Template: "Try another command"},
|
||||
},
|
||||
expected: "\nSession active\n" + header + " Try another command\n",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
var w strings.Builder
|
||||
hooks.PrintMessages(&w, tc.messages, func(message hooks.Response) []string {
|
||||
if message.Template == "" {
|
||||
return nil
|
||||
}
|
||||
lines, err := hooks.ParseTemplate(message.Template, nil)
|
||||
assert.NilError(t, err)
|
||||
return lines
|
||||
})
|
||||
assert.Equal(t, w.String(), tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintNextSteps(t *testing.T) {
|
||||
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
|
||||
|
||||
tests := []struct {
|
||||
@@ -34,6 +112,11 @@ func TestPrintHookMessages(t *testing.T) {
|
||||
" Foo\n" +
|
||||
" bar\n",
|
||||
},
|
||||
{
|
||||
doc: "preserve empty and indented lines",
|
||||
messages: []string{"", " Details", ""},
|
||||
expectedOutput: header + " \n Details\n \n",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@@ -43,7 +44,16 @@ func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
|
||||
if n := strings.Count(out, "\n"); n > maxMessages {
|
||||
return nil, fmt.Errorf("hook template contains too many messages (%d): maximum is %d", n, maxMessages)
|
||||
}
|
||||
return strings.SplitN(out, "\n", maxMessages), nil
|
||||
lines := strings.SplitN(out, "\n", maxMessages)
|
||||
|
||||
// Skip messages containing only whitespace, but preserve empty lines
|
||||
// in messages that have content (e.g., for paragraph spacing).
|
||||
if !slices.ContainsFunc(lines, func(line string) bool {
|
||||
return strings.TrimSpace(line) != ""
|
||||
}) {
|
||||
return nil, nil
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
||||
|
||||
@@ -33,7 +33,17 @@ func TestParseTemplate(t *testing.T) {
|
||||
{
|
||||
doc: "empty template",
|
||||
template: "",
|
||||
expectedOutput: []string{""},
|
||||
expectedOutput: nil,
|
||||
},
|
||||
{
|
||||
doc: "only empty lines",
|
||||
template: "\n\n",
|
||||
expectedOutput: nil,
|
||||
},
|
||||
{
|
||||
doc: "whitespace-only lines",
|
||||
template: " \n\t\n ",
|
||||
expectedOutput: nil,
|
||||
},
|
||||
{
|
||||
doc: "plain message",
|
||||
|
||||
@@ -49,11 +49,18 @@ func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, sub
|
||||
}
|
||||
|
||||
func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
|
||||
nextSteps := invokeAndCollectHooks(ctx, cfg, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
|
||||
hooks.PrintNextSteps(subCommand.ErrOrStderr(), nextSteps)
|
||||
messages := invokeAndCollectHooks(ctx, cfg, rootCmd, invokedCommand, flags, cmdErrorMessage)
|
||||
hooks.PrintMessages(subCommand.ErrOrStderr(), messages, func(message hooks.Response) []string {
|
||||
lines, err := hooks.ParseTemplate(message.Template, subCommand)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Debug("Failed to evaluate hook template")
|
||||
return nil
|
||||
}
|
||||
return lines
|
||||
})
|
||||
}
|
||||
|
||||
func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
|
||||
func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []hooks.Response {
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -64,17 +71,17 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
|
||||
}
|
||||
|
||||
pluginDirs := getPluginDirs(cfg)
|
||||
nextSteps := make([]string, 0, len(pluginsCfg))
|
||||
var collected []hooks.Response
|
||||
|
||||
tryInvokeHook := func(pluginName string, pluginCfg map[string]string) (messages []string, ok bool, err error) {
|
||||
tryInvokeHook := func(pluginName string, pluginCfg map[string]string) (hooks.Response, error) {
|
||||
match, matched := pluginMatch(pluginCfg, subCmdStr, cmdErrorMessage)
|
||||
if !matched {
|
||||
return nil, false, nil
|
||||
return hooks.Response{}, nil
|
||||
}
|
||||
|
||||
p, err := getPlugin(pluginName, pluginDirs, rootCmd)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
return hooks.Response{}, err
|
||||
}
|
||||
|
||||
resp, err := p.RunHook(ctx, hooks.Request{
|
||||
@@ -83,29 +90,23 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
|
||||
CommandError: cmdErrorMessage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
return hooks.Response{}, err
|
||||
}
|
||||
|
||||
var message hooks.Response
|
||||
if err := json.Unmarshal(resp, &message); err != nil {
|
||||
return nil, false, fmt.Errorf("failed to unmarshal hook response (%q): %w", string(resp), err)
|
||||
return hooks.Response{}, fmt.Errorf("failed to unmarshal hook response (%q): %w", string(resp), err)
|
||||
}
|
||||
|
||||
// currently the only hook type
|
||||
if message.Type != hooks.NextSteps {
|
||||
return nil, false, errors.New("unexpected hook response type: " + strconv.Itoa(int(message.Type)))
|
||||
if message.Type != hooks.NextSteps && message.Type != hooks.GenericMessage {
|
||||
return hooks.Response{}, errors.New("unexpected hook response type: " + strconv.Itoa(int(message.Type)))
|
||||
}
|
||||
|
||||
messages, err = hooks.ParseTemplate(message.Template, subCmd)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return messages, true, nil
|
||||
return message, nil
|
||||
}
|
||||
|
||||
for pluginName, pluginCfg := range pluginsCfg {
|
||||
messages, ok, err := tryInvokeHook(pluginName, pluginCfg)
|
||||
message, err := tryInvokeHook(pluginName, pluginCfg)
|
||||
if err != nil {
|
||||
// skip misbehaving plugins, but don't halt execution
|
||||
logrus.WithFields(logrus.Fields{
|
||||
@@ -114,38 +115,13 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
|
||||
}).Debug("Plugin hook invocation failed")
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
if message.Template == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var appended bool
|
||||
nextSteps, appended = appendNextSteps(nextSteps, messages)
|
||||
if !appended {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"plugin": pluginName,
|
||||
}).Debug("Plugin responded with an empty hook message; ignoring")
|
||||
}
|
||||
collected = append(collected, message)
|
||||
}
|
||||
return nextSteps
|
||||
}
|
||||
|
||||
// appendNextSteps appends the processed hook output to the nextSteps slice.
|
||||
// If the processed hook output is empty, it is not appended.
|
||||
// Empty lines are not stripped if there's at least one non-empty line.
|
||||
func appendNextSteps(nextSteps []string, processed []string) ([]string, bool) {
|
||||
empty := true
|
||||
for _, l := range processed {
|
||||
if strings.TrimSpace(l) != "" {
|
||||
empty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if empty {
|
||||
return nextSteps, false
|
||||
}
|
||||
|
||||
return append(nextSteps, processed...), true
|
||||
return collected
|
||||
}
|
||||
|
||||
// pluginMatch takes a plugin configuration and a string representing the
|
||||
|
||||
@@ -2,6 +2,11 @@ package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
@@ -258,38 +263,6 @@ func TestMatchHookConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendNextSteps(t *testing.T) {
|
||||
testCases := []struct {
|
||||
processed []string
|
||||
expectedOut []string
|
||||
}{
|
||||
{
|
||||
processed: []string{},
|
||||
expectedOut: []string{},
|
||||
},
|
||||
{
|
||||
processed: []string{"", ""},
|
||||
expectedOut: []string{},
|
||||
},
|
||||
{
|
||||
processed: []string{"Some hint", "", "Some other hint"},
|
||||
expectedOut: []string{"Some hint", "", "Some other hint"},
|
||||
},
|
||||
{
|
||||
processed: []string{"Hint 1", "Hint 2"},
|
||||
expectedOut: []string{"Hint 1", "Hint 2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got, appended := appendNextSteps([]string{}, tc.processed)
|
||||
assert.Check(t, is.DeepEqual(got, tc.expectedOut))
|
||||
assert.Check(t, is.Equal(appended, len(got) > 0))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPluginHooksPassesErrorMessage(t *testing.T) {
|
||||
cfg := configfile.New("")
|
||||
cfg.Plugins = map[string]map[string]string{
|
||||
@@ -336,7 +309,7 @@ func TestInvokeAndCollectHooksErrorHooksSkippedOnSuccess(t *testing.T) {
|
||||
// On success, error-hooks should not match, so the plugin
|
||||
// binary is never looked up and no results are returned.
|
||||
result := invokeAndCollectHooks(
|
||||
context.Background(), cfg, root, sub,
|
||||
context.Background(), cfg, root,
|
||||
"build", map[string]string{}, "",
|
||||
)
|
||||
assert.Check(t, is.Len(result, 0))
|
||||
@@ -349,7 +322,7 @@ func TestInvokeAndCollectHooksNoPlugins(t *testing.T) {
|
||||
root.AddCommand(sub)
|
||||
|
||||
result := invokeAndCollectHooks(
|
||||
context.Background(), cfg, root, sub,
|
||||
context.Background(), cfg, root,
|
||||
"build", map[string]string{}, "some error",
|
||||
)
|
||||
assert.Check(t, is.Len(result, 0))
|
||||
@@ -368,8 +341,108 @@ func TestInvokeAndCollectHooksCancelledContext(t *testing.T) {
|
||||
cancel() // cancel immediately
|
||||
|
||||
result := invokeAndCollectHooks(
|
||||
ctx, cfg, root, sub,
|
||||
ctx, cfg, root,
|
||||
"build", map[string]string{}, "exit status 1",
|
||||
)
|
||||
assert.Check(t, is.Nil(result))
|
||||
}
|
||||
|
||||
func TestRunHooksMessages(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("test plugins require a POSIX shell")
|
||||
}
|
||||
|
||||
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
|
||||
for _, tc := range []struct {
|
||||
doc string
|
||||
responses map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
doc: "generic message preserves formatting and expands templates",
|
||||
responses: map[string]string{
|
||||
"status": `{"Type":1,"Template":"Status for {{command}}\n\n Details"}`,
|
||||
},
|
||||
expected: "\nStatus for build\n\n Details\n",
|
||||
},
|
||||
{
|
||||
doc: "omitted type defaults to next steps",
|
||||
responses: map[string]string{
|
||||
"suggestion": `{"Template":"Try another command"}`,
|
||||
},
|
||||
expected: header + " Try another command\n",
|
||||
},
|
||||
{
|
||||
doc: "generic messages precede shared next steps header",
|
||||
responses: map[string]string{
|
||||
"statusone": `{"Type":1,"Template":"Session active"}`,
|
||||
"statustwo": `{"Type":1,"Template":"Session active"}`,
|
||||
"suggestionone": `{"Type":0,"Template":"Try another command"}`,
|
||||
"suggestiontwo": `{"Type":0,"Template":"Try another command"}`,
|
||||
},
|
||||
expected: "\nSession active\nSession active\n" + header + " Try another command\n Try another command\n",
|
||||
},
|
||||
{
|
||||
doc: "empty responses produce no output",
|
||||
responses: map[string]string{
|
||||
"status": `{"Type":1,"Template":" \n\t"}`,
|
||||
"suggestion": `{"Type":0,"Template":""}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "empty generic message does not affect next steps",
|
||||
responses: map[string]string{
|
||||
"status": `{"Type":1,"Template":""}`,
|
||||
"suggestion": `{"Type":0,"Template":"Try another command"}`,
|
||||
},
|
||||
expected: header + " Try another command\n",
|
||||
},
|
||||
{
|
||||
doc: "invalid responses do not affect other plugins",
|
||||
responses: map[string]string{
|
||||
"unknown": `{"Type":99,"Template":"Ignored"}`,
|
||||
"invalid": `{"Type":1,"Template":"{{"}`,
|
||||
"status": `{"Type":1,"Template":"Session active"}`,
|
||||
"suggestion": `{"Type":0,"Template":"Try another command"}`,
|
||||
},
|
||||
expected: "\nSession active\n" + header + " Try another command\n",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
pluginDir := t.TempDir()
|
||||
cfg := configfile.New("")
|
||||
cfg.CLIPluginsExtraDirs = []string{pluginDir}
|
||||
cfg.Plugins = make(map[string]map[string]string)
|
||||
for name, response := range tc.responses {
|
||||
plugin := fmt.Sprintf(`#!/bin/sh
|
||||
case "$1" in
|
||||
docker-cli-plugin-metadata)
|
||||
printf '%%s\n' '{"SchemaVersion":"0.1.0","Vendor":"test"}'
|
||||
;;
|
||||
*)
|
||||
test "$2" = docker-cli-plugin-hooks || exit 1
|
||||
printf 'called\n' >> "$0.calls"
|
||||
printf '%%s\n' '%s'
|
||||
;;
|
||||
esac
|
||||
`, response)
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-"+name), []byte(plugin), 0o755))
|
||||
cfg.Plugins[name] = map[string]string{"hooks": "build"}
|
||||
}
|
||||
|
||||
root := &cobra.Command{Use: "docker"}
|
||||
sub := &cobra.Command{Use: "build"}
|
||||
root.AddCommand(sub)
|
||||
var output strings.Builder
|
||||
sub.SetErr(&output)
|
||||
|
||||
runHooks(context.Background(), cfg, root, sub, "build", nil, "")
|
||||
assert.Equal(t, output.String(), tc.expected)
|
||||
for name := range tc.responses {
|
||||
calls, err := os.ReadFile(filepath.Join(pluginDir, "docker-"+name+".calls"))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(calls), "called\n", "each plugin hook must run exactly once")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
@@ -32,6 +33,11 @@ import (
|
||||
// called.
|
||||
var PersistentPreRunE func(*cobra.Command, []string) error
|
||||
|
||||
// finalizeHookTimeout is the timeout given for running the finalize hook.
|
||||
// It contains the timeout for exporting metrics and a little bit extra in case
|
||||
// shutdown does not respond to the export timeout promptly.
|
||||
const finalizeHookTimeout = command.ExportTimeout + 10*time.Millisecond
|
||||
|
||||
// RunPlugin executes the specified plugin command
|
||||
func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadata.Metadata) error {
|
||||
tcmd := newPluginCommand(dockerCli, plugin, meta)
|
||||
@@ -69,6 +75,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
|
||||
stopInstrumentation(err)
|
||||
return err
|
||||
}
|
||||
cobra.OnFinalize(func() { finalizeHook(dockerCli) })
|
||||
})
|
||||
return retErr
|
||||
}
|
||||
@@ -83,6 +90,59 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
|
||||
return cmd.Execute()
|
||||
}
|
||||
|
||||
// finalizeHook is a hook to be run when the CLI has finished executing the run command.
|
||||
// This happens after any post run methods but also always runs even when an error happens.
|
||||
func finalizeHook(dockerCli *command.DockerCli) {
|
||||
// Use a new context for the export so that the command being cancelled
|
||||
// doesn't affect the metrics, and we get metrics for cancelled commands.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), command.ExportTimeout)
|
||||
defer cancel()
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
wait bool
|
||||
)
|
||||
if tp, ok := dockerCli.TracerProvider().(command.TracerProvider); ok {
|
||||
wg.Go(func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
})
|
||||
wait = true
|
||||
}
|
||||
|
||||
if mp, ok := dockerCli.MeterProvider().(command.MeterProvider); ok {
|
||||
wg.Go(func() {
|
||||
if err := mp.Shutdown(ctx); err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
})
|
||||
wait = true
|
||||
}
|
||||
|
||||
// Wait for the wait group in another goroutine if any were created.
|
||||
if !wait {
|
||||
return
|
||||
}
|
||||
|
||||
// Spawn a goroutine to wait for the wait group.
|
||||
// If the shutdown does not promptly hang up with the
|
||||
// cancellation of the context, we leave this goroutine
|
||||
// orphaned so the process can exit.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(finalizeHookTimeout):
|
||||
// Ensure we still exit even if the shutdown doesn't work properly.
|
||||
// We give 10 extra milliseconds for the cleanup to happen.
|
||||
}
|
||||
}
|
||||
|
||||
// Run is the top-level entry point to the CLI plugin framework. It should
|
||||
// be called from the plugin's "main()" function. It initializes a new
|
||||
// [command.DockerCli] instance with the given options before calling
|
||||
@@ -212,8 +272,8 @@ func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra
|
||||
Use: metadata.MetadataSubcommandName,
|
||||
Hidden: true,
|
||||
// Suppress the global/parent PersistentPreRunE, which
|
||||
// needlessly initializes the client and tries to
|
||||
// connect to the daemon.
|
||||
// needlessly initializes the client and tries to connect to the
|
||||
// daemon.
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
|
||||
+19
-1
@@ -160,7 +160,25 @@ func (tcmd *TopLevelCommand) HandleGlobalFlags() (*cobra.Command, []string, erro
|
||||
return nil, nil, cmd.FlagErrorFunc()(cmd, err)
|
||||
}
|
||||
|
||||
return cmd, flags.Args(), nil
|
||||
args := flags.Args()
|
||||
|
||||
// A leading "--help"/"-h" is consumed above as the root's persistent
|
||||
// help flag and stripped from args, so the target subcommand never sees
|
||||
// it. That's fine for most subcommands, which inherit the same help
|
||||
// flag instance, but "run" and "create" register their own local
|
||||
// "help" flag (to avoid a shorthand clash with "-h"/"--hostname"),
|
||||
// which shadows the persistent one and is left false. Propagate the
|
||||
// value so "docker --help run <image>" shows help like "docker run
|
||||
// --help" does, instead of running the container.
|
||||
if help, _ := flags.GetBool("help"); help {
|
||||
if sub, _, err := cmd.Find(args); err == nil && sub != cmd {
|
||||
if sub.Flags().Lookup("help") != nil {
|
||||
_ = sub.Flags().Set("help", "true")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cmd, args, nil
|
||||
}
|
||||
|
||||
// Initialize finalises global option parsing and initializes the docker client.
|
||||
|
||||
@@ -108,3 +108,45 @@ func TestDecoratedName(t *testing.T) {
|
||||
topLevelCommand.Annotations = map[string]string{metadata.CommandAnnotationPlugin: "true"}
|
||||
assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand*")
|
||||
}
|
||||
|
||||
// TestHandleGlobalFlagsHelpPropagation covers https://github.com/docker/cli/issues/7473:
|
||||
// a leading "--help" is consumed as the root's persistent help flag and
|
||||
// stripped from args, so a subcommand that registers its own local "help"
|
||||
// flag (as "run" and "create" do, to avoid a shorthand clash with "-h") never
|
||||
// sees it and runs instead of showing help.
|
||||
func TestHandleGlobalFlagsHelpPropagation(t *testing.T) {
|
||||
newRootCmd := func() (root, sub *cobra.Command) {
|
||||
root = &cobra.Command{Use: "docker"}
|
||||
root.PersistentFlags().BoolP("help", "h", false, "Print usage")
|
||||
|
||||
sub = &cobra.Command{Use: "run", RunE: func(cmd *cobra.Command, args []string) error { return nil }}
|
||||
sub.Flags().Bool("help", false, "Print usage")
|
||||
root.AddCommand(sub)
|
||||
return root, sub
|
||||
}
|
||||
|
||||
t.Run("propagates to the shadowing local help flag", func(t *testing.T) {
|
||||
root, sub := newRootCmd()
|
||||
tcmd := &TopLevelCommand{cmd: root, args: []string{"--help", "run", "hello-world"}}
|
||||
|
||||
_, args, err := tcmd.HandleGlobalFlags()
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, args, []string{"run", "hello-world"})
|
||||
|
||||
help, err := sub.Flags().GetBool("help")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, help, true)
|
||||
})
|
||||
|
||||
t.Run("leaves the local help flag alone without --help", func(t *testing.T) {
|
||||
root, sub := newRootCmd()
|
||||
tcmd := &TopLevelCommand{cmd: root, args: []string{"run", "hello-world"}}
|
||||
|
||||
_, _, err := tcmd.HandleGlobalFlags()
|
||||
assert.NilError(t, err)
|
||||
|
||||
help, err := sub.Flags().GetBool("help")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, help, false)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -146,24 +146,27 @@ func (c *ContainerContext) ID() string {
|
||||
// slash (/) prefix stripped. Additional names for the container (related to the
|
||||
// legacy `--link` feature) are omitted when formatting "truncated".
|
||||
func (c *ContainerContext) Names() string {
|
||||
var b strings.Builder
|
||||
for i, n := range c.c.Names {
|
||||
name := strings.TrimPrefix(n, "/")
|
||||
if c.trunc {
|
||||
// When printing truncated, we only print a single name.
|
||||
//
|
||||
// Pick the first name that's not a legacy link (does not have
|
||||
// slashes inside the name itself (e.g., "/other-container/link")).
|
||||
// Normally this would be the first name found.
|
||||
if strings.IndexByte(name, '/') == -1 {
|
||||
if c.trunc {
|
||||
// When printing truncated, we only print a single name.
|
||||
//
|
||||
// Pick the first name that's not a legacy link (does not have
|
||||
// slashes inside the name itself (e.g., "/other-container/link")).
|
||||
// Normally this would be the first name found.
|
||||
for _, n := range c.c.Names {
|
||||
if name := strings.TrimPrefix(n, "/"); strings.IndexByte(name, '/') == -1 {
|
||||
return name
|
||||
}
|
||||
continue
|
||||
}
|
||||
// No name without slashes was found; this can happen when connected
|
||||
// to a classic Swarm API, which prefixes names with the node-name
|
||||
// ("/<node-name>/<container-name>"). Fall back to printing all names.
|
||||
}
|
||||
var b strings.Builder
|
||||
for i, n := range c.c.Names {
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
b.WriteString(name)
|
||||
b.WriteString(strings.TrimPrefix(n, "/"))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -48,6 +48,25 @@ func TestContainerPsContext(t *testing.T) {
|
||||
expValue: "foobar_baz",
|
||||
call: ctx.Names,
|
||||
},
|
||||
{
|
||||
container: container.Summary{Names: []string{"/other/link", "/foobar_baz"}},
|
||||
trunc: true,
|
||||
expValue: "foobar_baz",
|
||||
call: ctx.Names,
|
||||
},
|
||||
{
|
||||
container: container.Summary{Names: []string{"/foobar_baz", "/other/link"}},
|
||||
expValue: "foobar_baz,other/link",
|
||||
call: ctx.Names,
|
||||
},
|
||||
{
|
||||
// Names returned by a classic Swarm API are prefixed with the
|
||||
// node-name; see https://github.com/docker/cli/pull/6875
|
||||
container: container.Summary{Names: []string{"/node-1/foobar_baz"}},
|
||||
trunc: true,
|
||||
expValue: "node-1/foobar_baz",
|
||||
call: ctx.Names,
|
||||
},
|
||||
{
|
||||
container: container.Summary{Image: "ubuntu"},
|
||||
trunc: true,
|
||||
@@ -257,6 +276,7 @@ func TestContainerPsContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:dupword // ignore duplicate "ubuntu" in test fixtures
|
||||
func TestContainerContextWrite(t *testing.T) {
|
||||
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
||||
expectedTime := time.Unix(unixTime, 0).String()
|
||||
|
||||
@@ -260,8 +260,11 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
contextDir = tempDir
|
||||
case build.ContextTypeRemote:
|
||||
buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, options.context, options.dockerfileName)
|
||||
if err != nil && options.quiet {
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), progBuff)
|
||||
if err != nil {
|
||||
if options.quiet {
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), progBuff)
|
||||
}
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unable to prepare context: path %q not found", options.context)
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
@@ -172,6 +174,35 @@ RUN echo hello world
|
||||
assert.DeepEqual(t, fakeBuild.filenames(t), []string{"Dockerfile"})
|
||||
}
|
||||
|
||||
// TestRunBuildRemoteContextDownloadError verifies that remote context
|
||||
// download errors are propagated.
|
||||
//
|
||||
// regression test for https://github.com/docker/cli/issues/7372
|
||||
func TestRunBuildRemoteContextDownloadError(t *testing.T) {
|
||||
t.Setenv("DOCKER_BUILDKIT", "0")
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
imageBuildCalled := false
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imageBuildFunc: func(ctx context.Context, buildContext io.Reader, options client.ImageBuildOptions) (client.ImageBuildResult, error) {
|
||||
imageBuildCalled = true
|
||||
return client.ImageBuildResult{Body: io.NopCloser(bytes.NewReader(nil))}, nil
|
||||
},
|
||||
})
|
||||
|
||||
options := newBuildOptions()
|
||||
options.context = server.URL
|
||||
|
||||
err := runBuild(context.Background(), cli, options)
|
||||
|
||||
assert.ErrorContains(t, err, "unable to download remote context")
|
||||
assert.Assert(t, !imageBuildCalled, "ImageBuild should not be called when downloading the remote context fails")
|
||||
}
|
||||
|
||||
type fakeBuild struct {
|
||||
context *tar.Reader
|
||||
options client.ImageBuildOptions
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/manifest/store"
|
||||
"github.com/docker/cli/internal/registryclient"
|
||||
@@ -20,6 +22,23 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// similar to https://github.com/containerd/platforms/blob/v1.0.0-rc.5/database.go#L29-L38
|
||||
knownOSes = []string{
|
||||
"aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos",
|
||||
"ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris",
|
||||
"windows", "zos",
|
||||
}
|
||||
|
||||
// similar to https://github.com/containerd/platforms/blob/v1.0.0-rc.5/database.go#L51-L60
|
||||
knownArches = []string{
|
||||
"386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64",
|
||||
"ppc64le", "loong64", "mips", "mipsle", "mips64", "mips64le", "mips64p32",
|
||||
"mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc",
|
||||
"sparc64", "wasm",
|
||||
}
|
||||
)
|
||||
|
||||
type annotateOptions struct {
|
||||
target string // the target manifest list name (also transaction ID)
|
||||
image string // the manifest to annotate within the list
|
||||
@@ -97,6 +116,8 @@ func newAnnotateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags.StringSliceVar(&opts.osFeatures, "os-features", []string{}, "Set operating system feature")
|
||||
flags.StringVar(&opts.variant, "variant", "", "Set architecture variant")
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("os", completion.FromList(knownOSes...))
|
||||
_ = cmd.RegisterFlagCompletionFunc("arch", completion.FromList(knownArches...))
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -139,8 +160,15 @@ func runManifestAnnotate(dockerCLI command.Cli, opts annotateOptions) error {
|
||||
imageManifest.Descriptor.Platform.OSVersion = opts.osVersion
|
||||
}
|
||||
|
||||
if !isValidOSArch(imageManifest.Descriptor.Platform.OS, imageManifest.Descriptor.Platform.Architecture) {
|
||||
return fmt.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
|
||||
// Validate platform syntax, without restricting OS/architecture combinations.
|
||||
//
|
||||
// TODO(thaJeztah): Implement a platforms.Validate in containerd/platforms and replace this code.
|
||||
p := imageManifest.Descriptor.Platform
|
||||
if p.OS == "" || p.Architecture == "" {
|
||||
return fmt.Errorf("manifest entry for image has missing os/architecture: %q/%q", p.OS, p.Architecture)
|
||||
}
|
||||
if _, err := platforms.Parse(platforms.FormatAll(*p)); err != nil {
|
||||
return fmt.Errorf("manifest entry for image has invalid platform: %w", err)
|
||||
}
|
||||
return manifestStore.Save(targetRef, imgRef, imageManifest)
|
||||
}
|
||||
|
||||
@@ -58,15 +58,16 @@ func TestManifestAnnotate(t *testing.T) {
|
||||
assert.ErrorContains(t, cmd.Execute(), expectedError)
|
||||
|
||||
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
|
||||
cmd.Flags().Set("os", "freebsd")
|
||||
cmd.Flags().Set("arch", "fake")
|
||||
cmd.Flags().Set("os-version", "1")
|
||||
cmd.Flags().Set("os-features", "feature1")
|
||||
cmd.Flags().Set("variant", "v7")
|
||||
expectedError = "manifest entry for image has unsupported os/arch combination"
|
||||
assert.NilError(t, cmd.Flags().Set("os", "freebsd"))
|
||||
assert.NilError(t, cmd.Flags().Set("arch", "<invalid>"))
|
||||
assert.NilError(t, cmd.Flags().Set("os-version", "1"))
|
||||
assert.NilError(t, cmd.Flags().Set("os-features", "feature1"))
|
||||
assert.NilError(t, cmd.Flags().Set("variant", "v7"))
|
||||
|
||||
expectedError = "manifest entry for image has invalid platform"
|
||||
assert.ErrorContains(t, cmd.Execute(), expectedError)
|
||||
|
||||
cmd.Flags().Set("arch", "arm")
|
||||
assert.NilError(t, cmd.Flags().Set("arch", "arm"))
|
||||
assert.NilError(t, cmd.Execute())
|
||||
|
||||
cmd = newInspectCommand(cli)
|
||||
|
||||
@@ -9,53 +9,6 @@ import (
|
||||
"github.com/docker/cli/cli/manifest/types"
|
||||
)
|
||||
|
||||
type osArch struct {
|
||||
os string
|
||||
arch string
|
||||
}
|
||||
|
||||
// Remove any unsupported os/arch combo
|
||||
// list of valid os/arch values (see "Optional Environment Variables" section
|
||||
// of https://go.dev/doc/install/source
|
||||
// Added linux/s390x as we know System z support already exists
|
||||
// Keep in sync with _docker_manifest_annotate in contrib/completion/bash/docker
|
||||
var validOSArches = map[osArch]bool{
|
||||
{os: "darwin", arch: "386"}: true,
|
||||
{os: "darwin", arch: "amd64"}: true,
|
||||
{os: "darwin", arch: "arm"}: true,
|
||||
{os: "darwin", arch: "arm64"}: true,
|
||||
{os: "dragonfly", arch: "amd64"}: true,
|
||||
{os: "freebsd", arch: "386"}: true,
|
||||
{os: "freebsd", arch: "amd64"}: true,
|
||||
{os: "freebsd", arch: "arm"}: true,
|
||||
{os: "linux", arch: "386"}: true,
|
||||
{os: "linux", arch: "amd64"}: true,
|
||||
{os: "linux", arch: "arm"}: true,
|
||||
{os: "linux", arch: "arm64"}: true,
|
||||
{os: "linux", arch: "ppc64le"}: true,
|
||||
{os: "linux", arch: "mips64"}: true,
|
||||
{os: "linux", arch: "mips64le"}: true,
|
||||
{os: "linux", arch: "riscv64"}: true,
|
||||
{os: "linux", arch: "s390x"}: true,
|
||||
{os: "netbsd", arch: "386"}: true,
|
||||
{os: "netbsd", arch: "amd64"}: true,
|
||||
{os: "netbsd", arch: "arm"}: true,
|
||||
{os: "openbsd", arch: "386"}: true,
|
||||
{os: "openbsd", arch: "amd64"}: true,
|
||||
{os: "openbsd", arch: "arm"}: true,
|
||||
{os: "plan9", arch: "386"}: true,
|
||||
{os: "plan9", arch: "amd64"}: true,
|
||||
{os: "solaris", arch: "amd64"}: true,
|
||||
{os: "windows", arch: "386"}: true,
|
||||
{os: "windows", arch: "amd64"}: true,
|
||||
}
|
||||
|
||||
func isValidOSArch(os string, arch string) bool {
|
||||
// check for existence of this combo
|
||||
_, ok := validOSArches[osArch{os, arch}]
|
||||
return ok
|
||||
}
|
||||
|
||||
func normalizeReference(ref string) (reference.Named, error) {
|
||||
namedRef, err := reference.ParseNormalizedNamed(ref)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/docker/cli/internal/tui"
|
||||
registrytypes "github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -226,7 +227,7 @@ func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, auth
|
||||
return resp.Auth.Status, err
|
||||
}
|
||||
|
||||
func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, defaultUsername, serverAddress string) (msg string, _ error) {
|
||||
func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, defaultUsername, serverAddress string) (message string, _ error) {
|
||||
// Some links documenting this:
|
||||
// - https://code.google.com/archive/p/mintty/issues/56
|
||||
// - https://github.com/docker/docker/issues/15272
|
||||
@@ -240,13 +241,12 @@ func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, de
|
||||
|
||||
// If we're logging into the index server and the user didn't provide a username or password, use the device flow
|
||||
if serverAddress == registry.IndexServer && opts.user == "" && opts.password == "" {
|
||||
var err error
|
||||
msg, err = loginWithDeviceCodeFlow(ctx, dockerCLI)
|
||||
// if the error represents a failure to initiate the device-code flow,
|
||||
// then we fallback to regular cli credentials login
|
||||
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
|
||||
msg, err := loginWithDeviceCodeFlow(ctx, dockerCLI)
|
||||
if err == nil || !errors.Is(err, manager.ErrDeviceLoginStartFail) {
|
||||
return msg, err
|
||||
}
|
||||
|
||||
logrus.WithError(err).Debug("failed to start web-based login")
|
||||
_, _ = fmt.Fprint(dockerCLI.Err(), "Failed to start web-based login - falling back to command line login...\n\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -210,9 +210,9 @@ func TestResourceOptionsToResourceRequirementsSwap(t *testing.T) {
|
||||
r, err = swapOptions.ToResourceRequirements(flags)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, r.SwapBytes != nil)
|
||||
assert.Check(t, is.Equal(*(r.SwapBytes), int64(86000)))
|
||||
assert.Check(t, is.Equal(*r.SwapBytes, int64(86000)))
|
||||
assert.Check(t, r.MemorySwappiness != nil)
|
||||
assert.Check(t, is.Equal(*(r.MemorySwappiness), int64(23)))
|
||||
assert.Check(t, is.Equal(*r.MemorySwappiness, int64(23)))
|
||||
}
|
||||
|
||||
func TestToServiceNetwork(t *testing.T) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const exportTimeout = 50 * time.Millisecond
|
||||
const ExportTimeout = 50 * time.Millisecond
|
||||
|
||||
// TracerProvider is an extension of the trace.TracerProvider interface for CLI programs.
|
||||
type TracerProvider interface {
|
||||
@@ -205,7 +205,7 @@ func (r *cliReader) Shutdown(ctx context.Context) error {
|
||||
// We don't want CLI metrics to prevent the CLI from exiting
|
||||
// so if there's some kind of issue we need to abort pretty
|
||||
// quickly.
|
||||
ctx, cancel := context.WithTimeout(ctx, exportTimeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, ExportTimeout)
|
||||
defer cancel()
|
||||
|
||||
return r.ForceFlush(ctx)
|
||||
|
||||
@@ -109,7 +109,7 @@ func dockerSpanExporter(ctx context.Context, cli Cli) []sdktrace.TracerProviderO
|
||||
otel.Handle(err)
|
||||
return nil
|
||||
}
|
||||
return []sdktrace.TracerProviderOption{sdktrace.WithBatcher(exp, sdktrace.WithExportTimeout(exportTimeout))}
|
||||
return []sdktrace.TracerProviderOption{sdktrace.WithBatcher(exp, sdktrace.WithExportTimeout(ExportTimeout))}
|
||||
}
|
||||
|
||||
func dockerMetricExporter(ctx context.Context, cli Cli) []sdkmetric.Option {
|
||||
|
||||
@@ -85,7 +85,7 @@ func startCobraCommandTimer(mp metric.MeterProvider, attrs []attribute.KeyValue)
|
||||
return func(err error) {
|
||||
// Use a new context for the export so that the command being cancelled
|
||||
// doesn't affect the metrics, and we get metrics for cancelled commands.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), exportTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ExportTimeout)
|
||||
defer cancel()
|
||||
|
||||
duration := float64(time.Since(start)) / float64(time.Millisecond)
|
||||
|
||||
@@ -255,7 +255,6 @@ func mergeLoggingConfig(dst, src reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func mergeUlimitsConfig(dst, src reflect.Value) error {
|
||||
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
|
||||
dst.Elem().Set(src.Elem())
|
||||
@@ -263,7 +262,6 @@ func mergeUlimitsConfig(dst, src reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func mergeShellCommand(dst, src reflect.Value) error {
|
||||
if src.Len() != 0 {
|
||||
dst.Set(src)
|
||||
@@ -271,7 +269,6 @@ func mergeShellCommand(dst, src reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func mergeServiceNetworkConfig(dst, src reflect.Value) error {
|
||||
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
|
||||
dst.Elem().FieldByName("Aliases").Set(src.Elem().FieldByName("Aliases"))
|
||||
@@ -285,7 +282,6 @@ func mergeServiceNetworkConfig(dst, src reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func mergeUint64(dst, src reflect.Value) error {
|
||||
if !src.IsNil() {
|
||||
dst.Elem().Set(src.Elem())
|
||||
|
||||
+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=
|
||||
|
||||
@@ -27,8 +27,13 @@ func TerminatePrompt(ctx context.Context, t *testing.T, cmd *cobra.Command, cli
|
||||
}))
|
||||
cli.SetOut(outStream)
|
||||
|
||||
r, _, err := os.Pipe()
|
||||
r, w, err := os.Pipe()
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = w.Close()
|
||||
_ = r.Close()
|
||||
})
|
||||
|
||||
cli.SetIn(streams.NewIn(r))
|
||||
|
||||
notifyCtx, notifyCancel := context.WithCancel(ctx)
|
||||
@@ -58,8 +63,6 @@ func TerminatePrompt(ctx context.Context, t *testing.T, cmd *cobra.Command, cli
|
||||
}()
|
||||
}
|
||||
|
||||
assert.Check(t, cli.OutBuffer().Len() > 0)
|
||||
|
||||
// a small delay to ensure the plugin is prompting
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/moby/moby/api/types/jsonstream"
|
||||
registrytypes "github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/client/pkg/jsonmessage"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
@@ -46,7 +47,7 @@ func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *Repo
|
||||
// Count the times of calling for handleTarget,
|
||||
// if it is called more that once, that should be considered an error in a trusted push.
|
||||
cnt := 0
|
||||
handleTarget := func(msg jsonstream.JSONMessage) {
|
||||
handleTarget := func(msg jsonstream.Message) {
|
||||
cnt++
|
||||
if cnt > 1 {
|
||||
// handleTarget should only be called once. This will be treated as an error.
|
||||
@@ -78,14 +79,14 @@ func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *Repo
|
||||
default:
|
||||
// We want trust signatures to always take an explicit tag,
|
||||
// otherwise it will act as an untrusted push.
|
||||
if err := jsonstream.Display(ctx, in, ioStreams.Out()); err != nil {
|
||||
if err := displayJSONMessages(ctx, in, ioStreams.Out(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(ioStreams.Err(), "No tag specified, skipping trust metadata push")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := jsonstream.Display(ctx, in, ioStreams.Out(), jsonstream.WithAuxCallback(handleTarget)); err != nil {
|
||||
if err := displayJSONMessages(ctx, in, ioStreams.Out(), handleTarget); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -147,3 +148,34 @@ func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *Repo
|
||||
_, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ctxReader struct {
|
||||
err chan error
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *ctxReader) Read(p []byte) (int, error) {
|
||||
select {
|
||||
case err := <-r.err:
|
||||
return 0, err
|
||||
default:
|
||||
return r.r.Read(p)
|
||||
}
|
||||
}
|
||||
|
||||
// displayJSONMessages prints the JSON messages from in to out. It returns
|
||||
// ctx.Err() if ctx is canceled, but not when in is closed.
|
||||
func displayJSONMessages(ctx context.Context, in io.Reader, out *streams.Out, auxCallback func(jsonstream.Message)) error {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
reader := &ctxReader{err: make(chan error, 1), r: in}
|
||||
stop := context.AfterFunc(ctx, func() { reader.err <- ctx.Err() })
|
||||
defer stop()
|
||||
|
||||
if err := jsonmessage.DisplayJSONMessagesStream(reader, out, out.FD(), out.IsTerminal(), auxCallback); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/commands"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands.Register(newTrustCommand)
|
||||
}
|
||||
|
||||
// newTrustCommand returns a cobra command for `trust` subcommands.
|
||||
func newTrustCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "trust",
|
||||
Short: "Manage trust on Docker images",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCLI.Err()),
|
||||
|
||||
DisableFlagsInUseLine: true,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newRevokeCommand(dockerCLI),
|
||||
newSignCommand(dockerCLI),
|
||||
newTrustKeyCommand(dockerCLI),
|
||||
newTrustSignerCommand(dockerCLI),
|
||||
newInspectCommand(dockerCLI),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cmd/docker-trust/internal/trust"
|
||||
"github.com/docker/cli/internal/lazyregexp"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/theupdateframework/notary"
|
||||
"github.com/theupdateframework/notary/trustmanager"
|
||||
@@ -41,7 +41,7 @@ func newKeyGenerateCommand(dockerCLI command.Streams) *cobra.Command {
|
||||
}
|
||||
|
||||
// key names can use lowercase alphanumeric + _ + - characters
|
||||
var validKeyName = lazyregexp.New(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
|
||||
var validKeyName = regexp.MustCompile(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
|
||||
|
||||
// validate that all of the key names are unique and are alphanumeric + _ + -
|
||||
// and that we do not already have public key files in the target dir on disk
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cmd/docker-trust/internal/trust"
|
||||
"github.com/docker/cli/internal/lazyregexp"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
@@ -45,7 +45,7 @@ func newSignerAddCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
var validSignerName = lazyregexp.New(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
|
||||
var validSignerName = regexp.MustCompile(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
|
||||
|
||||
func addSigner(ctx context.Context, dockerCLI command.Cli, options signerAddOptions) error {
|
||||
signerName := options.signer
|
||||
|
||||
+80
-14
@@ -7,9 +7,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
pluginmanager "github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
@@ -43,8 +45,11 @@ import (
|
||||
// The effective config directory is also passed as DOCKER_CONFIG.
|
||||
// The provider must provision into that context store without changing the saved
|
||||
// current context or recursively forwarding --cloud.
|
||||
// It inherits the environment, receives no interactive stdin, and sends progress
|
||||
// to stderr.
|
||||
// It inherits the environment and sends progress and prompts to stderr. Stdin
|
||||
// is forwarded only when both stdin and stderr are terminals with file handles;
|
||||
// wrapped Windows consoles use the corresponding standard handles. Otherwise
|
||||
// the provider receives EOF and must not prompt or open a terminal
|
||||
// separately. Piped and redirected input belongs to the requested command.
|
||||
// Stdout must contain exactly one JSON object:
|
||||
//
|
||||
// {"DOCKER_CONTEXT":"provisioned-context"}
|
||||
@@ -95,22 +100,31 @@ func processCloud(ctx context.Context, dockerCli *command.DockerCli, cmd *cobra.
|
||||
// the __resolve-context contract.
|
||||
const cloudResolverFeature = "cloud-context-resolver"
|
||||
|
||||
const cloudPluginHelp = "to use --cloud, install the plugin. See https://docs.docker.com/go/cloud-flag/"
|
||||
|
||||
func cloudProvider(dockerCli config.Provider, rootCmd *cobra.Command) (*pluginmanager.Plugin, error) {
|
||||
provider := dockerCli.ConfigFile().Features["cloud"]
|
||||
if provider == "" {
|
||||
provider = "offload"
|
||||
}
|
||||
var help string
|
||||
if provider == "offload" {
|
||||
help = "\n\n" + cloudPluginHelp
|
||||
}
|
||||
|
||||
plugin, err := pluginmanager.GetPlugin(provider, dockerCli, rootCmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cloud resolver plugin %q unavailable: %w", provider, err)
|
||||
if provider == "offload" && errdefs.IsNotFound(err) {
|
||||
return nil, errors.New(cloudPluginHelp)
|
||||
}
|
||||
return nil, fmt.Errorf("plugin %q unavailable: %w%s", provider, err, help)
|
||||
}
|
||||
if plugin.Err != nil {
|
||||
return nil, fmt.Errorf("invalid cloud resolver plugin %q: %w", provider, plugin.Err)
|
||||
return nil, fmt.Errorf("invalid plugin %q: %w%s", provider, plugin.Err, help)
|
||||
}
|
||||
|
||||
if supported, _ := plugin.Features[cloudResolverFeature].(bool); !supported {
|
||||
return nil, fmt.Errorf("plugin %q does not support cloud context resolution", provider)
|
||||
return nil, fmt.Errorf("plugin %q does not support --cloud%s", provider, help)
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
@@ -139,7 +153,7 @@ func resolveCloudContext(ctx context.Context, dockerCli *command.DockerCli, root
|
||||
|
||||
cmd := exec.CommandContext(ctx, plugin.Path, "--config="+config.Dir(), plugin.Name, "__resolve-context", "--", name) // #nosec G204 -- executable validated through CLI plugin discovery
|
||||
cmd.Env = append(os.Environ(), config.EnvOverrideConfigDir+"="+config.Dir(), metadata.ReexecEnvvar+"="+os.Args[0])
|
||||
cmd.Stderr = dockerCli.Err()
|
||||
setResolverStdio(cmd, dockerCli)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
@@ -160,21 +174,57 @@ func resolveCloudContext(ctx context.Context, dockerCli *command.DockerCli, root
|
||||
return "", errors.New("cloud resolver must return a non-default DOCKER_CONTEXT")
|
||||
}
|
||||
|
||||
// Do not allow a missing context or endpoint to fall back to the local engine.
|
||||
meta, err := dockerCli.ContextStore().GetMetadata(response.DockerContext)
|
||||
if err := validateResolvedContext(dockerCli, response.DockerContext); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.DockerContext, nil
|
||||
}
|
||||
|
||||
// setResolverStdio connects the resolver to the terminal when both stdin and
|
||||
// stderr are terminals, so the plugin can prompt interactively. Otherwise
|
||||
// stderr is forwarded and stdin is left unset.
|
||||
func setResolverStdio(cmd *exec.Cmd, dockerCli *command.DockerCli) {
|
||||
cmd.Stderr = dockerCli.Err()
|
||||
stdinTerminal := dockerCli.In().IsTerminal()
|
||||
stderrTerminal := dockerCli.Err().IsTerminal()
|
||||
stdin, stdinFile := dockerCli.In().File()
|
||||
stderr, stderrFile := dockerCli.Err().File()
|
||||
if runtime.GOOS == "windows" {
|
||||
// term.StdStreams can wrap console handles for terminal emulation,
|
||||
// preventing File from exposing them to the child process.
|
||||
if stdinTerminal && !stdinFile {
|
||||
stdin, stdinFile = os.Stdin, true
|
||||
}
|
||||
if stderrTerminal && !stderrFile {
|
||||
stderr, stderrFile = os.Stderr, true
|
||||
}
|
||||
}
|
||||
if stdinTerminal && stderrTerminal && stdinFile && stderrFile {
|
||||
// Pass files directly: wrapping them makes os/exec copy through pipes,
|
||||
// hiding terminal identity and potentially consuming the command's input.
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stderr = stderr
|
||||
}
|
||||
}
|
||||
|
||||
// validateResolvedContext checks that the context exists and has a Docker
|
||||
// endpoint host, so a missing context or endpoint cannot fall back to the
|
||||
// local engine.
|
||||
func validateResolvedContext(dockerCli *command.DockerCli, name string) error {
|
||||
meta, err := dockerCli.ContextStore().GetMetadata(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("loading resolved context %q: %w", response.DockerContext, err)
|
||||
return fmt.Errorf("loading resolved context %q: %w", name, err)
|
||||
}
|
||||
|
||||
endpoint, err := contextdocker.EndpointFromContext(meta)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid resolved context %q: %w", response.DockerContext, err)
|
||||
return fmt.Errorf("invalid resolved context %q: %w", name, err)
|
||||
}
|
||||
if endpoint.Host == "" {
|
||||
return "", fmt.Errorf("resolved context %q has no Docker endpoint host", response.DockerContext)
|
||||
return fmt.Errorf("resolved context %q has no Docker endpoint host", name)
|
||||
}
|
||||
|
||||
return response.DockerContext, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// cloudHelpRequest avoids provisioning for help and shell completion.
|
||||
@@ -199,7 +249,17 @@ func cloudHelpRequest(rootCmd *cobra.Command, args []string) (bool, error) {
|
||||
if err != nil || cmd == rootCmd || pluginmanager.IsPluginCommand(cmd) {
|
||||
// Plugin flags are opaque. Only recognize help immediately after the
|
||||
// plugin name; deeper help is available through "docker help PLUGIN".
|
||||
return len(args) > 1 && (args[1] == "--help" || args[1] == "-h" || args[1] == "--help=true"), nil
|
||||
return len(args) > 1 && isHelpFlag(args[1]), nil
|
||||
}
|
||||
|
||||
// "build", "bake", "builder", and "image build" are builtin commands that
|
||||
// processAliases may still rewrite into a call to the builder plugin
|
||||
// (buildx) after cloud resolution runs, so their flag set here is only the
|
||||
// legacy builder's and does not reflect what will actually parse the
|
||||
// remaining args. Treat them like a plugin command above: only recognize
|
||||
// help immediately after the resolved command path.
|
||||
if _, _, _, forwarded := forwardBuilder(builderDefaultPlugin, args, args); forwarded {
|
||||
return len(remaining) > 0 && isHelpFlag(remaining[0]), nil
|
||||
}
|
||||
|
||||
cmd.InitDefaultHelpFlag()
|
||||
@@ -226,6 +286,12 @@ func cloudHelpRequest(rootCmd *cobra.Command, args []string) (bool, error) {
|
||||
return help, nil
|
||||
}
|
||||
|
||||
// isHelpFlag reports whether arg requests help, in any of the forms pflag
|
||||
// accepts for a boolean flag.
|
||||
func isHelpFlag(arg string) bool {
|
||||
return arg == "--help" || arg == "-h" || arg == "--help=true"
|
||||
}
|
||||
|
||||
// cloudPluginArgs only rewrites the global prefix, leaving subcommand arguments
|
||||
// untouched. Parsing also distinguishes --cloud from another flag's value.
|
||||
func cloudPluginArgs(cmd *cobra.Command, osArgs []string, contextName string) ([]string, error) {
|
||||
|
||||
+169
-29
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
@@ -62,6 +64,11 @@ func TestCloudHelpRequest(t *testing.T) {
|
||||
{name: "container help", args: []string{"run", "alpine", "--help"}},
|
||||
{name: "flag value", args: []string{"run", "--name", "--help", "alpine"}},
|
||||
{name: "help disabled", args: []string{"run", "--help=false", "alpine"}},
|
||||
{name: "build help", args: []string{"build", "--help"}, help: true},
|
||||
{name: "build buildkit-only flag", args: []string{"build", "--secret", "id=s,src=./s", "."}},
|
||||
{name: "image build buildkit-only flag", args: []string{"image", "build", "--push", "."}},
|
||||
{name: "builder build buildkit-only flag", args: []string{"builder", "build", "--ssh", "default", "."}},
|
||||
{name: "bake help", args: []string{"bake", "--help"}, help: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
@@ -169,19 +176,21 @@ func TestCloudResolution(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
response string
|
||||
exit int
|
||||
wantName string
|
||||
wantErr string
|
||||
plugin bool
|
||||
skip bool
|
||||
provider string
|
||||
noProvider bool
|
||||
legacyProvider bool
|
||||
command []string
|
||||
visible bool
|
||||
name string
|
||||
args []string
|
||||
response string
|
||||
exit int
|
||||
wantName string
|
||||
wantErr string
|
||||
plugin bool
|
||||
skip bool
|
||||
provider string
|
||||
noProvider bool
|
||||
legacyProvider bool
|
||||
invalidProvider bool
|
||||
wantHelp bool
|
||||
command []string
|
||||
visible bool
|
||||
}{
|
||||
{
|
||||
name: "default target",
|
||||
@@ -276,28 +285,45 @@ func TestCloudResolution(t *testing.T) {
|
||||
args: []string{"--cloud"},
|
||||
noProvider: true,
|
||||
legacyProvider: true,
|
||||
wantErr: `plugin "offload" does not support cloud context resolution`,
|
||||
wantErr: `plugin "offload" does not support --cloud`,
|
||||
wantHelp: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "invalid default provider",
|
||||
args: []string{"--cloud"},
|
||||
noProvider: true,
|
||||
invalidProvider: true,
|
||||
wantErr: `invalid plugin "offload": plugin metadata does not define a vendor`,
|
||||
wantHelp: true,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "invalid configured provider",
|
||||
args: []string{"--cloud"},
|
||||
invalidProvider: true,
|
||||
wantErr: `invalid plugin "foobar": plugin metadata does not define a vendor`,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "legacy configured provider",
|
||||
args: []string{"--cloud"},
|
||||
legacyProvider: true,
|
||||
wantErr: `plugin "foobar" does not support cloud context resolution`,
|
||||
wantErr: `plugin "foobar" does not support --cloud`,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "configured provider unavailable",
|
||||
args: []string{"--cloud"},
|
||||
provider: "missing",
|
||||
wantErr: `cloud resolver plugin "missing" unavailable`,
|
||||
wantErr: `plugin "missing" unavailable`,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "provider cannot be a path",
|
||||
args: []string{"--cloud"},
|
||||
provider: "../foobar",
|
||||
wantErr: `cloud resolver plugin "../foobar" unavailable`,
|
||||
wantErr: `plugin "../foobar" unavailable`,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
@@ -370,10 +396,7 @@ func TestCloudResolution(t *testing.T) {
|
||||
if !tc.noProvider {
|
||||
installedProvider = "foobar"
|
||||
cfg := configfile.New(filepath.Join(configDir, config.ConfigFileName))
|
||||
provider := tc.provider
|
||||
if provider == "" {
|
||||
provider = "foobar"
|
||||
}
|
||||
provider := cmp.Or(tc.provider, "foobar")
|
||||
cfg.Features = map[string]string{"cloud": provider}
|
||||
assert.NilError(t, cfg.Save())
|
||||
}
|
||||
@@ -381,12 +404,12 @@ func TestCloudResolution(t *testing.T) {
|
||||
pluginDir := filepath.Join(configDir, "cli-plugins")
|
||||
assert.NilError(t, os.MkdirAll(pluginDir, 0o755))
|
||||
|
||||
response := tc.response
|
||||
if response == "" {
|
||||
response = `{"DOCKER_CONTEXT":"resolved"}`
|
||||
}
|
||||
response := cmp.Or(tc.response, `{"DOCKER_CONTEXT":"resolved"}`)
|
||||
|
||||
providerMetadata := metadata.Metadata{SchemaVersion: "0.1.0", Vendor: "test"}
|
||||
if tc.invalidProvider {
|
||||
providerMetadata.Vendor = ""
|
||||
}
|
||||
if !tc.legacyProvider {
|
||||
providerMetadata.Features = map[string]any{cloudResolverFeature: true}
|
||||
}
|
||||
@@ -448,6 +471,7 @@ printf '%s\n' "$@" > "$CLOUD_TEST_ARGS"
|
||||
if tc.wantErr != "" {
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Check(t, is.Contains(stderr.String(), tc.wantErr))
|
||||
assert.Equal(t, strings.Contains(stderr.String(), cloudPluginHelp), tc.wantHelp, stderr.String())
|
||||
_, statErr := os.Stat(dispatchFile)
|
||||
assert.Assert(t, os.IsNotExist(statErr))
|
||||
assert.Equal(t, stdout.String(), "")
|
||||
@@ -473,16 +497,132 @@ printf '%s\n' "$@" > "$CLOUD_TEST_ARGS"
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Contains(stderr.String(), "provisioning"))
|
||||
name := tc.wantName
|
||||
if name == "" {
|
||||
name = "default"
|
||||
}
|
||||
name := cmp.Or(tc.wantName, "default")
|
||||
assert.Equal(t, string(invocation), "--config="+configDir+"\n"+installedProvider+"\n__resolve-context\n--\n"+name+"\n")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudResolverInput(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("fixture plugins use shell scripts and pseudo-terminals")
|
||||
}
|
||||
|
||||
executable, err := os.Executable()
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
stdinType string
|
||||
terminalErr bool
|
||||
prompt bool
|
||||
}{
|
||||
{name: "interactive", stdinType: "terminal", terminalErr: true, prompt: true},
|
||||
{name: "piped stdin", stdinType: "pipe", terminalErr: true},
|
||||
{name: "redirected stdin", stdinType: "file", terminalErr: true},
|
||||
{name: "redirected stderr", stdinType: "terminal"},
|
||||
{name: "noninteractive", stdinType: "pipe"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CLI_HOOKS", "false")
|
||||
configDir := t.TempDir()
|
||||
pluginDir := filepath.Join(configDir, "cli-plugins")
|
||||
assert.NilError(t, os.MkdirAll(pluginDir, 0o755))
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-offload"), []byte(`#!/bin/sh
|
||||
if [ "$1" = docker-cli-plugin-metadata ]; then
|
||||
echo '{"SchemaVersion":"0.1.0","Vendor":"test","Features":{"cloud-context-resolver":true}}'
|
||||
exit 0
|
||||
fi
|
||||
if [ -t 0 ]; then
|
||||
[ -t 2 ] || exit 1
|
||||
printf 'Continue? ' >&2
|
||||
IFS= read -r reply || exit 1
|
||||
printf '%s\n' "$reply" > "$DOCKER_CONFIG/resolver-input"
|
||||
elif IFS= read -r unexpected; then
|
||||
echo 'resolver consumed command input' >&2
|
||||
exit 1
|
||||
fi
|
||||
echo '{"DOCKER_CONTEXT":"resolved"}'
|
||||
`), 0o755))
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-cloudtest"), []byte(`#!/bin/sh
|
||||
if [ "$1" = docker-cli-plugin-metadata ]; then
|
||||
echo '{"SchemaVersion":"0.1.0","Vendor":"test"}'
|
||||
exit 0
|
||||
fi
|
||||
IFS= read -r input || exit 1
|
||||
printf '%s\n' "$input"
|
||||
`), 0o755))
|
||||
|
||||
contextStore := store.New(filepath.Join(configDir, "contexts"), command.DefaultContextStoreConfig())
|
||||
assert.NilError(t, contextStore.CreateOrUpdate(store.Metadata{
|
||||
Name: "resolved",
|
||||
Endpoints: map[string]any{contextdocker.DockerEndpoint: contextdocker.EndpointMeta{Host: "tcp://127.0.0.1:1"}},
|
||||
}))
|
||||
|
||||
terminal, tty, err := pty.Open()
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = tty.Close()
|
||||
_ = terminal.Close()
|
||||
})
|
||||
|
||||
const commandInput = "input for the original command\n"
|
||||
var stdin *os.File
|
||||
switch tc.stdinType {
|
||||
case "terminal":
|
||||
stdin = tty
|
||||
input := commandInput
|
||||
if tc.prompt {
|
||||
input = "yes\n" + input
|
||||
}
|
||||
_, err = terminal.WriteString(input)
|
||||
assert.NilError(t, err)
|
||||
case "pipe":
|
||||
var writer *os.File
|
||||
stdin, writer, err = os.Pipe()
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() { _ = stdin.Close() })
|
||||
t.Cleanup(func() { _ = writer.Close() })
|
||||
_, err = writer.WriteString(commandInput)
|
||||
assert.NilError(t, err)
|
||||
assert.NilError(t, writer.Close())
|
||||
case "file":
|
||||
inputPath := filepath.Join(configDir, "command-input")
|
||||
assert.NilError(t, os.WriteFile(inputPath, []byte(commandInput), 0o600))
|
||||
stdin, err = os.Open(inputPath)
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() { _ = stdin.Close() })
|
||||
}
|
||||
|
||||
payload, err := json.Marshal([]string{"docker", "--config=" + configDir, "--cloud", "cloudtest"})
|
||||
assert.NilError(t, err)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := exec.CommandContext(ctx, executable, "-test.run=^TestCloudCommandProcess$")
|
||||
cmd.Env = append(os.Environ(), "CLOUD_TEST_COMMAND="+string(payload))
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
cmd.WaitDelay = time.Second
|
||||
if tc.terminalErr {
|
||||
cmd.Stderr = tty
|
||||
}
|
||||
assert.NilError(t, cmd.Run(), stderr.String())
|
||||
assert.Equal(t, stdout.String(), commandInput)
|
||||
|
||||
reply, err := os.ReadFile(filepath.Join(configDir, "resolver-input"))
|
||||
if tc.prompt {
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(reply), "yes\n")
|
||||
} else {
|
||||
assert.Assert(t, os.IsNotExist(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Run startup in a separate process because it installs process-wide signal
|
||||
// handlers that intentionally outlive an individual command.
|
||||
func TestCloudCommandProcess(t *testing.T) {
|
||||
|
||||
+16
-3
@@ -571,11 +571,24 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
|
||||
return nil
|
||||
}
|
||||
if !errdefs.IsNotFound(err) {
|
||||
// For plugin not found we fall through to
|
||||
// cmd.Execute() which deals with reporting
|
||||
// "command not found" in a consistent way.
|
||||
return err
|
||||
}
|
||||
if args[0] == "offload" {
|
||||
// Report this before cmd.ExecuteContext(): plugin-specific flags such as
|
||||
// --cpus otherwise fail parsing before the root command's RunE.
|
||||
return errors.New("docker: to use 'docker offload', install the plugin.\nSee https://docs.docker.com/go/cloud-flag/")
|
||||
}
|
||||
// Other missing or invalid plugins fall through to cmd.ExecuteContext(),
|
||||
// which reports the standard unknown-command error.
|
||||
//
|
||||
// Cobra handles "--version" before it checks for unknown commands,
|
||||
// so unset it to make "docker nonexistent --version" fail instead
|
||||
// of printing the version. A global "--version" flag (as in
|
||||
// "docker --version nonexistent") was already parsed, and still
|
||||
// prints the version.
|
||||
if !cmd.Flags().Changed("version") {
|
||||
cmd.Version = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4156,6 +4156,7 @@ _docker_manifest_annotate() {
|
||||
amd64
|
||||
arm
|
||||
arm64
|
||||
loong64
|
||||
mips64
|
||||
mips64le
|
||||
ppc64le
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository.
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG ALPINE_VERSION=3.24
|
||||
|
||||
FROM alpine:${ALPINE_VERSION} AS gen
|
||||
RUN apk add --no-cache bash git
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
ARG GO_VERSION=1.27.3
|
||||
|
||||
# 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
|
||||
# that's also available as alpine image variant for the Golang version used.
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG ALPINE_VERSION=3.24
|
||||
|
||||
# BUILDX_VERSION sets the version of buildx to install in the dev container.
|
||||
# It must be a valid tag in the docker.io/docker/buildx-bin image repository
|
||||
# on Docker Hub.
|
||||
ARG BUILDX_VERSION=0.34.1
|
||||
ARG BUILDX_VERSION=0.38.1
|
||||
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golang
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
ARG GO_VERSION=1.27.3
|
||||
|
||||
# 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
|
||||
# that's also available as alpine image variant for the Golang version used.
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG ALPINE_VERSION=3.24
|
||||
# GOLANGCI_LINT_VERSION sets the version of the golangci/golangci-lint image to use.
|
||||
ARG GOLANGCI_LINT_VERSION=v2.10.1
|
||||
ARG GOLANGCI_LINT_VERSION=v2.14.0
|
||||
|
||||
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.8
|
||||
ARG GO_VERSION=1.27.3
|
||||
|
||||
# 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
|
||||
# that's also available as alpine image variant for the Golang version used.
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG MODOUTDATED_VERSION=v0.8.0
|
||||
ARG ALPINE_VERSION=3.24
|
||||
ARG MODOUTDATED_VERSION=v0.9.0
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
@@ -44,8 +44,8 @@ created. Supported `Dockerfile` instructions:
|
||||
$ docker ps
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
c3f279d17e0a ubuntu:24.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:24.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
c3f279d17e0a ubuntu:26.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:26.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
|
||||
$ docker commit c3f279d17e0a svendowideit/testimage:version3
|
||||
|
||||
@@ -63,8 +63,8 @@ svendowideit/testimage version3 f5283438590d 16 sec
|
||||
$ docker ps
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
c3f279d17e0a ubuntu:24.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:24.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
c3f279d17e0a ubuntu:26.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:26.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
|
||||
$ docker inspect -f "{{ .Config.Env }}" c3f279d17e0a
|
||||
|
||||
@@ -85,8 +85,8 @@ $ docker inspect -f "{{ .Config.Env }}" f5283438590d
|
||||
$ docker ps
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
c3f279d17e0a ubuntu:24.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:24.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
c3f279d17e0a ubuntu:26.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:26.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
|
||||
$ docker commit --change='CMD ["apachectl", "-DFOREGROUND"]' -c "EXPOSE 80" c3f279d17e0a svendowideit/testimage:version4
|
||||
|
||||
@@ -100,6 +100,6 @@ $ docker ps
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
89373736e2e7 testimage:version4 "apachectl -DFOREGROU" 3 seconds ago Up 2 seconds 80/tcp distracted_fermat
|
||||
c3f279d17e0a ubuntu:24.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:24.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
c3f279d17e0a ubuntu:26.04 /bin/bash 7 days ago Up 25 hours desperate_dubinsky
|
||||
197387f1b436 ubuntu:26.04 /bin/bash 7 days ago Up 25 hours focused_hamilton
|
||||
```
|
||||
|
||||
@@ -33,7 +33,7 @@ Running `docker ps --no-trunc` showing 2 linked containers.
|
||||
$ docker ps --no-trunc
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
ca5534a51dd04bbcebe9b23ba05f389466cf0c190f1f8f182d7eea92a9671d00 ubuntu:24.04 bash 17 seconds ago Up 16 seconds 3300-3310/tcp webapp
|
||||
ca5534a51dd04bbcebe9b23ba05f389466cf0c190f1f8f182d7eea92a9671d00 ubuntu:26.04 bash 17 seconds ago Up 16 seconds 3300-3310/tcp webapp
|
||||
9ca9747b233100676a48cc7806131586213fa5dab86dd1972d6a8732e3a84a4d crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db
|
||||
```
|
||||
|
||||
@@ -240,13 +240,13 @@ CONTAINER ID IMAGE COMMAND CREATED
|
||||
919e1179bdb8 ubuntu-c1 "top" About a minute ago Up About a minute admiring_lovelace
|
||||
```
|
||||
|
||||
Match containers based on the `ubuntu` version `24.04` image:
|
||||
Match containers based on the `ubuntu` version `26.04` image:
|
||||
|
||||
```console
|
||||
$ docker ps --filter ancestor=ubuntu:24.04
|
||||
$ docker ps --filter ancestor=ubuntu:26.04
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
82a598284012 ubuntu:24.04 "top" 3 minutes ago Up 3 minutes sleepy_bose
|
||||
82a598284012 ubuntu:26.04 "top" 3 minutes ago Up 3 minutes sleepy_bose
|
||||
```
|
||||
|
||||
The following matches containers based on the layer `d0e008c6cf02` or an image
|
||||
@@ -256,7 +256,7 @@ that have this layer in its layer stack.
|
||||
$ docker ps --filter ancestor=d0e008c6cf02
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
82a598284012 ubuntu:24.04 "top" 3 minutes ago Up 3 minutes sleepy_bose
|
||||
82a598284012 ubuntu:26.04 "top" 3 minutes ago Up 3 minutes sleepy_bose
|
||||
```
|
||||
|
||||
#### Create time
|
||||
|
||||
@@ -66,18 +66,18 @@ docker.io/library/debian:latest
|
||||
Docker images can consist of multiple layers. In the example above, the image
|
||||
consists of a single layer; `e756f3fdd6a3`.
|
||||
|
||||
Layers can be reused by images. For example, the `debian:bookworm` image shares
|
||||
its layer with the `debian:latest`. Pulling the `debian:bookworm` image therefore
|
||||
Layers can be reused by images. For example, the `debian:trixie` image shares
|
||||
its layer with the `debian:latest`. Pulling the `debian:trixie` image therefore
|
||||
only pulls its metadata, but not its layers, because the layer is already present
|
||||
locally:
|
||||
|
||||
```console
|
||||
$ docker image pull debian:bookworm
|
||||
$ docker image pull debian:trixie
|
||||
|
||||
bookworm: Pulling from library/debian
|
||||
trixie: Pulling from library/debian
|
||||
Digest: sha256:3f1d6c17773a45c97bd8f158d665c9709d7b29ed7917ac934086ad96f92e4510
|
||||
Status: Downloaded newer image for debian:bookworm
|
||||
docker.io/library/debian:bookworm
|
||||
Status: Downloaded newer image for debian:trixie
|
||||
docker.io/library/debian:trixie
|
||||
```
|
||||
|
||||
To see which images are present locally, use the [`docker images`](image_ls.md)
|
||||
@@ -87,13 +87,13 @@ command:
|
||||
$ docker images
|
||||
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
debian bookworm 4eacea30377a 8 days ago 124MB
|
||||
debian trixie 4eacea30377a 8 days ago 124MB
|
||||
debian latest 4eacea30377a 8 days ago 124MB
|
||||
```
|
||||
|
||||
Docker uses a content-addressable image store, and the image ID is a SHA256
|
||||
digest covering the image's configuration and layers. In the example above,
|
||||
`debian:bookworm` and `debian:latest` have the same image ID because they are
|
||||
`debian:trixie` and `debian:latest` have the same image ID because they are
|
||||
the same image tagged with different names. Because they are the same image,
|
||||
their layers are stored only once and do not consume extra disk space.
|
||||
|
||||
@@ -106,8 +106,8 @@ refer to [understand images, containers, and storage drivers](https://docs.docke
|
||||
So far, you've pulled images by their name (and "tag"). Using names and tags is
|
||||
a convenient way to work with images. When using tags, you can `docker pull` an
|
||||
image again to make sure you have the most up-to-date version of that image.
|
||||
For example, `docker pull ubuntu:24.04` pulls the latest version of the Ubuntu
|
||||
24.04 image.
|
||||
For example, `docker pull ubuntu:26.04` pulls the latest version of the Ubuntu
|
||||
26.04 image.
|
||||
|
||||
In some cases you don't want images to be updated to newer versions, but prefer
|
||||
to use a fixed version of an image. Docker enables you to pull an image by its
|
||||
@@ -116,16 +116,16 @@ of an image to pull. Doing so, allows you to "pin" an image to that version,
|
||||
and guarantee that the image you're using is always the same.
|
||||
|
||||
To know the digest of an image, pull the image first. Let's pull the latest
|
||||
`ubuntu:24.04` image from Docker Hub:
|
||||
`ubuntu:26.04` image from Docker Hub:
|
||||
|
||||
```console
|
||||
$ docker pull ubuntu:24.04
|
||||
$ docker pull ubuntu:26.04
|
||||
|
||||
24.04: Pulling from library/ubuntu
|
||||
26.04: Pulling from library/ubuntu
|
||||
125a6e411906: Pull complete
|
||||
Digest: sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30
|
||||
Status: Downloaded newer image for ubuntu:24.04
|
||||
docker.io/library/ubuntu:24.04
|
||||
Status: Downloaded newer image for ubuntu:26.04
|
||||
docker.io/library/ubuntu:26.04
|
||||
```
|
||||
|
||||
Docker prints the digest of the image after the pull has finished. In the example
|
||||
@@ -213,11 +213,11 @@ shorthand) to see the images that were pulled. The example below shows all the
|
||||
```console
|
||||
$ docker image ls --filter reference=ubuntu
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
ubuntu 22.04 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu jammy 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu 24.04 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu 24.04 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu noble 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu 26.04 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu latest 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu noble 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu resolute 35a88802559d 6 weeks ago 78.1MB
|
||||
```
|
||||
|
||||
### Cancel a pull
|
||||
|
||||
@@ -180,13 +180,12 @@ code, and authenticate.
|
||||
```console
|
||||
$ docker login
|
||||
|
||||
USING WEB-BASED LOGIN
|
||||
To sign in with credentials on the command line, use 'docker login -u <username>'
|
||||
Using web-based login. To sign in with credentials on the command line, use 'docker login -u <username>'
|
||||
|
||||
Your one-time device confirmation code is: LNFR-PGCJ
|
||||
Press ENTER to open your browser or submit your device code here: https://login.docker.com/activate
|
||||
Open this URL to authenticate: https://login.docker.com/activate?user_code=LNFR-PGCJ
|
||||
|
||||
Waiting for authentication in the browser…
|
||||
Press ENTER to open the URL in your browser, or Ctrl+C to cancel.
|
||||
```
|
||||
|
||||
After entering the code in your browser, you are authenticated to Docker Hub
|
||||
|
||||
@@ -706,7 +706,7 @@ follows:
|
||||
| `node.platform.os` | Node operating system | `node.platform.os==windows` |
|
||||
| `node.platform.arch` | Node architecture | `node.platform.arch==x86_64` |
|
||||
| `node.labels` | User-defined node labels | `node.labels.security==high` |
|
||||
| `engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-24.04` |
|
||||
| `engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-26.04` |
|
||||
|
||||
`engine.labels` apply to Docker Engine labels like operating system, drivers,
|
||||
etc. Swarm administrators add `node.labels` for operational purposes by using
|
||||
|
||||
@@ -339,8 +339,8 @@ $ docker events --filter 'type=network'
|
||||
|
||||
$ docker events --filter 'container=container_1' --filter 'container=container_2'
|
||||
|
||||
2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu:24.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:24.04)
|
||||
2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu:26.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:26.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (imager=redis:7.2)
|
||||
2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:7.2)
|
||||
|
||||
|
||||
+19
-19
@@ -35,7 +35,7 @@ reference to create or run a container based on an image.
|
||||
|
||||
An image tag is the image version, which defaults to `latest` when omitted. Use
|
||||
the tag to run a container from specific version of an image. For example, to
|
||||
run version `24.04` of the `ubuntu` image: `docker run ubuntu:24.04`.
|
||||
run version `26.04` of the `ubuntu` image: `docker run ubuntu:26.04`.
|
||||
|
||||
#### Image digests
|
||||
|
||||
@@ -406,14 +406,14 @@ We have four ways to set user memory usage:
|
||||
Examples:
|
||||
|
||||
```console
|
||||
$ docker run -it ubuntu:24.04 /bin/bash
|
||||
$ docker run -it ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
We set nothing about memory, this means the processes in the container can use
|
||||
as much memory and swap memory as they need.
|
||||
|
||||
```console
|
||||
$ docker run -it -m 300M --memory-swap -1 ubuntu:24.04 /bin/bash
|
||||
$ docker run -it -m 300M --memory-swap -1 ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
We set memory limit and disabled swap memory limit, this means the processes in
|
||||
@@ -421,7 +421,7 @@ the container can use 300M memory and as much swap memory as they need (if the
|
||||
host supports swap memory).
|
||||
|
||||
```console
|
||||
$ docker run -it -m 300M ubuntu:24.04 /bin/bash
|
||||
$ docker run -it -m 300M ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
We set memory limit only, this means the processes in the container can use
|
||||
@@ -430,7 +430,7 @@ We set memory limit only, this means the processes in the container can use
|
||||
would be 2*300M, so processes can use 300M swap memory as well.
|
||||
|
||||
```console
|
||||
$ docker run -it -m 300M --memory-swap 1G ubuntu:24.04 /bin/bash
|
||||
$ docker run -it -m 300M --memory-swap 1G ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
We set both memory and swap memory, so the processes in the container can use
|
||||
@@ -456,7 +456,7 @@ The following example limits the memory (`-m`) to 500M and sets the memory
|
||||
reservation to 200M.
|
||||
|
||||
```console
|
||||
$ docker run -it -m 500M --memory-reservation 200M ubuntu:24.04 /bin/bash
|
||||
$ docker run -it -m 500M --memory-reservation 200M ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
Under this configuration, when the container consumes memory more than 200M and
|
||||
@@ -466,7 +466,7 @@ memory below 200M.
|
||||
The following example set memory reservation to 1G without a hard memory limit.
|
||||
|
||||
```console
|
||||
$ docker run -it --memory-reservation 1G ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --memory-reservation 1G ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
The container can use as much memory as it needs. The memory reservation setting
|
||||
@@ -484,13 +484,13 @@ The following example limits the memory to 100M and disables the OOM killer for
|
||||
this container:
|
||||
|
||||
```console
|
||||
$ docker run -it -m 100M --oom-kill-disable ubuntu:24.04 /bin/bash
|
||||
$ docker run -it -m 100M --oom-kill-disable ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
The following example, illustrates a dangerous way to use the flag:
|
||||
|
||||
```console
|
||||
$ docker run -it --oom-kill-disable ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --oom-kill-disable ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
The container has unlimited memory which can cause the host to run out memory
|
||||
@@ -510,7 +510,7 @@ between 0 and 100. A value of 0 turns off anonymous page swapping. A value of
|
||||
For example, you can set:
|
||||
|
||||
```console
|
||||
$ docker run -it --memory-swappiness=0 ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --memory-swappiness=0 ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
Setting the `--memory-swappiness` option is helpful when you want to retain the
|
||||
@@ -561,7 +561,7 @@ And usually `--cpu-period` should work with `--cpu-quota`.
|
||||
Examples:
|
||||
|
||||
```console
|
||||
$ docker run -it --cpu-period=50000 --cpu-quota=25000 ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --cpu-period=50000 --cpu-quota=25000 ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
If there is 1 CPU, this means the container can get 50% CPU worth of run-time every 50ms.
|
||||
@@ -582,13 +582,13 @@ We can set cpus in which to allow execution for containers.
|
||||
Examples:
|
||||
|
||||
```console
|
||||
$ docker run -it --cpuset-cpus="1,3" ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --cpuset-cpus="1,3" ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
This means processes in container can be executed on cpu 1 and cpu 3.
|
||||
|
||||
```console
|
||||
$ docker run -it --cpuset-cpus="0-2" ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --cpuset-cpus="0-2" ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
This means processes in container can be executed on cpu 0, cpu 1 and cpu 2.
|
||||
@@ -599,14 +599,14 @@ on NUMA systems.
|
||||
Examples:
|
||||
|
||||
```console
|
||||
$ docker run -it --cpuset-mems="1,3" ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --cpuset-mems="1,3" ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
This example restricts the processes in the container to only use memory from
|
||||
memory nodes 1 and 3.
|
||||
|
||||
```console
|
||||
$ docker run -it --cpuset-mems="0-2" ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --cpuset-mems="0-2" ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
This example restricts the processes in the container to only use memory from
|
||||
@@ -637,8 +637,8 @@ For example, the commands below create two containers with different blkio
|
||||
weight:
|
||||
|
||||
```console
|
||||
$ docker run -it --name c1 --blkio-weight 300 ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --name c2 --blkio-weight 600 ubuntu:24.04 /bin/bash
|
||||
$ docker run -it --name c1 --blkio-weight 300 ubuntu:26.04 /bin/bash
|
||||
$ docker run -it --name c2 --blkio-weight 600 ubuntu:26.04 /bin/bash
|
||||
```
|
||||
|
||||
If you do block IO in the two containers at the same time, by, for example:
|
||||
@@ -854,11 +854,11 @@ For interacting with the network stack, instead of using `--privileged` they
|
||||
should use `--cap-add=NET_ADMIN` to modify the network interfaces.
|
||||
|
||||
```console
|
||||
$ docker run -it --rm ubuntu:24.04 ip link add dummy0 type dummy
|
||||
$ docker run -it --rm ubuntu:26.04 ip link add dummy0 type dummy
|
||||
|
||||
RTNETLINK answers: Operation not permitted
|
||||
|
||||
$ docker run -it --rm --cap-add=NET_ADMIN ubuntu:24.04 ip link add dummy0 type dummy
|
||||
$ docker run -it --rm --cap-add=NET_ADMIN ubuntu:26.04 ip link add dummy0 type dummy
|
||||
```
|
||||
|
||||
To mount a FUSE based filesystem, you need to combine both `--cap-add` and
|
||||
|
||||
@@ -62,6 +62,22 @@ func TestNonexistingHelp(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestNonexistingVersion ensures correct behaviour when invoking a
|
||||
// nonexistent plugin with `--version`.
|
||||
func TestNonexistingVersion(t *testing.T) {
|
||||
run, _, cleanup := prepare(t)
|
||||
defer cleanup()
|
||||
|
||||
for _, flag := range []string{"--version", "-v"} {
|
||||
res := icmd.RunCmd(run("nonexistent", flag))
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Out: icmd.None,
|
||||
})
|
||||
golden.Assert(t, res.Stderr(), "docker-nonexistent-err.golden")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunBad ensures correct behaviour when running an existent but invalid plugin
|
||||
func TestRunBad(t *testing.T) {
|
||||
run, _, cleanup := prepare(t)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,5 +30,5 @@ func TestOauthLogin(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
|
||||
output, _ := io.ReadAll(p)
|
||||
assert.Check(t, strings.Contains(string(output), "USING WEB-BASED LOGIN"), string(output))
|
||||
assert.Check(t, strings.Contains(string(output), "Using web-based login"), string(output))
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
@@ -17,8 +18,6 @@ import (
|
||||
"github.com/docker/cli/internal/registry"
|
||||
"github.com/docker/cli/internal/tui"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/pkg/browser"
|
||||
)
|
||||
|
||||
@@ -83,19 +82,18 @@ var ErrDeviceLoginStartFail = errors.New("failed to start device code flow login
|
||||
// The retrieved tokens are stored in the credentials store (under a separate
|
||||
// key), and the refresh token is concatenated with the client ID.
|
||||
func (m *OAuthManager) LoginDevice(ctx context.Context, w io.Writer) (*types.AuthConfig, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
state, err := m.api.GetDeviceCode(ctx, m.audience)
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to start device code login: %v", err)
|
||||
return nil, ErrDeviceLoginStartFail
|
||||
return nil, fmt.Errorf("%w: %w", ErrDeviceLoginStartFail, err)
|
||||
}
|
||||
|
||||
if state.UserCode == "" {
|
||||
logrus.Debugf("failed to start device code login: missing user code")
|
||||
return nil, ErrDeviceLoginStartFail
|
||||
return nil, fmt.Errorf("%w: missing user code", ErrDeviceLoginStartFail)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(w, aec.Bold.Apply("\nUSING WEB-BASED LOGIN"))
|
||||
|
||||
var out tui.Output
|
||||
switch stream := w.(type) {
|
||||
case *streams.Out:
|
||||
@@ -103,9 +101,10 @@ func (m *OAuthManager) LoginDevice(ctx context.Context, w io.Writer) (*types.Aut
|
||||
default:
|
||||
out = tui.NewOutput(streams.NewOut(w))
|
||||
}
|
||||
out.PrintNote("To sign in with credentials on the command line, use 'docker login -u <username>'\n")
|
||||
_, _ = fmt.Fprintf(w, "\nYour one-time device confirmation code is: "+aec.Bold.Apply("%s\n"), state.UserCode)
|
||||
_, _ = fmt.Fprintf(w, aec.Bold.Apply("Press ENTER")+" to open your browser or submit your device code here: "+aec.Underline.Apply("%s\n"), strings.Split(state.VerificationURI, "?")[0])
|
||||
out.PrintNote("Using web-based login. To sign in with credentials on the command line, use 'docker login -u <username>'\n")
|
||||
_, _ = fmt.Fprintln(w, "Your one-time device confirmation code is:", aec.Bold.Apply(state.UserCode))
|
||||
_, _ = fmt.Fprintln(w, "Open this URL to authenticate:", aec.Underline.Apply(state.VerificationURI))
|
||||
_, _ = fmt.Fprintln(w, "\n"+aec.Bold.Apply("Press ENTER")+" to open the URL in your browser, or "+aec.Bold.Apply("Ctrl+C")+" to cancel.")
|
||||
|
||||
tokenResChan := make(chan api.TokenResponse)
|
||||
waitForTokenErrChan := make(chan error)
|
||||
@@ -120,11 +119,25 @@ func (m *OAuthManager) LoginDevice(ctx context.Context, w io.Writer) (*types.Aut
|
||||
|
||||
go func() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
_, _ = reader.ReadString('\n')
|
||||
_ = m.openBrowser(state.VerificationURI)
|
||||
if _, err := reader.ReadString('\n'); err != nil || ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
err := m.openBrowser(state.VerificationURI)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
_, _ = fmt.Fprintln(w, "No browser found. Open the URL above manually to continue.")
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(w, "Failed to open browser:", err)
|
||||
_, _ = fmt.Fprintln(w, "Open the URL above manually to continue.")
|
||||
}
|
||||
return
|
||||
}
|
||||
_, _ = fmt.Fprintln(w, "\nOpening browser…")
|
||||
}()
|
||||
|
||||
_, _ = fmt.Fprint(w, "\nWaiting for authentication in the browser…\n")
|
||||
var tokenRes api.TokenResponse
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -69,10 +69,10 @@ func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.API
|
||||
// get the http transport, this will be used in a client to upload manifest
|
||||
base := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: endpoint.TLSConfig,
|
||||
DisableKeepAlives: true,
|
||||
|
||||
@@ -27,8 +27,13 @@ func TerminatePrompt(ctx context.Context, t *testing.T, cmd *cobra.Command, cli
|
||||
}))
|
||||
cli.SetOut(outStream)
|
||||
|
||||
r, _, err := os.Pipe()
|
||||
r, w, err := os.Pipe()
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = w.Close()
|
||||
_ = r.Close()
|
||||
})
|
||||
|
||||
cli.SetIn(streams.NewIn(r))
|
||||
|
||||
notifyCtx, notifyCancel := context.WithCancel(ctx)
|
||||
@@ -58,8 +63,6 @@ func TerminatePrompt(ctx context.Context, t *testing.T, cmd *cobra.Command, cli
|
||||
}()
|
||||
}
|
||||
|
||||
assert.Check(t, cli.OutBuffer().Len() > 0)
|
||||
|
||||
// a small delay to ensure the plugin is prompting
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
|
||||
|
||||
+14
-6
@@ -83,16 +83,24 @@ set as the **URL**, the repository is cloned locally and then sent as the contex
|
||||
Add a line to /etc/hosts. The format is hostname=ip, or hostname:ip.
|
||||
The **--add-host** option can be set multiple times.
|
||||
|
||||
**--build-arg** *variable*
|
||||
name and value of a **buildarg**.
|
||||
**--build-arg** *name*[=*value*]
|
||||
Set a build-time variable. The *name* is the variable name, as declared
|
||||
by an `ARG` instruction in the Dockerfile. If *value* is omitted, the
|
||||
value of the variable *name* from the local environment is used; if it is
|
||||
not set there either, the default from the `ARG` instruction (if any) is
|
||||
used.
|
||||
|
||||
Build-time variables are available as environment variables to commands
|
||||
run by the Dockerfile's `RUN` instructions, and can be used for variable
|
||||
expansion in other Dockerfile instructions. An environment variable defined
|
||||
by an `ENV` instruction always overrides an `ARG` of the same name. This
|
||||
mechanism is not meant for passing secret values.
|
||||
|
||||
For example, if you want to pass a value for `http_proxy`, use
|
||||
`--build-arg=http_proxy="http://some.proxy.url"`
|
||||
|
||||
Users pass these values at build-time. Docker uses the `buildargs` as the
|
||||
environment context for command(s) run via the Dockerfile's `RUN` instruction
|
||||
or for variable expansion in other Dockerfile instructions. This is not meant
|
||||
for passing secret values. [Read more about the buildargs instruction](https://docs.docker.com/engine/reference/builder/#arg)
|
||||
See also https://docs.docker.com/reference/dockerfile/#arg and
|
||||
https://docs.docker.com/reference/cli/docker/buildx/build/#build-arg.
|
||||
|
||||
**--cache-from** ""
|
||||
Set image that will be used as a build cache source.
|
||||
|
||||
@@ -48,10 +48,10 @@ Valid placeholders for the Go template are listed below:
|
||||
|
||||
$ docker container ls -a
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
a87ecb4f327c ubuntu:24.04 /bin/sh -c #(nop) MA 20 minutes ago Exit 0 desperate_brattain
|
||||
a87ecb4f327c ubuntu:26.04 /bin/sh -c #(nop) MA 20 minutes ago Exit 0 desperate_brattain
|
||||
01946d9d34d8 busybox /bin/sh -c #(nop) MA 33 minutes ago Exit 0 thirsty_bell
|
||||
c1d3b0166030 acffc0358b9e /bin/sh -c yum -y up 2 weeks ago Exit 1 determined_torvalds
|
||||
41d50ecd2f57 ubuntu:24.04 /bin/sh -c #(nop) MA 2 weeks ago Exit 0 drunk_pike
|
||||
41d50ecd2f57 ubuntu:26.04 /bin/sh -c #(nop) MA 2 weeks ago Exit 0 drunk_pike
|
||||
|
||||
## Display only IDs of all containers, including non-running
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ The title REPOSITORY for the first title may seem confusing. It is essentially
|
||||
the image name. However, because you can tag a specific image, and multiple tags
|
||||
(image instances) can be associated with a single name, the name is really a
|
||||
repository for all tagged images of the same name. For example consider an image
|
||||
called ubuntu. It may be tagged with 24.04 or 22.04, etc. to manage different
|
||||
called ubuntu. It may be tagged with 26.04 or 24.04, etc. to manage different
|
||||
versions.
|
||||
|
||||
## Filters
|
||||
|
||||
+19
-19
@@ -27,17 +27,17 @@ Docker Engine uses the `:latest` tag as a default. This example pulls the
|
||||
Docker images can consist of multiple layers. In the example above, the image
|
||||
consists of a single layer; `e756f3fdd6a3`.
|
||||
|
||||
Layers can be reused by images. For example, the `debian:bookworm` image shares
|
||||
its layer with the `debian:latest`. Pulling the `debian:bookworm` image therefore
|
||||
Layers can be reused by images. For example, the `debian:trixie` image shares
|
||||
its layer with the `debian:latest`. Pulling the `debian:trixie` image therefore
|
||||
only pulls its metadata, but not its layers, because the layer is already present
|
||||
locally:
|
||||
|
||||
$ docker image pull debian:bookworm
|
||||
$ docker image pull debian:trixie
|
||||
|
||||
bookworm: Pulling from library/debian
|
||||
trixie: Pulling from library/debian
|
||||
Digest: sha256:3f1d6c17773a45c97bd8f158d665c9709d7b29ed7917ac934086ad96f92e4510
|
||||
Status: Downloaded newer image for debian:bookworm
|
||||
docker.io/library/debian:bookworm
|
||||
Status: Downloaded newer image for debian:trixie
|
||||
docker.io/library/debian:trixie
|
||||
|
||||
To see which images are present locally, use the **docker-images(1)**
|
||||
command:
|
||||
@@ -45,12 +45,12 @@ command:
|
||||
$ docker images
|
||||
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
debian bookworm 4eacea30377a 8 days ago 124MB
|
||||
debian trixie 4eacea30377a 8 days ago 124MB
|
||||
debian latest 4eacea30377a 8 days ago 124MB
|
||||
|
||||
Docker uses a content-addressable image store, and the image ID is a SHA256
|
||||
digest covering the image's configuration and layers. In the example above,
|
||||
`debian:bookworm` and `debian:latest` have the same image ID because they are
|
||||
`debian:trixie` and `debian:latest` have the same image ID because they are
|
||||
the *same* image tagged with different names. Because they are the same image,
|
||||
their layers are stored only once and do not consume extra disk space.
|
||||
|
||||
@@ -64,8 +64,8 @@ in the online documentation.
|
||||
So far, you've pulled images by their name (and "tag"). Using names and tags is
|
||||
a convenient way to work with images. When using tags, you can `docker image pull` an
|
||||
image again to make sure you have the most up-to-date version of that image.
|
||||
For example, `docker image pull ubuntu:24.04` pulls the latest version of the Ubuntu
|
||||
24.04 image.
|
||||
For example, `docker image pull ubuntu:26.04` pulls the latest version of the Ubuntu
|
||||
26.04 image.
|
||||
|
||||
In some cases you don't want images to be updated to newer versions, but prefer
|
||||
to use a fixed version of an image. Docker enables you to pull an image by its
|
||||
@@ -74,15 +74,15 @@ of an image to pull. Doing so, allows you to "pin" an image to that version,
|
||||
and guarantee that the image you're using is always the same.
|
||||
|
||||
To know the digest of an image, pull the image first. Let's pull the latest
|
||||
`ubuntu:24.04` image from Docker Hub:
|
||||
`ubuntu:26.04` image from Docker Hub:
|
||||
|
||||
$ docker image pull ubuntu:24.04
|
||||
$ docker image pull ubuntu:26.04
|
||||
|
||||
24.04: Pulling from library/ubuntu
|
||||
26.04: Pulling from library/ubuntu
|
||||
125a6e411906: Pull complete
|
||||
Digest: sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30
|
||||
Status: Downloaded newer image for ubuntu:24.04
|
||||
docker.io/library/ubuntu:24.04
|
||||
Status: Downloaded newer image for ubuntu:26.04
|
||||
docker.io/library/ubuntu:26.04
|
||||
|
||||
Docker prints the digest of the image after the pull has finished. In the example
|
||||
above, the digest of the image is:
|
||||
@@ -159,11 +159,11 @@ images that are present locally:
|
||||
|
||||
$ docker image ls --filter reference=ubuntu
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
ubuntu 22.04 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu jammy 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu 24.04 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu 24.04 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu noble 8a3cdc4d1ad3 3 weeks ago 77.9MB
|
||||
ubuntu 26.04 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu latest 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu noble 35a88802559d 6 weeks ago 78.1MB
|
||||
ubuntu resolute 35a88802559d 6 weeks ago 78.1MB
|
||||
|
||||
## Cancel a pull
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ The following example outputs all events that were generated in the last 3 minut
|
||||
relative to the current time on the client machine:
|
||||
|
||||
# docker events --since '3m'
|
||||
2015-05-12T11:51:30.999999999Z07:00 4386fb97867d: (from ubuntu:24.04) die
|
||||
2015-05-12T15:52:12.999999999Z07:00 4386fb97867d: (from ubuntu:24.04) stop
|
||||
2015-05-12T11:51:30.999999999Z07:00 4386fb97867d: (from ubuntu:26.04) die
|
||||
2015-05-12T15:52:12.999999999Z07:00 4386fb97867d: (from ubuntu:26.04) stop
|
||||
2015-05-12T15:53:45.999999999Z07:00 7805c1d35632: (from redis:7.2) die
|
||||
2015-05-12T15:54:03.999999999Z07:00 7805c1d35632: (from redis:7.2) stop
|
||||
|
||||
@@ -97,21 +97,21 @@ Lines. For information about JSON Lines, refer to https://jsonlines.org .
|
||||
## Filters
|
||||
|
||||
$ docker events --filter 'event=stop'
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:24.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:26.04)
|
||||
2014-09-03T17:42:14.999999999Z07:00 container stop 7805c1d35632 (image=redis:7.2)
|
||||
|
||||
$ docker events --filter 'image=ubuntu:24.04'
|
||||
2014-05-10T17:42:14.999999999Z07:00 container start 4386fb97867d (image=ubuntu:24.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container die 4386fb97867d (image=ubuntu:24.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:24.04)
|
||||
$ docker events --filter 'image=ubuntu:26.04'
|
||||
2014-05-10T17:42:14.999999999Z07:00 container start 4386fb97867d (image=ubuntu:26.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container die 4386fb97867d (image=ubuntu:26.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:26.04)
|
||||
|
||||
$ docker events --filter 'container=7805c1d35632'
|
||||
2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (image=redis:7.2)
|
||||
2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image= redis:7.2)
|
||||
|
||||
$ docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d'
|
||||
2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu:24.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:24.04)
|
||||
2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu:26.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu:26.04)
|
||||
2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (image=redis:7.2)
|
||||
2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:7.2)
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ Server:
|
||||
seccomp
|
||||
Profile: builtin
|
||||
cgroupns
|
||||
Kernel Version: 5.15.0-25-generic
|
||||
Operating System: Ubuntu 22.04 LTS
|
||||
Kernel Version: 6.8.0-31-generic
|
||||
Operating System: Ubuntu 24.04 LTS
|
||||
OSType: linux
|
||||
Architecture: x86_64
|
||||
CPUs: 1
|
||||
|
||||
@@ -88,13 +88,20 @@ if [ "$CGO_ENABLED" = "1" ] && [ "$(go env GOOS)" != "windows" ]; then
|
||||
fi
|
||||
export GO_BUILDMODE
|
||||
|
||||
GO_LDFLAGS="${GO_LDFLAGS:-}"
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.GitCommit=${GITCOMMIT}\""
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.BuildTime=${BUILDTIME}\""
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.Version=${VERSION}\""
|
||||
if test -n "${PLATFORM}"; then
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.PlatformName=${PLATFORM}\""
|
||||
fi
|
||||
# Omit DWARF debug information to reduce binary size, but retain the symbol
|
||||
# table for tools that inspect Go binaries. Pass GO_LDFLAGS="-w=0" to retain DWARF.
|
||||
GO_LDFLAGS="-w ${GO_LDFLAGS:-}"
|
||||
|
||||
# Quotes are interpreted by Go's -ldflags parser, not the shell.
|
||||
# shellcheck disable=SC2089
|
||||
{
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.GitCommit=${GITCOMMIT}\""
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.BuildTime=${BUILDTIME}\""
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.Version=${VERSION}\""
|
||||
if test -n "${PLATFORM}"; then
|
||||
GO_LDFLAGS="$GO_LDFLAGS -X \"github.com/docker/cli/cli/version.PlatformName=${PLATFORM}\""
|
||||
fi
|
||||
}
|
||||
if [ "$CGO_ENABLED" = "1" ] && [ "$GO_LINKMODE" = "static" ] && [ "$(go env GOOS)" = "linux" ]; then
|
||||
GO_LDFLAGS="$GO_LDFLAGS -linkmode external -extldflags -static"
|
||||
fi
|
||||
@@ -128,7 +135,7 @@ if [ -n "$GO_STRIP" ]; then
|
||||
# /usr/local/go/pkg/tool/linux_amd64/link: /usr/local/go/pkg/tool/linux_amd64/link: running strip failed: exit status 1
|
||||
# llvm-strip: error: unsupported load command (cmd=0x5)
|
||||
# more info: https://github.com/docker/cli/pull/3717
|
||||
GO_LDFLAGS="$GO_LDFLAGS -s -w"
|
||||
GO_LDFLAGS="$GO_LDFLAGS -s"
|
||||
fi
|
||||
export GO_LDFLAGS="$GO_LDFLAGS" # https://github.com/koalaman/shellcheck/issues/2064
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+31
-30
@@ -21,24 +21,24 @@ require (
|
||||
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/docker-credential-helpers v0.9.12
|
||||
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
|
||||
github.com/fvbommel/sortorder v1.2.1
|
||||
github.com/go-jose/go-jose/v4 v4.1.7
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
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.1
|
||||
github.com/moby/moby/client v0.6.1
|
||||
github.com/moby/moby/api v1.56.2-0.20261106011722-402d0a16f131
|
||||
github.com/moby/moby/client v0.6.3-0.20261106011722-402d0a16f131
|
||||
github.com/moby/patternmatcher v0.6.1
|
||||
github.com/moby/swarmkit/v2 v2.1.2
|
||||
github.com/moby/swarmkit/v2 v2.1.3
|
||||
github.com/moby/sys/atomicwriter v0.1.0
|
||||
github.com/moby/sys/capability v0.4.0
|
||||
github.com/moby/sys/capability v0.5.0
|
||||
github.com/moby/sys/sequential v0.7.0
|
||||
github.com/moby/sys/signal v0.7.1
|
||||
github.com/moby/sys/symlink v0.3.0
|
||||
@@ -52,26 +52,26 @@ require (
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0
|
||||
go.opentelemetry.io/otel v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0
|
||||
go.opentelemetry.io/otel/metric v1.46.0
|
||||
go.opentelemetry.io/otel/sdk v1.46.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0
|
||||
go.opentelemetry.io/otel/trace v1.46.0
|
||||
go.yaml.in/yaml/v3 v3.0.5
|
||||
golang.org/x/sync v0.23.0
|
||||
golang.org/x/sys v0.48.0
|
||||
golang.org/x/term v0.46.0
|
||||
golang.org/x/text v0.42.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0
|
||||
go.opentelemetry.io/otel v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0
|
||||
go.opentelemetry.io/otel/metric v1.47.0
|
||||
go.opentelemetry.io/otel/sdk v1.47.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0
|
||||
go.opentelemetry.io/otel/trace v1.47.0
|
||||
go.yaml.in/yaml/v3 v3.0.6
|
||||
golang.org/x/sync v0.24.0
|
||||
golang.org/x/sys v0.49.1-0.20261022125458-f5cb0306ac0e // includes fix for https://go.dev/issue/82324
|
||||
golang.org/x/term v0.47.0
|
||||
golang.org/x/text v0.43.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
tags.cncf.io/container-device-interface v1.1.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20260917205352-e937bb47801a // indirect
|
||||
github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0 // indirect; see https://github.com/microsoft/hcsshim/pull/2545
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc // indirect
|
||||
github.com/Microsoft/go-winio v0.6.3 // indirect
|
||||
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.20.1 // indirect
|
||||
github.com/klauspost/compress v1.20.2 // 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
|
||||
@@ -99,13 +99,14 @@ require (
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.etcd.io/raft/v3 v3.6.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 // indirect
|
||||
go.opentelemetry.io/otel/log v1.47.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/mod v0.42.0 // indirect
|
||||
golang.org/x/net v0.62.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
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
)
|
||||
|
||||
+64
-65
@@ -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-20260917205352-e937bb47801a h1:4hxax9ktNjSDoFn1tZSeL6gXMTRMpO0FzjdKfT01jgY=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20260917205352-e937bb47801a/go.mod h1:3EWSSOZ50kb+arhww0qIaEXHToib/3FjBj9Jj5lcP5g=
|
||||
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/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc h1:58s5YzGPNAP7+e1uemaL0ax/NPSeJOHdRpRIowd+JDk=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc/go.mod h1:DMNF6Kxv+EcuUvVuNkvC3f+BjEEmc/Fn9aU+qiD8zpU=
|
||||
github.com/Microsoft/go-winio v0.6.3 h1:M/52tsoyTfwaxajLnXZm7PqkbMGRH8iSWP+8fcg9MQ4=
|
||||
github.com/Microsoft/go-winio v0.6.3/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=
|
||||
@@ -35,10 +35,10 @@ github.com/docker/cli-docs-tool v0.11.0 h1:7d8QARFb7QEobizqxmEM7fOteZEHwH/zWgHQt
|
||||
github.com/docker/cli-docs-tool v0.11.0/go.mod h1:ma8BKiisUo8D6W05XEYIh3oa1UbgrZhi1nowyKFJa8Q=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
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/docker-credential-helpers v0.9.12 h1:hAgjJcMuC17DPf0Kl/HQSZQ7cxVo+wEA8Oo1SzkmqFg=
|
||||
github.com/docker/docker-credential-helpers v0.9.12/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
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=
|
||||
@@ -49,10 +49,10 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
|
||||
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
|
||||
github.com/fvbommel/sortorder v1.2.0 h1:TRIiRiGX+djh3Yf4FVxmWmAcYfIr5dH0NbzJWOSAWZk=
|
||||
github.com/fvbommel/sortorder v1.2.0/go.mod h1:LbhO04ijZIeUuvz9B9BkI/qYrpZZEn1gWhxv4QjUKVs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/fvbommel/sortorder v1.2.1 h1:ASc85RbthwLEqzuiCk7WQQiopfyGWQiEYZVJ3Qa4A+U=
|
||||
github.com/fvbommel/sortorder v1.2.1/go.mod h1:LbhO04ijZIeUuvz9B9BkI/qYrpZZEn1gWhxv4QjUKVs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -78,28 +78,28 @@ 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.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
|
||||
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||
github.com/klauspost/compress v1.20.2 h1:v5tiZvm1KGLyg5d5oXdXe3lsSPrbEUiKoXGia//I7Qo=
|
||||
github.com/klauspost/compress v1.20.2/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.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.1 h1:gvfKmdcWw+9MzXYP0vAUKQtBTGu1L8475K2nsmL7O98=
|
||||
github.com/moby/moby/client v0.6.1/go.mod h1:XHgTFqz9NCgS/VuoxXMmEDVmVgXQ4vFEc15wsCIwb24=
|
||||
github.com/moby/moby/api v1.56.2-0.20261106011722-402d0a16f131 h1:tPz9ahj8F7AjQw62XQFP65kmoxF3rXTYSit3q06/yQc=
|
||||
github.com/moby/moby/api v1.56.2-0.20261106011722-402d0a16f131/go.mod h1:pRZ1QRtx6AVhdckeFYo4DUX4ASNF0SCllhyY6aj2mgg=
|
||||
github.com/moby/moby/client v0.6.3-0.20261106011722-402d0a16f131 h1:ReIO7lalQVE4BetZuIvrxScg8Rxv4Sk74XRs06ryRDU=
|
||||
github.com/moby/moby/client v0.6.3-0.20261106011722-402d0a16f131/go.mod h1:dPyRteJDuLdP8to3kWmNAU/tbMwSFCE88yKHvY/nZ9k=
|
||||
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=
|
||||
github.com/moby/swarmkit/v2 v2.1.2/go.mod h1:GQ6T0ij2oBbWX10OHwpvK449xfXkQMZ8J+B+eQ4mgp4=
|
||||
github.com/moby/swarmkit/v2 v2.1.3 h1:UcwtwkIRjr75qSgUWCTIuzKUzAXlidxsiqVMnyxSF1w=
|
||||
github.com/moby/swarmkit/v2 v2.1.3/go.mod h1:KhkxT93Hm4wHoNUPJ4JRD384pS7OODFGzJWoDRj9bLE=
|
||||
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/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
||||
github.com/moby/sys/capability v0.5.0 h1:Bm4oD9xGCtZguPnaoQy1/jvKTSwZHMvM0FrnxNYtGYU=
|
||||
github.com/moby/sys/capability v0.5.0/go.mod h1:i+0cXvNSriGrL3C6sxnmlmGL5HVU0OdxocizXdC8A+g=
|
||||
github.com/moby/sys/mount v0.3.5 h1:eS3fsZTjHaBihwjp4/+5Z3jxqLXYsbwxqpVSfFv3M00=
|
||||
github.com/moby/sys/mount v0.3.5/go.mod h1:WUQDO+/uCiCIkIztx8SrwIDVn2dtMFRBebRhpDFT71M=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
@@ -155,64 +155,65 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
|
||||
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU=
|
||||
go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
|
||||
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0 h1:qkDYCAFiZXLcs1L4aY+tP2wguQ4kURANqHOQMA2et2s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0/go.mod h1:tkipS4DRzmpAmvg+Gw4++O1IdDq6TVDnvnYU6cmbQVs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 h1:w53CDeOA/Kurp7yRsegSr6pbbr759dOvJ+yNmWM6Hxs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0/go.mod h1:BOmGMCbAtvcJiSJ+hLuhgPLdDbimnraSl8irz3iY8sY=
|
||||
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
|
||||
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
|
||||
go.opentelemetry.io/otel/metric/x v0.68.0 h1:TA/cBT23D3MnxYPwHL7YFOdYGdx0A0v+s7Mzotpd1dU=
|
||||
go.opentelemetry.io/otel/metric/x v0.68.0/go.mod h1:agudOmvWhwUTjgibWDzxD2PoWYnpw5Ht5jISYOD2Hd4=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4=
|
||||
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
|
||||
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0 h1:LxwW/9ctSCv+QkE/cLR7M91ZIkXNMqJtEMi1vCw9U8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0/go.mod h1:tOsftB4SslBwwErVEPaenU2RpThXWPIU8DoJHEC4dyw=
|
||||
go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM=
|
||||
go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.47.0 h1:LaLVGJtIzHcvHjVs2OftmKcc+M9mdOqS8vZ1fBD1Yes=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.47.0/go.mod h1:vF6H5sNDuQBJWgSX00iZgbIqk2MZZoVMH0ar5t5bOpw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSdDdqt5h7Zw9YWmRlWRhI0jd54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxOpYPmMNUtOWhdb+nC1WELIgVf8z9higURrbzYU4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc=
|
||||
go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI=
|
||||
go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw=
|
||||
go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY=
|
||||
go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU=
|
||||
go.opentelemetry.io/otel/metric/x v0.69.0 h1:DjRLr15H83v+hCW7JA9NoJvOkYTtmq5YoDRbe9deYpM=
|
||||
go.opentelemetry.io/otel/metric/x v0.69.0/go.mod h1:uVvsMPMFFyj/HUQfrUnH3JjnOQ1dwFDorgFLRBasM0k=
|
||||
go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I=
|
||||
go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw=
|
||||
go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM=
|
||||
go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
go.yaml.in/yaml/v3 v3.0.6 h1:zcDfvjXEqTQmpjniUBLc91xYJMB2c4QOFxEF/LCEckM=
|
||||
go.yaml.in/yaml/v3 v3.0.6/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
|
||||
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
||||
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=
|
||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
||||
golang.org/x/sync v0.24.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-20220412211240-33da011f77ad/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=
|
||||
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/sys v0.49.1-0.20261022125458-f5cb0306ac0e h1:y3PdhEKVIFYc4c6gNZPEC7qPIoa9uH/2zIQEc0mw4R4=
|
||||
golang.org/x/sys v0.49.1-0.20261022125458-f5cb0306ac0e/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
|
||||
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
||||
golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
|
||||
golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -225,10 +226,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 h1:izFU9hz7aeLI/Mi1J0991ae+xcwRLr7hTqWnB/9aIIU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5/go.mod h1:3LhxRw4YYkf+ylAfgaY9JlVLFKhokkCV8duhLLe7+t0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc h1:rZHRz0ogq4Pid1IKtA/ivHi8XONVF5tqUQXA2TtgpkE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:K/n0XLazCJHjD73YuMyNJsN38BPPy1kfFnKxUXaThFU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc h1:4bNTbnb44EqGVy9HaQxSY2jnafSUdgV3qHtedvNpDKg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
@@ -236,7 +237,5 @@ google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
tags.cncf.io/container-device-interface v1.1.1 h1:YPwQz4xg8PlQ0yT/baR0BtLpTQROe4l6M1yuRgAu1vc=
|
||||
tags.cncf.io/container-device-interface v1.1.1/go.mod h1:S1PSJWYPD4Iom0/39mvr/VVFCfG0Yt14j20d0OYrY2M=
|
||||
|
||||
+7
-5
@@ -1,5 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
import "slices"
|
||||
|
||||
type csiEntryState struct {
|
||||
baseState
|
||||
}
|
||||
@@ -13,11 +15,11 @@ func (csiState csiEntryState) Handle(b byte) (s state, e error) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(alphabetics, b):
|
||||
case slices.Contains(alphabetics, b):
|
||||
return csiState.parser.ground, nil
|
||||
case sliceContains(csiCollectables, b):
|
||||
case slices.Contains(csiCollectables, b):
|
||||
return csiState.parser.csiParam, nil
|
||||
case sliceContains(executors, b):
|
||||
case slices.Contains(executors, b):
|
||||
return csiState, csiState.parser.execute()
|
||||
}
|
||||
|
||||
@@ -33,9 +35,9 @@ func (csiState csiEntryState) Transition(s state) error {
|
||||
return csiState.parser.csiDispatch()
|
||||
case csiState.parser.csiParam:
|
||||
switch {
|
||||
case sliceContains(csiParams, csiState.parser.context.currentChar):
|
||||
case slices.Contains(csiParams, csiState.parser.context.currentChar):
|
||||
_ = csiState.parser.collectParam()
|
||||
case sliceContains(intermeds, csiState.parser.context.currentChar):
|
||||
case slices.Contains(intermeds, csiState.parser.context.currentChar):
|
||||
_ = csiState.parser.collectInter()
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -1,5 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
import "slices"
|
||||
|
||||
type csiParamState struct {
|
||||
baseState
|
||||
}
|
||||
@@ -13,11 +15,11 @@ func (csiState csiParamState) Handle(b byte) (s state, e error) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(alphabetics, b):
|
||||
case slices.Contains(alphabetics, b):
|
||||
return csiState.parser.ground, nil
|
||||
case sliceContains(csiCollectables, b):
|
||||
case slices.Contains(csiCollectables, b):
|
||||
return csiState, csiState.parser.collectParam()
|
||||
case sliceContains(executors, b):
|
||||
case slices.Contains(executors, b):
|
||||
return csiState, csiState.parser.execute()
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -1,5 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
import "slices"
|
||||
|
||||
type escapeIntermediateState struct {
|
||||
baseState
|
||||
}
|
||||
@@ -12,11 +14,11 @@ func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(intermeds, b):
|
||||
case slices.Contains(intermeds, b):
|
||||
return escState, escState.parser.collectInter()
|
||||
case sliceContains(executors, b):
|
||||
case slices.Contains(executors, b):
|
||||
return escState, escState.parser.execute()
|
||||
case sliceContains(escapeIntermediateToGroundBytes, b):
|
||||
case slices.Contains(escapeIntermediateToGroundBytes, b):
|
||||
return escState.parser.ground, nil
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -1,5 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
import "slices"
|
||||
|
||||
type escapeState struct {
|
||||
baseState
|
||||
}
|
||||
@@ -16,11 +18,11 @@ func (escState escapeState) Handle(b byte) (s state, e error) {
|
||||
return escState.parser.csiEntry, nil
|
||||
case b == ANSI_OSC_STRING_ENTRY:
|
||||
return escState.parser.oscString, nil
|
||||
case sliceContains(executors, b):
|
||||
case slices.Contains(executors, b):
|
||||
return escState, escState.parser.execute()
|
||||
case sliceContains(escapeToGroundBytes, b):
|
||||
case slices.Contains(escapeToGroundBytes, b):
|
||||
return escState.parser.ground, nil
|
||||
case sliceContains(intermeds, b):
|
||||
case slices.Contains(intermeds, b):
|
||||
return escState.parser.escapeIntermediate, nil
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
import "slices"
|
||||
|
||||
type groundState struct {
|
||||
baseState
|
||||
}
|
||||
@@ -13,10 +15,10 @@ func (gs groundState) Handle(b byte) (s state, e error) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(printables, b):
|
||||
case slices.Contains(printables, b):
|
||||
return gs, gs.parser.print()
|
||||
|
||||
case sliceContains(executors, b):
|
||||
case slices.Contains(executors, b):
|
||||
return gs, gs.parser.execute()
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -20,12 +20,12 @@ type AnsiParser struct {
|
||||
oscString state
|
||||
stateMap []state
|
||||
|
||||
logf func(string, ...interface{})
|
||||
logf func(string, ...any)
|
||||
}
|
||||
|
||||
type Option func(*AnsiParser)
|
||||
|
||||
func WithLogf(f func(string, ...interface{})) Option {
|
||||
func WithLogf(f func(string, ...any)) Option {
|
||||
return func(ap *AnsiParser) {
|
||||
ap.logf = f
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Opti
|
||||
logger := log.New(logFile, "", log.LstdFlags)
|
||||
if ap.logf != nil {
|
||||
l := ap.logf
|
||||
ap.logf = func(s string, v ...interface{}) {
|
||||
ap.logf = func(s string, v ...any) {
|
||||
l(s, v...)
|
||||
logger.Printf(s, v...)
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Opti
|
||||
}
|
||||
|
||||
if ap.logf == nil {
|
||||
ap.logf = func(string, ...interface{}) {}
|
||||
ap.logf = func(string, ...any) {}
|
||||
}
|
||||
|
||||
ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ func getInts(params []string, minCount int, dflt int) []int {
|
||||
|
||||
if len(ints) < minCount {
|
||||
remaining := minCount - len(ints)
|
||||
for i := 0; i < remaining; i++ {
|
||||
for range remaining {
|
||||
ints = append(ints, dflt)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
import "slices"
|
||||
|
||||
type state interface {
|
||||
Enter() error
|
||||
Exit() error
|
||||
@@ -32,7 +34,7 @@ func (base baseState) Handle(b byte) (s state, e error) {
|
||||
return base.parser.escape, nil
|
||||
case b == OSC_STRING:
|
||||
return base.parser.oscString, nil
|
||||
case sliceContains(toGroundBytes, b):
|
||||
case slices.Contains(toGroundBytes, b):
|
||||
return base.parser.ground, nil
|
||||
}
|
||||
|
||||
@@ -52,7 +54,7 @@ func (base baseState) Transition(s state) error {
|
||||
execBytes = append(execBytes, 0x99)
|
||||
execBytes = append(execBytes, 0x9A)
|
||||
|
||||
if sliceContains(execBytes, base.parser.context.currentChar) {
|
||||
if slices.Contains(execBytes, base.parser.context.currentChar) {
|
||||
return base.parser.execute()
|
||||
}
|
||||
}
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package ansiterm
|
||||
|
||||
func sliceContains(bytes []byte, b byte) bool {
|
||||
for _, v := range bytes {
|
||||
if v == b {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
|
||||
+96
-90
@@ -1,14 +1,17 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//===========================================================================================================
|
||||
// ===========================================================================================================
|
||||
// IMPORTANT NOTE:
|
||||
//
|
||||
// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
|
||||
@@ -26,51 +29,50 @@ import (
|
||||
// -- The value is not referenced by the method after passing the pointer to Windows
|
||||
//
|
||||
// See http://golang.org/doc/go1.3.
|
||||
//===========================================================================================================
|
||||
// ===========================================================================================================
|
||||
|
||||
var (
|
||||
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||
kernel32DLL = windows.NewLazyDLL("kernel32.dll")
|
||||
|
||||
getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
|
||||
setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
|
||||
setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
|
||||
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
|
||||
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
|
||||
setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
|
||||
scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
|
||||
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
|
||||
setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
|
||||
writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
|
||||
readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
|
||||
waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
|
||||
)
|
||||
|
||||
// Windows Console constants
|
||||
// Console modes
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||
const (
|
||||
// Console modes
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||
ENABLE_PROCESSED_INPUT = 0x0001
|
||||
ENABLE_LINE_INPUT = 0x0002
|
||||
ENABLE_ECHO_INPUT = 0x0004
|
||||
ENABLE_WINDOW_INPUT = 0x0008
|
||||
ENABLE_MOUSE_INPUT = 0x0010
|
||||
ENABLE_INSERT_MODE = 0x0020
|
||||
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||
ENABLE_EXTENDED_FLAGS = 0x0080
|
||||
ENABLE_AUTO_POSITION = 0x0100
|
||||
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
|
||||
ENABLE_PROCESSED_INPUT = windows.ENABLE_PROCESSED_INPUT
|
||||
ENABLE_LINE_INPUT = windows.ENABLE_LINE_INPUT
|
||||
ENABLE_ECHO_INPUT = windows.ENABLE_ECHO_INPUT
|
||||
ENABLE_WINDOW_INPUT = windows.ENABLE_WINDOW_INPUT
|
||||
ENABLE_MOUSE_INPUT = windows.ENABLE_MOUSE_INPUT
|
||||
ENABLE_INSERT_MODE = windows.ENABLE_INSERT_MODE
|
||||
ENABLE_QUICK_EDIT_MODE = windows.ENABLE_QUICK_EDIT_MODE
|
||||
ENABLE_EXTENDED_FLAGS = windows.ENABLE_EXTENDED_FLAGS
|
||||
ENABLE_AUTO_POSITION = windows.ENABLE_AUTO_POSITION
|
||||
ENABLE_VIRTUAL_TERMINAL_INPUT = windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||
|
||||
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
||||
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
|
||||
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
|
||||
ENABLE_PROCESSED_OUTPUT = windows.ENABLE_PROCESSED_OUTPUT
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT = windows.ENABLE_WRAP_AT_EOL_OUTPUT
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
DISABLE_NEWLINE_AUTO_RETURN = windows.DISABLE_NEWLINE_AUTO_RETURN
|
||||
ENABLE_LVB_GRID_WORLDWIDE = windows.ENABLE_LVB_GRID_WORLDWIDE
|
||||
)
|
||||
|
||||
// Character attributes
|
||||
// Note:
|
||||
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
|
||||
// Clearing all foreground or background colors results in black; setting all creates white.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
|
||||
// Character attributes
|
||||
//
|
||||
// Foreground and background attributes can be combined to produce colors.
|
||||
// Clearing all foreground or background color bits produces black; setting
|
||||
// all color bits produces white.
|
||||
//
|
||||
// See https://learn.microsoft.com/windows/console/char-info-str.
|
||||
const (
|
||||
FOREGROUND_BLUE uint16 = 0x0001
|
||||
FOREGROUND_GREEN uint16 = 0x0002
|
||||
FOREGROUND_RED uint16 = 0x0004
|
||||
@@ -86,23 +88,29 @@ const (
|
||||
COMMON_LVB_MASK uint16 = 0xFF00
|
||||
COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
|
||||
COMMON_LVB_UNDERSCORE uint16 = 0x8000
|
||||
)
|
||||
|
||||
// Input event types
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||
KEY_EVENT = 0x0001
|
||||
MOUSE_EVENT = 0x0002
|
||||
WINDOW_BUFFER_SIZE_EVENT = 0x0004
|
||||
MENU_EVENT = 0x0008
|
||||
FOCUS_EVENT = 0x0010
|
||||
// Input event types
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||
const (
|
||||
KEY_EVENT = windows.KEY_EVENT
|
||||
MOUSE_EVENT = windows.MOUSE_EVENT
|
||||
WINDOW_BUFFER_SIZE_EVENT = windows.WINDOW_BUFFER_SIZE_EVENT
|
||||
MENU_EVENT = windows.MENU_EVENT
|
||||
FOCUS_EVENT = windows.FOCUS_EVENT
|
||||
)
|
||||
|
||||
// WaitForSingleObject return codes
|
||||
WAIT_ABANDONED = 0x00000080
|
||||
WAIT_FAILED = 0xFFFFFFFF
|
||||
WAIT_SIGNALED = 0x0000000
|
||||
WAIT_TIMEOUT = 0x00000102
|
||||
// WaitForSingleObject return codes.
|
||||
const (
|
||||
WAIT_ABANDONED = windows.WAIT_ABANDONED
|
||||
WAIT_FAILED = windows.WAIT_FAILED
|
||||
WAIT_SIGNALED = windows.WAIT_OBJECT_0
|
||||
WAIT_TIMEOUT = 0x00000102 // Keep as untyped int for backward compat; windows.WAIT_TIMEOUT is typed as syscall.Errno.
|
||||
)
|
||||
|
||||
// WaitForSingleObject wait duration
|
||||
WAIT_INFINITE = 0xFFFFFFFF
|
||||
// WaitForSingleObject wait durations.
|
||||
const (
|
||||
WAIT_INFINITE = windows.INFINITE
|
||||
WAIT_ONE_SECOND = 1000
|
||||
WAIT_HALF_SECOND = 500
|
||||
WAIT_QUARTER_SECOND = 250
|
||||
@@ -122,6 +130,8 @@ type (
|
||||
Visible int32
|
||||
}
|
||||
|
||||
// CONSOLE_SCREEN_BUFFER_INFO is identical to [windows.ConsoleScreenBufferInfo]
|
||||
// but implements [fmt.Stringer].
|
||||
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||
Size COORD
|
||||
CursorPosition COORD
|
||||
@@ -130,17 +140,11 @@ type (
|
||||
MaximumWindowSize COORD
|
||||
}
|
||||
|
||||
COORD struct {
|
||||
X int16
|
||||
Y int16
|
||||
}
|
||||
// COORD is identical to [windows.Coord] but implements [fmt.Stringer].
|
||||
COORD windows.Coord
|
||||
|
||||
SMALL_RECT struct {
|
||||
Left int16
|
||||
Top int16
|
||||
Right int16
|
||||
Bottom int16
|
||||
}
|
||||
// SMALL_RECT is identical to [windows.SmallRect] but implements [fmt.Stringer].
|
||||
SMALL_RECT windows.SmallRect
|
||||
|
||||
// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||
@@ -189,43 +193,45 @@ func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error
|
||||
// SetConsoleCursorPosition location of the console cursor.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
|
||||
func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
|
||||
r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
|
||||
use(coord)
|
||||
return checkError(r1, r2, err)
|
||||
return windows.SetConsoleCursorPosition(windows.Handle(handle), windows.Coord(coord))
|
||||
}
|
||||
|
||||
// GetConsoleMode gets the console mode for given file descriptor
|
||||
// GetConsoleMode gets the console mode for the given handle.
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
|
||||
func GetConsoleMode(handle uintptr) (mode uint32, err error) {
|
||||
err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
|
||||
err = windows.GetConsoleMode(windows.Handle(handle), &mode)
|
||||
return mode, err
|
||||
}
|
||||
|
||||
// SetConsoleMode sets the console mode for given file descriptor
|
||||
// SetConsoleMode sets the console mode for the given handle.
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||
func SetConsoleMode(handle uintptr, mode uint32) error {
|
||||
r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
|
||||
use(mode)
|
||||
return checkError(r1, r2, err)
|
||||
return windows.SetConsoleMode(windows.Handle(handle), mode)
|
||||
}
|
||||
|
||||
// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
|
||||
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||
info := CONSOLE_SCREEN_BUFFER_INFO{}
|
||||
err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
|
||||
if err != nil {
|
||||
var info windows.ConsoleScreenBufferInfo
|
||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(handle), &info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
|
||||
return &CONSOLE_SCREEN_BUFFER_INFO{
|
||||
Size: COORD(info.Size),
|
||||
CursorPosition: COORD(info.CursorPosition),
|
||||
Attributes: info.Attributes,
|
||||
Window: SMALL_RECT(info.Window),
|
||||
MaximumWindowSize: COORD(info.MaximumWindowSize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
|
||||
r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
|
||||
use(scrollRect)
|
||||
use(clipRect)
|
||||
use(destOrigin)
|
||||
use(char)
|
||||
runtime.KeepAlive(scrollRect)
|
||||
runtime.KeepAlive(clipRect)
|
||||
runtime.KeepAlive(destOrigin)
|
||||
runtime.KeepAlive(char)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
@@ -233,7 +239,7 @@ func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect S
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
|
||||
func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
|
||||
r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
|
||||
use(coord)
|
||||
runtime.KeepAlive(coord)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
@@ -242,7 +248,7 @@ func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
|
||||
func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
|
||||
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
|
||||
use(attribute)
|
||||
runtime.KeepAlive(attribute)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
@@ -251,8 +257,8 @@ func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
|
||||
func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
|
||||
r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
|
||||
use(isAbsolute)
|
||||
use(rect)
|
||||
runtime.KeepAlive(isAbsolute)
|
||||
runtime.KeepAlive(rect)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
@@ -260,9 +266,9 @@ func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) erro
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
|
||||
func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
|
||||
r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
|
||||
use(buffer)
|
||||
use(bufferSize)
|
||||
use(bufferCoord)
|
||||
runtime.KeepAlive(buffer)
|
||||
runtime.KeepAlive(bufferSize)
|
||||
runtime.KeepAlive(bufferCoord)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
@@ -270,7 +276,7 @@ func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bu
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
|
||||
func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
|
||||
r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
|
||||
use(buffer)
|
||||
runtime.KeepAlive(buffer)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
@@ -278,15 +284,19 @@ func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) erro
|
||||
// It returns true if the handle was signaled; false otherwise.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
|
||||
func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
|
||||
r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
|
||||
switch r1 {
|
||||
case WAIT_ABANDONED, WAIT_TIMEOUT:
|
||||
event, err := windows.WaitForSingleObject(windows.Handle(handle), msWait)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch event {
|
||||
case windows.WAIT_ABANDONED, uint32(windows.WAIT_TIMEOUT):
|
||||
return false, nil
|
||||
case WAIT_SIGNALED:
|
||||
case windows.WAIT_OBJECT_0:
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
use(msWait)
|
||||
return false, err
|
||||
}
|
||||
|
||||
// String helpers
|
||||
@@ -321,7 +331,3 @@ func coordToPointer(c COORD) uintptr {
|
||||
// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
|
||||
return uintptr(*((*uint32)(unsafe.Pointer(&c))))
|
||||
}
|
||||
|
||||
// use is a no-op, but the compiler cannot see that it is.
|
||||
// Calling use(p) ensures that p is kept live until that point.
|
||||
func use(p interface{}) {}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
|
||||
+11
-33
@@ -1,10 +1,12 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
const (
|
||||
horizontal = iota
|
||||
vertical
|
||||
line
|
||||
column
|
||||
)
|
||||
|
||||
func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
|
||||
@@ -58,44 +60,20 @@ func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
|
||||
position.X += int16(param)
|
||||
case vertical:
|
||||
position.Y += int16(param)
|
||||
case line:
|
||||
position.X = 0
|
||||
position.Y += int16(param)
|
||||
case column:
|
||||
position.X = int16(param) - 1
|
||||
}
|
||||
|
||||
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return h.setCursorPosition(position, h.getCursorWindow(info))
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
position := info.CursorPosition
|
||||
position.X = 0
|
||||
position.Y += int16(param)
|
||||
|
||||
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return h.moveCursor(line, param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
position := info.CursorPosition
|
||||
position.X = int16(param) - 1
|
||||
|
||||
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return h.moveCursor(column, param)
|
||||
}
|
||||
|
||||
+3
-13
@@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
@@ -50,12 +50,7 @@ func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD,
|
||||
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||
coordEnd.X, coordEnd.Y = xEnd, yEnd
|
||||
|
||||
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return h.clearRect(attributes, coordStart, coordEnd)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||
@@ -75,10 +70,5 @@ func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD,
|
||||
buffer[i] = char
|
||||
}
|
||||
|
||||
err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion)
|
||||
}
|
||||
|
||||
+4
-11
@@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
@@ -39,9 +39,8 @@ func (h *windowsAnsiEventHandler) deleteLines(param int) error {
|
||||
if start >= sr.top && start <= sr.bottom {
|
||||
sr.top = start
|
||||
return h.scroll(param, sr, info)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) insertLines(param int) error {
|
||||
@@ -72,10 +71,7 @@ func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSO
|
||||
Attributes: h.attributes,
|
||||
}
|
||||
|
||||
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
|
||||
@@ -111,8 +107,5 @@ func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *
|
||||
Attributes: h.attributes,
|
||||
}
|
||||
|
||||
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char)
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
|
||||
+19
-33
@@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package winterm
|
||||
|
||||
@@ -169,9 +169,7 @@ func (h *windowsAnsiEventHandler) executeLF() error {
|
||||
return err
|
||||
}
|
||||
h.logf("Resetting cursor position for LF without CR")
|
||||
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||
return err
|
||||
}
|
||||
return SetConsoleCursorPosition(h.fd, pos)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -373,7 +371,7 @@ func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
|
||||
}
|
||||
|
||||
window := h.getCursorWindow(info)
|
||||
position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
|
||||
position := COORD{X: window.Left + int16(col) - 1, Y: window.Top + int16(row) - 1}
|
||||
return h.setCursorPosition(position, window)
|
||||
}
|
||||
|
||||
@@ -423,7 +421,7 @@ func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
|
||||
targetWidth = 132
|
||||
}
|
||||
if info.Size.X < targetWidth {
|
||||
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||
if err := SetConsoleScreenBufferSize(h.fd, COORD{X: targetWidth, Y: info.Size.Y}); err != nil {
|
||||
h.logf("set buffer failed: %v", err)
|
||||
return err
|
||||
}
|
||||
@@ -436,12 +434,12 @@ func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
|
||||
return err
|
||||
}
|
||||
if info.Size.X > targetWidth {
|
||||
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||
if err := SetConsoleScreenBufferSize(h.fd, COORD{X: targetWidth, Y: info.Size.Y}); err != nil {
|
||||
h.logf("set buffer failed: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return SetConsoleCursorPosition(h.fd, COORD{0, 0})
|
||||
return SetConsoleCursorPosition(h.fd, COORD{X: 0, Y: 0})
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) ED(param int) error {
|
||||
@@ -468,15 +466,15 @@ func (h *windowsAnsiEventHandler) ED(param int) error {
|
||||
switch param {
|
||||
case 0:
|
||||
start = info.CursorPosition
|
||||
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||
end = COORD{X: info.Size.X - 1, Y: info.Size.Y - 1}
|
||||
|
||||
case 1:
|
||||
start = COORD{0, 0}
|
||||
start = COORD{X: 0, Y: 0}
|
||||
end = info.CursorPosition
|
||||
|
||||
case 2:
|
||||
start = COORD{0, 0}
|
||||
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||
start = COORD{X: 0, Y: 0}
|
||||
end = COORD{X: info.Size.X - 1, Y: info.Size.Y - 1}
|
||||
}
|
||||
|
||||
err = h.clearRange(h.attributes, start, end)
|
||||
@@ -495,9 +493,7 @@ func (h *windowsAnsiEventHandler) ED(param int) error {
|
||||
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
|
||||
return err
|
||||
}
|
||||
return SetConsoleWindowInfo(h.fd, true, window)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -525,23 +521,18 @@ func (h *windowsAnsiEventHandler) EL(param int) error {
|
||||
switch param {
|
||||
case 0:
|
||||
start = info.CursorPosition
|
||||
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||
end = COORD{X: info.Size.X, Y: info.CursorPosition.Y}
|
||||
|
||||
case 1:
|
||||
start = COORD{0, info.CursorPosition.Y}
|
||||
start = COORD{X: 0, Y: info.CursorPosition.Y}
|
||||
end = info.CursorPosition
|
||||
|
||||
case 2:
|
||||
start = COORD{0, info.CursorPosition.Y}
|
||||
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||
start = COORD{X: 0, Y: info.CursorPosition.Y}
|
||||
end = COORD{X: info.Size.X, Y: info.CursorPosition.Y}
|
||||
}
|
||||
|
||||
err = h.clearRange(h.attributes, start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return h.clearRange(h.attributes, start, end)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) IL(param int) error {
|
||||
@@ -611,12 +602,7 @@ func (h *windowsAnsiEventHandler) SGR(params []int) error {
|
||||
if h.inverted {
|
||||
attributes = invertAttributes(attributes)
|
||||
}
|
||||
err := SetConsoleTextAttribute(h.fd, attributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return SetConsoleTextAttribute(h.fd, attributes)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) SU(param int) error {
|
||||
@@ -702,8 +688,8 @@ func (h *windowsAnsiEventHandler) Flush() error {
|
||||
}
|
||||
|
||||
charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
|
||||
size := COORD{1, 1}
|
||||
position := COORD{0, 0}
|
||||
size := COORD{X: 1, Y: 1}
|
||||
position := COORD{X: 0, Y: 0}
|
||||
region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
|
||||
if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil {
|
||||
return err
|
||||
|
||||
+25
-7
@@ -3,8 +3,9 @@
|
||||
package winio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -20,16 +21,33 @@ import (
|
||||
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
var (
|
||||
ErrFileClosed = errors.New("file has already been closed")
|
||||
ErrTimeout = &timeoutError{}
|
||||
)
|
||||
// ErrFileClosed is returned when an operation is performed on a closed file.
|
||||
//
|
||||
// Deprecated: use [os.ErrClosed] instead.
|
||||
var ErrFileClosed = os.ErrClosed
|
||||
|
||||
// ErrTimeout is returned when an I/O operation exceeds its deadline.
|
||||
//
|
||||
// It matches both [context.DeadlineExceeded] and [os.ErrDeadlineExceeded]
|
||||
// with [errors.Is].
|
||||
var ErrTimeout error = &timeoutError{}
|
||||
|
||||
// timeoutError preserves the historical "i/o timeout" error while matching
|
||||
// both context.DeadlineExceeded and os.ErrDeadlineExceeded through errors.Is.
|
||||
// This mirrors the behavior of net.errTimeout:
|
||||
// https://github.com/golang/go/blob/go1.27.1/src/net/net.go
|
||||
//
|
||||
// TODO: consider replacing ErrTimeout with os.ErrDeadlineExceeded if os.ErrDeadlineExceeded is changed to match context.DeadlineExceeded.
|
||||
type timeoutError struct{}
|
||||
|
||||
func (*timeoutError) Error() string { return "i/o timeout" }
|
||||
func (*timeoutError) Timeout() bool { return true }
|
||||
func (*timeoutError) Temporary() bool { return true }
|
||||
func (*timeoutError) Unwrap() error { return os.ErrDeadlineExceeded }
|
||||
|
||||
func (*timeoutError) Is(err error) bool {
|
||||
return err == context.DeadlineExceeded
|
||||
}
|
||||
|
||||
type timeoutChan chan struct{}
|
||||
|
||||
@@ -143,7 +161,7 @@ func (f *win32File) prepareIO() (*ioOperation, error) {
|
||||
f.wgLock.RLock()
|
||||
if f.closing.Load() {
|
||||
f.wgLock.RUnlock()
|
||||
return nil, ErrFileClosed
|
||||
return nil, os.ErrClosed
|
||||
}
|
||||
f.wg.Add(1)
|
||||
f.wgLock.RUnlock()
|
||||
@@ -192,7 +210,7 @@ func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, er
|
||||
err = r.err
|
||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||
if f.closing.Load() {
|
||||
err = ErrFileClosed
|
||||
err = os.ErrClosed
|
||||
}
|
||||
} else if err != nil && f.socket {
|
||||
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||
|
||||
+1
-1
@@ -441,7 +441,7 @@ func canRedial(err error) bool {
|
||||
|
||||
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||
// translate from "file closed" to "socket closed"
|
||||
if errors.Is(err, ErrFileClosed) {
|
||||
if errors.Is(err, os.ErrClosed) {
|
||||
err = socket.ErrSocketClosed
|
||||
}
|
||||
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
||||
|
||||
+11
-12
@@ -102,12 +102,10 @@ func (status ntStatus) Err() error {
|
||||
return rtlNtStatusToDosError(status)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||
ErrPipeListenerClosed = net.ErrClosed
|
||||
|
||||
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||
)
|
||||
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||
//
|
||||
// Deprecated: use [net.ErrClosed] instead.
|
||||
var ErrPipeListenerClosed = net.ErrClosed
|
||||
|
||||
type win32Pipe struct {
|
||||
*win32File
|
||||
@@ -144,9 +142,10 @@ func (f *win32Pipe) Disconnect() error {
|
||||
}
|
||||
|
||||
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||
// It returns [io.ErrClosedPipe] if the write side is already closed.
|
||||
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||
if f.writeClosed {
|
||||
return errPipeWriteClosed
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
err := f.win32File.Flush()
|
||||
if err != nil {
|
||||
@@ -161,10 +160,10 @@ func (f *win32MessageBytePipe) CloseWrite() error {
|
||||
}
|
||||
|
||||
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||
// they are used to implement CloseWrite().
|
||||
// they are used to implement CloseWrite. It returns [io.ErrClosedPipe] if the write side is closed.
|
||||
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||
if f.writeClosed {
|
||||
return 0, errPipeWriteClosed
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
@@ -454,7 +453,7 @@ func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
||||
_ = p.Close()
|
||||
p = nil
|
||||
<-ch
|
||||
err = ErrPipeListenerClosed
|
||||
err = net.ErrClosed
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
@@ -479,7 +478,7 @@ func (l *win32PipeListener) listenerRoutine() {
|
||||
}
|
||||
}
|
||||
responseCh <- acceptResponse{p, err}
|
||||
closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno
|
||||
closed = err == net.ErrClosed //nolint:errorlint // err is Errno
|
||||
}
|
||||
}
|
||||
windows.Close(l.firstHandle)
|
||||
@@ -571,7 +570,7 @@ func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||
}
|
||||
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||
case <-l.doneCh:
|
||||
return nil, ErrPipeListenerClosed
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+33
-3
@@ -408,7 +408,10 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWe
|
||||
(*obj.protected)[k] = makeRawMessage(b)
|
||||
}
|
||||
|
||||
authData := obj.computeAuthData()
|
||||
authData, err := obj.computeAuthData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -428,6 +431,13 @@ func (ctx *genericEncrypter) Options() EncrypterOptions {
|
||||
}
|
||||
}
|
||||
|
||||
func validateContentEncryptionKeySize(cek []byte, cipher contentCipher) error {
|
||||
if len(cek) != cipher.keySize() {
|
||||
return ErrInvalidKeySize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrypt and validate the object and return the plaintext. This
|
||||
// function does not support multi-recipient. If you desire multi-recipient
|
||||
// decryption use DecryptMulti instead.
|
||||
@@ -485,7 +495,10 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
||||
tag: obj.tag,
|
||||
}
|
||||
|
||||
authData := obj.computeAuthData()
|
||||
authData, err := obj.computeAuthData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var plaintext []byte
|
||||
recipient := obj.recipients[0]
|
||||
@@ -495,6 +508,10 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
||||
if err != nil {
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
err = validateContentEncryptionKeySize(cek, cipher)
|
||||
if err != nil {
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
// Found a valid CEK -- let's try to decrypt.
|
||||
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||
@@ -520,6 +537,12 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
||||
//
|
||||
// Automatically decompresses plaintext, but returns an error if the decompressed
|
||||
// data would be >250kB or >3x the size of the compressed data, whichever is larger.
|
||||
//
|
||||
// Calling DecryptMulti() with PBES2 enabled can result in excessive CPU work.
|
||||
// The PBES2 key unwrapping algorithms are intentionally expensive, and have to
|
||||
// be run for each recipient. To be safe: don't allow PBES2 algorithms in calls
|
||||
// to ParseEncrypted / ParseEncryptedJSON. If your application requires PBES2,
|
||||
// prefer Decrypt(), which will only process single-recipient messages.
|
||||
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
||||
globalHeaders := obj.mergedHeaders(nil)
|
||||
|
||||
@@ -553,7 +576,10 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
|
||||
tag: obj.tag,
|
||||
}
|
||||
|
||||
authData := obj.computeAuthData()
|
||||
authData, err := obj.computeAuthData()
|
||||
if err != nil {
|
||||
return -1, Header{}, nil, err
|
||||
}
|
||||
|
||||
index := -1
|
||||
var plaintext []byte
|
||||
@@ -572,6 +598,10 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = validateContentEncryptionKeySize(cek, cipher)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Found a valid CEK -- let's try to decrypt.
|
||||
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||
|
||||
+6
-32
@@ -30,30 +30,6 @@ import (
|
||||
"github.com/go-jose/go-jose/v4/json"
|
||||
)
|
||||
|
||||
// Helper function to serialize known-good objects.
|
||||
// Precondition: value is not a nil pointer.
|
||||
func mustSerializeJSON(value interface{}) []byte {
|
||||
out, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// We never want to serialize the top-level value "null," since it's not a
|
||||
// valid JOSE message. But if a caller passes in a nil pointer to this method,
|
||||
// MarshalJSON will happily serialize it as the top-level value "null". If
|
||||
// that value is then embedded in another operation, for instance by being
|
||||
// base64-encoded and fed as input to a signing algorithm
|
||||
// (https://github.com/go-jose/go-jose/issues/22), the result will be
|
||||
// incorrect. Because this method is intended for known-good objects, and a nil
|
||||
// pointer is not a known-good object, we are free to panic in this case.
|
||||
// Note: It's not possible to directly check whether the data pointed at by an
|
||||
// interface is a nil pointer, so we do this hacky workaround.
|
||||
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
|
||||
if string(out) == "null" {
|
||||
panic("Tried to serialize a nil pointer.")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Strip all newlines and whitespace
|
||||
func stripWhitespace(data string) string {
|
||||
buf := strings.Builder{}
|
||||
@@ -122,6 +98,8 @@ func inflate(input []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
|
||||
//
|
||||
// A nil byteBuffer represents an empty slice of bytes.
|
||||
type byteBuffer struct {
|
||||
data []byte
|
||||
}
|
||||
@@ -169,13 +147,13 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
*b = *newBuffer(decoded)
|
||||
b.data = decoded
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *byteBuffer) base64() string {
|
||||
return base64.RawURLEncoding.EncodeToString(b.data)
|
||||
return base64.RawURLEncoding.EncodeToString(b.bytes())
|
||||
}
|
||||
|
||||
func (b *byteBuffer) bytes() []byte {
|
||||
@@ -186,12 +164,8 @@ func (b *byteBuffer) bytes() []byte {
|
||||
return b.data
|
||||
}
|
||||
|
||||
func (b byteBuffer) bigInt() *big.Int {
|
||||
return new(big.Int).SetBytes(b.data)
|
||||
}
|
||||
|
||||
func (b byteBuffer) toInt() int {
|
||||
return int(b.bigInt().Int64())
|
||||
func (b *byteBuffer) bigInt() *big.Int {
|
||||
return new(big.Int).SetBytes(b.bytes())
|
||||
}
|
||||
|
||||
func base64EncodeLen(sl []byte) int {
|
||||
|
||||
+13
@@ -671,6 +671,19 @@ func (d *decodeState) object(v reflect.Value) {
|
||||
for _, i := range f.index {
|
||||
if subv.Kind() == reflect.Ptr {
|
||||
if subv.IsNil() {
|
||||
// If a struct embeds a pointer to an unexported type,
|
||||
// it is not possible to set a newly allocated value
|
||||
// since the field is unexported.
|
||||
//
|
||||
// See https://golang.org/issue/21357
|
||||
if !subv.CanSet() {
|
||||
d.saveError(fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v", subv.Type().Elem()))
|
||||
// Invalidate subv to ensure d.value(subv) skips over
|
||||
// the JSON value without assigning it to subv.
|
||||
subv = reflect.Value{}
|
||||
destring = false
|
||||
break
|
||||
}
|
||||
subv.Set(reflect.New(subv.Type().Elem()))
|
||||
}
|
||||
subv = subv.Elem()
|
||||
|
||||
+13
-1
@@ -1025,7 +1025,19 @@ func typeFields(t reflect.Type) []field {
|
||||
// Scan f.typ for fields to include.
|
||||
for i := 0; i < f.typ.NumField(); i++ {
|
||||
sf := f.typ.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
if sf.Anonymous {
|
||||
t := sf.Type
|
||||
if t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
if !sf.IsExported() && t.Kind() != reflect.Struct {
|
||||
// Ignore embedded fields of unexported non-struct types.
|
||||
continue
|
||||
}
|
||||
// Do not ignore embedded fields of unexported struct types
|
||||
// since they may have exported fields.
|
||||
} else if !sf.IsExported() {
|
||||
// Ignore unexported non-embedded fields.
|
||||
continue
|
||||
}
|
||||
tag := sf.Tag.Get("json")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user