mirror of
https://github.com/docker/cli.git
synced 2026-11-09 06:22:58 -04:00
Compare commits
286
Commits
v29.5.0
...
v29.8.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
573a46e506 | ||
|
|
433ab01649 | ||
|
|
175b4e0a98 | ||
|
|
00de141abf | ||
|
|
1f46660e37 | ||
|
|
ff3273f3e4 | ||
|
|
6fccf35355 | ||
|
|
511f136927 | ||
|
|
b17df8fea3 | ||
|
|
dc47c19e55 | ||
|
|
3cd22ff935 | ||
|
|
45f589590b | ||
|
|
6b8da05715 | ||
|
|
65ad909cde | ||
|
|
52eb28a512 | ||
|
|
bb63125969 | ||
|
|
2a24c3c77d | ||
|
|
3f096b4ffd | ||
|
|
ebc3d5aeab | ||
|
|
6261c55c56 | ||
|
|
2c36f84eaf | ||
|
|
4f7a1841ba | ||
|
|
ef48c25aed | ||
|
|
f2514827f4 | ||
|
|
f8d6265672 | ||
|
|
744eee4bd4 | ||
|
|
d6041d1abc | ||
|
|
06275f9094 | ||
|
|
bf6f62cc7c | ||
|
|
376f03c858 | ||
|
|
de4a008614 | ||
|
|
f3f7da9f3d | ||
|
|
eed270f656 | ||
|
|
d072fe407f | ||
|
|
3fe73079aa | ||
|
|
28f756087e | ||
|
|
387b8e2af8 | ||
|
|
f7e66aa1ff | ||
|
|
55e4a7eef5 | ||
|
|
58d9cdd2d9 | ||
|
|
1612f4c767 | ||
|
|
cf53a284da | ||
|
|
0da5f45651 | ||
|
|
ce15202968 | ||
|
|
8659aa5d4c | ||
|
|
8d08c75e81 | ||
|
|
24af004cbf | ||
|
|
eb68ed19d2 | ||
|
|
8a375034e2 | ||
|
|
f755f48a12 | ||
|
|
a7c0741fa7 | ||
|
|
12892075d2 | ||
|
|
e1f9b284df | ||
|
|
4f84911bfe | ||
|
|
228f29e810 | ||
|
|
d8c3543407 | ||
|
|
2dd7b7e15b | ||
|
|
a7dcaa6fdb | ||
|
|
400b45f682 | ||
|
|
38887ec4ed | ||
|
|
904aef7f69 | ||
|
|
f08e60eb09 | ||
|
|
abfd89157d | ||
|
|
519eb45d03 | ||
|
|
8717af7168 | ||
|
|
e9452d6e78 | ||
|
|
a6014a702b | ||
|
|
2465fca604 | ||
|
|
52b15cc571 | ||
|
|
13590921a4 | ||
|
|
0b50545471 | ||
|
|
c1eba931e3 | ||
|
|
1a305be376 | ||
|
|
4c1c648df1 | ||
|
|
ee046d35c0 | ||
|
|
d70c3fd5e7 | ||
|
|
f6d6bede46 | ||
|
|
b47659808a | ||
|
|
af626e1ed7 | ||
|
|
6d35de4601 | ||
|
|
ad8dce1012 | ||
|
|
844210bd0f | ||
|
|
bd719d6703 | ||
|
|
52b1160c85 | ||
|
|
5ff2ff6311 | ||
|
|
775dd50364 | ||
|
|
3320996718 | ||
|
|
4b02fd04b0 | ||
|
|
487686142c | ||
|
|
e03b83ef06 | ||
|
|
8dfca49e6b | ||
|
|
808405b67d | ||
|
|
3f5a7b50ab | ||
|
|
40c63db4c8 | ||
|
|
977f88900e | ||
|
|
f6dfb40875 | ||
|
|
a9ac44ea6b | ||
|
|
358f0135e3 | ||
|
|
aa610f321d | ||
|
|
0d32eed829 | ||
|
|
be7865c09e | ||
|
|
186b7283e1 | ||
|
|
9f4301e8f5 | ||
|
|
89e882a808 | ||
|
|
4e6e8fe5c8 | ||
|
|
5b21d378b0 | ||
|
|
532dcc37f5 | ||
|
|
19a0ee3926 | ||
|
|
c700ac42b6 | ||
|
|
03b45495db | ||
|
|
8061c90dcd | ||
|
|
80698fa987 | ||
|
|
ee4c249cc2 | ||
|
|
9391c9889c | ||
|
|
90d14d3e7e | ||
|
|
349fad1635 | ||
|
|
a3ea9a43e0 | ||
|
|
fd78fdc62d | ||
|
|
8f3477fca1 | ||
|
|
7a54334eb0 | ||
|
|
617d772fcc | ||
|
|
7aef4c8479 | ||
|
|
dc997612d0 | ||
|
|
7ea9e2158c | ||
|
|
3519704227 | ||
|
|
0133bfa4fd | ||
|
|
09754bc280 | ||
|
|
48370883e0 | ||
|
|
18d4f4ff6d | ||
|
|
1423089969 | ||
|
|
5853360a94 | ||
|
|
f816d5c003 | ||
|
|
25a1d41669 | ||
|
|
45d1a6cccf | ||
|
|
ba55f0c3b1 | ||
|
|
9c1b7fc671 | ||
|
|
a8ddac1a8e | ||
|
|
2d978b80ae | ||
|
|
f57e528457 | ||
|
|
9ba113bc5d | ||
|
|
cd79c7ebfc | ||
|
|
042528819a | ||
|
|
8c9e2f353e | ||
|
|
a86b9aa542 | ||
|
|
182f56fe8c | ||
|
|
e5c424fd70 | ||
|
|
145e7f83cd | ||
|
|
e8ded3fce7 | ||
|
|
91db63cf5b | ||
|
|
d4218f26dc | ||
|
|
6d245daa91 | ||
|
|
2282b23f02 | ||
|
|
fd3497683d | ||
|
|
2abf92e95c | ||
|
|
40a8e8e754 | ||
|
|
da2622ed8e | ||
|
|
c4c704e62b | ||
|
|
1c742902b6 | ||
|
|
0f83acece4 | ||
|
|
3edebc433e | ||
|
|
635fed89d2 | ||
|
|
444bab12d9 | ||
|
|
8900f1d330 | ||
|
|
22b8f1396e | ||
|
|
d9c59c9cfe | ||
|
|
64307515e6 | ||
|
|
8fda97b545 | ||
|
|
f9dc4e413b | ||
|
|
f8a2d2b253 | ||
|
|
7eb15d3454 | ||
|
|
033b0ff9ff | ||
|
|
317bfd1231 | ||
|
|
1b4c7d7807 | ||
|
|
e45822e51d | ||
|
|
a97303090d | ||
|
|
d516a10f93 | ||
|
|
fb59821d45 | ||
|
|
ee2f737013 | ||
|
|
1f80e23560 | ||
|
|
244404379d | ||
|
|
d9698edc85 | ||
|
|
1d1562e004 | ||
|
|
8c2e80070b | ||
|
|
233cd4a643 | ||
|
|
5b600d015d | ||
|
|
e6decf4d85 | ||
|
|
a9284d1161 | ||
|
|
e7319c78af | ||
|
|
0f1dbdea86 | ||
|
|
fef3ef83fe | ||
|
|
9eff92c55a | ||
|
|
43f745b242 | ||
|
|
450df790a0 | ||
|
|
0df3977cb3 | ||
|
|
86cf0a5185 | ||
|
|
3b10370c40 | ||
|
|
a23b1c6770 | ||
|
|
3a197287d2 | ||
|
|
aaaeb80ceb | ||
|
|
278ac25271 | ||
|
|
54f330978e | ||
|
|
f4a4c1e2cf | ||
|
|
deacdc6feb | ||
|
|
c17c247ed4 | ||
|
|
b680c49f57 | ||
|
|
4748c4e4d3 | ||
|
|
9c7701eb48 | ||
|
|
ddc801807d | ||
|
|
83963b759c | ||
|
|
2e635d7baf | ||
|
|
1c22ca1aac | ||
|
|
55016421fd | ||
|
|
8b2a990843 | ||
|
|
a9c82c0a9f | ||
|
|
22d7ca46a3 | ||
|
|
a721bd651b | ||
|
|
51583aec0b | ||
|
|
399c9456a7 | ||
|
|
de24d1cbc0 | ||
|
|
49dc46afed | ||
|
|
c72acd0a08 | ||
|
|
b601b2577a | ||
|
|
e8f837ba7f | ||
|
|
abba6bb358 | ||
|
|
9caec62f25 | ||
|
|
6d9c126733 | ||
|
|
1aae5d7822 | ||
|
|
4ed0e4e65d | ||
|
|
90f2f30fd1 | ||
|
|
a11beec944 | ||
|
|
b50c369d8b | ||
|
|
d788f2d81f | ||
|
|
fe262bc2f9 | ||
|
|
d1341e7caf | ||
|
|
54636c8e2e | ||
|
|
a1cf501956 | ||
|
|
d1c06ef6b4 | ||
|
|
7dd053b1d1 | ||
|
|
37c3d316cc | ||
|
|
45f10f226e | ||
|
|
3cc61496db | ||
|
|
55b88882d7 | ||
|
|
b458dc9e81 | ||
|
|
1953194bd5 | ||
|
|
ac0419ea90 | ||
|
|
1aa0416b8a | ||
|
|
3a85952984 | ||
|
|
8d3fbdf570 | ||
|
|
69c3f5c7af | ||
|
|
0ae4aac726 | ||
|
|
36edcdf9f3 | ||
|
|
4e815a7e97 | ||
|
|
32e1269780 | ||
|
|
5f09e74d48 | ||
|
|
98df84cf86 | ||
|
|
20b3bc3f64 | ||
|
|
8a9d271adf | ||
|
|
f4f8ffb9d7 | ||
|
|
2aeb70a5b9 | ||
|
|
d476e1d9bf | ||
|
|
07bb5e458c | ||
|
|
224685e1ea | ||
|
|
9f16882de4 | ||
|
|
9e098b0f82 | ||
|
|
f3f823c78b | ||
|
|
02e2c2ecda | ||
|
|
45fc3b034a | ||
|
|
20f5e7c08c | ||
|
|
20debc9c01 | ||
|
|
cf5f060b4d | ||
|
|
9712e537b9 | ||
|
|
d6eded1632 | ||
|
|
4494f1af5a | ||
|
|
79eb04c7d8 | ||
|
|
1a3048fe6c | ||
|
|
9177c7fc6b | ||
|
|
77cb156764 | ||
|
|
382a92daa8 | ||
|
|
5c0919a947 | ||
|
|
a68dd7a4fb | ||
|
|
066d508bd3 | ||
|
|
2518b52d94 | ||
|
|
9f18a0a70c | ||
|
|
2944fd1daa | ||
|
|
ae9f429677 | ||
|
|
c41489ac39 |
@@ -5,7 +5,14 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- "area/testing"
|
||||
- "area/ci"
|
||||
- "status/2-code-review"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
codeql-actions:
|
||||
patterns:
|
||||
- "github/codeql-action/*"
|
||||
docker-actions:
|
||||
patterns:
|
||||
- "docker/*"
|
||||
|
||||
+39
-48
@@ -35,7 +35,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -63,10 +65,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
set: |
|
||||
@@ -78,63 +80,50 @@ jobs:
|
||||
working-directory: ./build
|
||||
run: |
|
||||
mkdir /tmp/out
|
||||
platform=${{ matrix.platform }}
|
||||
platformPair=${platform//\//-}
|
||||
platformPair=${PLATFORM//\//-}
|
||||
tar -cvzf "/tmp/out/docker-${platformPair}.tar.gz" .
|
||||
if [ -z "${{ matrix.use_glibc }}" ]; then
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}-glibc" >> $GITHUB_ENV
|
||||
fi
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: /tmp/out/*
|
||||
if-no-files-found: error
|
||||
|
||||
bin-image:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/cli' }}
|
||||
steps:
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||
with:
|
||||
images: dockereng/cli-bin
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
cwd://${{ steps.meta.outputs.bake-file }}
|
||||
targets: bin-image-cross
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=bin-image
|
||||
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||
uses: docker/github-builder/.github/workflows/bake.yml@58cb9f5b71b1836d6f690c1e95effdeb9b98cb8a # v1.17.0
|
||||
permissions:
|
||||
contents: read # same as global permission
|
||||
id-token: write # for signing attestation(s) and authenticating to Docker Hub with GitHub OIDC Token
|
||||
with:
|
||||
setup-qemu: true
|
||||
target: bin-image-cross
|
||||
cache: true
|
||||
cache-scope: bin-image
|
||||
output: image
|
||||
push: true
|
||||
vars: |
|
||||
VERSION=${{ github.ref }}
|
||||
meta-images: |
|
||||
dockereng/cli-bin
|
||||
meta-tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
registry-identities: |
|
||||
- type: dockerhub
|
||||
username: dockereng
|
||||
connection_id: ${{ vars.DOCKERHUB_OIDC_CONNECTIONID }}
|
||||
|
||||
prepare-plugins:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -143,7 +132,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -165,10 +156,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0
|
||||
with:
|
||||
targets: plugins-cross
|
||||
set: |
|
||||
|
||||
@@ -46,9 +46,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 2
|
||||
persist-credentials: false
|
||||
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
||||
# and is trying to be smart by searching for modules in every directory,
|
||||
# including vendor directories. If no module is found, it's creating one
|
||||
@@ -61,20 +62,20 @@ jobs:
|
||||
ln -s vendor.sum go.sum
|
||||
-
|
||||
name: Update Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: "1.26.3"
|
||||
go-version: "1.26.7"
|
||||
cache: false
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
with:
|
||||
category: "/language:go"
|
||||
|
||||
@@ -44,7 +44,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Update daemon.json
|
||||
run: |
|
||||
@@ -63,7 +65,7 @@ jobs:
|
||||
docker info
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
-
|
||||
name: Run ${{ matrix.target }}
|
||||
run: |
|
||||
@@ -74,7 +76,7 @@ jobs:
|
||||
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
files: ./build/coverage/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: PR Review - Trigger
|
||||
on:
|
||||
pull_request:
|
||||
types: [ready_for_review, opened, review_requested]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
permissions: {}
|
||||
|
||||
# Deduplicate simultaneous pull_request events for the same fork PR.
|
||||
# When reviewers are requested at the same time, GitHub fires multiple
|
||||
# review_requested events. Without this group each event triggers a
|
||||
# separate review via workflow_run, producing duplicate reviews.
|
||||
concurrency:
|
||||
group: pr-review-trigger-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
save-context:
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Save event context
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
COMMENT_JSON: ${{ toJSON(github.event.comment) }}
|
||||
run: |
|
||||
mkdir -p context
|
||||
printf '%s' "${{ github.event_name }}" > context/event_name.txt
|
||||
printf '%s' "$PR_NUMBER" > context/pr_number.txt
|
||||
printf '%s' "$PR_HEAD_SHA" > context/pr_head_sha.txt
|
||||
if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then
|
||||
printf '%s' "$COMMENT_JSON" > context/comment.json
|
||||
fi
|
||||
|
||||
- name: Upload context
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: pr-review-context
|
||||
path: context/
|
||||
retention-days: 1
|
||||
@@ -0,0 +1,23 @@
|
||||
name: PR Review
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
workflow_run:
|
||||
workflows: ["PR Review - Trigger"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
review:
|
||||
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@b08367e7e9a471822464be2bb9fdaa1c0fb6061b # v2.0.4
|
||||
permissions:
|
||||
contents: read # Read repository files and PR diffs
|
||||
pull-requests: write # Post review comments
|
||||
issues: write # Create security incident issues if secrets detected
|
||||
checks: write # (Optional) Show review progress as a check run
|
||||
id-token: write # Required for OIDC authentication to AWS Secrets Manager
|
||||
actions: write # Required since v2.0.3 — review-lock cache cleanup and feedback artifacts
|
||||
with:
|
||||
trigger-run-id: ${{ github.event_name == 'workflow_run' && format('{0}', github.event.workflow_run.id) || '' }}
|
||||
@@ -0,0 +1,157 @@
|
||||
name: Sync Docker release branch
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ inputs.release_branch }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_branch:
|
||||
description: Release branch to sync, for example 29.x
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: Tag to sync from, for example v29.6.0
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: Merge but don't push
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
sync-release-branch:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
outputs:
|
||||
base_sha: ${{ steps.sync.outputs.base_sha }}
|
||||
has_changes: ${{ steps.sync.outputs.has_changes }}
|
||||
temporary_branch: ${{ steps.sync.outputs.temporary_branch }}
|
||||
temporary_sha: ${{ steps.sync.outputs.temporary_sha }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate
|
||||
env:
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
RELEASE_BRANCH: ${{ inputs.release_branch }}
|
||||
run: |
|
||||
if [ "$BRANCH" != "master" ]; then
|
||||
echo "::error::This workflow is expected to be run on master, not $BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$RELEASE_BRANCH" = "master" ]; then
|
||||
echo "::error::The release branch must not be master"
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$RELEASE_BRANCH" =~ ^[0-9]+\.[x0-9]+$ ]]; then
|
||||
echo "::error::Invalid release branch name: '$RELEASE_BRANCH'. Expected format: 29.x"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Configure git author
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Sync release branch to tag range
|
||||
id: sync
|
||||
env:
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
RELEASE_BRANCH: ${{ inputs.release_branch }}
|
||||
RUN_ATTEMPT: ${{ github.run_attempt }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
base_sha=$(git rev-parse "origin/$RELEASE_BRANCH")
|
||||
temporary_branch="process/sync-release-branch/$RUN_ID-$RUN_ATTEMPT"
|
||||
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "temporary_branch=$temporary_branch" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Keep the master checkout unchanged so scripts run from the dispatched revision.
|
||||
release_worktree="$RUNNER_TEMP/release-branch"
|
||||
git worktree add --detach "$release_worktree" "$base_sha"
|
||||
cd "$release_worktree"
|
||||
|
||||
tags_file=$(mktemp)
|
||||
"$GITHUB_WORKSPACE/scripts/unmerged-tags" \
|
||||
"origin/$RELEASE_BRANCH" \
|
||||
"$TAG" \
|
||||
> "$tags_file"
|
||||
|
||||
echo >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "## Tags to sync" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo >> "$GITHUB_STEP_SUMMARY"
|
||||
sed 's/^/- /' "$tags_file" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
xargs -r "$GITHUB_WORKSPACE/scripts/sync-branch" < "$tags_file" | tee -a "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $(git rev-parse HEAD) == $(git rev-parse "origin/$RELEASE_BRANCH") ]]; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No changes to push"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
git push origin "HEAD:refs/heads/$temporary_branch"
|
||||
echo "temporary_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-branch:
|
||||
needs: sync-release-branch
|
||||
if: ${{ !inputs.dry_run && needs.sync-release-branch.outputs.has_changes == 'true' }}
|
||||
runs-on: ubuntu-24.04
|
||||
environment: docker-releases
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Push release branch
|
||||
env:
|
||||
BASE_SHA: ${{ needs.sync-release-branch.outputs.base_sha }}
|
||||
RELEASE_BRANCH: ${{ inputs.release_branch }}
|
||||
TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }}
|
||||
TEMPORARY_SHA: ${{ needs.sync-release-branch.outputs.temporary_sha }}
|
||||
run: |
|
||||
git fetch origin "$RELEASE_BRANCH"
|
||||
current_sha=$(git rev-parse "origin/$RELEASE_BRANCH")
|
||||
if [[ "$current_sha" != "$BASE_SHA" ]]; then
|
||||
echo "$RELEASE_BRANCH changed from $BASE_SHA to $current_sha"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch origin "$TEMPORARY_BRANCH"
|
||||
current_temporary_sha=$(git rev-parse FETCH_HEAD)
|
||||
if [[ "$current_temporary_sha" != "$TEMPORARY_SHA" ]]; then
|
||||
echo "$TEMPORARY_BRANCH changed from $TEMPORARY_SHA to $current_temporary_sha"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git push origin "FETCH_HEAD:$RELEASE_BRANCH"
|
||||
|
||||
- name: Delete temporary branch
|
||||
env:
|
||||
TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }}
|
||||
run: git push origin --delete "$TEMPORARY_BRANCH"
|
||||
@@ -30,15 +30,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0
|
||||
with:
|
||||
targets: test-coverage
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
files: ./build/coverage/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -60,14 +60,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: "1.26.3"
|
||||
go-version: "1.26.7"
|
||||
cache: false
|
||||
-
|
||||
name: Test
|
||||
@@ -81,7 +82,7 @@ jobs:
|
||||
shell: bash
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
files: /tmp/coverage.txt
|
||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
|
||||
@@ -2,6 +2,7 @@ name: validate-milestone
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -12,24 +13,46 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: VERSION
|
||||
|
||||
- name: Validate milestone matches VERSION
|
||||
run: |
|
||||
expected=$(cat VERSION)
|
||||
milestone="${{ github.event.pull_request.milestone.title }}"
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
MILESTONE: ${{ github.event.pull_request.milestone.title }}
|
||||
with:
|
||||
script: |
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
});
|
||||
core.info(`Modified files: ${files.map(f => f.filename).join(', ')}`);
|
||||
|
||||
if [[ -z "$milestone" ]]; then
|
||||
echo "::error::PR must have a milestone set (expected: $expected)"
|
||||
exit 1
|
||||
fi
|
||||
const touchesVersion = files.some(f => f.filename === 'VERSION');
|
||||
core.info(`Touches VERSION: ${touchesVersion}`);
|
||||
|
||||
if [[ "$milestone" != "$expected" ]]; then
|
||||
echo "::error::Milestone '$milestone' does not match VERSION '$expected'"
|
||||
exit 1
|
||||
fi
|
||||
// Use the PR's version when it bumps the file, base branch otherwise.
|
||||
// It's fine to trust the author in this case, it's not meant to be
|
||||
// a security gate, just a helpful check for maintainers.
|
||||
const ref = touchesVersion
|
||||
? context.payload.pull_request.head.sha
|
||||
: context.payload.pull_request.base.ref;
|
||||
|
||||
echo "Milestone: $milestone ✓"
|
||||
core.info(`Base ref: ${ref}`);
|
||||
|
||||
const resp = await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: 'VERSION',
|
||||
ref,
|
||||
});
|
||||
const expected = Buffer.from(resp.data.content, resp.data.encoding).toString('utf8').trim();
|
||||
const milestone = process.env.MILESTONE;
|
||||
|
||||
if (!milestone) {
|
||||
core.setFailed(`PR must have a milestone set (expected: ${expected})`);
|
||||
return;
|
||||
}
|
||||
if (milestone !== expected) {
|
||||
core.setFailed(`Milestone '${milestone}' does not match VERSION '${expected}'`);
|
||||
return;
|
||||
}
|
||||
core.info(`Milestone: ${milestone} ✓`);
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Run
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -48,7 +48,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Generate
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
@@ -74,7 +76,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Run
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
@@ -89,14 +93,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
path: src/github.com/docker/cli
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: "1.26.3"
|
||||
go-version: "1.26.7"
|
||||
cache: false
|
||||
-
|
||||
name: Run gocompat check
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: zizmor
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: crazy-max/.github/.github/workflows/zizmor.yml@f91220cba053bd54299ba645a599ee8ed436f222 # v1.11.0
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
with:
|
||||
min-severity: medium
|
||||
min-confidence: medium
|
||||
persona: pedantic
|
||||
+1
-1
@@ -5,7 +5,7 @@ run:
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
#
|
||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||
go: "1.26.3"
|
||||
go: "1.26.7"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ Alexander Larsson <alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com> <lk4d4@docker.com>
|
||||
Alexandre Beslic <alexandre.beslic@gmail.com> <abronan@docker.com>
|
||||
Alexandre Levavasseur <alexandre+oss@13x.fr>
|
||||
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
|
||||
Alicia Lauerman <alicia@eta.im> <allydevour@me.com>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com> <allen.sun@daocloud.io>
|
||||
@@ -354,9 +355,10 @@ Lorenzo Fontana <lo@linux.com> <fontanalorenzo@me.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Louis Opter <kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
Lovekesh Kumar <lovekesh.kumar@rtcamp.com>
|
||||
Luo Jiyin <luojiyin@hotmail.com>
|
||||
Luca Favatella <luca.favatella@erlang-solutions.com> <lucafavatella@users.noreply.github.com>
|
||||
Lukas Michael <lukas.23022005@gmail.com>
|
||||
Luke Marsden <me@lukemarsden.net> <luke@digital-crocus.com>
|
||||
Luo Jiyin <luojiyin@hotmail.com>
|
||||
Lyn <energylyn@zju.edu.cn>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com> <lyndaoleary@hotmail.com>
|
||||
@@ -403,6 +405,7 @@ Michael Huettermann <michael@huettermann.net>
|
||||
Michael Käufl <docker@c.michael-kaeufl.de> <michael-k@users.noreply.github.com>
|
||||
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Mickael Emirkanian <mickael.emirkanian@docker.com>
|
||||
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com> <30386061+doncicuto@users.noreply.github.com>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com> <MihaiBorobocea@gmail.com>
|
||||
@@ -572,6 +575,8 @@ Ulysses Souza <ulysses.souza@docker.com>
|
||||
Ulysses Souza <ulysses.souza@docker.com> <ulyssessouza@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com> <dungeonmaster18@users.noreply.github.com>
|
||||
Varun Hotani <varunhotani@gmail.com>
|
||||
Vibhu Anan <vibhuanand@outlook.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <dev@vvieux.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
|
||||
@@ -43,6 +43,7 @@ Alexander Larsson <alexl@redhat.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com>
|
||||
Alexander Ryabov <i@sepa.spb.ru>
|
||||
Alexandre González <agonzalezro@gmail.com>
|
||||
Alexandre Levavasseur <alexandre+oss@13x.fr>
|
||||
Alexandre Vallières-Lagacé <alexandre.valliereslagace@docker.com>
|
||||
Alexey Igrychev <alexey.igrychev@flant.com>
|
||||
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
|
||||
@@ -161,6 +162,7 @@ Chen Chuanliang <chen.chuanliang@zte.com.cn>
|
||||
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
|
||||
Chen Mingjie <chenmingjie0828@163.com>
|
||||
Chen Qiu <cheney-90@hotmail.com>
|
||||
Ching Wei Kang <164879897+WilliamK112@users.noreply.github.com>
|
||||
Chris Chinchilla <chris@chrischinchilla.com>
|
||||
Chris Couzens <ccouzens@gmail.com>
|
||||
Chris Gavin <chris@chrisgavin.me>
|
||||
@@ -366,7 +368,7 @@ Hugo Gabriel Eyherabide <hugogabriel.eyherabide@gmail.com>
|
||||
huqun <huqun@zju.edu.cn>
|
||||
Huu Nguyen <huu@prismskylabs.com>
|
||||
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
|
||||
Iain MacDonald <ijmacd@gmail.com>
|
||||
Iain MacDonald <IJMacD@gmail.com>
|
||||
Iain Samuel McLean Elder <iain@isme.es>
|
||||
Ian Campbell <ian.campbell@docker.com>
|
||||
Ian Philpot <ian.philpot@microsoft.com>
|
||||
@@ -552,6 +554,7 @@ Ludovic Temgoua Abanda <abandaludovic500@gmail.com>
|
||||
Luis Henrique Mulinari <luis.mulinari@gmail.com>
|
||||
Luka Hartwig <mail@lukahartwig.de>
|
||||
Lukas Heeren <lukas-heeren@hotmail.com>
|
||||
Lukas Michael <lukas.23022005@gmail.com>
|
||||
Lukasz Zajaczkowski <Lukasz.Zajaczkowski@ts.fujitsu.com>
|
||||
Luo Jiyin <luojiyin@hotmail.com>
|
||||
Lydell Manganti <LydellManganti@users.noreply.github.com>
|
||||
@@ -562,6 +565,7 @@ Maciej Kalisz <maciej.d.kalisz@gmail.com>
|
||||
Madhav Puri <madhav.puri@gmail.com>
|
||||
Madhu Venugopal <madhu@socketplane.io>
|
||||
Madhur Batra <madhurbatra097@gmail.com>
|
||||
Mahesh Thakur <maheshthakur9152@gmail.com>
|
||||
Malte Janduda <mail@janduda.net>
|
||||
Manjunath A Kumatagi <mkumatag@in.ibm.com>
|
||||
Mansi Nahar <mmn4185@rit.edu>
|
||||
@@ -589,10 +593,12 @@ Mathieu Rollet <matletix@gmail.com>
|
||||
Matt Gucci <matt9ucci@gmail.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Matteo Orefice <matteo.orefice@bites4bits.software>
|
||||
Matteo Panzeri <matteo1782@gmail.com>
|
||||
Matthew Heon <mheon@redhat.com>
|
||||
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
|
||||
Matthieu MOREL <matthieu.morel35@gmail.com>
|
||||
Mauro Porras P <mauroporrasp@gmail.com>
|
||||
Max Morozov <gtmax.yo@gmail.com>
|
||||
Max Shytikov <mshytikov@gmail.com>
|
||||
Max-Julian Pogner <max-julian@pogner.at>
|
||||
Maxime Petazzoni <max@signalfuse.com>
|
||||
@@ -617,6 +623,7 @@ Michael West <mwest@mdsol.com>
|
||||
Michael Zampani <michael.zampani@docker.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Michał Czeraszkiewicz <czerasz@gmail.com>
|
||||
Mickael Emirkanian <mickael.emirkanian@docker.com>
|
||||
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com>
|
||||
Mihuleacc Sergiu <mihuleac.sergiu@gmail.com>
|
||||
@@ -638,6 +645,7 @@ Mohammad Banikazemi <mb@us.ibm.com>
|
||||
Mohammad Hossein <mhm98035@gmail.com>
|
||||
Mohammed Aaqib Ansari <maaquib@gmail.com>
|
||||
Mohammed Aminu Futa <mohammedfuta2000@gmail.com>
|
||||
Mohammed Thaha <mohammedthahacse@gmail.com>
|
||||
Mohini Anne Dsouza <mohini3917@gmail.com>
|
||||
Moorthy RS <rsmoorthy@gmail.com>
|
||||
Morgan Bauer <mbauer@us.ibm.com>
|
||||
@@ -689,6 +697,7 @@ Olli Janatuinen <olli.janatuinen@gmail.com>
|
||||
Oscar Wieman <oscrx@icloud.com>
|
||||
Otto Kekäläinen <otto@seravo.fi>
|
||||
Ovidio Mallo <ovidio.mallo@gmail.com>
|
||||
Park Jaeon <me@finalchild.dev>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Patrick Böänziger <patrick.baenziger@bsi-software.com>
|
||||
Patrick Daigle <114765035+pdaig@users.noreply.github.com>
|
||||
@@ -715,6 +724,7 @@ Peter Jaffe <pjaffe@nevo.com>
|
||||
Peter Kehl <peter.kehl@gmail.com>
|
||||
Peter Nagy <xificurC@gmail.com>
|
||||
Peter Salvatore <peter@psftw.com>
|
||||
Peter Valdemar Mørch <peter@morch.com>
|
||||
Peter Waller <p@pwaller.net>
|
||||
Phil Estes <estesp@gmail.com>
|
||||
Philip Alexander Etling <paetling@gmail.com>
|
||||
@@ -739,6 +749,7 @@ Ray Tsang <rayt@google.com>
|
||||
Reficul <xuzhenglun@gmail.com>
|
||||
Remy Suen <remy.suen@gmail.com>
|
||||
Renaud Gaubert <rgaubert@nvidia.com>
|
||||
René Hermenau <rene-hermenau@users.noreply.github.com>
|
||||
Ricardo N Feliciano <FelicianoTech@gmail.com>
|
||||
Rich Moyse <rich@moyse.us>
|
||||
Richard Chen Zheng <58443436+rchenzheng@users.noreply.github.com>
|
||||
@@ -789,6 +800,7 @@ Scott Collier <emailscottcollier@gmail.com>
|
||||
Sean Christopherson <sean.j.christopherson@intel.com>
|
||||
Sean Rodman <srodman7689@gmail.com>
|
||||
Sebastiaan van Stijn <github@gone.nl>
|
||||
Seiya Miyata <odradek38@gmail.com>
|
||||
Sergey Tryuber <Sergeant007@users.noreply.github.com>
|
||||
Serhat Gülçiçek <serhat25@gmail.com>
|
||||
Sevki Hasirci <s@sevki.org>
|
||||
@@ -889,8 +901,10 @@ Umesh Yadav <umesh4257@gmail.com>
|
||||
Vaclav Struhar <struharv@gmail.com>
|
||||
Valentin Lorentz <progval+git@progval.net>
|
||||
Vardan Pogosian <vardan.pogosyan@gmail.com>
|
||||
Varun Hotani <varunhotani@gmail.com>
|
||||
Venkateswara Reddy Bukkasamudram <bukkasamudram@outlook.com>
|
||||
Veres Lajos <vlajos@gmail.com>
|
||||
Vibhu Anan <vibhuanand@outlook.com>
|
||||
Victor Vieux <victor.vieux@docker.com>
|
||||
Victoria Bialas <victoria.bialas@docker.com>
|
||||
Viktor Stanchev <me@viktorstanchev.com>
|
||||
|
||||
+1
-1
@@ -267,7 +267,7 @@ Then you just add a line to every git commit message:
|
||||
|
||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||
|
||||
Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
Use a known identity (sorry, no anonymous contributions.)
|
||||
|
||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||
commit automatically with `git commit -s`.
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.23
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.7
|
||||
|
||||
# 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.
|
||||
@@ -25,7 +25,7 @@ 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.33.0
|
||||
ARG BUILDX_VERSION=0.34.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
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[](https://pkg.go.dev/github.com/docker/cli)
|
||||
[](https://github.com/docker/cli/actions?query=workflow%3Abuild)
|
||||
[](https://github.com/docker/cli/actions?query=workflow%3Atest)
|
||||
[](https://goreportcard.com/report/github.com/docker/cli)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/docker/cli)
|
||||
[](https://codecov.io/gh/docker/cli)
|
||||
|
||||
|
||||
+33
-15
@@ -208,52 +208,70 @@ func TestInitializeFromClient(t *testing.T) {
|
||||
// Makes sure we don't hang forever on the initial connection.
|
||||
// https://github.com/docker/cli/issues/3652
|
||||
func TestInitializeFromClientHangs(t *testing.T) {
|
||||
const (
|
||||
// Sized against measured scheduler stalls:
|
||||
// under CPU pressure this test sees 40-90ms stalls;
|
||||
// this should give about 5x headroom.
|
||||
// See https://github.com/docker/cli/issues/6003.
|
||||
clientInitTimeout = 500 * time.Millisecond
|
||||
|
||||
// This is only a backstop against a genuine hang.
|
||||
// It should never be reached on a healthy run.
|
||||
// So, it should be fine to have a lenient timeout here.
|
||||
waitTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
socket := filepath.Join(tmpDir, "my.sock")
|
||||
l, err := net.Listen("unix", socket)
|
||||
assert.NilError(t, err)
|
||||
|
||||
receiveReqCh := make(chan bool)
|
||||
timeoutCtx, cancel := context.WithTimeout(context.TODO(), time.Second)
|
||||
defer cancel()
|
||||
// Buffered, so the handler can record
|
||||
// that it was reached without a reader
|
||||
// having to be ready at that instant.
|
||||
receivedReqCh := make(chan struct{}, 1)
|
||||
releaseHandlerCh := make(chan struct{})
|
||||
|
||||
// Simulate a server that hangs on connections.
|
||||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
select {
|
||||
case <-timeoutCtx.Done():
|
||||
case receiveReqCh <- true: // Blocks until someone receives on the channel.
|
||||
case receivedReqCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
<-releaseHandlerCh
|
||||
}))
|
||||
ts.Listener = l
|
||||
ts.Start()
|
||||
defer ts.Close()
|
||||
t.Cleanup(func() {
|
||||
close(releaseHandlerCh)
|
||||
ts.Close()
|
||||
})
|
||||
|
||||
opts := &flags.ClientOptions{Hosts: []string{"unix://" + socket}}
|
||||
configFile := &configfile.ConfigFile{}
|
||||
apiClient, err := NewAPIClientFromFlags(opts, configFile)
|
||||
assert.NilError(t, err)
|
||||
|
||||
initializedCh := make(chan bool)
|
||||
initErrCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
cli := &DockerCli{client: apiClient, initTimeout: time.Millisecond}
|
||||
cli := &DockerCli{client: apiClient, initTimeout: clientInitTimeout}
|
||||
err := cli.Initialize(flags.NewClientOptions())
|
||||
assert.Check(t, err)
|
||||
cli.CurrentVersion()
|
||||
close(initializedCh)
|
||||
initErrCh <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timeoutCtx.Done():
|
||||
case err := <-initErrCh:
|
||||
assert.Check(t, err)
|
||||
case <-time.After(waitTimeout):
|
||||
t.Fatal("timeout waiting for initialization to complete")
|
||||
case <-initializedCh:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timeoutCtx.Done():
|
||||
case <-receivedReqCh:
|
||||
case <-time.After(waitTimeout):
|
||||
t.Fatal("server never received an init request")
|
||||
case <-receiveReqCh:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +172,28 @@ func FromList(options ...string) cobra.CompletionFunc {
|
||||
return Unique(cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp))
|
||||
}
|
||||
|
||||
// WithPrefix prefixes every element in the slice with the given prefix.
|
||||
// It is a helper for building "--filter" completions, where each candidate
|
||||
// value is offered as "key=value".
|
||||
func WithPrefix(prefix string, values []string) []string {
|
||||
result := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
result[i] = prefix + v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// WithSuffix appends the given suffix to every element in the slice. It is a
|
||||
// helper for building "--filter" completions, where filter keys are offered
|
||||
// with a trailing "=" (combined with [cobra.ShellCompDirectiveNoSpace]).
|
||||
func WithSuffix(suffix string, values []string) []string {
|
||||
result := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
result[i] = v + suffix
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FileNames is a convenience function to use [cobra.ShellCompDirectiveDefault],
|
||||
// which indicates to let the shell perform its default behavior after
|
||||
// completions have been provided.
|
||||
|
||||
@@ -196,6 +196,18 @@ func TestCompleteFromList(t *testing.T) {
|
||||
assert.Check(t, is.DeepEqual(values, expected))
|
||||
}
|
||||
|
||||
func TestWithPrefix(t *testing.T) {
|
||||
assert.Check(t, is.DeepEqual(WithPrefix("node=", []string{"n1", "n2"}), []string{"node=n1", "node=n2"}))
|
||||
assert.Check(t, is.DeepEqual(WithPrefix("node=", []string{}), []string{}))
|
||||
assert.Check(t, is.DeepEqual(WithPrefix("", []string{"n1"}), []string{"n1"}))
|
||||
}
|
||||
|
||||
func TestWithSuffix(t *testing.T) {
|
||||
assert.Check(t, is.DeepEqual(WithSuffix("=", []string{"id", "name"}), []string{"id=", "name="}))
|
||||
assert.Check(t, is.DeepEqual(WithSuffix("=", []string{}), []string{}))
|
||||
assert.Check(t, is.DeepEqual(WithSuffix("", []string{"id"}), []string{"id"}))
|
||||
}
|
||||
|
||||
func TestCompleteImageNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
|
||||
@@ -71,7 +71,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags.SetInterspersed(false)
|
||||
|
||||
flags.StringVar(&options.name, "name", "", "Assign a name to the container")
|
||||
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "|`+PullImageMissing+`", "`+PullImageNever+`")`)
|
||||
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "`+PullImageMissing+`", "`+PullImageNever+`")`)
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
|
||||
flags.BoolVarP(&options.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
|
||||
_ = flags.SetAnnotation("use-api-socket", "experimentalCLI", nil) // Mark flag as experimental for now.
|
||||
|
||||
@@ -88,7 +88,6 @@ func runRm(ctx context.Context, dockerCLI command.Cli, opts *rmOptions) error {
|
||||
for _, name := range opts.containers {
|
||||
if err := <-errChan; err != nil {
|
||||
if opts.force && errdefs.IsNotFound(err) {
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
|
||||
continue
|
||||
}
|
||||
errs = append(errs, err)
|
||||
|
||||
@@ -52,6 +52,7 @@ func TestRemoveForce(t *testing.T) {
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
assert.Equal(t, cli.ErrBuffer().String(), "")
|
||||
sort.Strings(removed)
|
||||
assert.DeepEqual(t, removed, []string{"mycontainer", "nosuchcontainer"})
|
||||
})
|
||||
|
||||
@@ -458,7 +458,7 @@ func comparePorts(i, j container.PortSummary) bool {
|
||||
}
|
||||
|
||||
if i.IP != j.IP {
|
||||
return i.IP.String() < j.IP.String()
|
||||
return i.IP.Less(j.IP)
|
||||
}
|
||||
|
||||
if i.PublicPort != j.PublicPort {
|
||||
|
||||
@@ -946,6 +946,24 @@ func TestDisplayablePorts(t *testing.T) {
|
||||
},
|
||||
expected: "80/tcp, 80/udp, 1024/tcp, 1024/udp, 12345/sctp, 1.1.1.1:1024->80/tcp, 1.1.1.1:1024->80/udp, 2.1.1.1:1024->80/tcp, 2.1.1.1:1024->80/udp, 1.1.1.1:80->1024/tcp, 1.1.1.1:80->1024/udp, 2.1.1.1:80->1024/tcp, 2.1.1.1:80->1024/udp", //nolint:revive // ignore line-length-limit (revive)
|
||||
},
|
||||
{
|
||||
// host IPs are ordered numerically, not lexicographically:
|
||||
// "10.0.0.2" sorts as a string before "9.0.0.1".
|
||||
ports: []container.PortSummary{
|
||||
{
|
||||
IP: netip.MustParseAddr("10.0.0.2"),
|
||||
PublicPort: 8080,
|
||||
PrivatePort: 80,
|
||||
Type: "tcp",
|
||||
}, {
|
||||
IP: netip.MustParseAddr("9.0.0.1"),
|
||||
PublicPort: 8081,
|
||||
PrivatePort: 80,
|
||||
Type: "tcp",
|
||||
},
|
||||
},
|
||||
expected: "9.0.0.1:8081->80/tcp, 10.0.0.2:8080->80/tcp",
|
||||
},
|
||||
}
|
||||
|
||||
for _, port := range cases {
|
||||
|
||||
@@ -123,6 +123,7 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
return err
|
||||
}
|
||||
|
||||
var notes []string
|
||||
defer func() {
|
||||
_ = responseBody.Close()
|
||||
for _, note := range notes {
|
||||
@@ -131,18 +132,16 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
}()
|
||||
|
||||
if opts.quiet {
|
||||
err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux()))
|
||||
err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux(¬es, out)))
|
||||
if err == nil {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||
}
|
||||
return err
|
||||
}
|
||||
return jsonstream.Display(ctx, responseBody, dockerCli.Out(), jsonstream.WithAuxCallback(handleAux()))
|
||||
return jsonstream.Display(ctx, responseBody, dockerCli.Out(), jsonstream.WithAuxCallback(handleAux(¬es, out)))
|
||||
}
|
||||
|
||||
var notes []string
|
||||
|
||||
func handleAux() func(jm jsonstream.JSONMessage) {
|
||||
func handleAux(notes *[]string, out tui.Output) func(jm jsonstream.JSONMessage) {
|
||||
return func(jm jsonstream.JSONMessage) {
|
||||
b := []byte(*jm.Aux)
|
||||
|
||||
@@ -150,10 +149,10 @@ func handleAux() func(jm jsonstream.JSONMessage) {
|
||||
err := json.Unmarshal(b, &stripped)
|
||||
if err == nil && stripped.ManifestPushedInsteadOfIndex {
|
||||
note := fmt.Sprintf("Not all multiplatform-content is present and only the available single-platform image was pushed\n%s -> %s",
|
||||
aec.RedF.Apply(stripped.OriginalIndex.Digest.String()),
|
||||
aec.GreenF.Apply(stripped.SelectedManifest.Digest.String()),
|
||||
out.Color(aec.RedF).Apply(stripped.OriginalIndex.Digest.String()),
|
||||
out.Color(aec.GreenF).Apply(stripped.SelectedManifest.Digest.String()),
|
||||
)
|
||||
notes = append(notes, note)
|
||||
*notes = append(*notes, note)
|
||||
}
|
||||
|
||||
var missing auxprogress.ContentMissing
|
||||
@@ -166,7 +165,7 @@ func handleAux() func(jm jsonstream.JSONMessage) {
|
||||
Make sure you have all the referenced content and try again.
|
||||
|
||||
You can also push only a single platform specific manifest directly by specifying the platform you want to push with the --platform flag.`
|
||||
notes = append(notes, note)
|
||||
*notes = append(*notes, note)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/auxprogress"
|
||||
"github.com/moby/moby/client"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -86,3 +91,28 @@ func TestNewPushCommandSuccess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPushRespectsNoColorForAuxNotes(t *testing.T) {
|
||||
t.Setenv("NO_COLOR", "1")
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePushFunc: func(ref string, options client.ImagePushOptions) (client.ImagePushResponse, error) {
|
||||
aux, err := json.Marshal(auxprogress.ManifestPushedInsteadOfIndex{
|
||||
ManifestPushedInsteadOfIndex: true,
|
||||
OriginalIndex: ocispec.Descriptor{Digest: "sha256:1111111111111111111111111111111111111111111111111111111111111111"},
|
||||
SelectedManifest: ocispec.Descriptor{Digest: "sha256:2222222222222222222222222222222222222222222222222222222222222222"},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
line := append([]byte(`{"aux":`), aux...)
|
||||
line = append(line, '}', '\n')
|
||||
return fakeStreamResult{ReadCloser: io.NopCloser(bytes.NewReader(line))}, nil
|
||||
},
|
||||
})
|
||||
cli.Out().SetIsTerminal(true)
|
||||
|
||||
err := runPush(t.Context(), cli, pushOptions{remote: "image:tag"})
|
||||
assert.NilError(t, err)
|
||||
|
||||
out := cli.OutBuffer().String()
|
||||
assert.Assert(t, strings.Contains(out, "sha256:1111111111111111111111111111111111111111111111111111111111111111 -> sha256:2222222222222222222222222222222222222222222222222222222222222222"))
|
||||
assert.Assert(t, !strings.Contains(out, "\x1b["), "output should not contain ANSI escape codes, output: %s", out)
|
||||
}
|
||||
|
||||
@@ -48,27 +48,6 @@ func newManifestStore(dockerCLI command.Cli) store.Store {
|
||||
return store.NewStore(filepath.Join(config.Dir(), "manifests"))
|
||||
}
|
||||
|
||||
// authConfigKey is the key used to store credentials for Docker Hub. It is
|
||||
// a copy of [registry.IndexServer].
|
||||
//
|
||||
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer
|
||||
const authConfigKey = "https://index.docker.io/v1/"
|
||||
|
||||
// getAuthConfigKey special-cases using the full index address of the official
|
||||
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
||||
//
|
||||
// It is similar to [registry.GetAuthConfigKey], but does not require on
|
||||
// [registrytypes.IndexInfo] as intermediate.
|
||||
//
|
||||
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#GetAuthConfigKey
|
||||
// [registrytypes.IndexInfo]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/api/types/registry#IndexInfo
|
||||
func getAuthConfigKey(domainName string) string {
|
||||
if domainName == "docker.io" || domainName == "index.docker.io" {
|
||||
return authConfigKey
|
||||
}
|
||||
return domainName
|
||||
}
|
||||
|
||||
// newRegistryClient returns a client for communicating with a Docker distribution
|
||||
// registry
|
||||
func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient.RegistryClient {
|
||||
@@ -78,8 +57,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient
|
||||
}
|
||||
cfg := dockerCLI.ConfigFile()
|
||||
resolver := func(ctx context.Context, domainName string) registry.AuthConfig {
|
||||
configKey := getAuthConfigKey(domainName)
|
||||
a, _ := cfg.GetAuthConfig(configKey)
|
||||
a, _ := cfg.GetAuthConfig(domainName)
|
||||
return registry.AuthConfig{
|
||||
Username: a.Username,
|
||||
Password: a.Password,
|
||||
|
||||
@@ -2,12 +2,26 @@ package node
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// nodePsFilters are the filters that can be used with "docker node ps --filter".
|
||||
nodePsFilters = []string{"desired-state", "id", "label", "name"}
|
||||
|
||||
// taskDesiredStates are the valid values for the "desired-state" task filter.
|
||||
taskDesiredStates = []string{
|
||||
string(swarm.TaskStateRunning),
|
||||
string(swarm.TaskStateShutdown),
|
||||
string(swarm.TaskStateAccepted),
|
||||
}
|
||||
)
|
||||
|
||||
// completeNodeNames offers completion for swarm node (host)names and optional IDs.
|
||||
// By default, only names are returned.
|
||||
// Set DOCKER_COMPLETION_SHOW_NODE_IDS=yes to also complete IDs.
|
||||
@@ -35,3 +49,23 @@ func completeNodeNames(dockerCLI completion.APIClientProvider) cobra.CompletionF
|
||||
return names, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// completeNodePsFilters provides completion for the filters that can be used
|
||||
// with "docker node ps --filter".
|
||||
func completeNodePsFilters(_ completion.APIClientProvider) cobra.CompletionFunc {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
key, _, ok := strings.Cut(toComplete, "=")
|
||||
if !ok {
|
||||
return completion.WithSuffix("=", nodePsFilters), cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
switch key {
|
||||
case "desired-state":
|
||||
return completion.WithPrefix("desired-state=", taskDesiredStates), cobra.ShellCompDirectiveNoFileComp
|
||||
case "id", "name", "label":
|
||||
// Task IDs, names, and labels are not easily discoverable; only offer the key.
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
default:
|
||||
return completion.WithSuffix("=", nodePsFilters), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCompleteNodePsFilters(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
toComplete string
|
||||
expected []string
|
||||
directive cobra.ShellCompDirective
|
||||
}{
|
||||
{
|
||||
doc: "no input offers the filter keys",
|
||||
toComplete: "",
|
||||
expected: []string{"desired-state=", "id=", "label=", "name="},
|
||||
directive: cobra.ShellCompDirectiveNoSpace,
|
||||
},
|
||||
{
|
||||
doc: "desired-state values",
|
||||
toComplete: "desired-state=",
|
||||
expected: []string{"desired-state=running", "desired-state=shutdown", "desired-state=accepted"},
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "label offers no values",
|
||||
toComplete: "label=",
|
||||
expected: nil,
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "unknown key falls back to the filter keys",
|
||||
toComplete: "bogus=",
|
||||
expected: []string{"desired-state=", "id=", "label=", "name="},
|
||||
directive: cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
completions, directive := completeNodePsFilters(cli)(newPsCommand(cli), nil, tc.toComplete)
|
||||
assert.DeepEqual(t, completions, tc.expected)
|
||||
assert.Equal(t, directive, tc.directive)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,8 @@ func newPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("filter", completeNodePsFilters(dockerCLI))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
+12
-7
@@ -97,17 +97,22 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
|
||||
// If defaultUsername is not empty, the username prompt includes that username
|
||||
// and the user can hit enter without inputting a username to use that default
|
||||
// username.
|
||||
func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword, defaultUsername, serverAddress string) (registrytypes.AuthConfig, error) {
|
||||
func PromptUserForCredentials(ctx context.Context, cli Streams, argUser, argPassword, defaultUsername, serverAddress string) (registrytypes.AuthConfig, error) {
|
||||
// On Windows, force the use of the regular OS stdin stream.
|
||||
//
|
||||
// StdStreams() may wrap stdin with windowsconsole.NewAnsiReader to
|
||||
// emulate VT input on consoles that do not support it natively, but
|
||||
// that wrapper has historically caused interactive prompts to hang
|
||||
// or behave incorrectly.
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/moby/moby/issues/14336
|
||||
// - https://github.com/moby/moby/issues/14210
|
||||
// - https://github.com/moby/moby/pull/17738
|
||||
//
|
||||
// TODO(thaJeztah): we need to confirm if this special handling is still needed, as we may not be doing this in other places.
|
||||
stdIn := cli.In()
|
||||
if runtime.GOOS == "windows" {
|
||||
cli.SetIn(streams.NewIn(os.Stdin))
|
||||
// TODO(thaJeztah); change to io.Reader and skip wrapping once prompt.DisableInputEcho no longer requires a streams.In
|
||||
stdIn = streams.NewIn(os.Stdin)
|
||||
}
|
||||
|
||||
argUser = strings.TrimSpace(argUser)
|
||||
@@ -132,7 +137,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
}
|
||||
|
||||
var err error
|
||||
argUser, err = prompt.ReadInput(ctx, cli.In(), cli.Out(), msg)
|
||||
argUser, err = prompt.ReadInput(ctx, stdIn, cli.Out(), msg)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
@@ -146,7 +151,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
|
||||
isEmpty := strings.TrimSpace(argPassword) == ""
|
||||
if isEmpty {
|
||||
restoreInput, err := prompt.DisableInputEcho(cli.In())
|
||||
restoreInput, err := prompt.DisableInputEcho(stdIn)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
@@ -166,7 +171,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
"To create a PAT, visit " + aec.Underline.Apply("https://app.docker.com/settings") + "\n\n")
|
||||
}
|
||||
|
||||
argPassword, err = prompt.ReadInput(ctx, cli.In(), cli.Out(), "Password: ")
|
||||
argPassword, err = prompt.ReadInput(ctx, stdIn, cli.Out(), "Password: ")
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringVarP(&opts.user, "username", "u", "", "Username")
|
||||
flags.StringVarP(&opts.password, "password", "p", "", "Password or Personal Access Token (PAT)")
|
||||
flags.StringVarP(&opts.password, "password", "p", "", `Password or Personal Access Token (PAT), or "-" to read from stdin`)
|
||||
flags.BoolVar(&opts.passwordStdin, "password-stdin", false, "Take the Password or Personal Access Token (PAT) from stdin")
|
||||
|
||||
return cmd
|
||||
@@ -72,8 +72,8 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
//
|
||||
// TODO(thaJeztah); combine with verifyLoginOptions, but this requires rewrites of many tests.
|
||||
func verifyLoginFlags(flags *pflag.FlagSet, opts loginOptions) error {
|
||||
if flags.Changed("password-stdin") {
|
||||
if flags.Changed("password") {
|
||||
if flags.Changed("password-stdin") || opts.password == "-" {
|
||||
if flags.Changed("password") && opts.password != "-" {
|
||||
return errors.New("conflicting options: cannot specify both --password and --password-stdin")
|
||||
}
|
||||
if !flags.Changed("username") {
|
||||
@@ -122,6 +122,11 @@ func readSecretFromStdin(r io.Reader) (string, error) {
|
||||
}
|
||||
|
||||
func verifyLoginOptions(dockerCLI command.Streams, opts *loginOptions) error {
|
||||
if opts.password == "-" {
|
||||
opts.password = ""
|
||||
opts.passwordStdin = true
|
||||
}
|
||||
|
||||
if opts.password != "" {
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
|
||||
}
|
||||
|
||||
@@ -339,6 +339,57 @@ func TestRunLogin(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password dash reads password from stdin",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
stdIn: "my password\r\n",
|
||||
input: loginOptions{
|
||||
serverAddress: "reg1",
|
||||
user: "my-username",
|
||||
password: "-",
|
||||
},
|
||||
expectedCredentials: map[string]configtypes.AuthConfig{
|
||||
"reg1": {
|
||||
Username: "my-username",
|
||||
Password: "my password",
|
||||
ServerAddress: "reg1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password dash empty stdin",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
input: loginOptions{
|
||||
serverAddress: "reg1",
|
||||
user: "my-username",
|
||||
password: "-",
|
||||
},
|
||||
expectedErr: `password is empty`,
|
||||
expectedCredentials: map[string]configtypes.AuthConfig{
|
||||
"reg1": {
|
||||
Username: "my-username",
|
||||
ServerAddress: "reg1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password dash and password stdin read password from stdin",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
stdIn: "my password\r\n",
|
||||
input: loginOptions{
|
||||
serverAddress: "reg1",
|
||||
user: "my-username",
|
||||
password: "-",
|
||||
passwordStdin: true,
|
||||
},
|
||||
expectedCredentials: map[string]configtypes.AuthConfig{
|
||||
"reg1": {
|
||||
Username: "my-username",
|
||||
Password: "my password",
|
||||
ServerAddress: "reg1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "password with leading and trailing spaces",
|
||||
priorCredentials: map[string]configtypes.AuthConfig{},
|
||||
@@ -397,7 +448,7 @@ func TestRunLogin(t *testing.T) {
|
||||
cfg := configfile.New(filepath.Join(tmpDir, "config.json"))
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetConfigFile(cfg)
|
||||
if tc.input.passwordStdin {
|
||||
if tc.input.passwordStdin || tc.input.password == "-" || tc.stdIn != "" {
|
||||
if tc.expectedErr == "TEST_READ_ERR" {
|
||||
cli.SetIn(streams.NewIn(io.NopCloser(iotest.ErrReader(errors.New(tc.expectedErr)))))
|
||||
} else {
|
||||
@@ -632,6 +683,21 @@ func TestLoginValidateFlags(t *testing.T) {
|
||||
args: []string{"--password-stdin", "--password", ""},
|
||||
expectedErr: `conflicting options: cannot specify both --password and --password-stdin`,
|
||||
},
|
||||
{
|
||||
name: "password stdin and password dash without stdin",
|
||||
args: []string{"--password-stdin", "--username", "my-username", "--password", "-"},
|
||||
expectedErr: `password is empty`,
|
||||
},
|
||||
{
|
||||
name: "password dash without username",
|
||||
args: []string{"--password", "-"},
|
||||
expectedErr: `the --password-stdin option requires --username to be set`,
|
||||
},
|
||||
{
|
||||
name: "short password dash without username",
|
||||
args: []string{"-p", "-"},
|
||||
expectedErr: `the --password-stdin option requires --username to be set`,
|
||||
},
|
||||
{
|
||||
name: "empty --password",
|
||||
args: []string{"--password", ""},
|
||||
@@ -658,3 +724,10 @@ func TestLoginValidateFlags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginHelpDocumentsPasswordDash(t *testing.T) {
|
||||
cmd := newLoginCommand(test.NewFakeCli(&fakeClient{}))
|
||||
flag := cmd.Flags().Lookup("password")
|
||||
assert.Check(t, flag != nil)
|
||||
assert.Check(t, is.Contains(flag.Usage, `"-"`))
|
||||
}
|
||||
|
||||
@@ -2,12 +2,32 @@ package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// serviceListFilters are the filters that can be used with "docker service ls --filter".
|
||||
serviceListFilters = []string{"id", "label", "mode", "name"}
|
||||
|
||||
// serviceModes are the valid values for the "mode" filter of "docker service ls".
|
||||
serviceModes = []string{"replicated", "global", "replicated-job", "global-job"}
|
||||
|
||||
// servicePsFilters are the filters that can be used with "docker service ps --filter".
|
||||
servicePsFilters = []string{"desired-state", "id", "name", "node"}
|
||||
|
||||
// taskDesiredStates are the valid values for the "desired-state" task filter.
|
||||
taskDesiredStates = []string{
|
||||
string(swarm.TaskStateRunning),
|
||||
string(swarm.TaskStateShutdown),
|
||||
string(swarm.TaskStateAccepted),
|
||||
}
|
||||
)
|
||||
|
||||
// completeServiceNames offers completion for swarm service names and optional IDs.
|
||||
// By default, only names are returned.
|
||||
// Set DOCKER_COMPLETION_SHOW_SERVICE_IDS=yes to also complete IDs.
|
||||
@@ -31,3 +51,74 @@ func completeServiceNames(dockerCLI completion.APIClientProvider) cobra.Completi
|
||||
return names, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// completeServiceListFilters provides completion for the filters that can be
|
||||
// used with "docker service ls --filter".
|
||||
func completeServiceListFilters(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
key, _, ok := strings.Cut(toComplete, "=")
|
||||
if !ok {
|
||||
return completion.WithSuffix("=", serviceListFilters), cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
switch key {
|
||||
case "id", "name":
|
||||
return completion.WithPrefix(key+"=", serviceNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
|
||||
case "mode":
|
||||
return completion.WithPrefix("mode=", serviceModes), cobra.ShellCompDirectiveNoFileComp
|
||||
case "label":
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
default:
|
||||
return completion.WithSuffix("=", serviceListFilters), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// completeServicePsFilters provides completion for the filters that can be
|
||||
// used with "docker service ps --filter".
|
||||
func completeServicePsFilters(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
key, _, ok := strings.Cut(toComplete, "=")
|
||||
if !ok {
|
||||
return completion.WithSuffix("=", servicePsFilters), cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
switch key {
|
||||
case "desired-state":
|
||||
return completion.WithPrefix("desired-state=", taskDesiredStates), cobra.ShellCompDirectiveNoFileComp
|
||||
case "node":
|
||||
return completion.WithPrefix("node=", nodeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
|
||||
case "id", "name":
|
||||
// Task IDs and names are not easily discoverable; only offer the key.
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
default:
|
||||
return completion.WithSuffix("=", servicePsFilters), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serviceNames contacts the API to get a list of service names.
|
||||
// In case of an error, an empty list is returned.
|
||||
func serviceNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
||||
res, err := dockerCLI.Client().ServiceList(cmd.Context(), client.ServiceListOptions{})
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
names := make([]string, 0, len(res.Items))
|
||||
for _, service := range res.Items {
|
||||
names = append(names, service.Spec.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// nodeNames contacts the API to get a list of node (host)names.
|
||||
// In case of an error, an empty list is returned.
|
||||
func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
||||
res, err := dockerCLI.Client().NodeList(cmd.Context(), client.NodeListOptions{})
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
names := make([]string, 0, len(res.Items))
|
||||
for _, node := range res.Items {
|
||||
names = append(names, node.Description.Hostname)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCompleteServicePsFilters(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
client *fakeClient
|
||||
toComplete string
|
||||
expected []string
|
||||
directive cobra.ShellCompDirective
|
||||
}{
|
||||
{
|
||||
doc: "no input offers the filter keys",
|
||||
toComplete: "",
|
||||
expected: []string{"desired-state=", "id=", "name=", "node="},
|
||||
directive: cobra.ShellCompDirectiveNoSpace,
|
||||
},
|
||||
{
|
||||
doc: "desired-state values",
|
||||
toComplete: "desired-state=",
|
||||
expected: []string{"desired-state=running", "desired-state=shutdown", "desired-state=accepted"},
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "node values",
|
||||
client: &fakeClient{
|
||||
nodeListFunc: func(_ context.Context, _ client.NodeListOptions) (client.NodeListResult, error) {
|
||||
return client.NodeListResult{
|
||||
Items: []swarm.Node{
|
||||
*builders.Node(builders.Hostname("n1")),
|
||||
*builders.Node(builders.Hostname("n2")),
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
toComplete: "node=",
|
||||
expected: []string{"node=n1", "node=n2"},
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "node values on API error",
|
||||
client: &fakeClient{
|
||||
nodeListFunc: func(_ context.Context, _ client.NodeListOptions) (client.NodeListResult, error) {
|
||||
return client.NodeListResult{}, errors.New("API error")
|
||||
},
|
||||
},
|
||||
toComplete: "node=",
|
||||
expected: []string{},
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "id offers no values",
|
||||
toComplete: "id=",
|
||||
expected: nil,
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "unknown key falls back to the filter keys",
|
||||
toComplete: "bogus=",
|
||||
expected: []string{"desired-state=", "id=", "name=", "node="},
|
||||
directive: cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(tc.client)
|
||||
completions, directive := completeServicePsFilters(cli)(newPsCommand(cli), nil, tc.toComplete)
|
||||
assert.DeepEqual(t, completions, tc.expected)
|
||||
assert.Equal(t, directive, tc.directive)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteServiceListFilters(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
client *fakeClient
|
||||
toComplete string
|
||||
expected []string
|
||||
directive cobra.ShellCompDirective
|
||||
}{
|
||||
{
|
||||
doc: "no input offers the filter keys",
|
||||
toComplete: "",
|
||||
expected: []string{"id=", "label=", "mode=", "name="},
|
||||
directive: cobra.ShellCompDirectiveNoSpace,
|
||||
},
|
||||
{
|
||||
doc: "mode values",
|
||||
toComplete: "mode=",
|
||||
expected: []string{"mode=replicated", "mode=global", "mode=replicated-job", "mode=global-job"},
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "name values",
|
||||
client: &fakeClient{
|
||||
serviceListFunc: func(_ context.Context, _ client.ServiceListOptions) (client.ServiceListResult, error) {
|
||||
return client.ServiceListResult{
|
||||
Items: []swarm.Service{
|
||||
*builders.Service(builders.ServiceName("s1")),
|
||||
*builders.Service(builders.ServiceName("s2")),
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
toComplete: "name=",
|
||||
expected: []string{"name=s1", "name=s2"},
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "name values on API error",
|
||||
client: &fakeClient{
|
||||
serviceListFunc: func(_ context.Context, _ client.ServiceListOptions) (client.ServiceListResult, error) {
|
||||
return client.ServiceListResult{}, errors.New("API error")
|
||||
},
|
||||
},
|
||||
toComplete: "name=",
|
||||
expected: []string{},
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "label offers no values",
|
||||
toComplete: "label=",
|
||||
expected: nil,
|
||||
directive: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
doc: "unknown key falls back to the filter keys",
|
||||
toComplete: "bogus=",
|
||||
expected: []string{"id=", "label=", "mode=", "name="},
|
||||
directive: cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(tc.client)
|
||||
completions, directive := completeServiceListFilters(cli)(newListCommand(cli), nil, tc.toComplete)
|
||||
assert.DeepEqual(t, completions, tc.expected)
|
||||
assert.Equal(t, directive, tc.directive)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,8 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags.StringVar(&options.format, "format", "", flagsHelper.FormatHelp)
|
||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("filter", completeServiceListFilters(dockerCLI))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -687,15 +688,13 @@ func (options *serviceOptions) makeEnv() ([]string, error) {
|
||||
}
|
||||
currentEnv := make([]string, 0, len(envVariables))
|
||||
for _, env := range envVariables { // need to process each var, in order
|
||||
k, _, _ := strings.Cut(env, "=")
|
||||
for i, current := range currentEnv { // remove duplicates
|
||||
if current == env {
|
||||
continue // no update required, may hide this behind flag to preserve order of envVariables
|
||||
}
|
||||
if strings.HasPrefix(current, k+"=") {
|
||||
currentEnv = append(currentEnv[:i], currentEnv[i+1:]...)
|
||||
}
|
||||
if slices.Contains(currentEnv, env) {
|
||||
continue // no update required, may hide this behind flag to preserve order of envVariables
|
||||
}
|
||||
k, _, _ := strings.Cut(env, "=")
|
||||
currentEnv = slices.DeleteFunc(currentEnv, func(current string) bool { // remove duplicates
|
||||
return strings.HasPrefix(current, k+"=")
|
||||
})
|
||||
currentEnv = append(currentEnv, env)
|
||||
}
|
||||
|
||||
|
||||
@@ -373,3 +373,49 @@ func TestToServiceSysCtls(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
|
||||
}
|
||||
|
||||
func TestMakeEnv(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
env []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
doc: "no duplicates",
|
||||
env: []string{"one=1", "two=2"},
|
||||
expected: []string{"one=1", "two=2"},
|
||||
},
|
||||
{
|
||||
doc: "same variable repeated",
|
||||
env: []string{"one=1", "one=1"},
|
||||
expected: []string{"one=1"},
|
||||
},
|
||||
{
|
||||
doc: "same variable repeated, then overridden",
|
||||
env: []string{"one=1", "one=1", "one=2"},
|
||||
expected: []string{"one=2"},
|
||||
},
|
||||
{
|
||||
doc: "repeated variable last",
|
||||
env: []string{"one=1", "two=2", "two=2"},
|
||||
expected: []string{"one=1", "two=2"},
|
||||
},
|
||||
{
|
||||
doc: "last value wins",
|
||||
env: []string{"one=1", "two=2", "one=3"},
|
||||
expected: []string{"two=2", "one=3"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
o := newServiceOptions()
|
||||
for _, env := range tc.env {
|
||||
assert.NilError(t, o.env.Set(env))
|
||||
}
|
||||
actual, err := o.makeEnv()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(tc.expected, actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ func newPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("filter", completeServicePsFilters(dockerCLI))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
@@ -1213,11 +1213,9 @@ func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
|
||||
if rm.IPAddr != "" && rm.IPAddr != ip {
|
||||
continue
|
||||
}
|
||||
for i, h := range hostNames {
|
||||
if h == rm.Host {
|
||||
hostNames = append(hostNames[:i], hostNames[i+1:]...)
|
||||
}
|
||||
}
|
||||
hostNames = slices.DeleteFunc(hostNames, func(h string) bool {
|
||||
return h == rm.Host
|
||||
})
|
||||
}
|
||||
if len(hostNames) > 0 {
|
||||
newHosts = append(newHosts, fmt.Sprintf("%s %s", ip, strings.Join(hostNames, " ")))
|
||||
|
||||
@@ -1727,3 +1727,18 @@ func TestUpdateUlimits(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateHostsRemoveRepeatedHost(t *testing.T) {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
flags.Set("host-rm", "host1")
|
||||
|
||||
//nolint:dupword // ignore "Duplicate words (host1) found"
|
||||
hosts := []string{"127.0.0.1 host1 host1 host2", "127.0.0.2 host2 host1 host1"}
|
||||
|
||||
err := updateHosts(flags, &hosts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
// All occurrences of `host1` should be removed, also if the same host
|
||||
// is listed multiple times in the same entry.
|
||||
assert.Check(t, is.DeepEqual([]string{"127.0.0.1 host2", "127.0.0.2 host2"}, hosts))
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ services:
|
||||
foo:
|
||||
image: busybox
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "version must be a string"))
|
||||
assert.Check(t, is.ErrorContains(err, "version: must be a string"))
|
||||
}
|
||||
|
||||
func TestV1Unsupported(t *testing.T) {
|
||||
@@ -427,7 +427,7 @@ func TestV1Unsupported(t *testing.T) {
|
||||
foo:
|
||||
image: busybox
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "(root) Additional property foo is not allowed"))
|
||||
assert.Check(t, is.ErrorContains(err, "(root): additional property 'foo' is not allowed"))
|
||||
|
||||
_, err = loadYAML(`
|
||||
version: "1.0"
|
||||
@@ -445,14 +445,14 @@ services:
|
||||
- foo:
|
||||
image: busybox
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "services must be a mapping"))
|
||||
assert.Check(t, is.ErrorContains(err, "services: must be a mapping"))
|
||||
|
||||
_, err = loadYAML(`
|
||||
version: "3"
|
||||
services:
|
||||
foo: busybox
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "services.foo must be a mapping"))
|
||||
assert.Check(t, is.ErrorContains(err, "services.foo: must be a mapping"))
|
||||
|
||||
_, err = loadYAML(`
|
||||
version: "3"
|
||||
@@ -460,14 +460,14 @@ networks:
|
||||
- default:
|
||||
driver: bridge
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "networks must be a mapping"))
|
||||
assert.Check(t, is.ErrorContains(err, "networks: must be a mapping"))
|
||||
|
||||
_, err = loadYAML(`
|
||||
version: "3"
|
||||
networks:
|
||||
default: bridge
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "networks.default must be a mapping"))
|
||||
assert.Check(t, is.ErrorContains(err, "networks.default: must be null or a mapping"))
|
||||
|
||||
_, err = loadYAML(`
|
||||
version: "3"
|
||||
@@ -475,14 +475,14 @@ volumes:
|
||||
- data:
|
||||
driver: local
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "volumes must be a mapping"))
|
||||
assert.Check(t, is.ErrorContains(err, "volumes: must be a mapping"))
|
||||
|
||||
_, err = loadYAML(`
|
||||
version: "3"
|
||||
volumes:
|
||||
data: local
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "volumes.data must be a mapping"))
|
||||
assert.Check(t, is.ErrorContains(err, "volumes.data: must be null or a mapping"))
|
||||
}
|
||||
|
||||
func TestNonStringImage(t *testing.T) {
|
||||
@@ -492,7 +492,7 @@ services:
|
||||
foo:
|
||||
image: ["busybox", "latest"]
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "services.foo.image must be a string"))
|
||||
assert.Check(t, is.ErrorContains(err, "services.foo.image: must be a string"))
|
||||
}
|
||||
|
||||
func TestIgnoreBuildProperties(t *testing.T) {
|
||||
@@ -553,7 +553,7 @@ services:
|
||||
environment:
|
||||
FOO: ["1"]
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "services.dict-env.environment.FOO must be a string, number or null"))
|
||||
assert.Check(t, is.ErrorContains(err, "services.dict-env.environment.FOO: must be null or a number or a string"))
|
||||
}
|
||||
|
||||
func TestInvalidEnvironmentObject(t *testing.T) {
|
||||
@@ -564,7 +564,7 @@ services:
|
||||
image: busybox
|
||||
environment: "FOO=1"
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "services.dict-env.environment must be a mapping"))
|
||||
assert.Check(t, is.ErrorContains(err, "services.dict-env.environment: must be a mapping"))
|
||||
}
|
||||
|
||||
func TestLoadWithEnvironmentInterpolation(t *testing.T) {
|
||||
@@ -899,7 +899,7 @@ func TestInvalidResource(t *testing.T) {
|
||||
impossible:
|
||||
x: 1
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "Additional property impossible is not allowed"))
|
||||
assert.Check(t, is.ErrorContains(err, "additional property 'impossible' is not allowed"))
|
||||
}
|
||||
|
||||
func TestInvalidExternalAndDriverCombination(t *testing.T) {
|
||||
@@ -1033,7 +1033,7 @@ services:
|
||||
tmpfs:
|
||||
size: 10000
|
||||
`)
|
||||
assert.Check(t, is.ErrorContains(err, "services.tmpfs.volumes.0 Additional property tmpfs is not allowed"))
|
||||
assert.Check(t, is.ErrorContains(err, "services.tmpfs.volumes.0: additional property 'tmpfs' is not allowed"))
|
||||
}
|
||||
|
||||
func TestLoadBindMountSourceMustNotBeEmpty(t *testing.T) {
|
||||
@@ -1191,7 +1191,7 @@ services:
|
||||
tmpfs:
|
||||
size: -1
|
||||
`)
|
||||
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size Must be greater than or equal to 0")
|
||||
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size: must be greater than or equal to 0")
|
||||
}
|
||||
|
||||
func TestLoadTmpfsVolumeSizeMustBeInteger(t *testing.T) {
|
||||
@@ -1206,7 +1206,7 @@ services:
|
||||
tmpfs:
|
||||
size: 0.0001
|
||||
`)
|
||||
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size must be a integer")
|
||||
assert.ErrorContains(t, err, "services.tmpfs.volumes.0.tmpfs.size: must be an integer")
|
||||
}
|
||||
|
||||
func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
|
||||
|
||||
+167
-67
@@ -4,14 +4,20 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
"github.com/santhosh-tekuri/jsonschema/v6"
|
||||
"github.com/santhosh-tekuri/jsonschema/v6/kind"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,34 +27,29 @@ const (
|
||||
|
||||
type portsFormatChecker struct{}
|
||||
|
||||
func (portsFormatChecker) IsFormat(input any) bool {
|
||||
var portSpec string
|
||||
|
||||
switch p := input.(type) {
|
||||
case string:
|
||||
portSpec = p
|
||||
case *big.Rat:
|
||||
portSpec = strings.Split(p.String(), "/")[0]
|
||||
func (portsFormatChecker) Validate(input any) error {
|
||||
switch input.(type) {
|
||||
case string, json.Number,
|
||||
int, int8, int16, int32, int64,
|
||||
uint, uint8, uint16, uint32, uint64,
|
||||
float32, float64:
|
||||
_, err := nat.ParsePortSpec(fmt.Sprint(input))
|
||||
return err
|
||||
default:
|
||||
// Formats only apply to supported string/number values.
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := nat.ParsePortSpec(portSpec)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type durationFormatChecker struct{}
|
||||
|
||||
func (durationFormatChecker) IsFormat(input any) bool {
|
||||
func (durationFormatChecker) Validate(input any) error {
|
||||
value, ok := input.(string)
|
||||
if !ok {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
_, err := time.ParseDuration(value)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
gojsonschema.FormatCheckers.Add("ports", portsFormatChecker{})
|
||||
gojsonschema.FormatCheckers.Add("duration", durationFormatChecker{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Version returns the version of the config, defaulting to the latest "3.x"
|
||||
@@ -82,44 +83,84 @@ func Validate(config map[string]any, version string) error {
|
||||
return fmt.Errorf("unsupported Compose file version: %s", version)
|
||||
}
|
||||
|
||||
schemaLoader := gojsonschema.NewStringLoader(string(schemaData))
|
||||
dataLoader := gojsonschema.NewGoLoader(config)
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, dataLoader)
|
||||
schemaDoc, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
return toError(result)
|
||||
compiler := jsonschema.NewCompiler()
|
||||
compiler.RegisterFormat(&jsonschema.Format{Name: "ports", Validate: portsFormatChecker{}.Validate})
|
||||
compiler.RegisterFormat(&jsonschema.Format{Name: "duration", Validate: durationFormatChecker{}.Validate})
|
||||
if err := compiler.AddResource("schema.json", schemaDoc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schema, err := compiler.Compile("schema.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := schema.Validate(toJSONValue(config)); err != nil {
|
||||
var validationErr *jsonschema.ValidationError
|
||||
if errors.As(err, &validationErr) {
|
||||
return getMostSpecificError(validationErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toError(result *gojsonschema.Result) error {
|
||||
err := getMostSpecificError(result.Errors())
|
||||
return err
|
||||
func toJSONValue(v any) any {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch value := reflect.ValueOf(v); value.Kind() { //nolint: exhaustive // only need to handle maps and slices.
|
||||
case reflect.Map:
|
||||
result := make(map[string]any, value.Len())
|
||||
iter := value.MapRange()
|
||||
for iter.Next() {
|
||||
result[iter.Key().String()] = toJSONValue(iter.Value().Interface())
|
||||
}
|
||||
return result
|
||||
case reflect.Slice:
|
||||
result := make([]any, value.Len())
|
||||
for i := range value.Len() {
|
||||
result[i] = toJSONValue(value.Index(i).Interface())
|
||||
}
|
||||
return result
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
jsonschemaOneOf = "number_one_of"
|
||||
jsonschemaAnyOf = "number_any_of"
|
||||
)
|
||||
var printer = message.NewPrinter(language.English)
|
||||
|
||||
func getDescription(err validationError) string {
|
||||
switch err.parent.Type() {
|
||||
case "invalid_type":
|
||||
if expectedType, ok := err.parent.Details()["expected"].(string); ok {
|
||||
return "must be a " + humanReadableType(expectedType)
|
||||
switch parent := err.parent.ErrorKind.(type) {
|
||||
case *kind.Type:
|
||||
types := make([]string, len(parent.Want))
|
||||
for i, typ := range parent.Want {
|
||||
types[i] = humanReadableType(typ)
|
||||
}
|
||||
case jsonschemaOneOf, jsonschemaAnyOf:
|
||||
if err.child == nil {
|
||||
return err.parent.Description()
|
||||
return "must be " + strings.Join(types, " or ")
|
||||
case *kind.AnyOf, *kind.OneOf:
|
||||
if err.child != nil {
|
||||
return getDescription(validationError{parent: err.child})
|
||||
}
|
||||
return err.child.Description()
|
||||
return err.parent.ErrorKind.LocalizedString(printer)
|
||||
case *kind.AdditionalProperties:
|
||||
if len(parent.Properties) == 1 {
|
||||
return fmt.Sprintf("additional property '%s' is not allowed", parent.Properties[0])
|
||||
}
|
||||
return err.parent.ErrorKind.LocalizedString(printer)
|
||||
case *kind.Minimum:
|
||||
want, _ := parent.Want.Float64()
|
||||
return fmt.Sprintf("must be greater than or equal to %v", want)
|
||||
default:
|
||||
return err.parent.ErrorKind.LocalizedString(printer)
|
||||
}
|
||||
return err.parent.Description()
|
||||
}
|
||||
|
||||
func humanReadableType(definition string) string {
|
||||
@@ -134,26 +175,39 @@ func humanReadableType(definition string) string {
|
||||
allTypes[len(allTypes)-1],
|
||||
)
|
||||
}
|
||||
if definition == "object" {
|
||||
return "mapping"
|
||||
switch definition {
|
||||
case "object":
|
||||
return "a mapping"
|
||||
case "array":
|
||||
return "a list"
|
||||
case "integer":
|
||||
return "an integer"
|
||||
case "null":
|
||||
return "null"
|
||||
default:
|
||||
return "a " + definition
|
||||
}
|
||||
if definition == "array" {
|
||||
return "list"
|
||||
}
|
||||
return definition
|
||||
}
|
||||
|
||||
type validationError struct {
|
||||
parent gojsonschema.ResultError
|
||||
child gojsonschema.ResultError
|
||||
parent *jsonschema.ValidationError
|
||||
child *jsonschema.ValidationError
|
||||
}
|
||||
|
||||
func (err validationError) Error() string {
|
||||
description := getDescription(err)
|
||||
return fmt.Sprintf("%s %s", err.parent.Field(), description)
|
||||
validationErr := err.parent
|
||||
if err.child != nil {
|
||||
validationErr = err.child
|
||||
}
|
||||
field := strings.Join(validationErr.InstanceLocation, ".")
|
||||
if field == "" {
|
||||
field = "(root)"
|
||||
}
|
||||
return field + ": " + getDescription(err)
|
||||
}
|
||||
|
||||
func getMostSpecificError(errs []gojsonschema.ResultError) validationError {
|
||||
func getMostSpecificError(result *jsonschema.ValidationError) validationError {
|
||||
errs := flattenErrors(result)
|
||||
mostSpecificError := 0
|
||||
for i, err := range errs {
|
||||
if specificity(err) > specificity(errs[mostSpecificError]) {
|
||||
@@ -162,28 +216,74 @@ func getMostSpecificError(errs []gojsonschema.ResultError) validationError {
|
||||
}
|
||||
|
||||
if specificity(err) == specificity(errs[mostSpecificError]) {
|
||||
// Invalid type errors win in a tie-breaker for most specific field name
|
||||
if err.Type() == "invalid_type" && errs[mostSpecificError].Type() != "invalid_type" {
|
||||
// Invalid type errors win in a tie-breaker for most specific field name.
|
||||
if isTypeError(err) && !isTypeError(errs[mostSpecificError]) {
|
||||
mostSpecificError = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mostSpecificError+1 == len(errs) {
|
||||
return validationError{parent: errs[mostSpecificError]}
|
||||
err := validationError{parent: errs[mostSpecificError]}
|
||||
switch err.parent.ErrorKind.(type) {
|
||||
case *kind.OneOf, *kind.AnyOf:
|
||||
err.child = mostSpecificCause(err.parent)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch errs[mostSpecificError].Type() {
|
||||
case "number_one_of", "number_any_of":
|
||||
return validationError{
|
||||
parent: errs[mostSpecificError],
|
||||
child: errs[mostSpecificError+1],
|
||||
func mostSpecificCause(err *jsonschema.ValidationError) *jsonschema.ValidationError {
|
||||
var best *jsonschema.ValidationError
|
||||
|
||||
for _, cause := range err.Causes {
|
||||
for _, candidate := range flattenErrors(cause) {
|
||||
switch candidate.ErrorKind.(type) {
|
||||
case *kind.OneOf, *kind.AnyOf:
|
||||
if nested := mostSpecificCause(candidate); nested != nil {
|
||||
candidate = nested
|
||||
}
|
||||
}
|
||||
|
||||
if best == nil || specificity(candidate) > specificity(best) {
|
||||
best = candidate
|
||||
continue
|
||||
}
|
||||
|
||||
if specificity(candidate) == specificity(best) {
|
||||
// Within oneOf/anyOf, an error from an alternative whose
|
||||
// type matched is more useful than a type mismatch from
|
||||
// another alternative.
|
||||
if !isTypeError(candidate) && isTypeError(best) {
|
||||
best = candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return validationError{parent: errs[mostSpecificError]}
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
func specificity(err gojsonschema.ResultError) int {
|
||||
return len(strings.Split(err.Field(), "."))
|
||||
func flattenErrors(err *jsonschema.ValidationError) []*jsonschema.ValidationError {
|
||||
var errs []*jsonschema.ValidationError
|
||||
var walk func(*jsonschema.ValidationError)
|
||||
walk = func(err *jsonschema.ValidationError) {
|
||||
switch err.ErrorKind.(type) {
|
||||
case *kind.Schema, *kind.Group, *kind.Reference:
|
||||
for _, cause := range err.Causes {
|
||||
walk(cause)
|
||||
}
|
||||
default:
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
walk(err)
|
||||
return errs
|
||||
}
|
||||
|
||||
func isTypeError(err *jsonschema.ValidationError) bool {
|
||||
_, ok := err.ErrorKind.(*kind.Type)
|
||||
return ok
|
||||
}
|
||||
|
||||
func specificity(err *jsonschema.ValidationError) int {
|
||||
return len(err.InstanceLocation)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -114,6 +115,18 @@ func TestValidatePorts(t *testing.T) {
|
||||
ports: []string{"8006-8000:8005-8000"},
|
||||
hasError: true,
|
||||
},
|
||||
{
|
||||
ports: []any{json.Number("8080")},
|
||||
hasError: false,
|
||||
},
|
||||
{
|
||||
ports: []any{json.Number("65536")},
|
||||
hasError: true,
|
||||
},
|
||||
{
|
||||
ports: []any{json.Number("1.5")},
|
||||
hasError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
@@ -127,7 +140,7 @@ func TestValidatePorts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
if tc.hasError {
|
||||
assert.ErrorContains(t, Validate(config, "3"), "services.foo.ports.0 Does not match format 'ports'")
|
||||
assert.ErrorContains(t, Validate(config, "3"), "is not valid ports")
|
||||
} else {
|
||||
assert.NilError(t, Validate(config, "3"))
|
||||
}
|
||||
@@ -146,7 +159,7 @@ func TestValidateUndefinedTopLevelOption(t *testing.T) {
|
||||
}
|
||||
|
||||
err := Validate(config, "3.0")
|
||||
assert.ErrorContains(t, err, "Additional property helicopters is not allowed")
|
||||
assert.ErrorContains(t, err, "additional property 'helicopters' is not allowed")
|
||||
}
|
||||
|
||||
func TestValidateAllowsXTopLevelFields(t *testing.T) {
|
||||
@@ -230,7 +243,7 @@ func TestValidateCredentialSpecs(t *testing.T) {
|
||||
}
|
||||
err := Validate(config, tc.version)
|
||||
if tc.expectedErr != "" {
|
||||
assert.ErrorContains(t, err, fmt.Sprintf("Additional property %s is not allowed", tc.expectedErr))
|
||||
assert.ErrorContains(t, err, fmt.Sprintf("additional property '%s' is not allowed", tc.expectedErr))
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
||||
// stored as hostname or as hostname including scheme (in legacy configuration
|
||||
// files).
|
||||
//
|
||||
// It's the equivalent to [registry.ConvertToHostname] in the daemon.
|
||||
// It's based on [registry.ConvertToHostname] from Moby daemon.
|
||||
//
|
||||
// [registry.ConvertToHostname]: https://pkg.go.dev/github.com/moby/moby/v2@v2.0.0-beta.7/daemon/pkg/registry#ConvertToHostname
|
||||
func ConvertToHostname(maybeURL string) string {
|
||||
@@ -117,7 +117,48 @@ func ConvertToHostname(maybeURL string) string {
|
||||
}
|
||||
return net.JoinHostPort(u.Hostname(), u.Port())
|
||||
}
|
||||
|
||||
if hostName := hostFromURLFallback(stripped); hostName != "" {
|
||||
return hostName
|
||||
}
|
||||
}
|
||||
hostName, _, _ := strings.Cut(stripped, "/")
|
||||
return hostName
|
||||
}
|
||||
|
||||
// hostFromURLFallback extracts a host from scheme URLs that net/url rejects.
|
||||
// Go rejects unbracketed IPv6 literals in URL hosts since
|
||||
// https://github.com/golang/go/commit/0c28789bd7dfc55099cac86a3212dda0d6c091f6
|
||||
func hostFromURLFallback(maybeURL string) string {
|
||||
_, rest, ok := strings.Cut(maybeURL, "://")
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
hostName, _, _ := strings.Cut(rest, "/")
|
||||
if hostName == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.Count(hostName, ":") > 1 && !strings.HasPrefix(hostName, "[") {
|
||||
portStart := strings.LastIndex(hostName, ":")
|
||||
addr, port := hostName[:portStart], hostName[portStart+1:]
|
||||
if addr != "" && isPort(port) {
|
||||
return net.JoinHostPort(addr, port)
|
||||
}
|
||||
}
|
||||
|
||||
return hostName
|
||||
}
|
||||
|
||||
func isPort(port string) bool {
|
||||
if port == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range port {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -65,11 +65,11 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
|
||||
+10
-10
@@ -265,14 +265,14 @@ 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -285,14 +285,14 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
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=
|
||||
|
||||
+4
-3
@@ -8,6 +8,7 @@
|
||||
DOCKER_CLI_MOUNTS ?= -v "$(CURDIR)":/go/src/github.com/docker/cli
|
||||
DOCKER_CLI_CONTAINER_NAME ?=
|
||||
DOCKER_CLI_GO_BUILD_CACHE ?= y
|
||||
DOCKER_SOCK ?= $(or $(patsubst unix://%,%,$(filter unix://%,$(shell docker context inspect --format '{{.Endpoints.docker.Host}}'))),/var/run/docker.sock)
|
||||
|
||||
# Sets the name of the company that produced the windows binary.
|
||||
PACKAGER_NAME ?=
|
||||
@@ -62,7 +63,7 @@ dynbinary: ## build dynamically linked binary
|
||||
.PHONY: dev
|
||||
dev: build_docker_image ## start a build container in interactive mode for in-container development
|
||||
$(DOCKER_RUN) -it \
|
||||
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
|
||||
--mount 'type=bind,src=$(DOCKER_SOCK),dst=/var/run/docker.sock' \
|
||||
$(DEV_DOCKER_IMAGE_NAME)
|
||||
|
||||
shell: dev ## alias for dev
|
||||
@@ -134,14 +135,14 @@ test-e2e: test-e2e-local test-e2e-connhelper-ssh ## run all e2e tests
|
||||
test-e2e-local: build-e2e-image # run experimental e2e tests
|
||||
docker run --rm $(ENVVARS) \
|
||||
--mount type=bind,src=$(CURDIR)/build/coverage,dst=/tmp/coverage \
|
||||
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
|
||||
--mount 'type=bind,src=$(DOCKER_SOCK),dst=/var/run/docker.sock' \
|
||||
$(E2E_IMAGE_NAME)
|
||||
|
||||
.PHONY: test-e2e-connhelper-ssh
|
||||
test-e2e-connhelper-ssh: build-e2e-image # run experimental SSH-connection helper e2e tests
|
||||
docker run --rm $(ENVVARS) -e TEST_CONNHELPER=ssh \
|
||||
--mount type=bind,src=$(CURDIR)/build/coverage,dst=/tmp/coverage \
|
||||
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
|
||||
--mount 'type=bind,src=$(DOCKER_SOCK),dst=/var/run/docker.sock' \
|
||||
$(E2E_IMAGE_NAME)
|
||||
|
||||
.PHONY: help
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.7
|
||||
|
||||
# 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
|
||||
@@ -10,7 +10,7 @@ ARG ALPINE_VERSION=3.23
|
||||
# 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.33.0
|
||||
ARG BUILDX_VERSION=0.34.1
|
||||
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golang
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.7
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG GO_VERSION=1.26.7
|
||||
|
||||
# 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
|
||||
|
||||
@@ -113,6 +113,7 @@ The following table provides an overview of the current status of deprecated fea
|
||||
| Deprecated | [`-h` shorthand for `--help`](#-h-shorthand-for---help) | v1.12 | v17.09 |
|
||||
| Removed | [`-e` and `--email` flags on `docker login`](#-e-and---email-flags-on-docker-login) | v1.11 | v17.06 |
|
||||
| Deprecated | [Separator (`:`) of `--security-opt` flag on `docker run`](#separator--of---security-opt-flag-on-docker-run) | v1.11 | v17.06 |
|
||||
| Deprecated | [Links on the default bridge network](#links-on-the-default-bridge-network) | v1.10 | - |
|
||||
| Deprecated | [Ambiguous event fields in API](#ambiguous-event-fields-in-api) | v1.10 | - |
|
||||
| Removed | [`-f` flag on `docker tag`](#-f-flag-on-docker-tag) | v1.10 | v1.12 |
|
||||
| Removed | [HostConfig at API container start](#hostconfig-at-api-container-start) | v1.10 | v1.12 |
|
||||
@@ -1203,6 +1204,19 @@ The `docker login` no longer automatically registers an account with the target
|
||||
|
||||
The flag `--security-opt` doesn't use the colon separator (`:`) anymore to divide keys and values, it uses the equal symbol (`=`) for consistency with other similar flags, like `--storage-opt`.
|
||||
|
||||
### Links on the default bridge network
|
||||
|
||||
**Deprecated in release: v1.10**
|
||||
**Target for removal in release: v30.0**
|
||||
|
||||
The `--link` option on `docker create` and `docker run`, when used with no
|
||||
`--network` specified, was deprecated in v1.10 and will be removed in a future
|
||||
release. Custom networks should be used instead. Docker 29.6 added a deprecation
|
||||
warning when this option is used for the default bridge network.
|
||||
|
||||
Note that the `--link` option is still supported when a non-default network
|
||||
is used.
|
||||
|
||||
### Ambiguous event fields in API
|
||||
|
||||
**Deprecated in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)**
|
||||
|
||||
@@ -74,9 +74,14 @@ The sequence diagrams below depict an allow and deny authorization flow:
|
||||
Each request sent to the plugin includes the authenticated user, the HTTP
|
||||
headers, and the request/response body. Only the user name and the
|
||||
authentication method used are passed to the plugin. Most importantly, no user
|
||||
credentials or tokens are passed. Finally, not all request/response bodies
|
||||
are sent to the authorization plugin. Only those request/response bodies where
|
||||
the `Content-Type` is either `text/*` or `application/json` are sent.
|
||||
credentials or tokens are passed.
|
||||
|
||||
> [!NOTE]
|
||||
> Authorization plugins enforce requests to the Docker daemon's HTTP API only. gRPC method
|
||||
> calls, whether dispatched natively or upgraded through `POST /grpc`, are not subject to authorization.
|
||||
> Furthermore, HTTP request/response bodies where the `Content-Type` is `application/json` are forwarded;
|
||||
> bodies of any other type are not visible to the plugin and cannot be used for enforcement,
|
||||
> even though the daemon acts on this data.
|
||||
|
||||
For commands that can potentially hijack the HTTP connection (`HTTP
|
||||
Upgrade`), such as `exec`, the authorization plugin is only called for the
|
||||
@@ -86,6 +91,22 @@ passed to the authorization plugins. For commands that return chunked HTTP
|
||||
response, such as `logs` and `events`, only the HTTP request is sent to the
|
||||
authorization plugins.
|
||||
|
||||
The Engine's authorization middleware fails closed: when a plugin returns an error or returns `Allow: false`,
|
||||
the request is denied and the error is surfaced to the client. Plugins should also fail closed: if the plugin
|
||||
cannot confidently evaluate a request, it should return an error or `Allow: false`.
|
||||
|
||||
> [!WARNING]
|
||||
> Because the plugin receives the [**raw** request body](#authzpluginauthzreq) from the daemon, it must
|
||||
> apply the same decoding semantics as the daemon to be sure it evaluates the request the daemon will
|
||||
> act on. The daemon decodes JSON with Go's [`encoding/json.Unmarshal`](https://pkg.go.dev/encoding/json#Unmarshal).
|
||||
>
|
||||
> The same requirement applies to the response body. Plugins that depend on `ResponseBody`
|
||||
> inspection for redaction or content-filtering should restrict their policies to endpoints
|
||||
> whose response is produced as a single write (typical of REST-style API responses). For
|
||||
> commands whose responses are streamed or are likely to exceed the [buffer](#response-body-size-and-partial-buffering) through multiple
|
||||
> writes, do not rely on `ResponseBody` for security-relevant decisions; perform the filtering
|
||||
> in a separate layer in front of the daemon.
|
||||
|
||||
### Response body size and partial buffering
|
||||
|
||||
The internal buffer that holds the response body between the daemon's HTTP
|
||||
@@ -102,15 +123,6 @@ is the practical effect of this 64 KiB threshold combined with the
|
||||
is immediately drained to the client and is therefore no longer available
|
||||
for plugin inspection by the time the handler returns.
|
||||
|
||||
> [!NOTE]
|
||||
> Plugins that depend on `ResponseBody` inspection for redaction or
|
||||
> content-filtering should restrict their policies to endpoints whose
|
||||
> response is produced as a single write (typical of REST-style API
|
||||
> responses). For commands whose responses are streamed or are likely to
|
||||
> exceed the buffer through multiple writes, do not rely on `ResponseBody`
|
||||
> for security-relevant decisions; perform the filtering in a separate
|
||||
> layer in front of the daemon.
|
||||
|
||||
During request/response processing, some authorization flows might
|
||||
need to do additional queries to the Docker daemon. To complete such flows,
|
||||
plugins can call the daemon API similar to a regular user. To enable these
|
||||
@@ -234,7 +246,7 @@ Name | Type | Description
|
||||
User | string | The user identification
|
||||
Authentication method | string | The authentication method used
|
||||
Request method | enum | The HTTP method (GET/DELETE/POST)
|
||||
Request URI | string | The HTTP request URI including API version (e.g., v.1.17/containers/json)
|
||||
Request URI | string | The HTTP request URI including API version, as sent by the client (e.g., v.1.17/containers/json)
|
||||
Request headers | map[string]string | Request headers as key value pairs (without the authorization header)
|
||||
Request body | []byte | Raw request body
|
||||
|
||||
@@ -257,7 +269,7 @@ Name | Type | Description
|
||||
User | string | The user identification
|
||||
Authentication method | string | The authentication method used
|
||||
Request method | string | The HTTP method (GET/DELETE/POST)
|
||||
Request URI | string | The HTTP request URI including API version (e.g., v.1.17/containers/json)
|
||||
Request URI | string | The HTTP request URI including API version, as sent by the client (e.g., v.1.17/containers/json)
|
||||
Request headers | map[string]string | Request headers as key value pairs (without the authorization header)
|
||||
Request body | []byte | Raw request body
|
||||
Response status code | int | Status code from the Docker daemon
|
||||
|
||||
@@ -87,7 +87,7 @@ Create a new container
|
||||
| `--privileged` | `bool` | | Give extended privileges to this container |
|
||||
| `-p`, `--publish` | `list` | | Publish a container's port(s) to the host |
|
||||
| `-P`, `--publish-all` | `bool` | | Publish all exposed ports to random ports |
|
||||
| `--pull` | `string` | `missing` | Pull image before creating (`always`, `\|missing`, `never`) |
|
||||
| `--pull` | `string` | `missing` | Pull image before creating (`always`, `missing`, `never`) |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress the pull output |
|
||||
| `--read-only` | `bool` | | Mount the container's root filesystem as read only |
|
||||
| `--restart` | `string` | `no` | Restart policy to apply when a container exits |
|
||||
|
||||
@@ -9,14 +9,14 @@ Fetch the logs of a container
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------|
|
||||
| `--details` | `bool` | | Show extra details provided to logs |
|
||||
| `-f`, `--follow` | `bool` | | Follow log output |
|
||||
| `--since` | `string` | | Show logs since timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs |
|
||||
| `-t`, `--timestamps` | `bool` | | Show timestamps |
|
||||
| [`--until`](#until) | `string` | | Show logs before a timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------|
|
||||
| [`--details`](#details) | `bool` | | Show extra details provided to logs |
|
||||
| [`-f`](#follow), [`--follow`](#follow) | `bool` | | Follow log output |
|
||||
| [`--since`](#since) | `string` | | Show logs since timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
| [`-n`](#tail), [`--tail`](#tail) | `string` | `all` | Number of lines to show from the end of the logs |
|
||||
| [`-t`](#timestamps), [`--timestamps`](#timestamps) | `bool` | | Show timestamps |
|
||||
| [`--until`](#until) | `string` | | Show logs before a timestamp (e.g. `2013-01-02T13:23:37Z`) or relative (e.g. `42m` for 42 minutes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -28,21 +28,34 @@ The `docker logs` command batch-retrieves logs present at the time of execution.
|
||||
For more information about selecting and configuring logging drivers, refer to
|
||||
[Configure logging drivers](https://docs.docker.com/engine/logging/configure/).
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="follow"></a> Stream log output (-f, --follow)
|
||||
|
||||
The `docker logs --follow` command will continue streaming the new output from
|
||||
the container's `STDOUT` and `STDERR`.
|
||||
|
||||
### <a name="tail"></a> Retrieve the last logs (-n, --tail)
|
||||
|
||||
Passing a negative number or a non-integer to `--tail` is invalid and the
|
||||
value is set to `all` in that case.
|
||||
|
||||
|
||||
### <a name="timestamps"></a> Retrieve logs with timestamps (-t, --timestamps)
|
||||
|
||||
The `docker logs --timestamps` command will add an [RFC3339Nano timestamp](https://pkg.go.dev/time#RFC3339Nano)
|
||||
, for example `2014-09-16T06:17:46.000000000Z`, to each
|
||||
log entry. To ensure that the timestamps are aligned the
|
||||
nano-second part of the timestamp will be padded with zero when necessary.
|
||||
|
||||
### <a name="details"></a> Retrieve logs with additional attributes (--details)
|
||||
|
||||
The `docker logs --details` command will add on extra attributes, such as
|
||||
environment variables and labels, provided to `--log-opt` when creating the
|
||||
container.
|
||||
|
||||
### <a name="since"></a> Retrieve logs generated since a specific point in time (--since)
|
||||
|
||||
The `--since` option shows only the container logs generated after
|
||||
a given date. You can specify the date as an RFC 3339 date, a UNIX
|
||||
timestamp, or a Go duration string (e.g. `1m30s`, `3h`). Besides RFC3339 date
|
||||
@@ -56,7 +69,6 @@ seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
||||
fraction of a second no more than nine digits long. You can combine the
|
||||
`--since` option with either or both of the `--follow` or `--tail` options.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="until"></a> Retrieve logs until a specific point in time (--until)
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ Create a new container
|
||||
| `--privileged` | `bool` | | Give extended privileges to this container |
|
||||
| `-p`, `--publish` | `list` | | Publish a container's port(s) to the host |
|
||||
| `-P`, `--publish-all` | `bool` | | Publish all exposed ports to random ports |
|
||||
| `--pull` | `string` | `missing` | Pull image before creating (`always`, `\|missing`, `never`) |
|
||||
| `--pull` | `string` | `missing` | Pull image before creating (`always`, `missing`, `never`) |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress the pull output |
|
||||
| `--read-only` | `bool` | | Mount the container's root filesystem as read only |
|
||||
| `--restart` | `string` | `no` | Restart policy to apply when a container exits |
|
||||
|
||||
@@ -431,7 +431,7 @@ a `docker` command. You can use the following protocols:
|
||||
|
||||
| Scheme | Description | Example |
|
||||
|----------------------------------------|---------------------------|----------------------------------|
|
||||
| `unix://[<path>]` | Unix socket (Linux only) | `unix:///var/run/docker.sock` |
|
||||
| `unix://[<path>]` | Unix socket | `unix:///var/run/docker.sock` |
|
||||
| `tcp://[<IP or host>[:port]]` | TCP connection | `tcp://174.17.0.1:2376` |
|
||||
| `ssh://[username@]<IP or host>[:port]` | SSH connection | `ssh://user@192.168.64.5` |
|
||||
| `npipe://[<name>]` | Named pipe (Windows only) | `npipe:////./pipe/docker_engine` |
|
||||
|
||||
@@ -6,11 +6,11 @@ Defaults to Docker Hub if no server is specified.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------|:---------|:--------|:------------------------------------------------------------|
|
||||
| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT) |
|
||||
| [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin |
|
||||
| [`-u`](#username), [`--username`](#username) | `string` | | Username |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------|:---------|:--------|:-------------------------------------------------------------------|
|
||||
| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT), or `-` to read from stdin |
|
||||
| [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin |
|
||||
| [`-u`](#username), [`--username`](#username) | `string` | | Username |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -244,6 +244,13 @@ The following example reads a password from a file, and passes it to the
|
||||
$ cat ~/my_password.txt | docker login --username foo --password-stdin
|
||||
```
|
||||
|
||||
You can also pass `-` as the value for `--password` or `-p` to read the
|
||||
password from `STDIN`.
|
||||
|
||||
```console
|
||||
$ cat ~/my_password.txt | docker login --username foo --password -
|
||||
```
|
||||
|
||||
## Related commands
|
||||
|
||||
* [logout](logout.md)
|
||||
|
||||
@@ -1057,9 +1057,9 @@ $ echo $?
|
||||
|
||||
##### On Linux
|
||||
|
||||
The default location of the configuration file on Linux is
|
||||
`/etc/docker/daemon.json`. Use the `--config-file` flag to specify a
|
||||
non-default location.
|
||||
The default location of the configuration file on Linux is `/etc/docker/daemon.json`.
|
||||
If the file does not exist, you need to create it first. Use the `--config-file`
|
||||
flag to specify a non-default location.
|
||||
|
||||
The following is a full example of the allowed configuration options on Linux:
|
||||
|
||||
@@ -1208,8 +1208,9 @@ The following is a full example of the allowed configuration options on Linux:
|
||||
##### On Windows
|
||||
|
||||
The default location of the configuration file on Windows is
|
||||
`%programdata%\docker\config\daemon.json`. Use the `--config-file` flag
|
||||
to specify a non-default location.
|
||||
`%programdata%\docker\config\daemon.json`. If the file does not exist, you need
|
||||
to create it first. Use the `--config-file` flag to specify a non-default
|
||||
location.
|
||||
|
||||
The following is a full example of the allowed configuration options on Windows:
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Generated by gen-certs.sh at setup time
|
||||
testdata/registry/certs/ca.crt
|
||||
testdata/registry/certs/ca.key
|
||||
testdata/registry/certs/tlsregistry.crt
|
||||
testdata/registry/certs/tlsregistry.key
|
||||
+28
-2
@@ -3,9 +3,35 @@ services:
|
||||
registry:
|
||||
image: 'registry:3'
|
||||
|
||||
privateregistry:
|
||||
build:
|
||||
context: ./testdata/registry
|
||||
environment:
|
||||
- REGISTRY_HTTP_ADDR=0.0.0.0:5001
|
||||
- REGISTRY_HTTP_DEBUG_ADDR=0.0.0.0:5002
|
||||
- REGISTRY_AUTH=htpasswd
|
||||
- REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
|
||||
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
|
||||
|
||||
tlsregistry:
|
||||
build:
|
||||
context: ./testdata/registry
|
||||
environment:
|
||||
- REGISTRY_HTTP_ADDR=0.0.0.0:5003
|
||||
- REGISTRY_HTTP_DEBUG_ADDR=0.0.0.0:5004
|
||||
- REGISTRY_AUTH=htpasswd
|
||||
- REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
|
||||
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
|
||||
- REGISTRY_HTTP_TLS_CERTIFICATE=/certs/tlsregistry.crt
|
||||
- REGISTRY_HTTP_TLS_KEY=/certs/tlsregistry.key
|
||||
|
||||
engine:
|
||||
image: 'docker:${ENGINE_VERSION:-29}-dind'
|
||||
build:
|
||||
context: ./testdata
|
||||
dockerfile: engine/Dockerfile
|
||||
args:
|
||||
ENGINE_VERSION: ${ENGINE_VERSION:-29}
|
||||
privileged: true
|
||||
command: ['--insecure-registry=registry:5000', '--experimental']
|
||||
command: ['--insecure-registry=registry:5000', '--insecure-registry=privateregistry:5001', '--experimental']
|
||||
environment:
|
||||
- DOCKER_TLS_CERTDIR=
|
||||
|
||||
@@ -209,7 +209,11 @@ func TestProcessTermination(t *testing.T) {
|
||||
|
||||
assert.NilError(t, result.Cmd.Process.Signal(syscall.SIGTERM))
|
||||
|
||||
icmd.WaitOnCmd(time.Second*10, result).Assert(t, icmd.Expected{
|
||||
// Use a generous timeout (20s) because when run through SSH connhelper,
|
||||
// the Docker engine may take longer to close the attach stream after
|
||||
// the container exits. This is a known timing difference across engine
|
||||
// versions (e.g. engine 25 over SSH connhelper).
|
||||
icmd.WaitOnCmd(time.Second*20, result).Assert(t, icmd.Expected{
|
||||
ExitCode: 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/e2e/internal/fixtures"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
// Regression test for https://github.com/docker/cli/issues/5963
|
||||
func TestPullPushPrivateRepository(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
registryPrefix string
|
||||
tagSuffix string
|
||||
}{
|
||||
{name: "insecure", registryPrefix: "privateregistry:5001", tagSuffix: "private"},
|
||||
{name: "tls", registryPrefix: "tlsregistry:5003", tagSuffix: "tls"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := fixtures.SetupConfigFile(t)
|
||||
t.Cleanup(dir.Remove)
|
||||
emptyConfigDir := t.TempDir()
|
||||
|
||||
sourceImage := fixtures.AlpineImage
|
||||
privateImage := tc.registryPrefix + "/private/alpine:test-" + tc.tagSuffix + "-pull-push"
|
||||
|
||||
runWithPrivateRegistryRetry(t,
|
||||
icmd.Command("docker", "pull", sourceImage),
|
||||
).Assert(t, icmd.Success)
|
||||
t.Cleanup(func() {
|
||||
icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success)
|
||||
})
|
||||
|
||||
icmd.RunCommand("docker", "tag", sourceImage, privateImage).Assert(t, icmd.Success)
|
||||
|
||||
pushNoAuth := runWithPrivateRegistryRetry(t,
|
||||
icmd.Command("docker", "push", privateImage),
|
||||
fixtures.WithConfig(emptyConfigDir),
|
||||
)
|
||||
pushNoAuth.Assert(t, icmd.Expected{ExitCode: 1})
|
||||
assertAuthDenied(t, pushNoAuth)
|
||||
|
||||
pushWithAuth := runWithPrivateRegistryRetry(t,
|
||||
icmd.Command("docker", "push", privateImage),
|
||||
fixtures.WithConfig(dir.Path()),
|
||||
)
|
||||
pushWithAuth.Assert(t, icmd.Success)
|
||||
// Docker omits the tag in the "push refers to repository" line; strip it before asserting.
|
||||
privateRepo := privateImage[:strings.LastIndex(privateImage, ":")]
|
||||
assert.Check(t, strings.Contains(pushWithAuth.Combined(), "The push refers to repository ["+privateRepo+"]"), pushWithAuth.Combined())
|
||||
|
||||
icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success)
|
||||
|
||||
pullNoAuth := runWithPrivateRegistryRetry(t,
|
||||
icmd.Command("docker", "pull", privateImage),
|
||||
fixtures.WithConfig(emptyConfigDir),
|
||||
)
|
||||
pullNoAuth.Assert(t, icmd.Expected{ExitCode: 1})
|
||||
assertAuthDenied(t, pullNoAuth)
|
||||
|
||||
pullWithAuth := runWithPrivateRegistryRetry(t,
|
||||
icmd.Command("docker", "pull", privateImage),
|
||||
fixtures.WithConfig(dir.Path()),
|
||||
)
|
||||
pullWithAuth.Assert(t, icmd.Success)
|
||||
assert.Check(t, strings.Contains(pullWithAuth.Combined(), privateImage), pullWithAuth.Combined())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertAuthDenied(t *testing.T, result *icmd.Result) {
|
||||
t.Helper()
|
||||
output := result.Combined()
|
||||
if isPrivateRegistryTransient(output) {
|
||||
t.Fatalf("private registry unavailable while expecting auth failure: %s", output)
|
||||
}
|
||||
|
||||
assert.Assert(t,
|
||||
strings.Contains(output, "requested access to the resource is denied") ||
|
||||
strings.Contains(output, "no basic auth credentials") ||
|
||||
strings.Contains(output, "unauthorized") ||
|
||||
strings.Contains(output, "authentication required"),
|
||||
output,
|
||||
)
|
||||
}
|
||||
|
||||
func runWithPrivateRegistryRetry(t *testing.T, cmd icmd.Cmd, opts ...icmd.CmdOp) *icmd.Result {
|
||||
t.Helper()
|
||||
|
||||
deadline := time.Now().Add(90 * time.Second)
|
||||
for {
|
||||
result := icmd.RunCmd(cmd, opts...)
|
||||
output := result.Combined()
|
||||
if isPrivateRegistryTransient(output) {
|
||||
if time.Now().Before(deadline) {
|
||||
t.Logf("waiting for private registry availability: %s", output)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func isPrivateRegistryTransient(output string) bool {
|
||||
return strings.Contains(output, "lookup privateregistry") ||
|
||||
strings.Contains(output, "lookup tlsregistry") ||
|
||||
strings.Contains(output, "lookup registry") ||
|
||||
strings.Contains(output, "no such host") ||
|
||||
strings.Contains(output, "server misbehaving") ||
|
||||
strings.Contains(output, "Temporary failure in name resolution") ||
|
||||
strings.Contains(output, "connection refused") ||
|
||||
strings.Contains(output, "i/o timeout") ||
|
||||
strings.Contains(output, "TLS handshake timeout") ||
|
||||
strings.Contains(output, "context deadline exceeded") ||
|
||||
strings.Contains(output, "connection reset by peer") ||
|
||||
strings.Contains(output, "unexpected EOF")
|
||||
}
|
||||
@@ -23,6 +23,12 @@ func SetupConfigFile(t *testing.T) fs.Dir {
|
||||
"auths": {
|
||||
"registry:5000": {
|
||||
"auth": "ZWlhaXM6cGFzc3dvcmQK"
|
||||
},
|
||||
"privateregistry:5001": {
|
||||
"auth": "ZTJlOnBhc3N3b3Jk"
|
||||
},
|
||||
"tlsregistry:5003": {
|
||||
"auth": "ZTJlOnBhc3N3b3Jk"
|
||||
}
|
||||
}}`), fs.WithDir("trust", fs.WithDir("private")))
|
||||
return *dir
|
||||
|
||||
+3
@@ -12,6 +12,9 @@ RUN apk --no-cache add openssl openssh-client openssh-server shadow && \
|
||||
useradd --create-home --shell /bin/sh --password $(head -c32 /dev/urandom | base64) penguin && \
|
||||
usermod -aG docker penguin && \
|
||||
ssh-keygen -A
|
||||
# Trust the tlsregistry CA so dockerd connects without --insecure-registry.
|
||||
COPY registry/certs/ca.crt /usr/local/share/ca-certificates/tlsregistry-ca.crt
|
||||
RUN update-ca-certificates
|
||||
# workaround: ssh session excludes /usr/local/bin from $PATH
|
||||
RUN ln -s /usr/local/bin/docker /usr/bin/docker
|
||||
COPY ./connhelper-ssh/entrypoint.sh /
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
ARG ENGINE_VERSION
|
||||
FROM docker:${ENGINE_VERSION}-dind
|
||||
|
||||
# Trust the tlsregistry CA so dockerd connects without --insecure-registry.
|
||||
COPY registry/certs/ca.crt /usr/local/share/ca-certificates/tlsregistry-ca.crt
|
||||
RUN update-ca-certificates
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
FROM registry:3
|
||||
COPY auth /auth
|
||||
COPY certs /certs
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
e2e:$2y$05$DxRBsGSy61vZsBgNVxwUh.UtZmlg3wZHMxYcHYAlupY7r1xbIiuoq
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Regenerate test certificates for the TLS-enabled private registry.
|
||||
# Run this from the repository root or from e2e/testdata/registry/certs/.
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# --- CA ---
|
||||
openssl genrsa -out ca.key 2048
|
||||
openssl req -new -x509 -days 3650 \
|
||||
-key ca.key \
|
||||
-subj '/CN=Test CA (TLS Registry)' \
|
||||
-out ca.crt
|
||||
|
||||
# --- Server cert for tlsregistry (signed by CA) ---
|
||||
cat > openssl-tlsregistry.cnf <<-EOF
|
||||
[v3_req]
|
||||
subjectAltName=DNS:tlsregistry
|
||||
EOF
|
||||
openssl genrsa -out tlsregistry.key 2048
|
||||
openssl req -new \
|
||||
-key tlsregistry.key \
|
||||
-subj '/CN=tlsregistry' \
|
||||
-out tlsregistry.csr
|
||||
openssl x509 -req -days 3650 \
|
||||
-in tlsregistry.csr \
|
||||
-CA ca.crt -CAkey ca.key \
|
||||
-CAcreateserial \
|
||||
-out tlsregistry.crt \
|
||||
-extfile openssl-tlsregistry.cnf \
|
||||
-extensions v3_req
|
||||
rm -f tlsregistry.csr ca.srl openssl-tlsregistry.cnf
|
||||
@@ -88,8 +88,18 @@ func Confirm(ctx context.Context, in io.Reader, out io.Writer, message string) (
|
||||
_, _ = out.Write([]byte(message))
|
||||
|
||||
// On Windows, force the use of the regular OS stdin stream.
|
||||
//
|
||||
// StdStreams() may wrap stdin with windowsconsole.NewAnsiReader to
|
||||
// emulate VT input on consoles that do not support it natively, but
|
||||
// that wrapper has historically caused interactive prompts to hang
|
||||
// or behave incorrectly.
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/moby/moby/issues/14336
|
||||
// - https://github.com/moby/moby/issues/14210
|
||||
// - https://github.com/moby/moby/pull/17738
|
||||
if runtime.GOOS == "windows" {
|
||||
in = streams.NewIn(os.Stdin)
|
||||
in = os.Stdin
|
||||
}
|
||||
|
||||
result := make(chan bool)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build tools
|
||||
|
||||
package main
|
||||
|
||||
import _ "github.com/cpuguy83/go-md2man/v2"
|
||||
@@ -0,0 +1,84 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestValidateThrottleBpsDevice(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input string
|
||||
expectedErr string
|
||||
expectedPath string
|
||||
expectedRate uint64
|
||||
}{
|
||||
{doc: "plain integer", input: "/dev/sda:1000", expectedPath: "/dev/sda", expectedRate: 1000},
|
||||
{doc: "with unit", input: "/dev/sda:1mb", expectedPath: "/dev/sda", expectedRate: 1048576},
|
||||
{doc: "zero", input: "/dev/sda:0", expectedPath: "/dev/sda", expectedRate: 0},
|
||||
{doc: "missing colon", input: "/dev/sda", expectedErr: "bad format: /dev/sda"},
|
||||
{doc: "empty device", input: ":1mb", expectedErr: "bad format: :1mb"},
|
||||
{doc: "missing /dev/ prefix", input: "sda:1mb", expectedErr: "bad format for device path: sda:1mb"},
|
||||
{doc: "non-numeric rate", input: "/dev/sda:foo", expectedErr: "invalid rate for device"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
v, err := ValidateThrottleBpsDevice(tc.input)
|
||||
if tc.expectedErr != "" {
|
||||
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
|
||||
assert.Check(t, is.Nil(v))
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(v.Path, tc.expectedPath))
|
||||
assert.Check(t, is.Equal(v.Rate, tc.expectedRate))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateThrottleIOpsDevice(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input string
|
||||
expectedErr string
|
||||
expectedPath string
|
||||
expectedRate uint64
|
||||
}{
|
||||
{doc: "valid integer", input: "/dev/sda:100", expectedPath: "/dev/sda", expectedRate: 100},
|
||||
{doc: "fractional rejected", input: "/dev/sda:1.5", expectedErr: "invalid rate for device"},
|
||||
{doc: "negative rejected", input: "/dev/sda:-5", expectedErr: "invalid rate for device"},
|
||||
{doc: "unit suffix rejected (iops are integers)", input: "/dev/sda:1mb", expectedErr: "invalid rate for device"},
|
||||
{doc: "missing /dev/ prefix", input: "sda:100", expectedErr: "bad format for device path: sda:100"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
v, err := ValidateThrottleIOpsDevice(tc.input)
|
||||
if tc.expectedErr != "" {
|
||||
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
|
||||
assert.Check(t, is.Nil(v))
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(v.Path, tc.expectedPath))
|
||||
assert.Check(t, is.Equal(v.Rate, tc.expectedRate))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestThrottledeviceOptSetGetList(t *testing.T) {
|
||||
opt := NewThrottledeviceOpt(ValidateThrottleBpsDevice)
|
||||
assert.NilError(t, opt.Set("/dev/sda:1mb"))
|
||||
assert.NilError(t, opt.Set("/dev/sdb:2mb"))
|
||||
|
||||
list := opt.GetList()
|
||||
assert.Assert(t, is.Len(list, 2))
|
||||
assert.Check(t, is.Equal(list[0].Path, "/dev/sda"))
|
||||
assert.Check(t, is.Equal(list[0].Rate, uint64(1048576)))
|
||||
assert.Check(t, is.Equal(list[1].Path, "/dev/sdb"))
|
||||
assert.Check(t, is.Equal(list[1].Rate, uint64(2097152)))
|
||||
|
||||
assert.Check(t, is.ErrorContains(opt.Set("/dev/sdc:bad"), "invalid rate for device"))
|
||||
assert.Check(t, is.Equal(opt.Type(), "list"))
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestValidateWeightDevice(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input string
|
||||
expectedErr string
|
||||
expectedPath string
|
||||
expectedWeight uint16
|
||||
}{
|
||||
{doc: "valid minimum", input: "/dev/sda:10", expectedPath: "/dev/sda", expectedWeight: 10},
|
||||
{doc: "valid maximum", input: "/dev/sda:1000", expectedPath: "/dev/sda", expectedWeight: 1000},
|
||||
{doc: "zero is accepted (unset)", input: "/dev/sda:0", expectedPath: "/dev/sda", expectedWeight: 0},
|
||||
{doc: "below minimum", input: "/dev/sda:9", expectedErr: "invalid weight for device: /dev/sda:9"},
|
||||
{doc: "above maximum", input: "/dev/sda:1001", expectedErr: "invalid weight for device: /dev/sda:1001"},
|
||||
{doc: "overflows uint16", input: "/dev/sda:70000", expectedErr: "invalid weight for device: /dev/sda:70000"},
|
||||
{doc: "missing colon", input: "/dev/sda", expectedErr: "bad format: /dev/sda"},
|
||||
{doc: "empty device", input: ":100", expectedErr: "bad format: :100"},
|
||||
{doc: "missing /dev/ prefix", input: "sda:100", expectedErr: "bad format for device path: sda:100"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
v, err := ValidateWeightDevice(tc.input)
|
||||
if tc.expectedErr != "" {
|
||||
assert.Check(t, is.Error(err, tc.expectedErr))
|
||||
assert.Check(t, is.Nil(v))
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(v.Path, tc.expectedPath))
|
||||
assert.Check(t, is.Equal(v.Weight, tc.expectedWeight))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeightdeviceOptSetGetList(t *testing.T) {
|
||||
opt := NewWeightdeviceOpt(ValidateWeightDevice)
|
||||
assert.NilError(t, opt.Set("/dev/sda:100"))
|
||||
assert.NilError(t, opt.Set("/dev/sdb:200"))
|
||||
|
||||
list := opt.GetList()
|
||||
assert.Assert(t, is.Len(list, 2))
|
||||
assert.Check(t, is.Equal(list[0].Path, "/dev/sda"))
|
||||
assert.Check(t, is.Equal(list[0].Weight, uint16(100)))
|
||||
assert.Check(t, is.Equal(list[1].Path, "/dev/sdb"))
|
||||
assert.Check(t, is.Equal(list[1].Weight, uint16(200)))
|
||||
|
||||
assert.Check(t, is.Error(opt.Set("/dev/sdc:1"), "invalid weight for device: /dev/sdc:1"))
|
||||
assert.Check(t, is.Equal(opt.Type(), "list"))
|
||||
}
|
||||
@@ -98,6 +98,16 @@ fi
|
||||
if [ "$CGO_ENABLED" = "1" ] && [ "$GO_LINKMODE" = "static" ] && [ "$(go env GOOS)" = "linux" ]; then
|
||||
GO_LDFLAGS="$GO_LDFLAGS -linkmode external -extldflags -static"
|
||||
fi
|
||||
|
||||
# We only import google.golang.org/grpc as an indirect dependency, and
|
||||
# do not make gRPC connections.
|
||||
#
|
||||
# grpcnotrace avoids importing golang.org/x/net/trace, which in turn enables
|
||||
# dead code elimination, which can reduce binary size when tracing is not needed.
|
||||
#
|
||||
# see https://github.com/grpc/grpc-go/blob/v1.81.1/trace_notrace.go#L23-L25
|
||||
GO_BUILDTAGS="$GO_BUILDTAGS grpcnotrace"
|
||||
|
||||
if [ "$CGO_ENABLED" = "1" ] && [ "$GO_LINKMODE" = "static" ]; then
|
||||
# compiling statically with CGO enabled requires osusergo and netgo to be set.
|
||||
GO_BUILDTAGS="$GO_BUILDTAGS osusergo netgo"
|
||||
|
||||
@@ -7,15 +7,16 @@ set -eu
|
||||
if ! command -v "$GO_MD2MAN" > /dev/null; then
|
||||
(
|
||||
set -x
|
||||
go build -mod=vendor -modfile=vendor.mod -o ./build/tools/go-md2man ./vendor/github.com/cpuguy83/go-md2man/v2
|
||||
# note: this installs all tools defined in go.mod/vendor.mod
|
||||
GOBIN="$(pwd)/build/tools" go install -mod=vendor tool
|
||||
)
|
||||
GO_MD2MAN=$(realpath ./build/tools/go-md2man)
|
||||
GO_MD2MAN="$(pwd)/build/tools/go-md2man"
|
||||
fi
|
||||
|
||||
mkdir -p man/man1
|
||||
(
|
||||
set -x
|
||||
go run -mod=vendor -modfile=vendor.mod -tags manpages ./man/generate.go --source "./man/src" --target "./man/man1"
|
||||
go run -mod=vendor -tags manpages ./man/generate.go --source "./man/src" --target "./man/man1"
|
||||
)
|
||||
|
||||
(
|
||||
|
||||
@@ -4,7 +4,7 @@ set -eu
|
||||
|
||||
(
|
||||
set -x
|
||||
go run -mod=vendor -modfile=vendor.mod -tags docsgen ./docs/generate/generate.go --formats md --source "./docs/reference/commandline" --target "./docs/reference/commandline"
|
||||
go run -mod=vendor -tags docsgen ./docs/generate/generate.go --formats md --source "./docs/reference/commandline" --target "./docs/reference/commandline"
|
||||
)
|
||||
|
||||
# remove generated help.md file
|
||||
|
||||
@@ -4,4 +4,4 @@ set -eu
|
||||
|
||||
mkdir -p docs/yaml
|
||||
set -x
|
||||
go run -mod=vendor -modfile=vendor.mod -tags docsgen ./docs/generate/generate.go --formats yaml --source "./docs/reference/commandline" --target "./docs/yaml"
|
||||
go run -mod=vendor -tags docsgen ./docs/generate/generate.go --formats yaml --source "./docs/reference/commandline" --target "./docs/yaml"
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Merge the given release tags.
|
||||
set -Eeuo pipefail
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "usage: $0 TAG..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tags_to_sync=("$@")
|
||||
|
||||
for tag_to_sync in "${tags_to_sync[@]}"; do
|
||||
if git merge --no-edit --no-ff "$tag_to_sync"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! git rev-parse --verify --quiet MERGE_HEAD > /dev/null || git diff --quiet --diff-filter=U; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git checkout "$tag_to_sync" -- '.'
|
||||
git add --all
|
||||
|
||||
# Can't use --no-edit with --continue
|
||||
EDITOR=true git merge --continue
|
||||
done
|
||||
|
||||
# Check that every tag is in the branch.
|
||||
# This catches cases where a merge did not actually incorporate one of the
|
||||
# requested release tags.
|
||||
for tag_to_sync in "${tags_to_sync[@]}"; do
|
||||
if ! git merge-base --is-ancestor "$tag_to_sync" HEAD; then
|
||||
echo "tag $tag_to_sync is not contained in $(git rev-parse --abbrev-ref HEAD)" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@@ -26,8 +26,55 @@ setup() {
|
||||
export TEST_CONNHELPER_SSH_ID_RSA_PUB
|
||||
file="${file}:./e2e/compose-env.connhelper-ssh.yaml"
|
||||
fi
|
||||
# Generate TLS certificates for the TLS-enabled private registry.
|
||||
# The certs are baked into the tlsregistry and engine container images,
|
||||
# so they must exist on disk before docker compose up --build.
|
||||
# gen-certs.sh handles its own directory navigation.
|
||||
certdir=e2e/testdata/registry/certs
|
||||
missing=0
|
||||
for f in ca.crt ca.key tlsregistry.crt tlsregistry.key; do
|
||||
if [ ! -f "${certdir}/${f}" ]; then
|
||||
missing=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -eq 1 ]; then
|
||||
sh e2e/testdata/registry/certs/gen-certs.sh
|
||||
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,
|
||||
# fail fast and surface logs instead of waiting on downstream DNS timeouts.
|
||||
local deadline=$((SECONDS + 120))
|
||||
while [ $SECONDS -lt $deadline ]; do
|
||||
local ok=1
|
||||
for svc in registry privateregistry tlsregistry engine; do
|
||||
cid="$(COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose ps -q "$svc" 2>/dev/null || true)"
|
||||
if [ -z "$cid" ]; then
|
||||
ok=0
|
||||
break
|
||||
fi
|
||||
if ! docker inspect -f '{{.State.Running}}' "$cid" 2>/dev/null | grep -q true; then
|
||||
ok=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$ok" -eq 1 ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ $SECONDS -ge $deadline ]; then
|
||||
echo "Timed out waiting for e2e services to start" >&2
|
||||
COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose ps >&2 || true
|
||||
for svc in registry privateregistry tlsregistry engine; do
|
||||
echo "--- logs: $svc ---" >&2
|
||||
COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose logs --no-color --tail=200 "$svc" >&2 || true
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local network="${project}_default"
|
||||
# TODO: only run if inside a container
|
||||
docker network connect "$network" "$(hostname)"
|
||||
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "usage: $0 <RELEASE_BRANCH> <TAG>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
release_branch="$1"
|
||||
tag="$2"
|
||||
|
||||
if [[ "$tag" == *"-rc."* ]]; then
|
||||
echo "error: RC tags cannot be used as sync targets" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$tag" != v* ]]; then
|
||||
echo "error: Tag must start with 'v' (e.g. v29.6.0)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git rev-parse --verify --quiet "refs/tags/$tag" > /dev/null; then
|
||||
echo "error: Tag $tag does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Return early the requested tag is already merged into release branch.
|
||||
if git merge-base --is-ancestor "$tag" "$release_branch"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git rev-parse --verify --quiet upstream/master > /dev/null 2>&1; then
|
||||
master="upstream/master"
|
||||
else
|
||||
master="origin/master"
|
||||
fi
|
||||
|
||||
if ! git merge-base --is-ancestor "$tag" "$master"; then
|
||||
echo "error: Tag $tag is not in $master" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get all docker release tags merged into master but not into release branch
|
||||
git tag --merged "$master" --no-merged "$release_branch" \
|
||||
| grep '^v' \
|
||||
| grep -v -- "-rc." \
|
||||
| sort -V \
|
||||
| awk -v tag="$tag" '{print} $0==tag{exit}'
|
||||
+2
-2
@@ -14,7 +14,7 @@ if [ -z "$TYP" ]; then
|
||||
fi
|
||||
|
||||
update() {
|
||||
(set -x ; go mod tidy -modfile=vendor.mod; go mod vendor -modfile=vendor.mod)
|
||||
(set -x ; go mod tidy; go mod vendor)
|
||||
}
|
||||
|
||||
validate() {
|
||||
@@ -31,7 +31,7 @@ outdated() {
|
||||
echo "go-mod-outdated not found. Install with 'go install github.com/psampaz/go-mod-outdated@v0.8.0'"
|
||||
exit 1
|
||||
fi
|
||||
(set -x ; go list -mod=vendor -mod=readonly -modfile=vendor.mod -u -m -json all | go-mod-outdated -update -direct)
|
||||
(set -x ; go list -mod=readonly -u -m -json all | go-mod-outdated -update -direct)
|
||||
}
|
||||
|
||||
case $TYP in
|
||||
|
||||
+23
-15
@@ -6,28 +6,36 @@
|
||||
# when the command is finished. This script should be dropped when this
|
||||
# repository is a proper Go module with a permanent go.mod.
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOTDIR="$(cd "${SCRIPTDIR}/.." && pwd)"
|
||||
|
||||
if test -e "${ROOTDIR}/go.mod"; then
|
||||
{
|
||||
scriptname=$(basename "$0")
|
||||
cat >&2 <<- EOF
|
||||
$scriptname: WARN: go.mod exists in the repository root!
|
||||
$scriptname: WARN: Using your go.mod instead of our generated version -- this may misbehave!
|
||||
EOF
|
||||
} >&2
|
||||
else
|
||||
cleanup_paths=()
|
||||
|
||||
create_symlink() {
|
||||
local target="$1"
|
||||
local link="$2"
|
||||
|
||||
if [ -e "$link" ]; then
|
||||
# see https://superuser.com/a/196698
|
||||
if ! [ "$link" -ef "${ROOTDIR}/${target}" ]; then
|
||||
echo "$(basename "$0"): WARN: $link exists but is not the expected symlink!" >&2
|
||||
echo "$(basename "$0"): WARN: Using your version instead of our generated version -- this may misbehave!" >&2
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
set -x
|
||||
ln -s "$target" "$link"
|
||||
cleanup_paths+=( "$link" )
|
||||
}
|
||||
|
||||
tee "${ROOTDIR}/go.mod" >&2 <<- EOF
|
||||
module github.com/docker/cli
|
||||
create_symlink "vendor.mod" "${ROOTDIR}/go.mod"
|
||||
create_symlink "vendor.sum" "${ROOTDIR}/go.sum"
|
||||
|
||||
go 1.24.0
|
||||
EOF
|
||||
trap 'rm -f "${ROOTDIR}/go.mod"' EXIT
|
||||
if [ "${#cleanup_paths[@]}" -gt 0 ]; then
|
||||
trap 'rm -f "${cleanup_paths[@]}"' EXIT
|
||||
fi
|
||||
|
||||
GO111MODULE=on GOTOOLCHAIN=local "$@"
|
||||
|
||||
+44
-43
@@ -6,20 +6,22 @@ module github.com/docker/cli
|
||||
|
||||
go 1.25.0
|
||||
|
||||
tool golang.org/x/mod/modfile // for module compatibility check
|
||||
tool (
|
||||
github.com/cpuguy83/go-md2man/v2 // for scripts/docs/generate-man.sh
|
||||
golang.org/x/mod/modfile // for module compatibility check
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containerd/log v0.1.0
|
||||
github.com/containerd/platforms v1.0.0-rc.4
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7
|
||||
github.com/containerd/platforms v1.0.0-rc.5
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli-docs-tool v0.11.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.9.7
|
||||
github.com/docker/go-connections v0.7.0
|
||||
github.com/docker/docker-credential-helpers v0.9.8
|
||||
github.com/docker/go-connections v0.8.1
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/go-jose/go-jose/v4 v4.1.4
|
||||
@@ -28,15 +30,15 @@ require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/mattn/go-runewidth v0.0.23
|
||||
github.com/moby/go-archive v0.2.0
|
||||
github.com/moby/moby/api v1.54.2
|
||||
github.com/moby/moby/client v0.4.1
|
||||
github.com/mattn/go-runewidth v0.0.28
|
||||
github.com/moby/go-archive v0.3.3
|
||||
github.com/moby/moby/api v1.55.0
|
||||
github.com/moby/moby/client v0.5.1
|
||||
github.com/moby/patternmatcher v0.6.1
|
||||
github.com/moby/swarmkit/v2 v2.1.2
|
||||
github.com/moby/sys/atomicwriter v0.1.0
|
||||
github.com/moby/sys/capability v0.4.0
|
||||
github.com/moby/sys/sequential v0.6.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
|
||||
github.com/moby/term v0.5.2
|
||||
@@ -44,24 +46,24 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3
|
||||
github.com/sirupsen/logrus v1.10.2
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
|
||||
go.opentelemetry.io/otel/metric v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/term v0.43.0
|
||||
golang.org/x/text v0.37.0
|
||||
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.22.0
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/term v0.45.0
|
||||
golang.org/x/text v0.41.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
tags.cncf.io/container-device-interface v1.1.0
|
||||
)
|
||||
@@ -74,37 +76,36 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/docker/go-events v0.0.0-20260608200158-dbf6103125a4 // indirect
|
||||
github.com/docker/go-metrics v0.1.0 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // 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.18.5 // indirect
|
||||
github.com/klauspost/compress v1.18.7 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/sys/user v0.4.1 // indirect
|
||||
github.com/moby/sys/userns v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // 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.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
|
||||
golang.org/x/mod v0.40.0 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // 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
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
)
|
||||
|
||||
+88
-150
@@ -6,10 +6,6 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
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=
|
||||
@@ -24,99 +20,78 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4=
|
||||
github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
|
||||
github.com/containerd/platforms v1.0.0-rc.5 h1:vXd569rDrz8LeMXzAnBsy6LADV5YtsD8oyaRarxdmSU=
|
||||
github.com/containerd/platforms v1.0.0-rc.5/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
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.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.7 h1:jaPIxEIDz5bQeghNAdzz0ETwMMnM4vzjZlxz3pWP4JA=
|
||||
github.com/docker/docker-credential-helpers v0.9.7/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4=
|
||||
github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q=
|
||||
github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M=
|
||||
github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-events v0.0.0-20260608200158-dbf6103125a4 h1:Bj+mzWc7MJqqD0UzTaPmwszW3ttOVjSFi84ZU5l+2I0=
|
||||
github.com/docker/go-events v0.0.0-20260608200158-dbf6103125a4/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.1.0 h1:r76KPNpstz+IvQKSWpYegSkkyzex0V3A1ZGVx6bhGlY=
|
||||
github.com/docker/go-metrics v0.1.0/go.mod h1:PciI3sONtB051kXALN1JoIlpcu54E1FuPh+4DuqEzyw=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
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.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
|
||||
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
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.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU=
|
||||
github.com/mattn/go-runewidth v0.0.28/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.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
|
||||
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
|
||||
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.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/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=
|
||||
@@ -125,150 +100,119 @@ github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w
|
||||
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/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
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=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
|
||||
github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o=
|
||||
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
||||
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
|
||||
github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0=
|
||||
github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y=
|
||||
github.com/moby/sys/userns v0.2.0 h1:nEtDtp7NCV/6dutSklNe8FrENPwFdc4mXnZqC/JWgXM=
|
||||
github.com/moby/sys/userns v0.2.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
|
||||
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
||||
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346 h1:TvtdmeYsYEij78hS4oxnwikoiLdIrgav3BA+CbhaDAI=
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
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/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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
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=
|
||||
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.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
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.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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/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.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
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/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -281,21 +225,15 @@ 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-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
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/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=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
||||
|
||||
+16
-9
@@ -87,19 +87,26 @@ func checkWindowsHostAndContainerCompat(host, ctr windowsOSVersion) bool {
|
||||
return host.Build == ctr.Build
|
||||
}
|
||||
|
||||
// Find the latest LTSC version that is earlier than the host version.
|
||||
// This is the earliest version of container that the host can run.
|
||||
// Find the floor of the compatible container range. Per the Windows stable
|
||||
// ABI policy, every host from LTSC N up to (but not including) LTSC N+1 can
|
||||
// run containers from LTSC N-1 up to the host build.
|
||||
//
|
||||
// If the host version is an LTSC, then it supports compatibility with
|
||||
// everything from the previous LTSC up to itself, so we want supportedLTSCRelease
|
||||
// to be the previous entry.
|
||||
// So we find the largest LTSC <= host.Build, then step one entry back to
|
||||
// get the floor. If host.Build is past the latest LTSC in the list
|
||||
// (e.g. a 26200 host, which is in the WS2025 generation), the floor is
|
||||
// still the previous LTSC (20348), not the latest LTSC itself.
|
||||
//
|
||||
// If no match is found, then we know that the host is LTSC2022 exactly,
|
||||
// since we already checked that it's not less than LTSC2022.
|
||||
// If host is the very first LTSC (or no entry matches, which is impossible
|
||||
// here since we already checked host.Build >= ltsc2022), use that LTSC as
|
||||
// the floor.
|
||||
var supportedLTSCRelease uint16 = ltsc2022
|
||||
for i := len(compatLTSCReleases) - 1; i >= 0; i-- {
|
||||
if host.Build > compatLTSCReleases[i] {
|
||||
supportedLTSCRelease = compatLTSCReleases[i]
|
||||
if host.Build >= compatLTSCReleases[i] {
|
||||
if i == 0 {
|
||||
supportedLTSCRelease = compatLTSCReleases[i]
|
||||
} else {
|
||||
supportedLTSCRelease = compatLTSCReleases[i-1]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
+37
-13
@@ -1,21 +1,19 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// dummyAddr is used to satisfy net.Addr for the in-mem socket
|
||||
// it is just stored as a string and returns the string for all calls
|
||||
type dummyAddr string
|
||||
// inmemAddr is used to satisfy net.Addr for the in-memory socket.
|
||||
type inmemAddr string
|
||||
|
||||
// Network returns the addr string, satisfies net.Addr
|
||||
func (a dummyAddr) Network() string {
|
||||
return string(a)
|
||||
}
|
||||
func (a inmemAddr) Network() string { return "inmem" }
|
||||
|
||||
// String returns the string form
|
||||
func (a dummyAddr) String() string {
|
||||
func (a inmemAddr) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
@@ -23,7 +21,7 @@ func (a dummyAddr) String() string {
|
||||
type InmemSocket struct {
|
||||
chConn chan net.Conn
|
||||
chClose chan struct{}
|
||||
addr dummyAddr
|
||||
addr inmemAddr
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
@@ -34,7 +32,7 @@ func NewInmemSocket(addr string, bufSize int) *InmemSocket {
|
||||
return &InmemSocket{
|
||||
chConn: make(chan net.Conn, bufSize),
|
||||
chClose: make(chan struct{}),
|
||||
addr: dummyAddr(addr),
|
||||
addr: inmemAddr(addr),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,15 +65,41 @@ func (s *InmemSocket) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dial is used to establish a connection with the in-mem server.
|
||||
// It returns a [net.ErrClosed] if the connection is already closed.
|
||||
// Dial establishes a connection with the in-memory listener.
|
||||
//
|
||||
// The network and addr parameters are accepted for compatibility with
|
||||
// conventional dialer APIs but are currently ignored.
|
||||
//
|
||||
// It is equivalent to calling DialContext with context.Background().
|
||||
// It returns [net.ErrClosed] if the listener has already been closed.
|
||||
func (s *InmemSocket) Dial(network, addr string) (net.Conn, error) {
|
||||
return s.DialContext(context.Background(), network, addr)
|
||||
}
|
||||
|
||||
// DialContext establishes a connection with the in-memory listener.
|
||||
//
|
||||
// The network and addr parameters are accepted for compatibility with
|
||||
// conventional dialer APIs but are currently ignored.
|
||||
//
|
||||
// If ctx is canceled before the connection is established, DialContext
|
||||
// returns the context error. It returns [net.ErrClosed] if the listener
|
||||
// has already been closed.
|
||||
func (s *InmemSocket) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srvConn, clientConn := net.Pipe()
|
||||
select {
|
||||
case s.chConn <- srvConn:
|
||||
return clientConn, nil
|
||||
case <-ctx.Done():
|
||||
_ = srvConn.Close()
|
||||
_ = clientConn.Close()
|
||||
return nil, ctx.Err()
|
||||
case <-s.chClose:
|
||||
_ = srvConn.Close()
|
||||
_ = clientConn.Close()
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
|
||||
return clientConn, nil
|
||||
}
|
||||
|
||||
+50
-19
@@ -47,38 +47,69 @@ For example:
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const supportsAbstractSockets = runtime.GOOS == "linux"
|
||||
|
||||
// SockOption sets up socket file's creating option
|
||||
type SockOption func(string) error
|
||||
|
||||
// NewUnixSocketWithOpts creates a unix socket with the specified options.
|
||||
// By default, socket permissions are 0000 (i.e.: no access for anyone); pass
|
||||
// WithChmod() and WithChown() to set the desired ownership and permissions.
|
||||
// NewUnixSocketWithOpts creates a Unix socket with the specified options.
|
||||
//
|
||||
// This function temporarily changes the system's "umask" to 0777 to work around
|
||||
// a race condition between creating the socket and setting its permissions. While
|
||||
// this should only be for a short duration, it may affect other processes that
|
||||
// create files/directories during that period.
|
||||
// On Unix platforms, socket permissions are 0000 by default, i.e. no access
|
||||
// for anyone. Pass WithChmod() and WithChown() to set the desired permissions
|
||||
// and ownership.
|
||||
//
|
||||
// On Windows, the socket uses Windows ACLs. Pass WithBasePermissions() to allow
|
||||
// Administrators and LocalSystem full access, or WithAdditionalUsersAndGroups()
|
||||
// to also grant generic read and write access to additional users or groups.
|
||||
//
|
||||
// Abstract Unix sockets (Go's Linux-specific "@" shorthand and the native
|
||||
// leading-NUL representation) are supported only on Linux. On other platforms,
|
||||
// attempts to use abstract socket addresses return an error. Because abstract
|
||||
// sockets have no filesystem representation, filesystem-specific socket
|
||||
// options are not supported.
|
||||
//
|
||||
// On platforms without abstract Unix socket support, attempts to use abstract
|
||||
// socket addresses return an error wrapping [errors.ErrUnsupported].
|
||||
func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error) {
|
||||
if isAbstractSocket(path) {
|
||||
if !supportsAbstractSockets {
|
||||
return nil, fmt.Errorf("abstract Unix socket %q is not supported on %s: %w", path, runtime.GOOS, errors.ErrUnsupported)
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return net.Listen("unix", path)
|
||||
}
|
||||
if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, err := listenUnix(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return listenUnix(path, opts...)
|
||||
}
|
||||
|
||||
for _, op := range opts {
|
||||
if err := op(path); err != nil {
|
||||
_ = l.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
// isAbstractSocket reports whether path is an abstract Unix socket address.
|
||||
//
|
||||
// Go recognizes two representations of abstract socket addresses:
|
||||
//
|
||||
// - On Linux, a path beginning with '@' is translated by the standard library
|
||||
// to the kernel's native leading-NUL representation.
|
||||
// See https://pkg.go.dev/net@go1.27rc2#UnixAddr.
|
||||
//
|
||||
// - A path beginning with a NUL byte uses the kernel's native representation
|
||||
// directly. See https://github.com/golang/go/issues/78615.
|
||||
//
|
||||
// The interpretation of these addresses is platform-dependent; this helper only
|
||||
// recognizes the syntax.
|
||||
func isAbstractSocket(path string) bool {
|
||||
return len(path) > 0 && (path[0] == '@' || path[0] == 0)
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// maxListenerBacklog is similar to the equivalent in stdlib;
|
||||
// https://github.com/golang/go/blob/go1.26.3/src/net/sock_bsd.go#L14-L39
|
||||
func maxListenerBacklog() int {
|
||||
var (
|
||||
n uint32
|
||||
err error
|
||||
)
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "ios":
|
||||
n, err = syscall.SysctlUint32("kern.ipc.somaxconn")
|
||||
case "freebsd":
|
||||
n, err = syscall.SysctlUint32("kern.ipc.soacceptqueue")
|
||||
case "netbsd":
|
||||
// NOTE: NetBSD has no somaxconn-like kernel state so far
|
||||
case "openbsd":
|
||||
n, err = syscall.SysctlUint32("kern.somaxconn")
|
||||
default:
|
||||
return syscall.SOMAXCONN
|
||||
}
|
||||
if n == 0 || err != nil {
|
||||
return syscall.SOMAXCONN
|
||||
}
|
||||
// FreeBSD stores the backlog in a uint16, as does Linux.
|
||||
// Assume the other BSDs do too. Truncate number to avoid wrapping.
|
||||
// See issue 5030.
|
||||
if n > 1<<16-1 {
|
||||
n = 1<<16 - 1
|
||||
}
|
||||
return int(n)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// maxListenerBacklog returns the maximum length of the queue of pending
|
||||
// connections for a listening socket.
|
||||
//
|
||||
// It is similar to in stdlib, but without the fallbacks for Kernel < 4.1.0;
|
||||
// https://github.com/golang/go/blob/go1.26.3/src/net/sock_linux.go#L33-L53
|
||||
func maxListenerBacklog() int {
|
||||
b, err := os.ReadFile("/proc/sys/net/core/somaxconn")
|
||||
if err != nil {
|
||||
return syscall.SOMAXCONN
|
||||
}
|
||||
n, err := strconv.Atoi(strings.TrimSpace(string(b)))
|
||||
if err != nil || n <= 0 {
|
||||
return syscall.SOMAXCONN
|
||||
}
|
||||
return n
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !windows
|
||||
|
||||
package sockets
|
||||
|
||||
import "syscall"
|
||||
|
||||
func maxListenerBacklog() int {
|
||||
return syscall.SOMAXCONN
|
||||
}
|
||||
+113
-18
@@ -3,14 +3,35 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// WithChown modifies the socket file's uid and gid
|
||||
// defaultSocketPerms is the default permission mode applied to newly created
|
||||
// Unix sockets. Sockets are created inaccessible by default; callers can
|
||||
// override this by passing [WithChmod].
|
||||
//
|
||||
// TODO(thaJeztah): Consider changing the default to 0o600, making the socket usable by its owner by default.
|
||||
const defaultSocketPerms os.FileMode = 0o000
|
||||
|
||||
// WithChown modifies the socket file's uid and gid.
|
||||
//
|
||||
// Abstract Unix sockets have no filesystem representation, so this option
|
||||
// returns an error wrapping [errors.ErrUnsupported] when used with an abstract
|
||||
// socket.
|
||||
func WithChown(uid, gid int) SockOption {
|
||||
return func(path string) error {
|
||||
if isAbstractSocket(path) {
|
||||
return &os.PathError{
|
||||
Op: "chown",
|
||||
Path: path,
|
||||
Err: fmt.Errorf("abstract Unix sockets do not support filesystem permissions: %w", errors.ErrUnsupported),
|
||||
}
|
||||
}
|
||||
if err := os.Chown(path, uid, gid); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -19,8 +40,19 @@ func WithChown(uid, gid int) SockOption {
|
||||
}
|
||||
|
||||
// WithChmod modifies socket file's access mode.
|
||||
//
|
||||
// Abstract Unix sockets have no filesystem representation, so this option
|
||||
// returns an error wrapping [errors.ErrUnsupported] when used with an abstract
|
||||
// socket.
|
||||
func WithChmod(mask os.FileMode) SockOption {
|
||||
return func(path string) error {
|
||||
if isAbstractSocket(path) {
|
||||
return &os.PathError{
|
||||
Op: "chmod",
|
||||
Path: path,
|
||||
Err: fmt.Errorf("abstract Unix sockets do not support filesystem permissions: %w", errors.ErrUnsupported),
|
||||
}
|
||||
}
|
||||
if err := os.Chmod(path, mask); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -28,27 +60,90 @@ func WithChmod(mask os.FileMode) SockOption {
|
||||
}
|
||||
}
|
||||
|
||||
// NewUnixSocket creates a unix socket with the specified path and group.
|
||||
// NewUnixSocket creates a Unix socket with the specified path and group.
|
||||
//
|
||||
// On Unix platforms, the socket is owned by root:gid and has permissions 0660.
|
||||
//
|
||||
// Abstract Unix sockets are not supported by this helper. Use [NewUnixSocketWithOpts]
|
||||
// without filesystem permission options instead.
|
||||
func NewUnixSocket(path string, gid int) (net.Listener, error) {
|
||||
return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660))
|
||||
}
|
||||
|
||||
func listenUnix(path string) (net.Listener, error) {
|
||||
// net.Listen does not allow for permissions to be set. As a result, when
|
||||
// specifying custom permissions ("WithChmod()"), there is a short time
|
||||
// between creating the socket and applying the permissions, during which
|
||||
// the socket permissions are Less restrictive than desired.
|
||||
func listenUnix(path string, opts ...SockOption) (_ net.Listener, retErr error) {
|
||||
// net.Listen does not allow permissions or ownership to be set between
|
||||
// bind(2), which creates the socket path, and listen(2), which makes it
|
||||
// possible for clients to connect.
|
||||
//
|
||||
// To work around this limitation of net.Listen(), we temporarily set the
|
||||
// umask to 0777, which forces the socket to be created with 000 permissions
|
||||
// (i.e.: no access for anyone). After that, WithChmod() must be used to set
|
||||
// the desired permissions.
|
||||
// Creating the socket manually lets us apply options after bind(2), but
|
||||
// before listen(2). This avoids temporarily relaxing the process umask while
|
||||
// still preventing a socket from becoming connectable before the requested
|
||||
// permissions are applied.
|
||||
//
|
||||
// We don't use "defer" here, to reset the umask to its original value as soon
|
||||
// as possible. Ideally we'd be able to detect if WithChmod() was passed as
|
||||
// an option, and skip changing umask if default permissions are used.
|
||||
origUmask := syscall.Umask(0o777)
|
||||
l, err := net.Listen("unix", path)
|
||||
syscall.Umask(origUmask)
|
||||
return l, err
|
||||
// See https://github.com/golang/go/issues/11822
|
||||
|
||||
// Similar to sysSocket in stdlib, but without the fast path for Linux.
|
||||
// https://github.com/golang/go/blob/go1.26.3/src/net/sys_cloexec.go#L18-L36
|
||||
syscall.ForkLock.RLock()
|
||||
fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||
if err == nil {
|
||||
syscall.CloseOnExec(fd) // No syscall.SOCK_CLOEXEC on macOS.
|
||||
}
|
||||
syscall.ForkLock.RUnlock()
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("socket", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if fd >= 0 {
|
||||
_ = syscall.Close(fd)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := syscall.Bind(fd, &syscall.SockaddrUnix{Name: path}); err != nil {
|
||||
return nil, os.NewSyscallError("bind", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
_ = syscall.Unlink(path)
|
||||
}
|
||||
}()
|
||||
|
||||
// Secure by default: the socket is not accessible at all
|
||||
// unless permission options are set through WithChmod.
|
||||
if err := os.Chmod(path, defaultSocketPerms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, op := range opts {
|
||||
if err := op(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := syscall.Listen(fd, listenerBacklog()); err != nil {
|
||||
return nil, os.NewSyscallError("listen", err)
|
||||
}
|
||||
|
||||
f := os.NewFile(uintptr(fd), "unix:"+path)
|
||||
fd = -1 // f now owns the original fd; prevent the defer from closing it.
|
||||
|
||||
// FileListener duplicates f, sets the duplicate close-on-exec and nonblocking,
|
||||
// and returns a net.Listener backed by that duplicate. The temporary *os.File
|
||||
// is no longer needed after this point.
|
||||
l, err := net.FileListener(f)
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ul, ok := l.(*net.UnixListener); ok {
|
||||
ul.SetUnlinkOnClose(true)
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// listenerBacklog is a caching wrapper around maxListenerBacklog.
|
||||
var listenerBacklog = sync.OnceValue(maxListenerBacklog)
|
||||
|
||||
+24
-10
@@ -48,7 +48,7 @@ func WithAdditionalUsersAndGroups(additionalUsersAndGroups []string) SockOption
|
||||
}
|
||||
sd, err := getSecurityDescriptor(additionalUsersAndGroups...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("looking up SID: %w", err)
|
||||
return err
|
||||
}
|
||||
return withSDDL(sd)(path)
|
||||
}
|
||||
@@ -85,12 +85,15 @@ func withSDDL(sddl string) SockOption {
|
||||
}
|
||||
}
|
||||
|
||||
// NewUnixSocket creates a new unix socket.
|
||||
// NewUnixSocket creates a new Unix socket.
|
||||
//
|
||||
// It sets [BasePermissions] on the socket path and grants the given additional
|
||||
// users and groups to generic read (GR) and write (GW) access. It returns
|
||||
// an error when failing to resolve any of the additional users and groups,
|
||||
// or when failing to apply the ACL.
|
||||
//
|
||||
// Abstract Unix sockets are not supported by this helper. Attempts to use
|
||||
// abstract socket addresses return an error wrapping [errors.ErrUnsupported].
|
||||
func NewUnixSocket(path string, additionalUsersAndGroups []string) (net.Listener, error) {
|
||||
var opts []SockOption
|
||||
if len(additionalUsersAndGroups) > 0 {
|
||||
@@ -103,27 +106,38 @@ func NewUnixSocket(path string, additionalUsersAndGroups []string) (net.Listener
|
||||
|
||||
// getSecurityDescriptor returns the DACL for the Unix socket.
|
||||
//
|
||||
// By default, it grants [BasePermissions], but allows for additional
|
||||
// users and groups to get generic read (GR) and write (GW) access. It
|
||||
// returns an error when failing to resolve any of the additional users
|
||||
// and groups.
|
||||
// By default, it grants [BasePermissions]. Additional users and groups
|
||||
// are granted generic read (GR) and write (GW) access. It returns an
|
||||
// error if any name cannot be resolved to a SID.
|
||||
func getSecurityDescriptor(additionalUsersAndGroups ...string) (string, error) {
|
||||
sddl := BasePermissions
|
||||
|
||||
// Grant generic read (GR) and write (GW) access to whatever
|
||||
// additional users or groups were specified.
|
||||
//
|
||||
// TODO(thaJeztah): should we fail on, or remove duplicates?
|
||||
// We keep duplicates; two identical allow ACEs are redundant,
|
||||
// but they do not create conflicting permissions, so should not error.
|
||||
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/20233ed8-a6c6-4097-aafa-dd545ed24428
|
||||
for _, g := range additionalUsersAndGroups {
|
||||
sid, err := winio.LookupSidByName(strings.TrimSpace(g))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("looking up SID: %w", err)
|
||||
}
|
||||
sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)
|
||||
sddl += "(A;;GRGW;;;" + sid + ")"
|
||||
}
|
||||
return sddl, nil
|
||||
}
|
||||
|
||||
func listenUnix(path string) (net.Listener, error) {
|
||||
return net.Listen("unix", path)
|
||||
func listenUnix(path string, opts ...SockOption) (net.Listener, error) {
|
||||
l, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, op := range opts {
|
||||
if err := op(path); err != nil {
|
||||
_ = l.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
+2
-1
@@ -78,7 +78,8 @@ func (b *Broadcaster) configure(ch chan configureRequest, sink Sink) error {
|
||||
select {
|
||||
case ch <- configureRequest{
|
||||
sink: sink,
|
||||
response: response}:
|
||||
response: response,
|
||||
}:
|
||||
ch = nil
|
||||
case err := <-response:
|
||||
return err
|
||||
|
||||
+4
-6
@@ -2,9 +2,7 @@ package events
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
// ErrSinkClosed is returned if a write is issued to a sink that has been
|
||||
// closed. If encountered, the error should be considered terminal and
|
||||
// retries will not be successful.
|
||||
ErrSinkClosed = fmt.Errorf("events: sink closed")
|
||||
)
|
||||
// ErrSinkClosed is returned if a write is issued to a sink that has been
|
||||
// closed. If encountered, the error should be considered terminal and
|
||||
// retries will not be successful.
|
||||
var ErrSinkClosed = fmt.Errorf("events: sink closed")
|
||||
|
||||
+7
-9
@@ -173,15 +173,13 @@ func (b *Breaker) Failure(event Event, err error) bool {
|
||||
return false // never drop events.
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultExponentialBackoffConfig provides a default configuration for
|
||||
// exponential backoff.
|
||||
DefaultExponentialBackoffConfig = ExponentialBackoffConfig{
|
||||
Base: time.Second,
|
||||
Factor: time.Second,
|
||||
Max: 20 * time.Second,
|
||||
}
|
||||
)
|
||||
// DefaultExponentialBackoffConfig provides a default configuration for
|
||||
// exponential backoff.
|
||||
var DefaultExponentialBackoffConfig = ExponentialBackoffConfig{
|
||||
Base: time.Second,
|
||||
Factor: time.Second,
|
||||
Max: 20 * time.Second,
|
||||
}
|
||||
|
||||
// ExponentialBackoffConfig configures backoff parameters.
|
||||
//
|
||||
|
||||
-425
@@ -1,425 +0,0 @@
|
||||
Attribution-ShareAlike 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International Public
|
||||
License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-ShareAlike 4.0 International Public License ("Public
|
||||
License"). To the extent this Public License may be interpreted as a
|
||||
contract, You are granted the Licensed Rights in consideration of Your
|
||||
acceptance of these terms and conditions, and the Licensor grants You
|
||||
such rights in consideration of benefits the Licensor receives from
|
||||
making the Licensed Material available under these terms and
|
||||
conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
l. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
m. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
|
||||
including for purposes of Section 3(b); and
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public licenses.
|
||||
Notwithstanding, Creative Commons may elect to apply one of its public
|
||||
licenses to material it publishes and in those instances will be
|
||||
considered the "Licensor." Except for the limited purpose of indicating
|
||||
that material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the public
|
||||
licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
+42
-32
@@ -1,59 +1,71 @@
|
||||
# go-metrics [](https://godoc.org/github.com/docker/go-metrics) 
|
||||
# go-metrics
|
||||
|
||||
[](https://pkg.go.dev/github.com/docker/go-metrics)
|
||||
|
||||
This package is small wrapper around the prometheus go client to help enforce convention and best practices for metrics collection in Docker projects.
|
||||
|
||||
## Best Practices
|
||||
|
||||
This packages is meant to be used for collecting metrics in Docker projects.
|
||||
It is not meant to be used as a replacement for the prometheus client but to help enforce consistent naming across metrics collected.
|
||||
If you have not already read the prometheus best practices around naming and labels you can read the page [here](https://prometheus.io/docs/practices/naming/).
|
||||
This packages is meant to be used for collecting metrics. It is not meant to be
|
||||
used as a replacement for the prometheus client but to help enforce consistent
|
||||
naming across metrics collected. If you have not already read the prometheus
|
||||
best practices around naming and labels you can read the naming section in the
|
||||
[prometheus documentation](https://prometheus.io/docs/practices/naming/).
|
||||
|
||||
The following are a few Docker specific rules that will help you name and work with metrics in your project.
|
||||
The following are a few conventions that help naming metrics in your project.
|
||||
|
||||
1. Namespace and Subsystem
|
||||
### Namespace and Subsystem
|
||||
|
||||
This package provides you with a namespace type that allows you to specify the same namespace and subsystem for your metrics.
|
||||
This package provides you with a namespace type that allows you to specify the
|
||||
same namespace and subsystem for your metrics.
|
||||
|
||||
```go
|
||||
ns := metrics.NewNamespace("engine", "daemon", metrics.Labels{
|
||||
"version": dockerversion.Version,
|
||||
"commit": dockerversion.GitCommit,
|
||||
"version": daemon.Version,
|
||||
"commit": daemon.GitCommit,
|
||||
})
|
||||
```
|
||||
|
||||
In the example above we are creating metrics for the Docker engine's daemon package.
|
||||
`engine` would be the namespace in this example where `daemon` is the subsystem or package where we are collecting the metrics.
|
||||
In the example above we are creating metrics for the daemon package.
|
||||
`engine` would be the namespace in this example where `daemon` is the subsystem
|
||||
or package where we are collecting the metrics.
|
||||
|
||||
A namespace also allows you to attach constant labels to the metrics such as the git commit and version that it is collecting.
|
||||
A namespace also allows you to attach constant labels to the metrics such as
|
||||
the git commit and version that it is collecting.
|
||||
|
||||
2. Declaring your Metrics
|
||||
### Declaring your Metrics
|
||||
|
||||
Try to keep all your metric declarations in one file.
|
||||
This makes it easy for others to see what constant labels are defined on the namespace and what labels are defined on the metrics when they are created.
|
||||
Try to keep all your metric declarations in one file. This makes it easy for
|
||||
others to see what constant labels are defined on the namespace and what
|
||||
labels are defined on the metrics when they are created.
|
||||
|
||||
3. Use labels instead of multiple metrics
|
||||
### Use labels instead of multiple metrics
|
||||
|
||||
Labels allow you to define one metric such as the time it takes to perform a certain action on an object.
|
||||
If we wanted to collect timings on various container actions such as create, start, and delete then we can define one metric called `container_actions` and use labels to specify the type of action.
|
||||
Labels allow you to define one metric such as the time it takes to perform a
|
||||
certain action on an object. If we wanted to collect timings on various container
|
||||
actions such as create, start, and delete then we can define one metric called
|
||||
`container_actions` and use labels to specify the type of action.
|
||||
|
||||
|
||||
```go
|
||||
containerActions = ns.NewLabeledTimer("container_actions", "The number of milliseconds it takes to process each container action", "action")
|
||||
```
|
||||
|
||||
The last parameter is the label name or key.
|
||||
When adding a data point to the metric you will use the `WithValues` function to specify the `action` that you are collecting for.
|
||||
The last parameter is the label name or key. When adding a data point to the
|
||||
metric you can use the `WithValues` function to specify the `action` that you
|
||||
are collecting for.
|
||||
|
||||
```go
|
||||
containerActions.WithValues("create").UpdateSince(start)
|
||||
```
|
||||
|
||||
4. Always use a unit
|
||||
### Always use a unit
|
||||
|
||||
The metric name should describe what you are measuring but you also need to provide the unit that it is being measured with.
|
||||
For a timer, the standard unit is seconds and a counter's standard unit is a total.
|
||||
For gauges you must provide the unit.
|
||||
This package provides a standard set of units for use within the Docker projects.
|
||||
The metric name should describe what you are measuring but you also need to
|
||||
provide the unit that it is being measured with. For a timer, the standard
|
||||
unit is seconds and a counter's standard unit is a total. For gauges you must
|
||||
provide the unit. This package provides a standard set of units for use within
|
||||
your projects.
|
||||
|
||||
```go
|
||||
Nanoseconds Unit = "nanoseconds"
|
||||
@@ -62,18 +74,16 @@ Bytes Unit = "bytes"
|
||||
Total Unit = "total"
|
||||
```
|
||||
|
||||
If you need to use a unit but it is not defined in the package please open a PR to add it but first try to see if one of the already created units will work for your metric, i.e. seconds or nanoseconds vs adding milliseconds.
|
||||
|
||||
## Docs
|
||||
|
||||
Package documentation can be found [here](https://godoc.org/github.com/docker/go-metrics).
|
||||
If you need to use a unit but it is not defined in the package please open
|
||||
a PR to add it but first try to see if one of the existing units work for
|
||||
your metric, i.e., seconds or nanoseconds vs adding milliseconds.
|
||||
|
||||
## HTTP Metrics
|
||||
|
||||
To instrument a http handler, you can wrap the code like this:
|
||||
|
||||
```go
|
||||
namespace := metrics.NewNamespace("docker_distribution", "http", metrics.Labels{"handler": "your_http_handler_name"})
|
||||
namespace := metrics.NewNamespace("api_server", "http", metrics.Labels{"handler": "your_http_handler_name"})
|
||||
httpMetrics := namespace.NewDefaultHttpMetrics()
|
||||
metrics.Register(namespace)
|
||||
instrumentedHandler = metrics.InstrumentHandler(httpMetrics, unInstrumentedHandler)
|
||||
@@ -88,4 +98,4 @@ If you need a custom metrics and it is generic enough to be used by multiple pro
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright © 2016 Docker, Inc. All rights reserved, except as follows. Code is released under the Apache 2.0 license. The README.md file, and files in the "docs" folder are licensed under the Creative Commons Attribution 4.0 International License under the terms and conditions set forth in the file "LICENSE.docs". You may obtain a duplicate copy of the same license, titled CC-BY-SA-4.0, at http://creativecommons.org/licenses/by/4.0/.
|
||||
Copyright © 2016 Docker, Inc. All rights reserved, except as follows. Code is released under the Apache 2.0 license.
|
||||
|
||||
+4
@@ -23,10 +23,12 @@ func (lc *labeledCounter) WithValues(vs ...string) Counter {
|
||||
return &counter{pc: lc.pc.WithLabelValues(vs...)}
|
||||
}
|
||||
|
||||
// Describe implements [prometheus.Collector].
|
||||
func (lc *labeledCounter) Describe(ch chan<- *prometheus.Desc) {
|
||||
lc.pc.Describe(ch)
|
||||
}
|
||||
|
||||
// Collect implements [prometheus.Collector].
|
||||
func (lc *labeledCounter) Collect(ch chan<- prometheus.Metric) {
|
||||
lc.pc.Collect(ch)
|
||||
}
|
||||
@@ -43,10 +45,12 @@ func (c *counter) Inc(vs ...float64) {
|
||||
c.pc.Add(sumFloat64(vs...))
|
||||
}
|
||||
|
||||
// Describe implements [prometheus.Collector].
|
||||
func (c *counter) Describe(ch chan<- *prometheus.Desc) {
|
||||
c.pc.Describe(ch)
|
||||
}
|
||||
|
||||
// Collect implements [prometheus.Collector].
|
||||
func (c *counter) Collect(ch chan<- prometheus.Metric) {
|
||||
c.pc.Collect(ch)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user