mirror of
https://github.com/docker/cli.git
synced 2026-08-26 02:24:12 -05:00
Compare commits
@@ -4,9 +4,6 @@ https://github.com/docker/cli/blob/master/CONTRIBUTING.md
|
||||
|
||||
** Make sure all your commits include a signature generated with `git commit -s` **
|
||||
|
||||
For additional information on our contributing process, read our contributing
|
||||
guide https://docs.docker.com/opensource/code/
|
||||
|
||||
If this is a bug fix, make sure your description includes "fixes #xxxx", or
|
||||
"closes #xxxx"
|
||||
|
||||
@@ -20,6 +17,7 @@ Provide the following information:
|
||||
**- How to verify it**
|
||||
|
||||
**- Human readable description for the release notes**
|
||||
|
||||
<!--
|
||||
Write a short (one line) summary that describes the changes in this
|
||||
pull request for inclusion in the changelog.
|
||||
@@ -28,10 +26,9 @@ It must be placed inside the below triple backticks section.
|
||||
NOTE: Only fill this section if changes introduced in this PR are user-facing.
|
||||
The PR must have a relevant impact/ label.
|
||||
-->
|
||||
```markdown changelog
|
||||
|
||||
```markdown changelog
|
||||
|
||||
```
|
||||
|
||||
**- A picture of a cute animal (not mandatory but encouraged)**
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- "area/testing"
|
||||
- "area/ci"
|
||||
- "status/2-code-review"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
+19
-14
@@ -35,7 +35,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
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@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
set: |
|
||||
@@ -78,17 +80,18 @@ 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/*
|
||||
@@ -101,20 +104,20 @@ jobs:
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: dockereng/cli-bin
|
||||
tags: |
|
||||
@@ -125,7 +128,7 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
@@ -143,7 +146,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -165,10 +170,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: plugins-cross
|
||||
set: |
|
||||
|
||||
@@ -46,9 +46,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
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@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: "1.26.2"
|
||||
go-version: "1.26.4"
|
||||
cache: false
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
category: "/language:go"
|
||||
|
||||
@@ -44,7 +44,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
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@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.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@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
files: ./build/coverage/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -30,15 +30,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
|
||||
uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: test-coverage
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
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@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: "1.26.2"
|
||||
go-version: "1.26.4"
|
||||
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@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
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@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -48,7 +48,9 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
path: src/github.com/docker/cli
|
||||
persist-credentials: false
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: "1.26.2"
|
||||
go-version: "1.26.4"
|
||||
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@9ba6e6f9450baf3b1237f8035c1fdc45932510bd # v1.8.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.2"
|
||||
go: "1.26.4"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
|
||||
+8
-5
@@ -66,7 +66,7 @@ anybody starts working on it.
|
||||
We are always thrilled to receive pull requests. We do our best to process them
|
||||
quickly. If your pull request is not accepted on the first try,
|
||||
don't get discouraged! Our contributor's guide explains [the review process we
|
||||
use for simple changes](https://github.com/docker/docker/blob/master/project/REVIEWING.md).
|
||||
use for simple changes](https://github.com/moby/moby/blob/master/project/REVIEWING.md).
|
||||
|
||||
### Talking to other Docker users and contributors
|
||||
|
||||
@@ -124,8 +124,7 @@ submitting a pull request.
|
||||
Update the documentation when creating or modifying features. Test your
|
||||
documentation changes for clarity, concision, and correctness, as well as a
|
||||
clean documentation build. See our contributors guide for [our style
|
||||
guide](https://docs.docker.com/contribute/style/grammar/) and instructions on [building
|
||||
the documentation](https://docs.docker.com/contribute/).
|
||||
guide](https://github.com/docker/docs/blob/main/STYLE.md).
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||
@@ -145,6 +144,7 @@ not enforced. Common prefixes are `docs: <message>`, `vendor: <message>`,
|
||||
or `telemetry: <message>`.
|
||||
|
||||
A standard commit.
|
||||
|
||||
```
|
||||
Fix the exploding flux capacitor
|
||||
|
||||
@@ -153,6 +153,7 @@ the sun and the moon align.
|
||||
```
|
||||
|
||||
Using a package as prefix.
|
||||
|
||||
```
|
||||
pkg/foo: prevent panic in flux capacitor
|
||||
|
||||
@@ -160,12 +161,14 @@ Calling function A causes the flux capacitor to blow up every time
|
||||
the sun and the moon align.
|
||||
```
|
||||
|
||||
Updating a specific vendored package.
|
||||
Updating a specific vendored dependency.
|
||||
|
||||
```
|
||||
vendor: github.com/docker/docker 6ac445c42bad (master, v28.0-dev)
|
||||
vendor: github.com/moby/moby/client v0.4.0
|
||||
```
|
||||
|
||||
Fixing a broken docs link.
|
||||
|
||||
```
|
||||
docs: fix style/lint issues in deprecated.md
|
||||
```
|
||||
|
||||
+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.2
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# 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
|
||||
|
||||
@@ -51,6 +51,18 @@ Run test:
|
||||
docker buildx bake test
|
||||
```
|
||||
|
||||
Run the unit test:
|
||||
|
||||
```
|
||||
$ make -f docker.Makefile test-unit
|
||||
```
|
||||
|
||||
Run the full test suite:
|
||||
|
||||
```
|
||||
$ make -f docker.Makefile test
|
||||
```
|
||||
|
||||
List all the available targets:
|
||||
|
||||
```shell
|
||||
@@ -62,7 +74,7 @@ make help
|
||||
Start an interactive development environment:
|
||||
|
||||
```shell
|
||||
make -f docker.Makefile shell
|
||||
make shell
|
||||
```
|
||||
|
||||
## Legal
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const maxMessages = 10
|
||||
|
||||
func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
|
||||
out := hookTemplate
|
||||
if strings.Contains(hookTemplate, "{{") {
|
||||
@@ -38,7 +40,10 @@ func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
|
||||
}
|
||||
out = b.String()
|
||||
}
|
||||
return strings.Split(out, "\n"), nil
|
||||
if n := strings.Count(out, "\n"); n > maxMessages {
|
||||
return nil, fmt.Errorf("hook template contains too many messages (%d): maximum is %d", n, maxMessages)
|
||||
}
|
||||
return strings.SplitN(out, "\n", maxMessages), nil
|
||||
}
|
||||
|
||||
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.25
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -102,6 +106,7 @@ func (c *configContext) Labels() string {
|
||||
for k, v := range mapLabels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
}
|
||||
slices.Sort(joinLabels)
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ container source to stdout.`,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symlinks in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress progress output during copy. Progress output is automatically suppressed if no terminal is attached")
|
||||
return cmd
|
||||
@@ -271,7 +271,7 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
|
||||
}
|
||||
|
||||
apiClient := dockerCLI.Client()
|
||||
// if client requests to follow symbol link, then must decide target file to be copied
|
||||
// if client requests to follow symlinks, then must decide target file to be copied
|
||||
var rebaseName string
|
||||
if copyConfig.followLink {
|
||||
src, err := apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{
|
||||
|
||||
@@ -371,7 +371,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
var binds []string
|
||||
volumes := copts.volumes.GetMap()
|
||||
// add any bind targets to the list of container volumes
|
||||
for bind := range copts.volumes.GetMap() {
|
||||
for bind := range volumes {
|
||||
parsed, err := volumespec.Parse(bind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -506,13 +506,13 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
// collect all the environment variables for the container
|
||||
envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetSlice(), copts.env.GetSlice())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("--env-file: %w", err)
|
||||
}
|
||||
|
||||
// collect all the labels for the container
|
||||
labels, err := opts.ReadKVStrings(copts.labelsFile.GetSlice(), copts.labels.GetSlice())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("--label-file: %w", err)
|
||||
}
|
||||
|
||||
pidMode := container.PidMode(copts.pidMode)
|
||||
|
||||
@@ -829,13 +829,6 @@ func TestParseRestartPolicy(t *testing.T) {
|
||||
Name: container.RestartPolicyAlways,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "always:1",
|
||||
expected: container.RestartPolicy{
|
||||
Name: container.RestartPolicyAlways,
|
||||
MaximumRetryCount: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "always:2:3",
|
||||
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
|
||||
@@ -861,6 +854,16 @@ func TestParseRestartPolicy(t *testing.T) {
|
||||
input: "unless-stopped:invalid",
|
||||
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
|
||||
},
|
||||
|
||||
// Unknown / invalid combinations: validation is handled by the daemon>
|
||||
{
|
||||
input: "anything:123",
|
||||
expected: container.RestartPolicy{Name: "anything", MaximumRetryCount: 123},
|
||||
},
|
||||
{
|
||||
input: "negative:-123",
|
||||
expected: container.RestartPolicy{Name: "negative", MaximumRetryCount: -123},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
@@ -937,13 +940,13 @@ func TestParseLoggingOpts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseEnvfileVariables(t *testing.T) {
|
||||
e := "open nonexistent: no such file or directory"
|
||||
expErr := "--env-file: open nonexistent: no such file or directory"
|
||||
if runtime.GOOS == "windows" {
|
||||
e = "open nonexistent: The system cannot find the file specified."
|
||||
expErr = "--env-file: open nonexistent: The system cannot find the file specified."
|
||||
}
|
||||
// env ko
|
||||
if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
|
||||
t.Fatalf("Expected an error with message '%s', got %v", e, err)
|
||||
if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != expErr {
|
||||
t.Fatalf("Expected an error with message '%s', got %v", expErr, err)
|
||||
}
|
||||
// env ok
|
||||
config, _, _, err := parseRun([]string{"--env-file=testdata/valid.env", "img", "cmd"})
|
||||
@@ -990,13 +993,13 @@ func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseLabelfileVariables(t *testing.T) {
|
||||
e := "open nonexistent: no such file or directory"
|
||||
expErr := "--label-file: open nonexistent: no such file or directory"
|
||||
if runtime.GOOS == "windows" {
|
||||
e = "open nonexistent: The system cannot find the file specified."
|
||||
expErr = "--label-file: open nonexistent: The system cannot find the file specified."
|
||||
}
|
||||
// label ko
|
||||
if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
|
||||
t.Fatalf("Expected an error with message '%s', got %v", e, err)
|
||||
if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != expErr {
|
||||
t.Fatalf("Expected an error with message '%s', got %v", expErr, err)
|
||||
}
|
||||
// label ok
|
||||
config, _, _, err := parseRun([]string{"--label-file=testdata/valid.label", "img", "cmd"})
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func newUseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "use CONTEXT",
|
||||
Short: "Set the current docker context",
|
||||
Short: "Set the default docker context",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
@@ -21,13 +21,14 @@ import (
|
||||
const (
|
||||
defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
|
||||
|
||||
namesHeader = "NAMES"
|
||||
commandHeader = "COMMAND"
|
||||
runningForHeader = "CREATED"
|
||||
mountsHeader = "MOUNTS"
|
||||
localVolumes = "LOCAL VOLUMES"
|
||||
networksHeader = "NETWORKS"
|
||||
platformHeader = "PLATFORM"
|
||||
namesHeader = "NAMES"
|
||||
commandHeader = "COMMAND"
|
||||
runningForHeader = "CREATED"
|
||||
mountsHeader = "MOUNTS"
|
||||
localVolumes = "LOCAL VOLUMES"
|
||||
networksHeader = "NETWORKS"
|
||||
platformHeader = "PLATFORM"
|
||||
healthStatusHeader = "HEALTH STATUS"
|
||||
)
|
||||
|
||||
// Platform wraps a [ocispec.Platform] to implement the stringer interface.
|
||||
@@ -121,6 +122,7 @@ func NewContainerContext() *ContainerContext {
|
||||
"LocalVolumes": localVolumes,
|
||||
"Networks": networksHeader,
|
||||
"Platform": platformHeader,
|
||||
"HealthStatus": healthStatusHeader,
|
||||
}
|
||||
return &containerCtx
|
||||
}
|
||||
@@ -352,6 +354,35 @@ func (c *ContainerContext) Networks() string {
|
||||
return strings.Join(networks, ",")
|
||||
}
|
||||
|
||||
// HealthStatus returns the container's health status (for example, "healthy","unhealthy", or "starting").
|
||||
// If no healthcheck is configured, an empty
|
||||
// string is returned.
|
||||
func (c *ContainerContext) HealthStatus() string {
|
||||
if c.c.Health != nil && c.c.Health.Status != "" {
|
||||
return string(c.c.Health.Status)
|
||||
}
|
||||
|
||||
// Fallback for API versions before v1.52, which include health only in Status text;
|
||||
// see https://github.com/moby/moby/pull/50281
|
||||
// see https://github.com/moby/moby/blob/docker-v29.4.3/daemon/container/health.go#L18-L43
|
||||
_, health, ok := strings.Cut(c.c.Status, "(")
|
||||
if !ok || !strings.HasSuffix(health, ")") {
|
||||
return ""
|
||||
}
|
||||
|
||||
health = strings.TrimSuffix(health, ")")
|
||||
health = strings.TrimPrefix(health, "health: ")
|
||||
|
||||
switch container.HealthStatus(health) {
|
||||
case container.Healthy, container.Unhealthy, container.Starting:
|
||||
return health
|
||||
case container.NoHealthcheck:
|
||||
return ""
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// DisplayablePorts returns formatted string representing open ports of container
|
||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
||||
// it's used by command 'docker ps'
|
||||
|
||||
@@ -494,6 +494,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
{
|
||||
"Command": `""`,
|
||||
"CreatedAt": expectedCreated,
|
||||
"HealthStatus": "",
|
||||
"ID": "containerID1",
|
||||
"Image": "ubuntu",
|
||||
"Labels": "",
|
||||
@@ -511,6 +512,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
{
|
||||
"Command": `""`,
|
||||
"CreatedAt": expectedCreated,
|
||||
"HealthStatus": "",
|
||||
"ID": "containerID2",
|
||||
"Image": "ubuntu",
|
||||
"Labels": "",
|
||||
@@ -528,6 +530,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
{
|
||||
"Command": `""`,
|
||||
"CreatedAt": expectedCreated,
|
||||
"HealthStatus": "",
|
||||
"ID": "containerID3",
|
||||
"Image": "ubuntu",
|
||||
"Labels": "",
|
||||
@@ -615,6 +618,7 @@ func TestContainerBackCompat(t *testing.T) {
|
||||
{field: "Image", expected: "docker.io/library/ubuntu"},
|
||||
{field: "Command", expected: `"/bin/sh"`},
|
||||
{field: "CreatedAt", expected: time.Unix(createdAtTime.Unix(), 0).String()},
|
||||
{field: "HealthStatus", expected: ""},
|
||||
{field: "RunningFor", expected: "12 months ago"},
|
||||
{field: "Ports", expected: "8080/tcp"},
|
||||
{field: "Status", expected: "running"},
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.25
|
||||
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -104,6 +108,7 @@ func (c *volumeContext) Labels() string {
|
||||
for k, v := range c.v.Labels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
}
|
||||
slices.Sort(joinLabels)
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,7 @@ func TestVolumeContext(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
ctx = c.volumeCtx
|
||||
v := c.call()
|
||||
if strings.Contains(v, ",") {
|
||||
test.CompareMultipleValues(t, v, c.expValue)
|
||||
} else if v != c.expValue {
|
||||
if v != c.expValue {
|
||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,6 @@ import (
|
||||
"github.com/moby/patternmatcher"
|
||||
)
|
||||
|
||||
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
//
|
||||
// Deprecated: this const is no longer used and will be removed in the next release.
|
||||
const DefaultDockerfileName string = "Dockerfile"
|
||||
|
||||
const (
|
||||
// defaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
defaultDockerfileName string = "Dockerfile"
|
||||
@@ -100,17 +95,6 @@ func filepathMatches(matcher *patternmatcher.PatternMatcher, file string) (bool,
|
||||
return matcher.MatchesOrParentMatches(file)
|
||||
}
|
||||
|
||||
// DetectArchiveReader detects whether the input stream is an archive or a
|
||||
// Dockerfile and returns a buffered version of input, safe to consume in lieu
|
||||
// of input. If an archive is detected, ok is set to true, and to false
|
||||
// otherwise, in which case it is safe to assume input represents the contents
|
||||
// of a Dockerfile.
|
||||
//
|
||||
// Deprecated: this utility was only used internally, and will be removed in the next release.
|
||||
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) {
|
||||
return detectArchiveReader(input)
|
||||
}
|
||||
|
||||
// detectArchiveReader detects whether the input stream is an archive or a
|
||||
// Dockerfile and returns a buffered version of input, safe to consume in lieu
|
||||
// of input. If an archive is detected, ok is set to true, and to false
|
||||
@@ -127,15 +111,6 @@ func detectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err er
|
||||
return newReadCloserWrapper(buf, func() error { return input.Close() }), isArchive(magic), nil
|
||||
}
|
||||
|
||||
// WriteTempDockerfile writes a Dockerfile stream to a temporary file with a
|
||||
// name specified by defaultDockerfileName and returns the path to the
|
||||
// temporary directory containing the Dockerfile.
|
||||
//
|
||||
// Deprecated: this utility was only used internally, and will be removed in the next release.
|
||||
func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
return writeTempDockerfile(rc)
|
||||
}
|
||||
|
||||
// writeTempDockerfile writes a Dockerfile stream to a temporary file with a
|
||||
// name specified by defaultDockerfileName and returns the path to the
|
||||
// temporary directory containing the Dockerfile.
|
||||
@@ -201,14 +176,6 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC
|
||||
}), defaultDockerfileName, nil
|
||||
}
|
||||
|
||||
// IsArchive checks for the magic bytes of a tar or any supported compression
|
||||
// algorithm.
|
||||
//
|
||||
// Deprecated: this utility was used internally and will be removed in the next release.
|
||||
func IsArchive(header []byte) bool {
|
||||
return isArchive(header)
|
||||
}
|
||||
|
||||
// isArchive checks for the magic bytes of a tar or any supported compression
|
||||
// algorithm.
|
||||
func isArchive(header []byte) bool {
|
||||
@@ -305,17 +272,6 @@ func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, er
|
||||
return localDir, relDockerfile, err
|
||||
}
|
||||
|
||||
// ResolveAndValidateContextPath uses the given context directory for a `docker build`
|
||||
// and returns the absolute path to the context directory.
|
||||
//
|
||||
// Deprecated: this utility was used internally and will be removed in the next
|
||||
// release. Use [DetectContextType] to detect the context-type, and use
|
||||
// [GetContextFromLocalDir], [GetContextFromLocalDir], [GetContextFromGitURL],
|
||||
// or [GetContextFromURL] instead.
|
||||
func ResolveAndValidateContextPath(givenContextDir string) (string, error) {
|
||||
return resolveAndValidateContextPath(givenContextDir)
|
||||
}
|
||||
|
||||
// resolveAndValidateContextPath uses the given context directory for a `docker build`
|
||||
// and returns the absolute path to the context directory.
|
||||
func resolveAndValidateContextPath(givenContextDir string) (string, error) {
|
||||
|
||||
@@ -211,6 +211,6 @@ func printAmbiguousHint(stdErr io.Writer, matchName string) {
|
||||
"save",
|
||||
"tag":
|
||||
|
||||
_, _ = fmt.Fprintf(stdErr, "\nNo images found matching %q: did you mean \"docker image %[1]s\"?\n", matchName)
|
||||
_, _ = fmt.Fprintf(stdErr, "No images found matching %q: did you mean \"docker image %[1]s\"?\n", matchName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
WARNING: This output is designed for human readability. For machine-readable output, please use --format.
|
||||
|
||||
No images found matching "ls": did you mean "docker image ls"?
|
||||
|
||||
@@ -6,14 +6,12 @@ package image
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/tui"
|
||||
"github.com/docker/go-units"
|
||||
imagetypes "github.com/moby/moby/api/types/image"
|
||||
@@ -241,10 +239,6 @@ func getPossibleChips(view treeView) (chips []imageChip) {
|
||||
}
|
||||
|
||||
func printImageTree(outs command.Streams, view treeView) {
|
||||
if streamRedirected(outs.Out()) {
|
||||
_, _ = fmt.Fprintln(outs.Err(), "WARNING: This output is designed for human readability. For machine-readable output, please use --format.")
|
||||
}
|
||||
|
||||
out := tui.NewOutput(outs.Out())
|
||||
isTerm := out.IsTerminal()
|
||||
|
||||
@@ -569,17 +563,3 @@ func widestFirstColumnValue(headers []imgColumn, images []topImage) int {
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func streamRedirected(s *streams.Out) bool {
|
||||
fd := s.FD()
|
||||
if os.Stdout.Fd() != fd {
|
||||
return true
|
||||
}
|
||||
|
||||
fi, err := os.Stdout.Stat()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return fi.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.25
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -115,6 +119,7 @@ func (c *networkContext) Labels() string {
|
||||
for k, v := range c.n.Labels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
}
|
||||
slices.Sort(joinLabels)
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
|
||||
@@ -68,9 +68,7 @@ func TestNetworkContext(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
ctx = c.networkCtx
|
||||
v := c.call()
|
||||
if strings.Contains(v, ",") {
|
||||
test.CompareMultipleValues(t, v, c.expValue)
|
||||
} else if v != c.expValue {
|
||||
if v != c.expValue {
|
||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-17
@@ -194,8 +194,7 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
configKey := getAuthConfigKey(reference.Domain(registryRef))
|
||||
authConfig, err := cfg.GetAuthConfig(configKey)
|
||||
authConfig, err := cfg.GetAuthConfig(reference.Domain(registryRef))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -211,18 +210,3 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.25
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -109,6 +113,7 @@ func (c *secretContext) Labels() string {
|
||||
for k, v := range mapLabels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
}
|
||||
slices.Sort(joinLabels)
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +85,7 @@ func Service(
|
||||
return swarm.ServiceSpec{}, err
|
||||
}
|
||||
|
||||
restartPolicy, err := convertRestartPolicy(
|
||||
service.Restart, service.Deploy.RestartPolicy)
|
||||
restartPolicy, err := convertRestartPolicy(service.Restart, service.Deploy.RestartPolicy)
|
||||
if err != nil {
|
||||
return swarm.ServiceSpec{}, err
|
||||
}
|
||||
@@ -472,37 +471,58 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
|
||||
// TODO: log if restart is being ignored
|
||||
if source == nil {
|
||||
policy, err := opts.ParseRestartPolicy(restart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case policy.IsNone():
|
||||
return nil, nil
|
||||
case policy.IsAlways(), policy.IsUnlessStopped():
|
||||
return &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionAny,
|
||||
}, nil
|
||||
case policy.IsOnFailure():
|
||||
attempts := uint64(policy.MaximumRetryCount)
|
||||
return &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionOnFailure,
|
||||
MaxAttempts: &attempts,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown restart policy: %s", restart)
|
||||
}
|
||||
// convertRestartPolicy converts the service's restart-policy. It prefers
|
||||
// service.deploy.restart_policy, but falls back to parsing the legacy
|
||||
// service.restart if service.deploy.restart_policy is not set.
|
||||
func convertRestartPolicy(restart string, restartPolicy *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
|
||||
// Use service.deploy.restart_policy, if set.
|
||||
if restartPolicy != nil {
|
||||
// TODO: log or error if both "service.restart" and "service.deploy.restartpolicy" are set.
|
||||
return &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyCondition(restartPolicy.Condition),
|
||||
Delay: composetypes.ConvertDurationPtr(restartPolicy.Delay),
|
||||
MaxAttempts: restartPolicy.MaxAttempts,
|
||||
Window: composetypes.ConvertDurationPtr(restartPolicy.Window),
|
||||
}, nil
|
||||
}
|
||||
if restart == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyCondition(source.Condition),
|
||||
Delay: composetypes.ConvertDurationPtr(source.Delay),
|
||||
MaxAttempts: source.MaxAttempts,
|
||||
Window: composetypes.ConvertDurationPtr(source.Window),
|
||||
}, nil
|
||||
// Fall back to the legacy service.restart restart-policy.
|
||||
policy, err := opts.ParseRestartPolicy(restart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if policy.MaximumRetryCount < 0 {
|
||||
return nil, errors.New("invalid restart policy: maximum retry count cannot be negative")
|
||||
}
|
||||
uint64Ptr := func(i int) *uint64 {
|
||||
if i <= 0 {
|
||||
return nil
|
||||
}
|
||||
p := uint64(i)
|
||||
return &p
|
||||
}
|
||||
|
||||
switch policy.Name {
|
||||
case container.RestartPolicyDisabled, "":
|
||||
return nil, nil
|
||||
case container.RestartPolicyAlways, container.RestartPolicyUnlessStopped:
|
||||
return &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionAny,
|
||||
MaxAttempts: uint64Ptr(policy.MaximumRetryCount),
|
||||
}, nil
|
||||
case container.RestartPolicyOnFailure:
|
||||
return &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionOnFailure,
|
||||
MaxAttempts: uint64Ptr(policy.MaximumRetryCount),
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid restart policy: unknown policy '%s' (must be one of '%s', '%s', '%s', or '%s')",
|
||||
policy.Name, container.RestartPolicyDisabled, container.RestartPolicyAlways, container.RestartPolicyOnFailure, container.RestartPolicyUnlessStopped,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig {
|
||||
|
||||
@@ -18,35 +18,52 @@ import (
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestConvertRestartPolicyFromNone(t *testing.T) {
|
||||
policy, err := convertRestartPolicy("no", nil)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual((*swarm.RestartPolicy)(nil), policy))
|
||||
}
|
||||
|
||||
func TestConvertRestartPolicyFromUnknown(t *testing.T) {
|
||||
_, err := convertRestartPolicy("unknown", nil)
|
||||
assert.Error(t, err, "unknown restart policy: unknown")
|
||||
}
|
||||
|
||||
func TestConvertRestartPolicyFromAlways(t *testing.T) {
|
||||
policy, err := convertRestartPolicy("always", nil)
|
||||
expected := &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionAny,
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(expected, policy))
|
||||
}
|
||||
|
||||
func TestConvertRestartPolicyFromFailure(t *testing.T) {
|
||||
policy, err := convertRestartPolicy("on-failure:4", nil)
|
||||
func TestConvertRestartPolicy(t *testing.T) {
|
||||
attempts := uint64(4)
|
||||
expected := &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionOnFailure,
|
||||
MaxAttempts: &attempts,
|
||||
tests := []struct {
|
||||
input string
|
||||
expected *swarm.RestartPolicy
|
||||
expError string
|
||||
}{
|
||||
{},
|
||||
{
|
||||
input: "no",
|
||||
},
|
||||
{
|
||||
input: "unknown",
|
||||
expError: "invalid restart policy: unknown policy 'unknown'",
|
||||
},
|
||||
{
|
||||
input: "always",
|
||||
expected: &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionAny,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on-failure:4",
|
||||
expected: &swarm.RestartPolicy{
|
||||
Condition: swarm.RestartPolicyConditionOnFailure,
|
||||
MaxAttempts: &attempts,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
name := tc.input
|
||||
if name == "" {
|
||||
name = "empty"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
policy, err := convertRestartPolicy(tc.input, nil)
|
||||
if tc.expError != "" {
|
||||
assert.Check(t, is.ErrorContains(err, tc.expError))
|
||||
assert.Check(t, is.Nil(policy))
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(policy, tc.expected))
|
||||
})
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(expected, policy))
|
||||
}
|
||||
|
||||
func strPtr(val string) *string {
|
||||
|
||||
@@ -117,20 +117,21 @@ func TestValidatePorts(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
config := dict{
|
||||
"version": "3.0",
|
||||
"services": dict{
|
||||
"foo": dict{
|
||||
"image": "busybox",
|
||||
"ports": tc.ports,
|
||||
t.Run(fmt.Sprint(tc.ports), func(t *testing.T) {
|
||||
config := dict{
|
||||
"services": dict{
|
||||
"foo": dict{
|
||||
"image": "busybox",
|
||||
"ports": tc.ports,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if tc.hasError {
|
||||
assert.ErrorContains(t, Validate(config, "3"), "services.foo.ports.0 Does not match format 'ports'")
|
||||
} else {
|
||||
assert.NilError(t, Validate(config, "3"))
|
||||
}
|
||||
}
|
||||
if tc.hasError {
|
||||
assert.ErrorContains(t, Validate(config, "3"), "services.foo.ports.0 Does not match format 'ports'")
|
||||
} else {
|
||||
assert.NilError(t, Validate(config, "3"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,34 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// 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.5.1+incompatible/registry#IndexServer
|
||||
const authConfigKey = "https://index.docker.io/v1/"
|
||||
|
||||
// getAuthConfigKey returns the canonical key used to look up stored
|
||||
// registry credentials for the given registry domain.
|
||||
//
|
||||
// For the official Docker Hub registry ("docker.io"), credentials are stored
|
||||
// under the historical full index address ("https://index.docker.io/v1/").
|
||||
//
|
||||
// For all other registries, the input is domainName to already be a normalized
|
||||
// hostname (optionally including ":port") and is returned unchanged.
|
||||
//
|
||||
// This function performs key normalization only; it does not validate or parse
|
||||
// the input.
|
||||
//
|
||||
// It is similar to [registry.GetAuthConfigKey] in the daemon.
|
||||
//
|
||||
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker@v28.5.1+incompatible/registry#GetAuthConfigKey
|
||||
func getAuthConfigKey(domainName string) string {
|
||||
if domainName == "docker.io" || domainName == "index.docker.io" {
|
||||
return authConfigKey
|
||||
}
|
||||
return domainName
|
||||
}
|
||||
|
||||
// ConfigFile ~/.docker/config.json file info
|
||||
type ConfigFile struct {
|
||||
AuthConfigs map[string]types.AuthConfig `json:"auths"`
|
||||
@@ -96,12 +124,12 @@ func New(fn string) *ConfigFile {
|
||||
|
||||
// LoadFromReader reads the configuration data given and sets up the auth config
|
||||
// information with given directory and populates the receiver object
|
||||
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||
if err := json.NewDecoder(configData).Decode(configFile); err != nil && !errors.Is(err, io.EOF) {
|
||||
func (c *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||
if err := json.NewDecoder(configData).Decode(c); err != nil && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
for addr, ac := range configFile.AuthConfigs {
|
||||
for addr, ac := range c.AuthConfigs {
|
||||
if ac.Auth != "" {
|
||||
ac.Username, ac.Password, err = decodeAuth(ac.Auth)
|
||||
if err != nil {
|
||||
@@ -110,33 +138,33 @@ func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||
}
|
||||
ac.Auth = ""
|
||||
ac.ServerAddress = addr
|
||||
configFile.AuthConfigs[addr] = ac
|
||||
c.AuthConfigs[addr] = ac
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainsAuth returns whether there is authentication configured
|
||||
// in this file or not.
|
||||
func (configFile *ConfigFile) ContainsAuth() bool {
|
||||
return configFile.CredentialsStore != "" ||
|
||||
len(configFile.CredentialHelpers) > 0 ||
|
||||
len(configFile.AuthConfigs) > 0
|
||||
func (c *ConfigFile) ContainsAuth() bool {
|
||||
return c.CredentialsStore != "" ||
|
||||
len(c.CredentialHelpers) > 0 ||
|
||||
len(c.AuthConfigs) > 0
|
||||
}
|
||||
|
||||
// GetAuthConfigs returns the mapping of repo to auth configuration
|
||||
func (configFile *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig {
|
||||
if configFile.AuthConfigs == nil {
|
||||
configFile.AuthConfigs = make(map[string]types.AuthConfig)
|
||||
func (c *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig {
|
||||
if c.AuthConfigs == nil {
|
||||
c.AuthConfigs = make(map[string]types.AuthConfig)
|
||||
}
|
||||
return configFile.AuthConfigs
|
||||
return c.AuthConfigs
|
||||
}
|
||||
|
||||
// SaveToWriter encodes and writes out all the authorization information to
|
||||
// the given writer
|
||||
func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||
func (c *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||
// Encode sensitive data into a new/temp struct
|
||||
tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs))
|
||||
for k, authConfig := range configFile.AuthConfigs {
|
||||
tmpAuthConfigs := make(map[string]types.AuthConfig, len(c.AuthConfigs))
|
||||
for k, authConfig := range c.AuthConfigs {
|
||||
authCopy := authConfig
|
||||
// encode and save the authstring, while blanking out the original fields
|
||||
authCopy.Auth = encodeAuth(&authCopy)
|
||||
@@ -146,18 +174,18 @@ func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||
tmpAuthConfigs[k] = authCopy
|
||||
}
|
||||
|
||||
saveAuthConfigs := configFile.AuthConfigs
|
||||
configFile.AuthConfigs = tmpAuthConfigs
|
||||
defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
||||
saveAuthConfigs := c.AuthConfigs
|
||||
c.AuthConfigs = tmpAuthConfigs
|
||||
defer func() { c.AuthConfigs = saveAuthConfigs }()
|
||||
|
||||
// User-Agent header is automatically set, and should not be stored in the configuration
|
||||
for v := range configFile.HTTPHeaders {
|
||||
for v := range c.HTTPHeaders {
|
||||
if strings.EqualFold(v, "User-Agent") {
|
||||
delete(configFile.HTTPHeaders, v)
|
||||
delete(c.HTTPHeaders, v)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(configFile, "", "\t")
|
||||
data, err := json.MarshalIndent(c, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -166,16 +194,16 @@ func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||
}
|
||||
|
||||
// Save encodes and writes out all the authorization information
|
||||
func (configFile *ConfigFile) Save() (retErr error) {
|
||||
if configFile.Filename == "" {
|
||||
func (c *ConfigFile) Save() (retErr error) {
|
||||
if c.Filename == "" {
|
||||
return errors.New("can't save config with empty filename")
|
||||
}
|
||||
|
||||
dir := filepath.Dir(configFile.Filename)
|
||||
dir := filepath.Dir(c.Filename)
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
temp, err := os.CreateTemp(dir, filepath.Base(configFile.Filename))
|
||||
temp, err := os.CreateTemp(dir, filepath.Base(c.Filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -189,7 +217,7 @@ func (configFile *ConfigFile) Save() (retErr error) {
|
||||
}
|
||||
}()
|
||||
|
||||
err = configFile.SaveToWriter(temp)
|
||||
err = c.SaveToWriter(temp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -199,7 +227,7 @@ func (configFile *ConfigFile) Save() (retErr error) {
|
||||
}
|
||||
|
||||
// Handle situation where the configfile is a symlink, and allow for dangling symlinks
|
||||
cfgFile := configFile.Filename
|
||||
cfgFile := c.Filename
|
||||
if f, err := filepath.EvalSymlinks(cfgFile); err == nil {
|
||||
cfgFile = f
|
||||
} else if os.IsNotExist(err) {
|
||||
@@ -217,16 +245,16 @@ func (configFile *ConfigFile) Save() (retErr error) {
|
||||
|
||||
// ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and
|
||||
// then checking this against any environment variables provided to the container
|
||||
func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts map[string]*string) map[string]*string {
|
||||
func (c *ConfigFile) ParseProxyConfig(host string, runOpts map[string]*string) map[string]*string {
|
||||
var cfgKey string
|
||||
|
||||
if _, ok := configFile.Proxies[host]; !ok {
|
||||
if _, ok := c.Proxies[host]; !ok {
|
||||
cfgKey = "default"
|
||||
} else {
|
||||
cfgKey = host
|
||||
}
|
||||
|
||||
config := configFile.Proxies[cfgKey]
|
||||
config := c.Proxies[cfgKey]
|
||||
permitted := map[string]*string{
|
||||
"HTTP_PROXY": &config.HTTPProxy,
|
||||
"HTTPS_PROXY": &config.HTTPSProxy,
|
||||
@@ -290,11 +318,11 @@ func decodeAuth(authStr string) (string, string, error) {
|
||||
|
||||
// GetCredentialsStore returns a new credentials store from the settings in the
|
||||
// configuration file
|
||||
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
|
||||
store := credentials.NewFileStore(configFile)
|
||||
func (c *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
|
||||
store := credentials.NewFileStore(c)
|
||||
|
||||
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
|
||||
store = newNativeStore(configFile, helper)
|
||||
if helper := getConfiguredCredentialStore(c, getAuthConfigKey(registryHostname)); helper != "" {
|
||||
store = newNativeStore(c, helper)
|
||||
}
|
||||
|
||||
envConfig := os.Getenv(DockerEnvConfigKey)
|
||||
@@ -357,8 +385,9 @@ var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentia
|
||||
}
|
||||
|
||||
// GetAuthConfig for a repository from the credential store
|
||||
func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) {
|
||||
return configFile.GetCredentialsStore(registryHostname).Get(registryHostname)
|
||||
func (c *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) {
|
||||
acKey := getAuthConfigKey(registryHostname)
|
||||
return c.GetCredentialsStore(acKey).Get(acKey)
|
||||
}
|
||||
|
||||
// getConfiguredCredentialStore returns the credential helper configured for the
|
||||
@@ -375,13 +404,13 @@ func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string
|
||||
|
||||
// GetAllCredentials returns all of the credentials stored in all of the
|
||||
// configured credential stores.
|
||||
func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) {
|
||||
func (c *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) {
|
||||
auths := make(map[string]types.AuthConfig)
|
||||
addAll := func(from map[string]types.AuthConfig) {
|
||||
maps.Copy(auths, from)
|
||||
}
|
||||
|
||||
defaultStore := configFile.GetCredentialsStore("")
|
||||
defaultStore := c.GetCredentialsStore("")
|
||||
newAuths, err := defaultStore.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -389,8 +418,8 @@ func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig,
|
||||
addAll(newAuths)
|
||||
|
||||
// Auth configs from a registry-specific helper should override those from the default store.
|
||||
for registryHostname := range configFile.CredentialHelpers {
|
||||
newAuth, err := configFile.GetAuthConfig(registryHostname)
|
||||
for registryHostname := range c.CredentialHelpers {
|
||||
newAuth, err := c.GetAuthConfig(registryHostname)
|
||||
if err != nil {
|
||||
// TODO(thaJeztah): use context-logger, so that this output can be suppressed (in tests).
|
||||
logrus.WithError(err).Warnf("Failed to get credentials for registry: %s", registryHostname)
|
||||
@@ -402,16 +431,16 @@ func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig,
|
||||
}
|
||||
|
||||
// GetFilename returns the file name that this config file is based on.
|
||||
func (configFile *ConfigFile) GetFilename() string {
|
||||
return configFile.Filename
|
||||
func (c *ConfigFile) GetFilename() string {
|
||||
return c.Filename
|
||||
}
|
||||
|
||||
// PluginConfig retrieves the requested option for the given plugin.
|
||||
func (configFile *ConfigFile) PluginConfig(pluginname, option string) (string, bool) {
|
||||
if configFile.Plugins == nil {
|
||||
func (c *ConfigFile) PluginConfig(pluginname, option string) (string, bool) {
|
||||
if c.Plugins == nil {
|
||||
return "", false
|
||||
}
|
||||
pluginConfig, ok := configFile.Plugins[pluginname]
|
||||
pluginConfig, ok := c.Plugins[pluginname]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
@@ -423,14 +452,14 @@ func (configFile *ConfigFile) PluginConfig(pluginname, option string) (string, b
|
||||
// plugin. Passing a value of "" will remove the option. If removing
|
||||
// the final config item for a given plugin then also cleans up the
|
||||
// overall plugin entry.
|
||||
func (configFile *ConfigFile) SetPluginConfig(pluginname, option, value string) {
|
||||
if configFile.Plugins == nil {
|
||||
configFile.Plugins = make(map[string]map[string]string)
|
||||
func (c *ConfigFile) SetPluginConfig(pluginname, option, value string) {
|
||||
if c.Plugins == nil {
|
||||
c.Plugins = make(map[string]map[string]string)
|
||||
}
|
||||
pluginConfig, ok := configFile.Plugins[pluginname]
|
||||
pluginConfig, ok := c.Plugins[pluginname]
|
||||
if !ok {
|
||||
pluginConfig = make(map[string]string)
|
||||
configFile.Plugins[pluginname] = pluginConfig
|
||||
c.Plugins[pluginname] = pluginConfig
|
||||
}
|
||||
if value != "" {
|
||||
pluginConfig[option] = value
|
||||
@@ -438,6 +467,6 @@ func (configFile *ConfigFile) SetPluginConfig(pluginname, option, value string)
|
||||
delete(pluginConfig, option)
|
||||
}
|
||||
if len(pluginConfig) == 0 {
|
||||
delete(configFile.Plugins, pluginname)
|
||||
delete(c.Plugins, pluginname)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,19 @@ package credentials
|
||||
|
||||
import "os/exec"
|
||||
|
||||
// DetectDefaultStore return the default credentials store for the platform if
|
||||
// no user-defined store is passed, and the store executable is available.
|
||||
func DetectDefaultStore(store string) string {
|
||||
if store != "" {
|
||||
// DetectDefaultStore returns the credentials store to use if no user-defined
|
||||
// custom helper is passed.
|
||||
//
|
||||
// Some platforms define a preferred helper, in which case it attempts to look
|
||||
// up the helper binary before falling back to the platform's default.
|
||||
//
|
||||
// If no user-defined helper is passed, and no helper is found, it returns an
|
||||
// empty string, which means credentials are stored unencrypted in the CLI's
|
||||
// config-file without the use of a credentials store.
|
||||
func DetectDefaultStore(customStore string) string {
|
||||
if customStore != "" {
|
||||
// use user-defined
|
||||
return store
|
||||
return customStore
|
||||
}
|
||||
|
||||
platformDefault := defaultCredentialsStore()
|
||||
|
||||
@@ -170,7 +170,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_pri
|
||||
# cp
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a cp -d "Copy files/folders between a container and the local filesystem"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from cp' -s a -l archive -d 'Archive mode (copy all uid/gid information)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from cp' -s L -l follow-link -d 'Always follow symbol link in SRC_PATH'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from cp' -s L -l follow-link -d 'Always follow symlinks in SRC_PATH'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from cp' -l help -d 'Print usage'
|
||||
|
||||
# create
|
||||
@@ -251,7 +251,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s p -l publish
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s P -l publish-all -d 'Publish all exposed ports to random ports'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l read-only -d "Mount the container's root filesystem as read only"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l restart -d 'Restart policy to apply when a container exits'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l rm -d 'Automatically remove the container when it exits'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l rm -d 'Automatically remove the container and its associated anonymous volumes when it exits'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l runtime -d 'Runtime to use for this container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l security-opt -d 'Security Options'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l shm-size -d 'Size of /dev/shm'
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
#
|
||||
# zsh completion for docker (http://docker.com)
|
||||
#
|
||||
# version: 0.3.0
|
||||
# github: https://github.com/felixr/docker-zsh-completion
|
||||
# github: https://github.com/docker/cli
|
||||
#
|
||||
# contributors:
|
||||
# - Felix Riedel
|
||||
@@ -706,7 +705,7 @@ __docker_container_subcommand() {
|
||||
local state
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -L --follow-link)"{-L,--follow-link}"[Always follow symbol link]" \
|
||||
"($help -L --follow-link)"{-L,--follow-link}"[Always follow symlinks]" \
|
||||
"($help -)1:container:->container" \
|
||||
"($help -)2:hostpath:_files" && ret=0
|
||||
case $state in
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.26.2
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# 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.2
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# 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.2
|
||||
ARG GO_VERSION=1.26.4
|
||||
|
||||
# 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,36 +0,0 @@
|
||||
# The non-reference docs have been moved!
|
||||
|
||||
<!-- This file is maintained within the docker/cli GitHub
|
||||
repository at https://github.com/docker/cli/. Make all
|
||||
pull requests against that repo. If you see this file in
|
||||
another repository, consider it read-only there, as it will
|
||||
periodically be overwritten by the definitive file. Pull
|
||||
requests which include edits to this file in other repositories
|
||||
will be rejected.
|
||||
-->
|
||||
|
||||
The documentation for Docker Engine has been merged into
|
||||
[the general documentation repo](https://github.com/docker/docker.github.io).
|
||||
|
||||
See the [README](https://github.com/docker/docker.github.io/blob/master/README.md)
|
||||
for instructions on contributing to and building the documentation.
|
||||
|
||||
If you'd like to edit the current published version of the Engine docs,
|
||||
do it in the master branch here:
|
||||
https://github.com/docker/docker.github.io/tree/master/engine
|
||||
|
||||
If you need to document the functionality of an upcoming Engine release,
|
||||
use the `vnext-engine` branch:
|
||||
https://github.com/docker/docker.github.io/tree/vnext-engine/engine
|
||||
|
||||
The reference docs have been left in docker/docker (this repo), which remains
|
||||
the place to edit them.
|
||||
|
||||
The docs in the general repo are open-source and we appreciate
|
||||
your feedback and pull requests!
|
||||
|
||||
# Generate docs
|
||||
|
||||
```shell
|
||||
$ make -f docker.Makefile yamldocs
|
||||
```
|
||||
@@ -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,35 @@ 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`.
|
||||
|
||||
### Response body size and partial buffering
|
||||
|
||||
The internal buffer that holds the response body between the daemon's HTTP
|
||||
handler and the plugin's response authorization callback (`responseModifier`,
|
||||
defined in [`pkg/authorization/response.go`](https://github.com/moby/moby/blob/master/pkg/authorization/response.go))
|
||||
has a fixed capacity of 64 KiB (`maxBufferSize`).
|
||||
|
||||
For most non-streaming endpoints the full response is buffered for plugin
|
||||
inspection regardless of total size, because Go's `encoding/json` encoder
|
||||
serializes the complete payload into a single underlying write. The
|
||||
streaming-response exclusion noted above (for example, `logs` and `events`)
|
||||
is the practical effect of this 64 KiB threshold combined with the
|
||||
`io.WriteFlusher` write pattern used by streaming handlers, where each write
|
||||
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
|
||||
@@ -209,7 +243,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
|
||||
|
||||
@@ -232,7 +266,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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Dockerfile reference
|
||||
|
||||
This file has moved to the BuildKit repository at https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md
|
||||
@@ -82,7 +82,7 @@ The following filter matches only services with the `project` label with the
|
||||
`project-a` value.
|
||||
|
||||
```console
|
||||
$ docker service ls --filter label=project=test
|
||||
$ docker config ls --filter label=project=project-a
|
||||
|
||||
ID NAME CREATED UPDATED
|
||||
mem02h8n73mybpgqjf0kfi1n0 test_config About an hour ago About an hour ago
|
||||
@@ -95,7 +95,7 @@ The `name` filter matches on all or prefix of a config's name.
|
||||
The following filter matches config with a name containing a prefix of `test`.
|
||||
|
||||
```console
|
||||
$ docker config ls --filter name=test_config
|
||||
$ docker config ls --filter name=test
|
||||
|
||||
ID NAME CREATED UPDATED
|
||||
mem02h8n73mybpgqjf0kfi1n0 test_config About an hour ago About an hour ago
|
||||
|
||||
@@ -17,7 +17,7 @@ container source to stdout.
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:-------|:--------|:-------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--archive` | `bool` | | Archive mode (copy all uid/gid information) |
|
||||
| `-L`, `--follow-link` | `bool` | | Always follow symbol link in SRC_PATH |
|
||||
| `-L`, `--follow-link` | `bool` | | Always follow symlinks in SRC_PATH |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress progress output during copy. Progress output is automatically suppressed if no terminal is attached |
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ CONTAINER ID IMAGE COMMAND CREATED
|
||||
9b6247364a03 busybox "top" 2 minutes ago Up 2 minutes nostalgic_stallman
|
||||
```
|
||||
|
||||
You can also filter for a substring in a name as this shows:
|
||||
You can filter for a substring in a name as this shows:
|
||||
|
||||
```console
|
||||
$ docker ps --filter "name=nostalgic"
|
||||
@@ -395,22 +395,23 @@ template.
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
| Placeholder | Description |
|
||||
|:--------------|:------------------------------------------------------------------------------------------------|
|
||||
| `.ID` | Container ID |
|
||||
| `.Image` | Image ID |
|
||||
| `.Command` | Quoted command |
|
||||
| `.CreatedAt` | Time when the container was created. |
|
||||
| `.RunningFor` | Elapsed time since the container was started. |
|
||||
| `.Ports` | Exposed ports. |
|
||||
| `.State` | Container status (for example; "created", "running", "exited"). |
|
||||
| `.Status` | Container status with details about duration and health-status. |
|
||||
| `.Size` | Container disk size. |
|
||||
| `.Names` | Container names. |
|
||||
| `.Labels` | All labels assigned to the container. |
|
||||
| `.Label` | Value of a specific label for this container. For example `'{{.Label "com.docker.swarm.cpu"}}'` |
|
||||
| `.Mounts` | Names of the volumes mounted in this container. |
|
||||
| `.Networks` | Names of the networks attached to this container. |
|
||||
| Placeholder | Description |
|
||||
|:----------------|:------------------------------------------------------------------------------------------------|
|
||||
| `.ID` | Container ID |
|
||||
| `.Image` | Image ID |
|
||||
| `.Command` | Quoted command |
|
||||
| `.CreatedAt` | Time when the container was created. |
|
||||
| `.RunningFor` | Elapsed time since the container was started. |
|
||||
| `.Ports` | Exposed ports. |
|
||||
| `.State` | Container status (for example; "created", "running", "exited"). |
|
||||
| `.Status` | Container status with details about duration and health-status. |
|
||||
| `.HealthStatus` | Container health status ("starting", "healthy", "unhealthy"; empty when unavailable). |
|
||||
| `.Size` | Container disk size. |
|
||||
| `.Names` | Container names. |
|
||||
| `.Labels` | All labels assigned to the container. |
|
||||
| `.Label` | Value of a specific label for this container. For example `'{{.Label "com.docker.swarm.cpu"}}'` |
|
||||
| `.Mounts` | Names of the volumes mounted in this container. |
|
||||
| `.Networks` | Names of the networks attached to this container. |
|
||||
|
||||
When using the `--format` option, the `ps` command will either output the data
|
||||
exactly as the template declares or, when using the `table` directive, includes
|
||||
|
||||
@@ -1233,7 +1233,7 @@ the container and remove the file system when the container exits, use the
|
||||
`--rm` flag:
|
||||
|
||||
```text
|
||||
--rm: Automatically remove the container when it exits
|
||||
--rm: Automatically remove the container and its associated anonymous volumes when it exits
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
@@ -15,7 +15,7 @@ Manage contexts
|
||||
| [`rm`](context_rm.md) | Remove one or more contexts |
|
||||
| [`show`](context_show.md) | Print the name of the current context |
|
||||
| [`update`](context_update.md) | Update a context |
|
||||
| [`use`](context_use.md) | Set the current docker context |
|
||||
| [`use`](context_use.md) | Set the default docker context |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,63 @@
|
||||
# context use
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Set the current docker context
|
||||
Set the default docker context
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Set the default context to use, when `DOCKER_HOST`, `DOCKER_CONTEXT` environment
|
||||
variables and `--host`, `--context` global options aren't set.
|
||||
To disable usage of contexts, you can use the special `default` context.
|
||||
The `docker context use` command sets the default context for the Docker CLI.
|
||||
|
||||
The `docker context use` command sets the Docker CLI’s default context by updating
|
||||
your CLI config (`~/.docker/config.json`). This change is persistent, affecting
|
||||
all shells and sessions that share that config, not just the current terminal.
|
||||
|
||||
For one-off commands or per-shell usage, use `--context` or the `DOCKER_CONTEXT`
|
||||
environment variable instead.
|
||||
|
||||
## Examples
|
||||
|
||||
### Set the default (sticky) context
|
||||
|
||||
This updates the CLI configuration and applies to new terminal sessions:
|
||||
|
||||
```bash
|
||||
$ docker context use my-context
|
||||
my-context
|
||||
|
||||
$ docker context show
|
||||
my-context
|
||||
```
|
||||
|
||||
### Use a context for a single command
|
||||
|
||||
Use the global `--context` flag to avoid changing the default:
|
||||
|
||||
```bash
|
||||
$ docker --context my-context ps
|
||||
```
|
||||
|
||||
### Use a context for the current shell session
|
||||
|
||||
Set `DOCKER_CONTEXT` to override the configured default in the current shell:
|
||||
|
||||
```bash
|
||||
$ export DOCKER_CONTEXT=my-context
|
||||
$ docker context show
|
||||
my-context
|
||||
```
|
||||
|
||||
To stop overriding:
|
||||
|
||||
```bash
|
||||
$ unset DOCKER_CONTEXT
|
||||
```
|
||||
|
||||
### Switch back to the default context
|
||||
|
||||
```bash
|
||||
$ docker context use default
|
||||
default
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ container source to stdout.
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:-------|:--------|:-------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--archive` | `bool` | | Archive mode (copy all uid/gid information) |
|
||||
| `-L`, `--follow-link` | `bool` | | Always follow symbol link in SRC_PATH |
|
||||
| `-L`, `--follow-link` | `bool` | | Always follow symlinks in SRC_PATH |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress progress output during copy. Progress output is automatically suppressed if no terminal is attached |
|
||||
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ COPY failed: forbidden path outside the build context: ../../some-dir ()
|
||||
```
|
||||
|
||||
BuildKit on the other hand strips leading relative paths that traverse outside
|
||||
of the build context. Re-using the previous example, the path `COPY
|
||||
of the build context. Reusing the previous example, the path `COPY
|
||||
../../some-dir .` evaluates to `COPY some-dir .` with BuildKit.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -32,13 +32,13 @@ use `docker pull`.
|
||||
### Proxy configuration
|
||||
|
||||
If you are behind an HTTP proxy server, for example in corporate settings,
|
||||
before open a connect to registry, you may need to configure the Docker
|
||||
daemon's proxy settings, refer to the [dockerd command-line reference](https://docs.docker.com/reference/cli/dockerd/#proxy-configuration)
|
||||
for details.
|
||||
you may have to configure the Docker daemon to use the proxy server for
|
||||
operations such as pulling and pushing images. Refer to the
|
||||
[dockerd command-line reference](https://docs.docker.com/reference/cli/dockerd/#proxy-configuration) for details.
|
||||
|
||||
### Concurrent downloads
|
||||
|
||||
By default the Docker daemon will pull three layers of an image at a time.
|
||||
By default the Docker daemon downloads three layers of an image at a time.
|
||||
If you are on a low bandwidth connection this may cause timeout issues and you may want to lower
|
||||
this via the `--max-concurrent-downloads` daemon option. See the
|
||||
[daemon documentation](https://docs.docker.com/reference/cli/dockerd/) for more details.
|
||||
|
||||
@@ -107,7 +107,7 @@ $ docker inspect --format='{{.Config.Image}}' $INSTANCE_ID
|
||||
You can loop over arrays and maps in the results to produce simple text output:
|
||||
|
||||
```console
|
||||
$ docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID
|
||||
$ docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{with $conf}}{{(index . 0).HostPort}}{{else}}none{{end}} {{end}}' $INSTANCE_ID
|
||||
```
|
||||
|
||||
### Find a specific port mapping
|
||||
|
||||
@@ -82,7 +82,7 @@ The following filter matches only services with the `project` label with the
|
||||
`project-a` value.
|
||||
|
||||
```console
|
||||
$ docker service ls --filter label=project=test
|
||||
$ docker secret ls --filter label=project=project-a
|
||||
|
||||
ID NAME CREATED UPDATED
|
||||
mem02h8n73mybpgqjf0kfi1n0 test_secret About an hour ago About an hour ago
|
||||
@@ -95,7 +95,7 @@ The `name` filter matches on all or prefix of a secret's name.
|
||||
The following filter matches secret with a name containing a prefix of `test`.
|
||||
|
||||
```console
|
||||
$ docker secret ls --filter name=test_secret
|
||||
$ docker secret ls --filter name=test
|
||||
|
||||
ID NAME CREATED UPDATED
|
||||
mem02h8n73mybpgqjf0kfi1n0 test_secret About an hour ago About an hour ago
|
||||
|
||||
@@ -58,7 +58,7 @@ desired root digest: sha256:05da740cf2577a25224c53019e2cce99bcc5ba09664ad6bb2a94
|
||||
rotated CA certificates: [> ] 0/2 nodes
|
||||
```
|
||||
|
||||
Once the rotation os finished (all the progress bars have completed) the now-current
|
||||
Once the rotation is finished (all the progress bars have completed) the now-current
|
||||
CA certificate will be printed:
|
||||
|
||||
```console
|
||||
|
||||
@@ -958,8 +958,6 @@ the `--cgroup-parent` option on the daemon.
|
||||
#### Daemon metrics
|
||||
|
||||
The `--metrics-addr` option takes a TCP address to serve the metrics API.
|
||||
This feature is still experimental, therefore, the daemon must be running in experimental
|
||||
mode for this feature to work.
|
||||
|
||||
To serve the metrics API on `localhost:9323` you would specify `--metrics-addr 127.0.0.1:9323`,
|
||||
allowing you to make requests on the API at `127.0.0.1:9323/metrics` to receive metrics in the
|
||||
@@ -1059,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:
|
||||
|
||||
@@ -1210,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:
|
||||
|
||||
|
||||
@@ -31,10 +31,6 @@ true
|
||||
|
||||
## Current experimental features
|
||||
|
||||
Docker service logs command to view logs for a Docker service. This is needed in Swarm mode.
|
||||
Option to squash image layers to the base image after successful builds.
|
||||
Checkpoint and restore support for Containers.
|
||||
Metrics (Prometheus) output for basic container, image, and daemon operations.
|
||||
|
||||
* [Checkpoint & Restore](../docs/reference/commandline/checkpoint.md)
|
||||
* [Docker build with --squash argument](../docs/reference/commandline/build.md#squash-an-images-layers---squash-experimental)
|
||||
* [Support for plugins on Swarm](https://github.com/moby/moby/pull/33575)
|
||||
|
||||
+2
-2
@@ -618,7 +618,7 @@ its root filesystem mounted as read only prohibiting any writes.
|
||||
Default is **no**.
|
||||
|
||||
**--rm** **true**|**false**
|
||||
Automatically remove the container when it exits. The default is **false**.
|
||||
Automatically remove the container and its associated anonymous volumes when it exits. The default is **false**.
|
||||
`--rm` flag can work together with `-d`, and auto-removal will be done on
|
||||
daemon side. Note that it's incompatible with any restart policy other than
|
||||
`none`.
|
||||
@@ -734,7 +734,7 @@ any options, the systems uses the following options:
|
||||
**-u**, **--user**=""
|
||||
Sets the username or UID used and optionally the groupname or GID for the specified command.
|
||||
|
||||
The followings examples are all valid:
|
||||
The following examples are all valid:
|
||||
--user [user | user:group | uid | uid:gid | user:gid | uid:group ]
|
||||
|
||||
Without this argument the command will be run as root in the container.
|
||||
|
||||
@@ -19,7 +19,7 @@ the same capabilities as the container, which may be limited. Set
|
||||
# USER
|
||||
`user` sets the username or UID used and optionally the groupname or GID for the specified command.
|
||||
|
||||
The followings examples are all valid:
|
||||
The following examples are all valid:
|
||||
--user [user | user:group | uid | uid:gid | user:gid | uid:group ]
|
||||
|
||||
Without this argument the command will be run as root in the container.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
The `docker volume` command has subcommands for managing data volumes. A data
|
||||
volume is a specially-designated directory that by-passes storage driver
|
||||
volume is a specially-designated directory that bypasses storage driver
|
||||
management.
|
||||
|
||||
Data volumes persist data independent of a container's life cycle. When you
|
||||
|
||||
+20
-12
@@ -70,28 +70,36 @@ func ConvertKVStringsToMapWithNil(values []string) map[string]*string {
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
|
||||
// ParseRestartPolicy parses a restart policy string ("name[:max-retries]")
|
||||
// into a [container.RestartPolicy].
|
||||
//
|
||||
// Parsing is syntactic only; semantic validation is deferred to the daemon/API.
|
||||
// An empty input returns a zero-value policy for backward compatibility. The
|
||||
// retry count, if set, must be an integer (negative values are allowed here
|
||||
// but may be rejected by the daemon).
|
||||
func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
|
||||
if policy == "" {
|
||||
// for backward-compatibility, we don't set the default ("no")
|
||||
// policy here, because older versions of the engine may not
|
||||
// support it.
|
||||
// For backward compatibility, do not set an explicit default ("no"),
|
||||
// as older daemons may not support it.
|
||||
return container.RestartPolicy{}, nil
|
||||
}
|
||||
|
||||
p := container.RestartPolicy{}
|
||||
k, v, ok := strings.Cut(policy, ":")
|
||||
if ok && k == "" {
|
||||
name, count, ok := strings.Cut(policy, ":")
|
||||
if ok && name == "" {
|
||||
return container.RestartPolicy{}, errors.New("invalid restart policy format: no policy provided before colon")
|
||||
}
|
||||
if v != "" {
|
||||
count, err := strconv.Atoi(v)
|
||||
|
||||
var retryCount int
|
||||
if count != "" {
|
||||
c, err := strconv.Atoi(count)
|
||||
if err != nil {
|
||||
return container.RestartPolicy{}, errors.New("invalid restart policy format: maximum retry count must be an integer")
|
||||
}
|
||||
p.MaximumRetryCount = count
|
||||
retryCount = c
|
||||
}
|
||||
|
||||
p.Name = container.RestartPolicyMode(k)
|
||||
return p, nil
|
||||
return container.RestartPolicy{
|
||||
Name: container.RestartPolicyMode(name),
|
||||
MaximumRetryCount: retryCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
+9
-9
@@ -12,13 +12,13 @@ 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.2
|
||||
github.com/containerd/platforms v1.0.0-rc.4
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7
|
||||
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.6
|
||||
github.com/docker/docker-credential-helpers v0.9.7
|
||||
github.com/docker/go-connections v0.7.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
@@ -33,7 +33,7 @@ require (
|
||||
github.com/moby/moby/api v1.54.2
|
||||
github.com/moby/moby/client v0.4.1
|
||||
github.com/moby/patternmatcher v0.6.1
|
||||
github.com/moby/swarmkit/v2 v2.1.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
|
||||
@@ -59,9 +59,9 @@ require (
|
||||
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.43.0
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/text v0.36.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/term v0.43.0
|
||||
golang.org/x/text v0.37.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
tags.cncf.io/container-device-interface v1.1.0
|
||||
)
|
||||
@@ -96,12 +96,12 @@ require (
|
||||
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/etcd/raft/v3 v3.5.16 // 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.35.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.55.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
|
||||
|
||||
+18
-18
@@ -24,8 +24,8 @@ 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.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
|
||||
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
|
||||
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/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=
|
||||
@@ -40,8 +40,8 @@ github.com/docker/cli-docs-tool v0.11.0 h1:7d8QARFb7QEobizqxmEM7fOteZEHwH/zWgHQt
|
||||
github.com/docker/cli-docs-tool v0.11.0/go.mod h1:ma8BKiisUo8D6W05XEYIh3oa1UbgrZhi1nowyKFJa8Q=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker-credential-helpers v0.9.6 h1:cT2PbRPSlnMmNTfT2TDMXRyQ1KMWHG7xoTLBcn1ZNv0=
|
||||
github.com/docker/docker-credential-helpers v0.9.6/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
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=
|
||||
@@ -119,8 +119,8 @@ github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/w
|
||||
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
|
||||
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.1 h1:yvTJ8MMCc3f0qTA44J6R59EZ5yZawdYopkpuLk4+ICU=
|
||||
github.com/moby/swarmkit/v2 v2.1.1/go.mod h1:mTTGIAz/59OGZR5Qe+QByIe3Nxc+sSuJkrsStFhr6Lg=
|
||||
github.com/moby/swarmkit/v2 v2.1.2 h1:1WDZAI6HVYNKdCG4zlXnTAPyLsLwuhRGWlHoOUf5Z6I=
|
||||
github.com/moby/swarmkit/v2 v2.1.2/go.mod h1:GQ6T0ij2oBbWX10OHwpvK449xfXkQMZ8J+B+eQ4mgp4=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||
@@ -202,8 +202,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
|
||||
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/etcd/raft/v3 v3.5.16 h1:zBXA3ZUpYs1AwiLGPafYAKKl/CORn/uaxYDwlNwndAk=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.16/go.mod h1:P4UP14AxofMJ/54boWilabqqWoW9eLodl6I5GdGzazI=
|
||||
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=
|
||||
@@ -236,16 +236,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
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/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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -261,14 +261,14 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.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/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.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/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=
|
||||
|
||||
+15
-22
@@ -1,32 +1,25 @@
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- copyloopvar
|
||||
- gofmt
|
||||
- goimports
|
||||
- dupword
|
||||
- gosec
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
|
||||
- unconvert
|
||||
- unused
|
||||
- govet
|
||||
- dupword # Checks for duplicate words in the source code
|
||||
disable:
|
||||
- errcheck
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test # e2e scripts
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
||||
+105
-1
@@ -152,6 +152,88 @@ func Only(platform specs.Platform) MatchComparer {
|
||||
return Ordered(platformVector(Normalize(platform))...)
|
||||
}
|
||||
|
||||
// OnlyOS returns a match comparer that matches only platforms with the same
|
||||
// OS, OS version, and OS features, regardless of architecture. When comparing,
|
||||
// it always ranks the best architecture match highest using the default
|
||||
// platform resolution logic.
|
||||
func OnlyOS(platform specs.Platform) MatchComparer {
|
||||
normalized := Normalize(platform)
|
||||
return onlyOSComparer{
|
||||
platform: normalized,
|
||||
osvM: newOSVersionMatcher(normalized),
|
||||
archOrder: orderedPlatformComparer{
|
||||
matchers: []Matcher{NewMatcher(normalized)},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newOSVersionMatcher(platform specs.Platform) osVerMatcher {
|
||||
if platform.OS == "windows" {
|
||||
return &windowsVersionMatcher{
|
||||
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type onlyOSComparer struct {
|
||||
platform specs.Platform
|
||||
osvM osVerMatcher
|
||||
archOrder orderedPlatformComparer
|
||||
}
|
||||
|
||||
func (c onlyOSComparer) matchOS(platform specs.Platform) bool {
|
||||
normalized := Normalize(platform)
|
||||
if c.platform.OS != normalized.OS {
|
||||
return false
|
||||
}
|
||||
if c.osvM != nil {
|
||||
if !c.osvM.Match(platform.OSVersion) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(normalized.OSFeatures) > 0 {
|
||||
if len(c.platform.OSFeatures) < len(normalized.OSFeatures) {
|
||||
return false
|
||||
}
|
||||
j := 0
|
||||
for _, feature := range normalized.OSFeatures {
|
||||
found := false
|
||||
for ; j < len(c.platform.OSFeatures); j++ {
|
||||
if feature == c.platform.OSFeatures[j] {
|
||||
found = true
|
||||
j++
|
||||
break
|
||||
}
|
||||
if feature < c.platform.OSFeatures[j] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c onlyOSComparer) Match(platform specs.Platform) bool {
|
||||
return c.matchOS(platform)
|
||||
}
|
||||
|
||||
func (c onlyOSComparer) Less(p1, p2 specs.Platform) bool {
|
||||
p1m := c.matchOS(p1)
|
||||
p2m := c.matchOS(p2)
|
||||
if p1m && !p2m {
|
||||
return true
|
||||
}
|
||||
if !p1m {
|
||||
return false
|
||||
}
|
||||
// Both match — rank by architecture preference
|
||||
return c.archOrder.Less(p1, p2)
|
||||
}
|
||||
|
||||
// OnlyStrict returns a match comparer for a single platform.
|
||||
//
|
||||
// Unlike Only, OnlyStrict does not match sub platforms.
|
||||
@@ -213,9 +295,20 @@ func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool
|
||||
return true
|
||||
}
|
||||
if p1m || p2m {
|
||||
if p1m && p2m {
|
||||
// Prefer one with most matching features
|
||||
if len(p1.OSFeatures) != len(p2.OSFeatures) {
|
||||
return len(p1.OSFeatures) > len(p2.OSFeatures)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0 {
|
||||
p1.OSFeatures = nil
|
||||
p2.OSFeatures = nil
|
||||
return c.Less(p1, p2)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -242,9 +335,20 @@ func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool {
|
||||
p2m = true
|
||||
}
|
||||
if p1m && p2m {
|
||||
return false
|
||||
if len(p1.OSFeatures) != len(p2.OSFeatures) {
|
||||
return len(p1.OSFeatures) > len(p2.OSFeatures)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If neither match and has features, strip features and compare
|
||||
if !p1m && !p2m && (len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0) {
|
||||
p1.OSFeatures = nil
|
||||
p2.OSFeatures = nil
|
||||
return c.Less(p1, p2)
|
||||
}
|
||||
|
||||
// If one matches, and the other does, sort match first
|
||||
return p1m && !p2m
|
||||
}
|
||||
|
||||
-2
@@ -45,7 +45,6 @@ func getMachineArch() (string, error) {
|
||||
// So we don't need to access the ARM registers to detect platform information
|
||||
// by ourselves. We can just parse these information from /proc/cpuinfo
|
||||
func getCPUInfo(pattern string) (info string, err error) {
|
||||
|
||||
cpuinfo, err := os.Open("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -75,7 +74,6 @@ func getCPUInfo(pattern string) (info string, err error) {
|
||||
|
||||
// getCPUVariantFromArch get CPU variant from arch through a system call
|
||||
func getCPUVariantFromArch(arch string) (string, error) {
|
||||
|
||||
var variant string
|
||||
|
||||
arch = strings.ToLower(arch)
|
||||
|
||||
+4
-4
@@ -24,10 +24,10 @@ import (
|
||||
)
|
||||
|
||||
func getCPUVariant() (string, error) {
|
||||
|
||||
var variant string
|
||||
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
switch runtime.GOOS {
|
||||
case "windows", "darwin":
|
||||
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
||||
// runtime.GOARCH to determine the variants
|
||||
switch runtime.GOARCH {
|
||||
@@ -38,7 +38,7 @@ func getCPUVariant() (string, error) {
|
||||
default:
|
||||
variant = "unknown"
|
||||
}
|
||||
} else if runtime.GOOS == "freebsd" {
|
||||
case "freebsd":
|
||||
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
|
||||
// detecting those variants is currently unimplemented
|
||||
switch runtime.GOARCH {
|
||||
@@ -47,7 +47,7 @@ func getCPUVariant() (string, error) {
|
||||
default:
|
||||
variant = "unknown"
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented)
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -17,6 +17,7 @@
|
||||
package platforms
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -162,3 +163,14 @@ func (c *windowsMatchComparer) Less(p1, p2 specs.Platform) bool {
|
||||
}
|
||||
return m1 && !m2
|
||||
}
|
||||
|
||||
type windowsStripFeaturesMatcher struct {
|
||||
Matcher
|
||||
}
|
||||
|
||||
func (m windowsStripFeaturesMatcher) Match(p specs.Platform) bool {
|
||||
if i := slices.Index(p.OSFeatures, "win32k"); i >= 0 {
|
||||
p.OSFeatures = slices.Delete(slices.Clone(p.OSFeatures), i, i+1)
|
||||
}
|
||||
return m.Matcher.Match(p)
|
||||
}
|
||||
|
||||
+168
-22
@@ -111,9 +111,11 @@ package platforms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -121,12 +123,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
|
||||
osAndVersionRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)\))?$`)
|
||||
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
|
||||
osRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.%-]*)((?:\+[A-Za-z0-9_.%-]+)*)\))?$`)
|
||||
)
|
||||
|
||||
const osAndVersionFormat = "%s(%s)"
|
||||
|
||||
// Platform is a type alias for convenience, so there is no need to import image-spec package everywhere.
|
||||
type Platform = specs.Platform
|
||||
|
||||
@@ -143,6 +143,10 @@ type Matcher interface {
|
||||
// functionality.
|
||||
//
|
||||
// Applications should opt to use `Match` over directly parsing specifiers.
|
||||
//
|
||||
// For OSFeatures, this matcher will match if the platform to match has
|
||||
// OSFeatures which are a subset of the OSFeatures of the platform
|
||||
// provided to NewMatcher.
|
||||
func NewMatcher(platform specs.Platform) Matcher {
|
||||
m := &matcher{
|
||||
Platform: Normalize(platform),
|
||||
@@ -152,6 +156,11 @@ func NewMatcher(platform specs.Platform) Matcher {
|
||||
m.osvM = &windowsVersionMatcher{
|
||||
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
|
||||
}
|
||||
|
||||
// In prior versions, the win32k os feature was not considered for matching,
|
||||
// strip out the win32k feature for comparison
|
||||
var stripped Matcher = windowsStripFeaturesMatcher{m}
|
||||
|
||||
// In prior versions, on windows, the returned matcher implements a
|
||||
// MatchComprarer interface.
|
||||
// This preserves that behavior for backwards compatibility.
|
||||
@@ -161,8 +170,9 @@ func NewMatcher(platform specs.Platform) Matcher {
|
||||
// It was likely intended to be used in `Ordered` but it is not since
|
||||
// `Less` that is implemented here ends up getting masked due to wrapping.
|
||||
if runtime.GOOS == "windows" {
|
||||
return &windowsMatchComparer{m}
|
||||
return &windowsMatchComparer{stripped}
|
||||
}
|
||||
return stripped
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -178,10 +188,39 @@ type matcher struct {
|
||||
|
||||
func (m *matcher) Match(platform specs.Platform) bool {
|
||||
normalized := Normalize(platform)
|
||||
return m.OS == normalized.OS &&
|
||||
if m.OS == normalized.OS &&
|
||||
m.Architecture == normalized.Architecture &&
|
||||
m.Variant == normalized.Variant &&
|
||||
m.matchOSVersion(platform)
|
||||
m.matchOSVersion(platform) {
|
||||
if len(normalized.OSFeatures) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(m.OSFeatures) >= len(normalized.OSFeatures) {
|
||||
// Ensure that normalized.OSFeatures is a subset of
|
||||
// m.OSFeatures
|
||||
j := 0
|
||||
for _, feature := range normalized.OSFeatures {
|
||||
found := false
|
||||
for ; j < len(m.OSFeatures); j++ {
|
||||
if feature == m.OSFeatures[j] {
|
||||
found = true
|
||||
j++
|
||||
break
|
||||
}
|
||||
// Since both lists are ordered, if the feature is less
|
||||
// than what is seen, it is not in the list
|
||||
if feature < m.OSFeatures[j] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *matcher) matchOSVersion(platform specs.Platform) bool {
|
||||
@@ -210,11 +249,14 @@ func ParseAll(specifiers []string) ([]specs.Platform, error) {
|
||||
|
||||
// Parse parses the platform specifier syntax into a platform declaration.
|
||||
//
|
||||
// Platform specifiers are in the format `<os>[(<OSVersion>)]|<arch>|<os>[(<OSVersion>)]/<arch>[/<variant>]`.
|
||||
// Platform specifiers are in the format `<os>[(<os options>)]|<arch>|<os>[(<os options>)]/<arch>[/<variant>]`.
|
||||
// The minimum required information for a platform specifier is the operating
|
||||
// system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)`
|
||||
// When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value,
|
||||
// and an empty string otherwise.
|
||||
// system or architecture. The "os options" may be OSVersion which can be part of the OS
|
||||
// like `windows(10.0.17763)`. When an OSVersion is specified, then specs.Platform.OSVersion is
|
||||
// populated with that value, and an empty string otherwise. The "os options" may also include an
|
||||
// array of OSFeatures, each feature prefixed with '+', without any other separator, and provided
|
||||
// after the OSVersion when the OSVersion is specified. An "os options" with version and features
|
||||
// is like `windows(10.0.17763+win32k)`.
|
||||
// If there is only a single string (no slashes), the
|
||||
// value will be matched against the known set of operating systems, then fall
|
||||
// back to the known set of architectures. The missing component will be
|
||||
@@ -231,14 +273,24 @@ func Parse(specifier string) (specs.Platform, error) {
|
||||
var p specs.Platform
|
||||
for i, part := range parts {
|
||||
if i == 0 {
|
||||
// First element is <os>[(<OSVersion>)]
|
||||
osVer := osAndVersionRe.FindStringSubmatch(part)
|
||||
if osVer == nil {
|
||||
return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osAndVersionRe.String(), errInvalidArgument)
|
||||
// First element is <os>[(<OSVersion>[+<OSFeature>]*)]
|
||||
osOptions := osRe.FindStringSubmatch(part)
|
||||
if osOptions == nil {
|
||||
return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osRe.String(), errInvalidArgument)
|
||||
}
|
||||
|
||||
p.OS = normalizeOS(osVer[1])
|
||||
p.OSVersion = osVer[2]
|
||||
p.OS = normalizeOS(osOptions[1])
|
||||
osVersion, err := decodeOSOption(osOptions[2])
|
||||
if err != nil {
|
||||
return specs.Platform{}, fmt.Errorf("%q has an invalid OS version %q: %w", specifier, osOptions[2], err)
|
||||
}
|
||||
p.OSVersion = osVersion
|
||||
if osOptions[3] != "" {
|
||||
p.OSFeatures, err = parseOSFeatures(osOptions[3][1:])
|
||||
if err != nil {
|
||||
return specs.Platform{}, fmt.Errorf("%q has invalid OS features: %w", specifier, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !specifierRe.MatchString(part) {
|
||||
return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument)
|
||||
@@ -296,6 +348,30 @@ func Parse(specifier string) (specs.Platform, error) {
|
||||
return specs.Platform{}, fmt.Errorf("%q: cannot parse platform specifier: %w", specifier, errInvalidArgument)
|
||||
}
|
||||
|
||||
func parseOSFeatures(s string) ([]string, error) {
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var features []string
|
||||
for raw := range strings.SplitSeq(s, "+") {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, fmt.Errorf("empty os feature: %w", errInvalidArgument)
|
||||
}
|
||||
feature, err := decodeOSOption(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid os feature %q: %w", raw, err)
|
||||
}
|
||||
if feature == "" {
|
||||
continue
|
||||
}
|
||||
features = append(features, feature)
|
||||
}
|
||||
|
||||
return features, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parses but panics if the specifier cannot be parsed.
|
||||
// Simplifies initialization of global variables.
|
||||
func MustParse(specifier string) specs.Platform {
|
||||
@@ -321,12 +397,77 @@ func FormatAll(platform specs.Platform) string {
|
||||
if platform.OS == "" {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
if platform.OSVersion != "" {
|
||||
OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion)
|
||||
return path.Join(OSAndVersion, platform.Architecture, platform.Variant)
|
||||
if platform.OSVersion == "" && len(platform.OSFeatures) == 0 {
|
||||
return path.Join(platform.OS, platform.Architecture, platform.Variant)
|
||||
}
|
||||
return path.Join(platform.OS, platform.Architecture, platform.Variant)
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(platform.OS)
|
||||
osv := encodeOSOption(platform.OSVersion)
|
||||
formatted := formatOSFeatures(platform.OSFeatures)
|
||||
if osv != "" || formatted != "" {
|
||||
b.Grow(len(osv) + len(formatted) + 3) // parens + maybe '+'
|
||||
b.WriteByte('(')
|
||||
if osv != "" {
|
||||
b.WriteString(osv)
|
||||
}
|
||||
if formatted != "" {
|
||||
b.WriteByte('+')
|
||||
b.WriteString(formatted)
|
||||
}
|
||||
b.WriteByte(')')
|
||||
}
|
||||
|
||||
return path.Join(b.String(), platform.Architecture, platform.Variant)
|
||||
}
|
||||
|
||||
func formatOSFeatures(features []string) string {
|
||||
if len(features) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !slices.IsSorted(features) {
|
||||
features = slices.Clone(features)
|
||||
slices.Sort(features)
|
||||
}
|
||||
var b strings.Builder
|
||||
var wrote bool
|
||||
var prev string
|
||||
for _, f := range features {
|
||||
if f == "" || f == prev {
|
||||
// skip empty and duplicate values
|
||||
continue
|
||||
}
|
||||
prev = f
|
||||
if wrote {
|
||||
b.WriteByte('+')
|
||||
}
|
||||
b.WriteString(encodeOSOption(f))
|
||||
wrote = true
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// osOptionReplacer encodes characters in OS option values (version and
|
||||
// features) that are ambiguous with the format syntax. The percent sign
|
||||
// must be replaced first to avoid double-encoding.
|
||||
var osOptionReplacer = strings.NewReplacer(
|
||||
"%", "%25",
|
||||
"+", "%2B",
|
||||
"(", "%28",
|
||||
")", "%29",
|
||||
"/", "%2F",
|
||||
)
|
||||
|
||||
func encodeOSOption(v string) string {
|
||||
return osOptionReplacer.Replace(v)
|
||||
}
|
||||
|
||||
func decodeOSOption(v string) (string, error) {
|
||||
if strings.Contains(v, "%") {
|
||||
return url.PathUnescape(v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Normalize validates and translate the platform to the canonical value.
|
||||
@@ -336,6 +477,11 @@ func FormatAll(platform specs.Platform) string {
|
||||
func Normalize(platform specs.Platform) specs.Platform {
|
||||
platform.OS = normalizeOS(platform.OS)
|
||||
platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant)
|
||||
if len(platform.OSFeatures) > 0 {
|
||||
platform.OSFeatures = slices.Clone(platform.OSFeatures)
|
||||
slices.Sort(platform.OSFeatures)
|
||||
platform.OSFeatures = slices.Compact(platform.OSFeatures)
|
||||
}
|
||||
|
||||
return platform
|
||||
}
|
||||
|
||||
+60
-60
@@ -9,7 +9,7 @@ import (
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
github_com_moby_swarmkit_v2_api_deepcopy "github.com/moby/swarmkit/v2/api/deepcopy"
|
||||
raftselector "github.com/moby/swarmkit/v2/manager/raftselector"
|
||||
raftpb "go.etcd.io/etcd/raft/v3/raftpb"
|
||||
raftpb "go.etcd.io/raft/v3/raftpb"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
metadata "google.golang.org/grpc/metadata"
|
||||
@@ -731,73 +731,73 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_d2c32e1e3c930c15 = []byte{
|
||||
// 1046 bytes of a gzipped FileDescriptorProto
|
||||
// 1044 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0x41, 0x73, 0xdb, 0x44,
|
||||
0x14, 0xc7, 0x25, 0x5b, 0x75, 0x92, 0xe7, 0x36, 0x09, 0x5b, 0x12, 0x14, 0xb5, 0x28, 0xae, 0xdb,
|
||||
0x14, 0xc7, 0x25, 0x5b, 0x75, 0x9a, 0xe7, 0x36, 0x09, 0x5b, 0x12, 0x14, 0xb5, 0x28, 0xae, 0x9b,
|
||||
0x19, 0x9c, 0x90, 0xc8, 0x83, 0xcb, 0x4c, 0x99, 0x42, 0x0f, 0x71, 0xe2, 0x99, 0x98, 0xb4, 0x4e,
|
||||
0x47, 0x49, 0x4a, 0x6f, 0x41, 0x96, 0x36, 0xae, 0xb0, 0xad, 0x35, 0xbb, 0x6b, 0x07, 0x2e, 0x4c,
|
||||
0x8f, 0x90, 0x13, 0x37, 0x18, 0x66, 0x3a, 0x1c, 0xe0, 0xdc, 0x0f, 0xc0, 0x27, 0xc8, 0x70, 0xea,
|
||||
0x8d, 0x9e, 0x32, 0xd4, 0xb9, 0xc3, 0x17, 0xe0, 0xc0, 0xec, 0x4a, 0x4a, 0x42, 0x22, 0x3b, 0x3e,
|
||||
0x70, 0xd2, 0x8e, 0xf7, 0xf7, 0x7f, 0xff, 0xb7, 0xbb, 0x6f, 0xdf, 0x1a, 0x16, 0x1a, 0x3e, 0x7f,
|
||||
0x70, 0xf2, 0x8e, 0xf7, 0xf7, 0x7f, 0xff, 0xb7, 0xbb, 0x6f, 0xdf, 0x0a, 0x16, 0x1a, 0x3e, 0x7f,
|
||||
0xd6, 0xad, 0x5b, 0x2e, 0x69, 0x17, 0x3d, 0xe2, 0x36, 0x31, 0x2d, 0xb2, 0x7d, 0x87, 0xb6, 0x9b,
|
||||
0x3e, 0x2f, 0x3a, 0x1d, 0xbf, 0x48, 0x9d, 0x3d, 0x6e, 0x75, 0x28, 0xe1, 0x04, 0xa1, 0x70, 0xde,
|
||||
0x8a, 0xe7, 0xad, 0xde, 0x07, 0xc6, 0xd2, 0x25, 0x72, 0x52, 0xff, 0x02, 0xbb, 0x9c, 0x85, 0x11,
|
||||
0x8c, 0xc5, 0x4b, 0x68, 0xfe, 0x75, 0x07, 0xc7, 0xec, 0x42, 0x83, 0x58, 0x98, 0xbb, 0x9e, 0xe5,
|
||||
0x93, 0xa2, 0xf8, 0xca, 0x4c, 0x8a, 0xbd, 0xbb, 0xf2, 0xdb, 0xa9, 0x9f, 0x49, 0xcc, 0x78, 0xbb,
|
||||
0x41, 0x1a, 0x44, 0x0e, 0x8b, 0x62, 0x14, 0xfd, 0x7a, 0x6f, 0x88, 0x99, 0x24, 0xea, 0xdd, 0xbd,
|
||||
0x62, 0xa7, 0xd5, 0x6d, 0xf8, 0x41, 0xf4, 0x09, 0x85, 0xf9, 0x97, 0x2a, 0x80, 0xed, 0xec, 0xf1,
|
||||
0x47, 0xb8, 0x5d, 0xc7, 0x14, 0xdd, 0x86, 0x31, 0xe1, 0xb5, 0xeb, 0x7b, 0xba, 0x9a, 0x53, 0x0b,
|
||||
0x5a, 0x19, 0xfa, 0x47, 0xf3, 0x19, 0x01, 0x54, 0xd7, 0xec, 0x8c, 0x98, 0xaa, 0x7a, 0x02, 0x0a,
|
||||
0x88, 0x87, 0x05, 0x94, 0xca, 0xa9, 0x85, 0x89, 0x10, 0xaa, 0x11, 0x0f, 0x0b, 0x48, 0x4c, 0x55,
|
||||
0x3d, 0x84, 0x40, 0x73, 0x3c, 0x8f, 0xea, 0x69, 0x41, 0xd8, 0x72, 0x8c, 0xca, 0x90, 0x61, 0xdc,
|
||||
0xe1, 0x5d, 0xa6, 0x6b, 0x39, 0xb5, 0x90, 0x2d, 0xdd, 0xb1, 0x2e, 0xee, 0xb2, 0x75, 0x9a, 0xcd,
|
||||
0x96, 0x64, 0xcb, 0xda, 0xe1, 0xd1, 0xbc, 0x62, 0x47, 0xca, 0xfc, 0x2d, 0xc8, 0x7e, 0x4a, 0xfc,
|
||||
0xc0, 0xc6, 0x5f, 0x76, 0x31, 0xe3, 0x27, 0x36, 0xea, 0xa9, 0x4d, 0xfe, 0x27, 0x15, 0xae, 0x86,
|
||||
0x0c, 0xeb, 0x90, 0x80, 0xe1, 0xd1, 0x56, 0xf5, 0x11, 0x8c, 0xb5, 0xa5, 0x2d, 0xd3, 0x53, 0xb9,
|
||||
0x74, 0x21, 0x5b, 0x32, 0x87, 0x67, 0x67, 0xc7, 0x38, 0x7a, 0x1f, 0xa6, 0x28, 0x6e, 0x93, 0x1e,
|
||||
0xf6, 0x76, 0xe3, 0x08, 0xe9, 0x5c, 0xba, 0xa0, 0x95, 0x53, 0xd3, 0x8a, 0x3d, 0x19, 0x4d, 0x85,
|
||||
0x22, 0x96, 0x2f, 0xc3, 0xd5, 0x87, 0xd8, 0xe9, 0xe1, 0x78, 0x01, 0x25, 0xd0, 0xc4, 0x8e, 0xc9,
|
||||
0xc4, 0x2e, 0xf7, 0x94, 0x6c, 0x7e, 0x0a, 0xae, 0x45, 0x31, 0xc2, 0x05, 0xe6, 0x1f, 0xc2, 0xdc,
|
||||
0x63, 0x4a, 0x5c, 0xcc, 0x58, 0xc8, 0x32, 0xe6, 0x34, 0x4e, 0x1c, 0x16, 0xc4, 0xc2, 0xe4, 0x2f,
|
||||
0x91, 0xc9, 0x94, 0x15, 0x96, 0x95, 0x15, 0x83, 0xf1, 0xfc, 0x7d, 0xed, 0xf9, 0x0f, 0x79, 0x25,
|
||||
0x7f, 0x13, 0x8c, 0xa4, 0x68, 0x91, 0xd7, 0x06, 0xe8, 0x5b, 0x9c, 0x62, 0xa7, 0xfd, 0x7f, 0x58,
|
||||
0xdd, 0x80, 0xb9, 0x84, 0x60, 0x91, 0xd3, 0x27, 0x30, 0x63, 0x63, 0x46, 0x5a, 0x3d, 0xbc, 0xe2,
|
||||
0x79, 0x54, 0xa4, 0x13, 0xd9, 0x8c, 0x72, 0x9e, 0xf9, 0x25, 0x98, 0x3d, 0xaf, 0x8e, 0xca, 0x21,
|
||||
0xa9, 0x66, 0x5a, 0x70, 0xbd, 0x1a, 0x70, 0x4c, 0x03, 0xa7, 0x25, 0xe2, 0xc4, 0x4e, 0xb3, 0x90,
|
||||
0x3a, 0x31, 0xc9, 0xf4, 0x8f, 0xe6, 0x53, 0xd5, 0x35, 0x3b, 0xe5, 0x7b, 0xe8, 0x01, 0x64, 0x1c,
|
||||
0x97, 0xfb, 0x24, 0x88, 0x6a, 0x65, 0x3e, 0xe9, 0xdc, 0xb6, 0x38, 0xa1, 0x78, 0x45, 0x62, 0x71,
|
||||
0x11, 0x87, 0xa2, 0xfc, 0x3f, 0x1a, 0x64, 0xcf, 0xcc, 0xa2, 0x8f, 0x4f, 0xc2, 0x09, 0xab, 0xc9,
|
||||
0xd2, 0xed, 0x4b, 0xc2, 0x6d, 0xf8, 0x81, 0x17, 0x07, 0x43, 0x56, 0x54, 0x41, 0x29, 0xb9, 0xe3,
|
||||
0x7a, 0x92, 0x54, 0xdc, 0xcd, 0x75, 0x25, 0xac, 0x1e, 0x74, 0x0f, 0xc6, 0x18, 0xa6, 0x3d, 0xdf,
|
||||
0xc5, 0xf2, 0x72, 0x66, 0x4b, 0x37, 0x12, 0xdd, 0x42, 0x64, 0x5d, 0xb1, 0x63, 0x5a, 0x18, 0x71,
|
||||
0x87, 0x35, 0xa3, 0xcb, 0x9b, 0x68, 0xb4, 0xed, 0xb0, 0xa6, 0x30, 0x12, 0x9c, 0x30, 0x0a, 0x30,
|
||||
0xdf, 0x27, 0xb4, 0xa9, 0x5f, 0x19, 0x6c, 0x54, 0x0b, 0x11, 0x61, 0x14, 0xd1, 0x42, 0xe8, 0xb6,
|
||||
0xba, 0x8c, 0x63, 0xaa, 0x67, 0x06, 0x0b, 0x57, 0x43, 0x44, 0x08, 0x23, 0x1a, 0x7d, 0x08, 0x19,
|
||||
0x86, 0x5d, 0x8a, 0xb9, 0x3e, 0x26, 0x75, 0x46, 0xf2, 0xca, 0x04, 0xb1, 0x2e, 0x5a, 0x8a, 0x1c,
|
||||
0xa1, 0xfb, 0x30, 0x4e, 0x31, 0x23, 0x5d, 0xea, 0x62, 0x7d, 0x5c, 0xea, 0x6e, 0x26, 0x5e, 0xc3,
|
||||
0x88, 0x59, 0x57, 0xec, 0x13, 0x1e, 0x3d, 0x80, 0x09, 0xfc, 0x15, 0xc7, 0x01, 0x13, 0x87, 0x37,
|
||||
0x21, 0xc5, 0xef, 0x26, 0x89, 0x2b, 0x31, 0xb4, 0xae, 0xd8, 0xa7, 0x0a, 0x91, 0xb0, 0x4b, 0x82,
|
||||
0x3d, 0xbf, 0xa1, 0xc3, 0xe0, 0x84, 0x57, 0x25, 0x21, 0x12, 0x0e, 0x59, 0xa1, 0xea, 0x91, 0x56,
|
||||
0xb7, 0x8d, 0xf5, 0xec, 0x60, 0xd5, 0x13, 0x49, 0x08, 0x55, 0xc8, 0x96, 0xc7, 0x21, 0xc3, 0x1d,
|
||||
0xda, 0xc0, 0x7c, 0xf1, 0x6f, 0x15, 0xa6, 0xce, 0x55, 0x13, 0x7a, 0x0f, 0xc6, 0x76, 0x6a, 0x1b,
|
||||
0xb5, 0xcd, 0xcf, 0x6a, 0xd3, 0x8a, 0x61, 0x1c, 0xbc, 0xc8, 0xcd, 0x9e, 0x23, 0x76, 0x82, 0x66,
|
||||
0x40, 0xf6, 0x03, 0x54, 0x82, 0xeb, 0x5b, 0xdb, 0x9b, 0x76, 0x65, 0x77, 0x65, 0x75, 0xbb, 0xba,
|
||||
0x59, 0xdb, 0x5d, 0xb5, 0x2b, 0x2b, 0xdb, 0x95, 0x69, 0xd5, 0x98, 0x3b, 0x78, 0x91, 0x9b, 0x39,
|
||||
0x27, 0x5a, 0xa5, 0xd8, 0xe1, 0xf8, 0x82, 0x66, 0xe7, 0xf1, 0x9a, 0xd0, 0xa4, 0x12, 0x35, 0x3b,
|
||||
0x1d, 0x2f, 0x49, 0x63, 0x57, 0x1e, 0x6d, 0x3e, 0xa9, 0x4c, 0xa7, 0x13, 0x35, 0xb6, 0x6c, 0xb2,
|
||||
0xc6, 0x3b, 0xdf, 0xfe, 0x62, 0x2a, 0xbf, 0xfd, 0x6a, 0x9e, 0x5f, 0x5d, 0xe9, 0xe7, 0x34, 0x68,
|
||||
0xe2, 0x5e, 0xa3, 0x03, 0x15, 0xd0, 0xc5, 0xe6, 0x86, 0x96, 0x93, 0x76, 0x70, 0x60, 0x4b, 0x35,
|
||||
0xac, 0x51, 0xf1, 0xa8, 0x93, 0xcd, 0xfc, 0xfe, 0xf2, 0xaf, 0x1f, 0x53, 0x53, 0x70, 0x4d, 0xf2,
|
||||
0xcb, 0x6d, 0x27, 0x70, 0x1a, 0x98, 0xa2, 0xef, 0x54, 0x78, 0xeb, 0x42, 0xfb, 0x43, 0x4b, 0xc9,
|
||||
0x97, 0x3f, 0xb9, 0xe5, 0x1a, 0xcb, 0x23, 0xd2, 0x43, 0x33, 0x29, 0xa8, 0xe8, 0x1b, 0x98, 0xfc,
|
||||
0x6f, 0xbb, 0x44, 0x0b, 0x83, 0x2e, 0xc1, 0x85, 0x86, 0x6c, 0x2c, 0x8e, 0x82, 0x0e, 0xcd, 0xa0,
|
||||
0xf4, 0x87, 0x0a, 0x93, 0xa7, 0x0f, 0x1d, 0x7b, 0xe6, 0x77, 0xd0, 0xe7, 0xa0, 0x89, 0x67, 0x1c,
|
||||
0x25, 0x36, 0xd7, 0x33, 0x7f, 0x02, 0x8c, 0xdc, 0x60, 0x60, 0xf8, 0x01, 0xb8, 0x70, 0x45, 0x3e,
|
||||
0xa4, 0x28, 0x31, 0xc2, 0xd9, 0x77, 0xda, 0xb8, 0x35, 0x84, 0x18, 0x6a, 0x52, 0xbe, 0x73, 0xf8,
|
||||
0xc6, 0x54, 0x5e, 0xbf, 0x31, 0x95, 0xe7, 0x7d, 0x53, 0x3d, 0xec, 0x9b, 0xea, 0xab, 0xbe, 0xa9,
|
||||
0xfe, 0xd9, 0x37, 0xd5, 0xef, 0x8f, 0x4d, 0xe5, 0xd5, 0xb1, 0xa9, 0xbc, 0x3e, 0x36, 0x95, 0xa7,
|
||||
0xe9, 0xa7, 0x5a, 0x3d, 0x23, 0xff, 0x93, 0xdd, 0xfd, 0x37, 0x00, 0x00, 0xff, 0xff, 0x4a, 0x56,
|
||||
0x23, 0xf6, 0xa8, 0x0a, 0x00, 0x00,
|
||||
0x8c, 0xc5, 0x4b, 0x68, 0xfe, 0x75, 0x07, 0xc7, 0xec, 0x7c, 0x83, 0x58, 0x98, 0xbb, 0x9e, 0xe5,
|
||||
0x13, 0x99, 0x44, 0xb1, 0x77, 0x57, 0xfe, 0x76, 0xea, 0x67, 0x72, 0x32, 0xde, 0x6e, 0x90, 0x06,
|
||||
0x91, 0xc3, 0xa2, 0x18, 0x45, 0xff, 0xde, 0x1b, 0xe2, 0x23, 0x89, 0x7a, 0x77, 0xaf, 0xd8, 0x69,
|
||||
0x75, 0x1b, 0x7e, 0x10, 0xfd, 0x84, 0xc2, 0xfc, 0x4b, 0x15, 0xc0, 0x76, 0xf6, 0xf8, 0x23, 0xdc,
|
||||
0xae, 0x63, 0x8a, 0xee, 0xc0, 0x98, 0xf0, 0xda, 0xf5, 0x3d, 0x5d, 0xcd, 0xa9, 0x05, 0xad, 0x0c,
|
||||
0xfd, 0xa3, 0xb9, 0x8c, 0x00, 0xaa, 0x6b, 0x76, 0x46, 0x4c, 0x55, 0x3d, 0x01, 0x05, 0xc4, 0xc3,
|
||||
0x02, 0x4a, 0xe5, 0xd4, 0xc2, 0x78, 0x08, 0xd5, 0x88, 0x87, 0x05, 0x24, 0xa6, 0xaa, 0x1e, 0x42,
|
||||
0xa0, 0x39, 0x9e, 0x47, 0xf5, 0xb4, 0x20, 0x6c, 0x39, 0x46, 0x65, 0xc8, 0x30, 0xee, 0xf0, 0x2e,
|
||||
0xd3, 0xb5, 0x9c, 0x5a, 0xc8, 0x96, 0xe6, 0xad, 0x8b, 0x1b, 0x6c, 0x9d, 0x66, 0xb3, 0x25, 0xd9,
|
||||
0xb2, 0x76, 0x78, 0x34, 0xa7, 0xd8, 0x91, 0x32, 0x7f, 0x1b, 0xb2, 0x9f, 0x12, 0x3f, 0xb0, 0xf1,
|
||||
0x97, 0x5d, 0xcc, 0xf8, 0x89, 0x8d, 0x7a, 0x6a, 0x93, 0xff, 0x49, 0x85, 0x6b, 0x21, 0xc3, 0x3a,
|
||||
0x24, 0x60, 0x78, 0xb4, 0x55, 0x7d, 0x04, 0x63, 0x6d, 0x69, 0xcb, 0xf4, 0x54, 0x2e, 0x5d, 0xc8,
|
||||
0x96, 0xcc, 0xe1, 0xd9, 0xd9, 0x31, 0x8e, 0xde, 0x87, 0x49, 0x8a, 0xdb, 0xa4, 0x87, 0xbd, 0xdd,
|
||||
0x38, 0x42, 0x3a, 0x97, 0x2e, 0x68, 0xe5, 0xd4, 0x94, 0x62, 0x4f, 0x44, 0x53, 0xa1, 0x88, 0xe5,
|
||||
0xcb, 0x70, 0xed, 0x21, 0x76, 0x7a, 0x38, 0x5e, 0x40, 0x09, 0x34, 0xb1, 0x63, 0x32, 0xb1, 0xcb,
|
||||
0x3d, 0x25, 0x9b, 0x9f, 0x84, 0xeb, 0x51, 0x8c, 0x70, 0x81, 0xf9, 0x87, 0x30, 0xfb, 0x98, 0x12,
|
||||
0x17, 0x33, 0x16, 0xb2, 0x8c, 0x39, 0x8d, 0x13, 0x87, 0x05, 0xb1, 0x30, 0xf9, 0x4f, 0x64, 0x32,
|
||||
0x69, 0x85, 0x65, 0x65, 0xc5, 0x60, 0x3c, 0x7f, 0x5f, 0x7b, 0xfe, 0x43, 0x5e, 0xc9, 0xdf, 0x02,
|
||||
0x23, 0x29, 0x5a, 0xe4, 0xb5, 0x01, 0xfa, 0x16, 0xa7, 0xd8, 0x69, 0xff, 0x1f, 0x56, 0x37, 0x61,
|
||||
0x36, 0x21, 0x58, 0xe4, 0xf4, 0x09, 0x4c, 0xdb, 0x98, 0x91, 0x56, 0x0f, 0xaf, 0x78, 0x1e, 0x15,
|
||||
0xe9, 0x44, 0x36, 0xa3, 0x9c, 0x67, 0x7e, 0x09, 0x66, 0xce, 0xab, 0xa3, 0x72, 0x48, 0xaa, 0x99,
|
||||
0x16, 0xdc, 0xa8, 0x06, 0x1c, 0xd3, 0xc0, 0x69, 0x89, 0x38, 0xb1, 0xd3, 0x0c, 0xa4, 0x4e, 0x4c,
|
||||
0x32, 0xfd, 0xa3, 0xb9, 0x54, 0x75, 0xcd, 0x4e, 0xf9, 0x1e, 0x7a, 0x00, 0x19, 0xc7, 0xe5, 0x3e,
|
||||
0x09, 0xa2, 0x5a, 0x99, 0x4b, 0x3a, 0xb7, 0x2d, 0x4e, 0x28, 0x5e, 0x91, 0x58, 0x5c, 0xc4, 0xa1,
|
||||
0x28, 0xff, 0x8f, 0x06, 0xd9, 0x33, 0xb3, 0xe8, 0xe3, 0x93, 0x70, 0xc2, 0x6a, 0xa2, 0x74, 0xe7,
|
||||
0x92, 0x70, 0x1b, 0x7e, 0xe0, 0xc5, 0xc1, 0x90, 0x15, 0x55, 0x50, 0x4a, 0xee, 0xb8, 0x9e, 0x24,
|
||||
0x15, 0x77, 0x73, 0x5d, 0x09, 0xab, 0x07, 0xdd, 0x83, 0x31, 0x86, 0x69, 0xcf, 0x77, 0xb1, 0xbc,
|
||||
0x9c, 0xd9, 0xd2, 0xcd, 0x44, 0xb7, 0x10, 0x59, 0x57, 0xec, 0x98, 0x16, 0x46, 0xdc, 0x61, 0xcd,
|
||||
0xe8, 0xf2, 0x26, 0x1a, 0x6d, 0x3b, 0xac, 0x29, 0x8c, 0x04, 0x27, 0x8c, 0x02, 0xcc, 0xf7, 0x09,
|
||||
0x6d, 0xea, 0x57, 0x06, 0x1b, 0xd5, 0x42, 0x44, 0x18, 0x45, 0xb4, 0x10, 0xba, 0xad, 0x2e, 0xe3,
|
||||
0x98, 0xea, 0x99, 0xc1, 0xc2, 0xd5, 0x10, 0x11, 0xc2, 0x88, 0x46, 0x1f, 0x42, 0x86, 0x61, 0x97,
|
||||
0x62, 0xae, 0x8f, 0x49, 0x9d, 0x91, 0xbc, 0x32, 0x41, 0xac, 0x8b, 0x96, 0x22, 0x47, 0xe8, 0x3e,
|
||||
0x5c, 0xa5, 0x98, 0x91, 0x2e, 0x75, 0xb1, 0x7e, 0x55, 0xea, 0x6e, 0x25, 0x5e, 0xc3, 0x88, 0x59,
|
||||
0x57, 0xec, 0x13, 0x1e, 0x3d, 0x80, 0x71, 0xfc, 0x15, 0xc7, 0x01, 0x13, 0x87, 0x37, 0x2e, 0xc5,
|
||||
0xef, 0x26, 0x89, 0x2b, 0x31, 0xb4, 0xae, 0xd8, 0xa7, 0x0a, 0x91, 0xb0, 0x4b, 0x82, 0x3d, 0xbf,
|
||||
0xa1, 0xc3, 0xe0, 0x84, 0x57, 0x25, 0x21, 0x12, 0x0e, 0x59, 0xa1, 0xea, 0x91, 0x56, 0xb7, 0x8d,
|
||||
0xf5, 0xec, 0x60, 0xd5, 0x13, 0x49, 0x08, 0x55, 0xc8, 0x96, 0xaf, 0x42, 0x86, 0x3b, 0xb4, 0x81,
|
||||
0xf9, 0xe2, 0xdf, 0x2a, 0x4c, 0x9e, 0xab, 0x26, 0xf4, 0x1e, 0x8c, 0xed, 0xd4, 0x36, 0x6a, 0x9b,
|
||||
0x9f, 0xd5, 0xa6, 0x14, 0xc3, 0x38, 0x78, 0x91, 0x9b, 0x39, 0x47, 0xec, 0x04, 0xcd, 0x80, 0xec,
|
||||
0x07, 0xa8, 0x04, 0x37, 0xb6, 0xb6, 0x37, 0xed, 0xca, 0xee, 0xca, 0xea, 0x76, 0x75, 0xb3, 0xb6,
|
||||
0xbb, 0x6a, 0x57, 0x56, 0xb6, 0x2b, 0x53, 0xaa, 0x31, 0x7b, 0xf0, 0x22, 0x37, 0x7d, 0x4e, 0xb4,
|
||||
0x4a, 0xb1, 0xc3, 0xf1, 0x05, 0xcd, 0xce, 0xe3, 0x35, 0xa1, 0x49, 0x25, 0x6a, 0x76, 0x3a, 0x5e,
|
||||
0x92, 0xc6, 0xae, 0x3c, 0xda, 0x7c, 0x52, 0x99, 0x4a, 0x27, 0x6a, 0x6c, 0xd9, 0x64, 0x8d, 0x77,
|
||||
0xbe, 0xfd, 0xc5, 0x54, 0x7e, 0xfb, 0xd5, 0x3c, 0xbf, 0xba, 0xd2, 0xcf, 0x69, 0xd0, 0xc4, 0xbd,
|
||||
0x46, 0x07, 0x2a, 0xa0, 0x8b, 0xcd, 0x0d, 0x2d, 0x27, 0xed, 0xe0, 0xc0, 0x96, 0x6a, 0x58, 0xa3,
|
||||
0xe2, 0x51, 0x27, 0x9b, 0xfe, 0xfd, 0xe5, 0x5f, 0x3f, 0xa6, 0x26, 0xe1, 0xba, 0xe4, 0x97, 0xdb,
|
||||
0x4e, 0xe0, 0x34, 0x30, 0x45, 0xdf, 0xa9, 0xf0, 0xd6, 0x85, 0xf6, 0x87, 0x96, 0x92, 0x2f, 0x7f,
|
||||
0x72, 0xcb, 0x35, 0x96, 0x47, 0xa4, 0x87, 0x66, 0x52, 0x50, 0xd1, 0x37, 0x30, 0xf1, 0xdf, 0x76,
|
||||
0x89, 0x16, 0x06, 0x5d, 0x82, 0x0b, 0x0d, 0xd9, 0x58, 0x1c, 0x05, 0x1d, 0x9a, 0x41, 0xe9, 0x0f,
|
||||
0x15, 0x26, 0x4e, 0x1f, 0x3a, 0xf6, 0xcc, 0xef, 0xa0, 0xcf, 0x41, 0x13, 0xcf, 0x38, 0x4a, 0x6c,
|
||||
0xae, 0x67, 0x3e, 0x02, 0x8c, 0xdc, 0x60, 0x60, 0xf8, 0x01, 0xb8, 0x70, 0x45, 0x3e, 0xa4, 0x28,
|
||||
0x31, 0xc2, 0xd9, 0x77, 0xda, 0xb8, 0x3d, 0x84, 0x18, 0x6a, 0x52, 0x9e, 0x3f, 0x7c, 0x63, 0x2a,
|
||||
0xaf, 0xdf, 0x98, 0xca, 0xf3, 0xbe, 0xa9, 0x1e, 0xf6, 0x4d, 0xf5, 0x55, 0xdf, 0x54, 0xff, 0xec,
|
||||
0x9b, 0xea, 0xf7, 0xc7, 0xa6, 0xf2, 0xea, 0xd8, 0x54, 0x5e, 0x1f, 0x9b, 0xca, 0xd3, 0xf4, 0x53,
|
||||
0xad, 0x9e, 0x91, 0xdf, 0x64, 0x77, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x32, 0xf6, 0xf1, 0xe7,
|
||||
0xa3, 0x0a, 0x00, 0x00,
|
||||
}
|
||||
|
||||
type authenticatedWrapperRaftServer struct {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ package docker.swarmkit.v1;
|
||||
|
||||
import "github.com/docker/swarmkit/api/objects.proto";
|
||||
import "github.com/docker/swarmkit/api/types.proto";
|
||||
import "go.etcd.io/etcd/raft/v3/raftpb/raft.proto";
|
||||
import "go.etcd.io/raft/v3/raftpb/raft.proto";
|
||||
import weak "gogoproto/gogo.proto";
|
||||
import weak "github.com/docker/swarmkit/protobuf/plugin/plugin.proto";
|
||||
|
||||
|
||||
+70
-35
@@ -67,8 +67,9 @@ func (ResourceType) EnumDescriptor() ([]byte, []int) {
|
||||
// Only the manager create a NEW task, and move the task to PENDING and ASSIGNED.
|
||||
// Afterward, the manager must rely on the agent to update the task status
|
||||
// (pre-run: preparing, ready, starting;
|
||||
// running;
|
||||
// end-state: complete, shutdown, failed, rejected)
|
||||
//
|
||||
// running;
|
||||
// end-state: complete, shutdown, failed, rejected)
|
||||
type TaskState int32
|
||||
|
||||
const (
|
||||
@@ -1085,6 +1086,7 @@ var xxx_messageInfo_DiscreteGenericResource proto.InternalMessageInfo
|
||||
// be either an integer (e.g: SSD=3) or a string (e.g: SSD=sda1)
|
||||
type GenericResource struct {
|
||||
// Types that are valid to be assigned to Resource:
|
||||
//
|
||||
// *GenericResource_NamedResourceSpec
|
||||
// *GenericResource_DiscreteResourceSpec
|
||||
Resource isGenericResource_Resource `protobuf_oneof:"resource"`
|
||||
@@ -2114,6 +2116,7 @@ type TaskStatus struct {
|
||||
// Container status contains container specific status information.
|
||||
//
|
||||
// Types that are valid to be assigned to RuntimeStatus:
|
||||
//
|
||||
// *TaskStatus_Container
|
||||
RuntimeStatus isTaskStatus_RuntimeStatus `protobuf_oneof:"runtime_status"`
|
||||
// HostPorts provides a list of ports allocated at the host
|
||||
@@ -2506,6 +2509,7 @@ type IssuanceStatus struct {
|
||||
State IssuanceStatus_State `protobuf:"varint,1,opt,name=state,proto3,enum=docker.swarmkit.v1.IssuanceStatus_State" json:"state,omitempty"`
|
||||
// Err is set if the Certificate Issuance is in an error state.
|
||||
// The following states should report a companion error:
|
||||
//
|
||||
// FAILED
|
||||
Err string `protobuf:"bytes,2,opt,name=err,proto3" json:"err,omitempty"`
|
||||
}
|
||||
@@ -3006,6 +3010,7 @@ var xxx_messageInfo_SpreadOver proto.InternalMessageInfo
|
||||
|
||||
type PlacementPreference struct {
|
||||
// Types that are valid to be assigned to Preference:
|
||||
//
|
||||
// *PlacementPreference_Spread
|
||||
Preference isPlacementPreference_Preference `protobuf_oneof:"Preference"`
|
||||
}
|
||||
@@ -3437,6 +3442,7 @@ type SecretReference struct {
|
||||
// Target specifies how this secret should be exposed to the task.
|
||||
//
|
||||
// Types that are valid to be assigned to Target:
|
||||
//
|
||||
// *SecretReference_File
|
||||
Target isSecretReference_Target `protobuf_oneof:"target"`
|
||||
}
|
||||
@@ -3517,6 +3523,7 @@ type ConfigReference struct {
|
||||
// Target specifies how this config should be exposed to the task.
|
||||
//
|
||||
// Types that are valid to be assigned to Target:
|
||||
//
|
||||
// *ConfigReference_File
|
||||
// *ConfigReference_Runtime
|
||||
Target isConfigReference_Target `protobuf_oneof:"target"`
|
||||
@@ -3826,6 +3833,7 @@ var xxx_messageInfo_Privileges proto.InternalMessageInfo
|
||||
// CredentialSpec for managed service account (Windows only).
|
||||
type Privileges_CredentialSpec struct {
|
||||
// Types that are valid to be assigned to Source:
|
||||
//
|
||||
// *Privileges_CredentialSpec_File
|
||||
// *Privileges_CredentialSpec_Registry
|
||||
// *Privileges_CredentialSpec_Config
|
||||
@@ -4113,6 +4121,7 @@ type VolumeAccessMode struct {
|
||||
// but the upstream is free to do so. However, one of these MUST be set.
|
||||
//
|
||||
// Types that are valid to be assigned to AccessType:
|
||||
//
|
||||
// *VolumeAccessMode_Block
|
||||
// *VolumeAccessMode_Mount
|
||||
AccessType isVolumeAccessMode_AccessType `protobuf_oneof:"access_type"`
|
||||
@@ -4338,12 +4347,12 @@ var xxx_messageInfo_VolumeSecret proto.InternalMessageInfo
|
||||
//
|
||||
// Without this two-step process, the following could happen:
|
||||
//
|
||||
// 1. ControllerPublishVolume is called and the Volume is successfully
|
||||
// published.
|
||||
// 2. A crash or leadership change disrupts the cluster before
|
||||
// the Volume with the updated VolumePublishStatus can be added to the
|
||||
// store.
|
||||
// 3. The Task that required the Volume to be published is deleted.
|
||||
// 1. ControllerPublishVolume is called and the Volume is successfully
|
||||
// published.
|
||||
// 2. A crash or leadership change disrupts the cluster before
|
||||
// the Volume with the updated VolumePublishStatus can be added to the
|
||||
// store.
|
||||
// 3. The Task that required the Volume to be published is deleted.
|
||||
//
|
||||
// In this case, the Volume would be published to the Node, but Swarm would be
|
||||
// unaware of this, and would additionally be unaware that the Volume _should_
|
||||
@@ -4608,21 +4617,27 @@ type TopologyRequirement struct {
|
||||
// accessible from at least one of the requisite topologies.
|
||||
//
|
||||
// Given
|
||||
// x = number of topologies provisioned volume is accessible from
|
||||
// n = number of requisite topologies
|
||||
//
|
||||
// x = number of topologies provisioned volume is accessible from
|
||||
// n = number of requisite topologies
|
||||
//
|
||||
// The CO MUST ensure n >= 1. The SP MUST ensure x >= 1
|
||||
// If x==n, then the SP MUST make the provisioned volume available to
|
||||
// all topologies from the list of requisite topologies. If it is
|
||||
// unable to do so, the SP MUST fail the CreateVolume call.
|
||||
// For example, if a volume should be accessible from a single zone,
|
||||
// and requisite =
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
//
|
||||
// then the provisioned volume MUST be accessible from the "region"
|
||||
// "R1" and the "zone" "Z2".
|
||||
// Similarly, if a volume should be accessible from two zones, and
|
||||
// requisite =
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// then the provisioned volume MUST be accessible from the "region"
|
||||
// "R1" and both "zone" "Z2" and "zone" "Z3".
|
||||
//
|
||||
@@ -4631,18 +4646,23 @@ type TopologyRequirement struct {
|
||||
// the CreateVolume call.
|
||||
// For example, if a volume should be accessible from a single zone,
|
||||
// and requisite =
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// then the SP may choose to make the provisioned volume available in
|
||||
// either the "zone" "Z2" or the "zone" "Z3" in the "region" "R1".
|
||||
// Similarly, if a volume should be accessible from two zones, and
|
||||
// requisite =
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"},
|
||||
// {"region": "R1", "zone": "Z4"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"},
|
||||
// {"region": "R1", "zone": "Z4"}
|
||||
//
|
||||
// then the provisioned volume MUST be accessible from any combination
|
||||
// of two unique topologies: e.g. "R1/Z2" and "R1/Z3", or "R1/Z2" and
|
||||
// "R1/Z4", or "R1/Z3" and "R1/Z4".
|
||||
//
|
||||
// "R1/Z4", or "R1/Z3" and "R1/Z4".
|
||||
//
|
||||
// If x>n, then the SP MUST make the provisioned volume available from
|
||||
// all topologies from the list of requisite topologies and MAY choose
|
||||
@@ -4651,7 +4671,9 @@ type TopologyRequirement struct {
|
||||
// CreateVolume call.
|
||||
// For example, if a volume should be accessible from two zones, and
|
||||
// requisite =
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
//
|
||||
// then the provisioned volume MUST be accessible from the "region"
|
||||
// "R1" and the "zone" "Z2" and the SP may select the second zone
|
||||
// independently, e.g. "R1/Z4".
|
||||
@@ -4680,10 +4702,14 @@ type TopologyRequirement struct {
|
||||
// Example 1:
|
||||
// Given a volume should be accessible from a single zone, and
|
||||
// requisite =
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// available from "zone" "Z3" in the "region" "R1" and fall back to
|
||||
// "zone" "Z2" in the "region" "R1" if that is not possible.
|
||||
@@ -4691,13 +4717,17 @@ type TopologyRequirement struct {
|
||||
// Example 2:
|
||||
// Given a volume should be accessible from a single zone, and
|
||||
// requisite =
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"},
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z5"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"},
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z5"}
|
||||
//
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
//
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// accessible from "zone" "Z4" in the "region" "R1" and fall back to
|
||||
// "zone" "Z2" in the "region" "R1" if that is not possible. If that
|
||||
@@ -4710,13 +4740,17 @@ type TopologyRequirement struct {
|
||||
// the volume is accessible from two zones, aka synchronously
|
||||
// replicated), and
|
||||
// requisite =
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"},
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z5"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z2"},
|
||||
// {"region": "R1", "zone": "Z3"},
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z5"}
|
||||
//
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z5"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// {"region": "R1", "zone": "Z5"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
//
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// accessible from the combination of the two "zones" "Z5" and "Z3" in
|
||||
// the "region" "R1". If that's not possible, it should fall back to
|
||||
@@ -4831,6 +4865,7 @@ type VolumeCapability struct {
|
||||
// following fields MUST be specified.
|
||||
//
|
||||
// Types that are valid to be assigned to AccessType:
|
||||
//
|
||||
// *VolumeCapability_Block
|
||||
// *VolumeCapability_Mount
|
||||
AccessType isVolumeCapability_AccessType `protobuf_oneof:"access_type"`
|
||||
|
||||
vendor/go.etcd.io/etcd/raft/v3/raftpb/confchange.go → vendor/go.etcd.io/raft/v3/raftpb/confchange.go
Generated
Vendored
+7
-1
@@ -35,7 +35,13 @@ func MarshalConfChange(c ConfChangeI) (EntryType, []byte, error) {
|
||||
var typ EntryType
|
||||
var ccdata []byte
|
||||
var err error
|
||||
if ccv1, ok := c.AsV1(); ok {
|
||||
if c == nil {
|
||||
// A nil data unmarshals into an empty ConfChangeV2 and has the benefit
|
||||
// that appendEntry can never refuse it based on its size (which
|
||||
// registers as zero).
|
||||
typ = EntryConfChangeV2
|
||||
ccdata = nil
|
||||
} else if ccv1, ok := c.AsV1(); ok {
|
||||
typ = EntryConfChange
|
||||
ccdata, err = ccv1.Marshal()
|
||||
} else {
|
||||
Generated
Vendored
+2
-2
@@ -17,7 +17,7 @@ package raftpb
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Equivalent returns a nil error if the inputs describe the same configuration.
|
||||
@@ -27,7 +27,7 @@ func (cs ConfState) Equivalent(cs2 ConfState) error {
|
||||
orig1, orig2 := cs1, cs2
|
||||
s := func(sl *[]uint64) {
|
||||
*sl = append([]uint64(nil), *sl...)
|
||||
sort.Slice(*sl, func(i, j int) bool { return (*sl)[i] < (*sl)[j] })
|
||||
slices.Sort(*sl)
|
||||
}
|
||||
|
||||
for _, cs := range []*ConfState{&cs1, &cs2} {
|
||||
Generated
Vendored
+247
-125
@@ -68,29 +68,34 @@ func (EntryType) EnumDescriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
// For description of different message types, see:
|
||||
// https://pkg.go.dev/go.etcd.io/etcd/raft/v3#hdr-MessageType
|
||||
// https://pkg.go.dev/go.etcd.io/raft/v3#hdr-MessageType
|
||||
type MessageType int32
|
||||
|
||||
const (
|
||||
MsgHup MessageType = 0
|
||||
MsgBeat MessageType = 1
|
||||
MsgProp MessageType = 2
|
||||
MsgApp MessageType = 3
|
||||
MsgAppResp MessageType = 4
|
||||
MsgVote MessageType = 5
|
||||
MsgVoteResp MessageType = 6
|
||||
MsgSnap MessageType = 7
|
||||
MsgHeartbeat MessageType = 8
|
||||
MsgHeartbeatResp MessageType = 9
|
||||
MsgUnreachable MessageType = 10
|
||||
MsgSnapStatus MessageType = 11
|
||||
MsgCheckQuorum MessageType = 12
|
||||
MsgTransferLeader MessageType = 13
|
||||
MsgTimeoutNow MessageType = 14
|
||||
MsgReadIndex MessageType = 15
|
||||
MsgReadIndexResp MessageType = 16
|
||||
MsgPreVote MessageType = 17
|
||||
MsgPreVoteResp MessageType = 18
|
||||
MsgHup MessageType = 0
|
||||
MsgBeat MessageType = 1
|
||||
MsgProp MessageType = 2
|
||||
MsgApp MessageType = 3
|
||||
MsgAppResp MessageType = 4
|
||||
MsgVote MessageType = 5
|
||||
MsgVoteResp MessageType = 6
|
||||
MsgSnap MessageType = 7
|
||||
MsgHeartbeat MessageType = 8
|
||||
MsgHeartbeatResp MessageType = 9
|
||||
MsgUnreachable MessageType = 10
|
||||
MsgSnapStatus MessageType = 11
|
||||
MsgCheckQuorum MessageType = 12
|
||||
MsgTransferLeader MessageType = 13
|
||||
MsgTimeoutNow MessageType = 14
|
||||
MsgReadIndex MessageType = 15
|
||||
MsgReadIndexResp MessageType = 16
|
||||
MsgPreVote MessageType = 17
|
||||
MsgPreVoteResp MessageType = 18
|
||||
MsgStorageAppend MessageType = 19
|
||||
MsgStorageAppendResp MessageType = 20
|
||||
MsgStorageApply MessageType = 21
|
||||
MsgStorageApplyResp MessageType = 22
|
||||
MsgForgetLeader MessageType = 23
|
||||
)
|
||||
|
||||
var MessageType_name = map[int32]string{
|
||||
@@ -113,28 +118,38 @@ var MessageType_name = map[int32]string{
|
||||
16: "MsgReadIndexResp",
|
||||
17: "MsgPreVote",
|
||||
18: "MsgPreVoteResp",
|
||||
19: "MsgStorageAppend",
|
||||
20: "MsgStorageAppendResp",
|
||||
21: "MsgStorageApply",
|
||||
22: "MsgStorageApplyResp",
|
||||
23: "MsgForgetLeader",
|
||||
}
|
||||
|
||||
var MessageType_value = map[string]int32{
|
||||
"MsgHup": 0,
|
||||
"MsgBeat": 1,
|
||||
"MsgProp": 2,
|
||||
"MsgApp": 3,
|
||||
"MsgAppResp": 4,
|
||||
"MsgVote": 5,
|
||||
"MsgVoteResp": 6,
|
||||
"MsgSnap": 7,
|
||||
"MsgHeartbeat": 8,
|
||||
"MsgHeartbeatResp": 9,
|
||||
"MsgUnreachable": 10,
|
||||
"MsgSnapStatus": 11,
|
||||
"MsgCheckQuorum": 12,
|
||||
"MsgTransferLeader": 13,
|
||||
"MsgTimeoutNow": 14,
|
||||
"MsgReadIndex": 15,
|
||||
"MsgReadIndexResp": 16,
|
||||
"MsgPreVote": 17,
|
||||
"MsgPreVoteResp": 18,
|
||||
"MsgHup": 0,
|
||||
"MsgBeat": 1,
|
||||
"MsgProp": 2,
|
||||
"MsgApp": 3,
|
||||
"MsgAppResp": 4,
|
||||
"MsgVote": 5,
|
||||
"MsgVoteResp": 6,
|
||||
"MsgSnap": 7,
|
||||
"MsgHeartbeat": 8,
|
||||
"MsgHeartbeatResp": 9,
|
||||
"MsgUnreachable": 10,
|
||||
"MsgSnapStatus": 11,
|
||||
"MsgCheckQuorum": 12,
|
||||
"MsgTransferLeader": 13,
|
||||
"MsgTimeoutNow": 14,
|
||||
"MsgReadIndex": 15,
|
||||
"MsgReadIndexResp": 16,
|
||||
"MsgPreVote": 17,
|
||||
"MsgPreVoteResp": 18,
|
||||
"MsgStorageAppend": 19,
|
||||
"MsgStorageAppendResp": 20,
|
||||
"MsgStorageApply": 21,
|
||||
"MsgStorageApplyResp": 22,
|
||||
"MsgForgetLeader": 23,
|
||||
}
|
||||
|
||||
func (x MessageType) Enum() *MessageType {
|
||||
@@ -386,18 +401,37 @@ type Message struct {
|
||||
From uint64 `protobuf:"varint,3,opt,name=from" json:"from"`
|
||||
Term uint64 `protobuf:"varint,4,opt,name=term" json:"term"`
|
||||
// logTerm is generally used for appending Raft logs to followers. For example,
|
||||
// (type=MsgApp,index=100,logTerm=5) means leader appends entries starting at
|
||||
// index=101, and the term of entry at index 100 is 5.
|
||||
// (type=MsgApp,index=100,logTerm=5) means the leader appends entries starting
|
||||
// at index=101, and the term of the entry at index 100 is 5.
|
||||
// (type=MsgAppResp,reject=true,index=100,logTerm=5) means follower rejects some
|
||||
// entries from its leader as it already has an entry with term 5 at index 100.
|
||||
LogTerm uint64 `protobuf:"varint,5,opt,name=logTerm" json:"logTerm"`
|
||||
Index uint64 `protobuf:"varint,6,opt,name=index" json:"index"`
|
||||
Entries []Entry `protobuf:"bytes,7,rep,name=entries" json:"entries"`
|
||||
Commit uint64 `protobuf:"varint,8,opt,name=commit" json:"commit"`
|
||||
Snapshot Snapshot `protobuf:"bytes,9,opt,name=snapshot" json:"snapshot"`
|
||||
Reject bool `protobuf:"varint,10,opt,name=reject" json:"reject"`
|
||||
RejectHint uint64 `protobuf:"varint,11,opt,name=rejectHint" json:"rejectHint"`
|
||||
Context []byte `protobuf:"bytes,12,opt,name=context" json:"context,omitempty"`
|
||||
// (type=MsgStorageAppendResp,index=100,logTerm=5) means the local node wrote
|
||||
// entries up to index=100 in stable storage, and the term of the entry at index
|
||||
// 100 was 5. This doesn't always mean that the corresponding MsgStorageAppend
|
||||
// message was the one that carried these entries, just that those entries were
|
||||
// stable at the time of processing the corresponding MsgStorageAppend.
|
||||
LogTerm uint64 `protobuf:"varint,5,opt,name=logTerm" json:"logTerm"`
|
||||
Index uint64 `protobuf:"varint,6,opt,name=index" json:"index"`
|
||||
Entries []Entry `protobuf:"bytes,7,rep,name=entries" json:"entries"`
|
||||
Commit uint64 `protobuf:"varint,8,opt,name=commit" json:"commit"`
|
||||
// (type=MsgStorageAppend,vote=5,term=10) means the local node is voting for
|
||||
// peer 5 in term 10. For MsgStorageAppends, the term, vote, and commit fields
|
||||
// will either all be set (to facilitate the construction of a HardState) if
|
||||
// any of the fields have changed or will all be unset if none of the fields
|
||||
// have changed.
|
||||
Vote uint64 `protobuf:"varint,13,opt,name=vote" json:"vote"`
|
||||
// snapshot is non-nil and non-empty for MsgSnap messages and nil for all other
|
||||
// message types. However, peer nodes running older binary versions may send a
|
||||
// non-nil, empty value for the snapshot field of non-MsgSnap messages. Code
|
||||
// should be prepared to handle such messages.
|
||||
Snapshot *Snapshot `protobuf:"bytes,9,opt,name=snapshot" json:"snapshot,omitempty"`
|
||||
Reject bool `protobuf:"varint,10,opt,name=reject" json:"reject"`
|
||||
RejectHint uint64 `protobuf:"varint,11,opt,name=rejectHint" json:"rejectHint"`
|
||||
Context []byte `protobuf:"bytes,12,opt,name=context" json:"context,omitempty"`
|
||||
// responses are populated by a raft node to instruct storage threads on how
|
||||
// to respond and who to respond to when the work associated with a message
|
||||
// is complete. Populated for MsgStorageAppend and MsgStorageApply messages.
|
||||
Responses []Message `protobuf:"bytes,14,rep,name=responses" json:"responses"`
|
||||
}
|
||||
|
||||
func (m *Message) Reset() { *m = Message{} }
|
||||
@@ -695,72 +729,76 @@ func init() {
|
||||
func init() { proto.RegisterFile("raft.proto", fileDescriptor_b042552c306ae59b) }
|
||||
|
||||
var fileDescriptor_b042552c306ae59b = []byte{
|
||||
// 1026 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x6e, 0xdb, 0x46,
|
||||
0x17, 0x25, 0x29, 0x5a, 0x3f, 0x57, 0xb2, 0x3c, 0xbe, 0xf1, 0x17, 0x10, 0x86, 0xc1, 0xe8, 0x53,
|
||||
0x52, 0x44, 0x70, 0x11, 0xb7, 0xd0, 0xa2, 0x28, 0xba, 0xf3, 0x4f, 0x00, 0xab, 0xb0, 0xdc, 0x54,
|
||||
0x76, 0xbc, 0x28, 0x50, 0x08, 0x63, 0x71, 0x44, 0xb3, 0x15, 0x39, 0x04, 0x39, 0x72, 0xed, 0x4d,
|
||||
0x51, 0xf4, 0x09, 0xba, 0xec, 0x26, 0xdb, 0x3e, 0x40, 0x9f, 0xc2, 0x4b, 0x03, 0xdd, 0x74, 0x15,
|
||||
0x34, 0xf6, 0x8b, 0x14, 0x33, 0x1c, 0x4a, 0x94, 0x6c, 0x64, 0xd1, 0xdd, 0xcc, 0xb9, 0x67, 0xee,
|
||||
0x9c, 0x73, 0xef, 0xe5, 0x10, 0x20, 0xa1, 0x63, 0xb1, 0x13, 0x27, 0x5c, 0x70, 0x2c, 0xcb, 0x75,
|
||||
0x7c, 0xbe, 0xb9, 0xe1, 0x73, 0x9f, 0x2b, 0xe8, 0x33, 0xb9, 0xca, 0xa2, 0xed, 0x9f, 0x61, 0xe5,
|
||||
0x75, 0x24, 0x92, 0x6b, 0x74, 0xc0, 0x3e, 0x65, 0x49, 0xe8, 0x58, 0x2d, 0xb3, 0x63, 0xef, 0xd9,
|
||||
0x37, 0xef, 0x9f, 0x19, 0x03, 0x85, 0xe0, 0x26, 0xac, 0xf4, 0x22, 0x8f, 0x5d, 0x39, 0xa5, 0x42,
|
||||
0x28, 0x83, 0xf0, 0x53, 0xb0, 0x4f, 0xaf, 0x63, 0xe6, 0x98, 0x2d, 0xb3, 0xd3, 0xec, 0xae, 0xef,
|
||||
0x64, 0x77, 0xed, 0xa8, 0x94, 0x32, 0x30, 0x4b, 0x74, 0x1d, 0x33, 0x44, 0xb0, 0x0f, 0xa8, 0xa0,
|
||||
0x8e, 0xdd, 0x32, 0x3b, 0x8d, 0x81, 0x5a, 0xb7, 0x7f, 0x31, 0x81, 0x9c, 0x44, 0x34, 0x4e, 0x2f,
|
||||
0xb8, 0xe8, 0x33, 0x41, 0x3d, 0x2a, 0x28, 0x7e, 0x01, 0x30, 0xe2, 0xd1, 0x78, 0x98, 0x0a, 0x2a,
|
||||
0xb2, 0xdc, 0xf5, 0x79, 0xee, 0x7d, 0x1e, 0x8d, 0x4f, 0x64, 0x40, 0xe7, 0xae, 0x8d, 0x72, 0x40,
|
||||
0x2a, 0x0d, 0x94, 0xd2, 0xa2, 0x89, 0x0c, 0x92, 0xfe, 0x84, 0xf4, 0x57, 0x34, 0xa1, 0x90, 0xf6,
|
||||
0x77, 0x50, 0xcd, 0x15, 0x48, 0x89, 0x52, 0x81, 0xba, 0xb3, 0x31, 0x50, 0x6b, 0xfc, 0x0a, 0xaa,
|
||||
0xa1, 0x56, 0xa6, 0x12, 0xd7, 0xbb, 0x4e, 0xae, 0x65, 0x59, 0xb9, 0xce, 0x3b, 0xe3, 0xb7, 0xdf,
|
||||
0x95, 0xa0, 0xd2, 0x67, 0x69, 0x4a, 0x7d, 0x86, 0xaf, 0xc0, 0x16, 0xf3, 0x5a, 0x3d, 0xc9, 0x73,
|
||||
0xe8, 0x70, 0xb1, 0x5a, 0x92, 0x86, 0x1b, 0x60, 0x09, 0xbe, 0xe0, 0xc4, 0x12, 0x5c, 0xda, 0x18,
|
||||
0x27, 0x7c, 0xc9, 0x86, 0x44, 0x66, 0x06, 0xed, 0x65, 0x83, 0xe8, 0x42, 0x65, 0xc2, 0x7d, 0xd5,
|
||||
0xdd, 0x95, 0x42, 0x30, 0x07, 0xe7, 0x65, 0x2b, 0x3f, 0x2c, 0xdb, 0x2b, 0xa8, 0xb0, 0x48, 0x24,
|
||||
0x01, 0x4b, 0x9d, 0x4a, 0xab, 0xd4, 0xa9, 0x77, 0x57, 0x17, 0x7a, 0x9c, 0xa7, 0xd2, 0x1c, 0xdc,
|
||||
0x82, 0xf2, 0x88, 0x87, 0x61, 0x20, 0x9c, 0x6a, 0x21, 0x97, 0xc6, 0xb0, 0x0b, 0xd5, 0x54, 0x57,
|
||||
0xcc, 0xa9, 0xa9, 0x4a, 0x92, 0xe5, 0x4a, 0xe6, 0x15, 0xcc, 0x79, 0x32, 0x63, 0xc2, 0x7e, 0x60,
|
||||
0x23, 0xe1, 0x40, 0xcb, 0xec, 0x54, 0xf3, 0x8c, 0x19, 0x86, 0x2f, 0x00, 0xb2, 0xd5, 0x61, 0x10,
|
||||
0x09, 0xa7, 0x5e, 0xb8, 0xb3, 0x80, 0xa3, 0x03, 0x95, 0x11, 0x8f, 0x04, 0xbb, 0x12, 0x4e, 0x43,
|
||||
0x35, 0x36, 0xdf, 0xb6, 0xbf, 0x87, 0xda, 0x21, 0x4d, 0xbc, 0x6c, 0x7c, 0xf2, 0x0a, 0x9a, 0x0f,
|
||||
0x2a, 0xe8, 0x80, 0x7d, 0xc9, 0x05, 0x5b, 0xfc, 0x38, 0x24, 0x52, 0x30, 0x5c, 0x7a, 0x68, 0xb8,
|
||||
0xfd, 0xa7, 0x09, 0xb5, 0xd9, 0xbc, 0xe2, 0x53, 0x28, 0xcb, 0x33, 0x49, 0xea, 0x98, 0xad, 0x52,
|
||||
0xc7, 0x1e, 0xe8, 0x1d, 0x6e, 0x42, 0x75, 0xc2, 0x68, 0x12, 0xc9, 0x88, 0xa5, 0x22, 0xb3, 0x3d,
|
||||
0xbe, 0x84, 0xb5, 0x8c, 0x35, 0xe4, 0x53, 0xe1, 0xf3, 0x20, 0xf2, 0x9d, 0x92, 0xa2, 0x34, 0x33,
|
||||
0xf8, 0x1b, 0x8d, 0xe2, 0x73, 0x58, 0xcd, 0x0f, 0x0d, 0x23, 0xe9, 0xd4, 0x56, 0xb4, 0x46, 0x0e,
|
||||
0x1e, 0xb3, 0x2b, 0x81, 0xcf, 0x01, 0xe8, 0x54, 0xf0, 0xe1, 0x84, 0xd1, 0x4b, 0xa6, 0x86, 0x21,
|
||||
0x2f, 0x68, 0x4d, 0xe2, 0x47, 0x12, 0x6e, 0xbf, 0x33, 0x01, 0xa4, 0xe8, 0xfd, 0x0b, 0x1a, 0xf9,
|
||||
0x0c, 0x3f, 0xd7, 0x63, 0x6b, 0xa9, 0xb1, 0x7d, 0x5a, 0xfc, 0x0c, 0x33, 0xc6, 0x83, 0xc9, 0x7d,
|
||||
0x09, 0x95, 0x88, 0x7b, 0x6c, 0x18, 0x78, 0xba, 0x28, 0x4d, 0x19, 0xbc, 0x7b, 0xff, 0xac, 0x7c,
|
||||
0xcc, 0x3d, 0xd6, 0x3b, 0x18, 0x94, 0x65, 0xb8, 0xe7, 0x15, 0xfb, 0x62, 0x2f, 0xf4, 0x05, 0x37,
|
||||
0xc1, 0x0a, 0x3c, 0xdd, 0x08, 0xd0, 0xa7, 0xad, 0xde, 0xc1, 0xc0, 0x0a, 0xbc, 0x76, 0x08, 0x64,
|
||||
0x7e, 0xf9, 0x49, 0x10, 0xf9, 0x93, 0xb9, 0x48, 0xf3, 0xbf, 0x88, 0xb4, 0x3e, 0x26, 0xb2, 0xfd,
|
||||
0x87, 0x09, 0x8d, 0x79, 0x9e, 0xb3, 0x2e, 0xee, 0x01, 0x88, 0x84, 0x46, 0x69, 0x20, 0x02, 0x1e,
|
||||
0xe9, 0x1b, 0xb7, 0x1e, 0xb9, 0x71, 0xc6, 0xc9, 0x27, 0x72, 0x7e, 0x0a, 0xbf, 0x84, 0xca, 0x48,
|
||||
0xb1, 0xb2, 0x8e, 0x17, 0x9e, 0x94, 0x65, 0x6b, 0xf9, 0x17, 0xa6, 0xe9, 0xc5, 0x9a, 0x95, 0x16,
|
||||
0x6a, 0xb6, 0x7d, 0x08, 0xb5, 0xd9, 0xbb, 0x8b, 0x6b, 0x50, 0x57, 0x9b, 0x63, 0x9e, 0x84, 0x74,
|
||||
0x42, 0x0c, 0x7c, 0x02, 0x6b, 0x0a, 0x98, 0xe7, 0x27, 0x26, 0xfe, 0x0f, 0xd6, 0x97, 0xc0, 0xb3,
|
||||
0x2e, 0xb1, 0xb6, 0xff, 0xb2, 0xa0, 0x5e, 0x78, 0x96, 0x10, 0xa0, 0xdc, 0x4f, 0xfd, 0xc3, 0x69,
|
||||
0x4c, 0x0c, 0xac, 0x43, 0xa5, 0x9f, 0xfa, 0x7b, 0x8c, 0x0a, 0x62, 0xea, 0xcd, 0x9b, 0x84, 0xc7,
|
||||
0xc4, 0xd2, 0xac, 0xdd, 0x38, 0x26, 0x25, 0x6c, 0x02, 0x64, 0xeb, 0x01, 0x4b, 0x63, 0x62, 0x6b,
|
||||
0xe2, 0x19, 0x17, 0x8c, 0xac, 0x48, 0x6d, 0x7a, 0xa3, 0xa2, 0x65, 0x1d, 0x95, 0x4f, 0x00, 0xa9,
|
||||
0x20, 0x81, 0x86, 0xbc, 0x8c, 0xd1, 0x44, 0x9c, 0xcb, 0x5b, 0xaa, 0xb8, 0x01, 0xa4, 0x88, 0xa8,
|
||||
0x43, 0x35, 0x44, 0x68, 0xf6, 0x53, 0xff, 0x6d, 0x94, 0x30, 0x3a, 0xba, 0xa0, 0xe7, 0x13, 0x46,
|
||||
0x00, 0xd7, 0x61, 0x55, 0x27, 0x92, 0x5f, 0xdc, 0x34, 0x25, 0x75, 0x4d, 0xdb, 0xbf, 0x60, 0xa3,
|
||||
0x1f, 0xbf, 0x9d, 0xf2, 0x64, 0x1a, 0x92, 0x86, 0xb4, 0xdd, 0x4f, 0x7d, 0xd5, 0xa0, 0x31, 0x4b,
|
||||
0x8e, 0x18, 0xf5, 0x58, 0x42, 0x56, 0xf5, 0xe9, 0xd3, 0x20, 0x64, 0x7c, 0x2a, 0x8e, 0xf9, 0x4f,
|
||||
0xa4, 0xa9, 0xc5, 0x0c, 0x18, 0xf5, 0xd4, 0xff, 0x8e, 0xac, 0x69, 0x31, 0x33, 0x44, 0x89, 0x21,
|
||||
0xda, 0xef, 0x9b, 0x84, 0x29, 0x8b, 0xeb, 0xfa, 0x56, 0xbd, 0x57, 0x1c, 0xdc, 0xfe, 0xd5, 0x84,
|
||||
0x8d, 0xc7, 0xc6, 0x03, 0xb7, 0xc0, 0x79, 0x0c, 0xdf, 0x9d, 0x0a, 0x4e, 0x0c, 0xfc, 0x04, 0xfe,
|
||||
0xff, 0x58, 0xf4, 0x6b, 0x1e, 0x44, 0xa2, 0x17, 0xc6, 0x93, 0x60, 0x14, 0xc8, 0x56, 0x7c, 0x8c,
|
||||
0xf6, 0xfa, 0x4a, 0xd3, 0xac, 0xed, 0x6b, 0x68, 0x2e, 0x7e, 0x14, 0xb2, 0x18, 0x73, 0x64, 0xd7,
|
||||
0xf3, 0xe4, 0xf8, 0x13, 0x03, 0x9d, 0xa2, 0xd8, 0x01, 0x0b, 0xf9, 0x25, 0x53, 0x11, 0x73, 0x31,
|
||||
0xf2, 0x36, 0xf6, 0xa8, 0xc8, 0x22, 0xd6, 0xa2, 0x91, 0x5d, 0xcf, 0x3b, 0xca, 0xde, 0x1e, 0x15,
|
||||
0x2d, 0xed, 0xbd, 0xb8, 0xf9, 0xe0, 0x1a, 0xb7, 0x1f, 0x5c, 0xe3, 0xe6, 0xce, 0x35, 0x6f, 0xef,
|
||||
0x5c, 0xf3, 0x9f, 0x3b, 0xd7, 0xfc, 0xed, 0xde, 0x35, 0x7e, 0xbf, 0x77, 0x8d, 0xdb, 0x7b, 0xd7,
|
||||
0xf8, 0xfb, 0xde, 0x35, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xee, 0xe3, 0x39, 0x8b, 0xbb, 0x08,
|
||||
0x00, 0x00,
|
||||
// 1102 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xcb, 0x6e, 0x23, 0x45,
|
||||
0x14, 0xed, 0x6e, 0x77, 0xfc, 0xb8, 0x76, 0x9c, 0x4a, 0xc5, 0x33, 0xd3, 0x8a, 0x22, 0x8f, 0xf1,
|
||||
0x0c, 0x1a, 0x2b, 0x68, 0x02, 0x32, 0x12, 0x42, 0xec, 0xf2, 0x18, 0x94, 0xa0, 0x38, 0x0c, 0x4e,
|
||||
0x26, 0x0b, 0x24, 0x14, 0x55, 0xdc, 0x95, 0x4e, 0x83, 0x5d, 0xd5, 0xaa, 0x2e, 0x87, 0x64, 0x83,
|
||||
0x10, 0x5f, 0xc0, 0x92, 0x0d, 0x5b, 0x3e, 0x80, 0x8f, 0x40, 0x59, 0x66, 0xc9, 0x6a, 0xc4, 0x24,
|
||||
0x7f, 0xc0, 0x17, 0xa0, 0xaa, 0xae, 0x7e, 0xd8, 0x89, 0x66, 0xc1, 0xae, 0xea, 0xdc, 0x53, 0xf7,
|
||||
0x9e, 0x7b, 0x6e, 0x57, 0x35, 0x80, 0x20, 0x67, 0x72, 0x23, 0x12, 0x5c, 0x72, 0x5c, 0x56, 0xeb,
|
||||
0xe8, 0x74, 0xb5, 0x15, 0xf0, 0x80, 0x6b, 0xe8, 0x63, 0xb5, 0x4a, 0xa2, 0xdd, 0x9f, 0x60, 0xe1,
|
||||
0x15, 0x93, 0xe2, 0x0a, 0x7b, 0xe0, 0x1e, 0x51, 0x31, 0xf1, 0x9c, 0x8e, 0xdd, 0x73, 0xb7, 0xdc,
|
||||
0xeb, 0xb7, 0x4f, 0xad, 0xa1, 0x46, 0xf0, 0x2a, 0x2c, 0xec, 0x31, 0x9f, 0x5e, 0x7a, 0xa5, 0x42,
|
||||
0x28, 0x81, 0xf0, 0x47, 0xe0, 0x1e, 0x5d, 0x45, 0xd4, 0xb3, 0x3b, 0x76, 0xaf, 0xd9, 0x5f, 0xde,
|
||||
0x48, 0x6a, 0x6d, 0xe8, 0x94, 0x2a, 0x90, 0x25, 0xba, 0x8a, 0x28, 0xc6, 0xe0, 0xee, 0x10, 0x49,
|
||||
0x3c, 0xb7, 0x63, 0xf7, 0x1a, 0x43, 0xbd, 0xee, 0xfe, 0x6c, 0x03, 0x3a, 0x64, 0x24, 0x8a, 0xcf,
|
||||
0xb9, 0x1c, 0x50, 0x49, 0x7c, 0x22, 0x09, 0xfe, 0x0c, 0x60, 0xc4, 0xd9, 0xd9, 0x49, 0x2c, 0x89,
|
||||
0x4c, 0x72, 0xd7, 0xf3, 0xdc, 0xdb, 0x9c, 0x9d, 0x1d, 0xaa, 0x80, 0xc9, 0x5d, 0x1b, 0xa5, 0x80,
|
||||
0x52, 0x1a, 0x6a, 0xa5, 0xc5, 0x26, 0x12, 0x48, 0xf5, 0x27, 0x55, 0x7f, 0xc5, 0x26, 0x34, 0xd2,
|
||||
0xfd, 0x16, 0xaa, 0xa9, 0x02, 0x25, 0x51, 0x29, 0xd0, 0x35, 0x1b, 0x43, 0xbd, 0xc6, 0x5f, 0x40,
|
||||
0x75, 0x62, 0x94, 0xe9, 0xc4, 0xf5, 0xbe, 0x97, 0x6a, 0x99, 0x57, 0x6e, 0xf2, 0x66, 0xfc, 0xee,
|
||||
0xbf, 0x25, 0xa8, 0x0c, 0x68, 0x1c, 0x93, 0x80, 0xe2, 0x97, 0xe0, 0xca, 0xdc, 0xab, 0x95, 0x34,
|
||||
0x87, 0x09, 0x17, 0xdd, 0x52, 0x34, 0xdc, 0x02, 0x47, 0xf2, 0x99, 0x4e, 0x1c, 0xc9, 0x55, 0x1b,
|
||||
0x67, 0x82, 0xcf, 0xb5, 0xa1, 0x90, 0xac, 0x41, 0x77, 0xbe, 0x41, 0xdc, 0x86, 0xca, 0x98, 0x07,
|
||||
0x7a, 0xba, 0x0b, 0x85, 0x60, 0x0a, 0xe6, 0xb6, 0x95, 0xef, 0xdb, 0xf6, 0x12, 0x2a, 0x94, 0x49,
|
||||
0x11, 0xd2, 0xd8, 0xab, 0x74, 0x4a, 0xbd, 0x7a, 0x7f, 0x71, 0x66, 0xc6, 0x69, 0x2a, 0xc3, 0xc1,
|
||||
0x6b, 0x50, 0x1e, 0xf1, 0xc9, 0x24, 0x94, 0x5e, 0xb5, 0x90, 0xcb, 0x60, 0x4a, 0xe2, 0x05, 0x97,
|
||||
0xd4, 0x5b, 0x2c, 0x4a, 0x54, 0x08, 0xee, 0x43, 0x35, 0x36, 0x5e, 0x7a, 0x35, 0xed, 0x31, 0x9a,
|
||||
0xf7, 0x58, 0xf3, 0xed, 0x61, 0xc6, 0x53, 0xb5, 0x04, 0xfd, 0x9e, 0x8e, 0xa4, 0x07, 0x1d, 0xbb,
|
||||
0x57, 0x4d, 0x6b, 0x25, 0x18, 0x7e, 0x0e, 0x90, 0xac, 0x76, 0x43, 0x26, 0xbd, 0x7a, 0xa1, 0x62,
|
||||
0x01, 0x57, 0xd6, 0x8c, 0x38, 0x93, 0xf4, 0x52, 0x7a, 0x0d, 0x35, 0x72, 0x53, 0x24, 0x05, 0xf1,
|
||||
0xa7, 0x50, 0x13, 0x34, 0x8e, 0x38, 0x8b, 0x69, 0xec, 0x35, 0xb5, 0x01, 0x4b, 0x73, 0x83, 0x4b,
|
||||
0x3f, 0xc3, 0x8c, 0xd7, 0xfd, 0x0e, 0x6a, 0xbb, 0x44, 0xf8, 0xc9, 0x37, 0x99, 0x8e, 0xc5, 0xbe,
|
||||
0x37, 0x96, 0xd4, 0x0d, 0xe7, 0x9e, 0x1b, 0xb9, 0x8b, 0xa5, 0xfb, 0x2e, 0x76, 0xff, 0xb4, 0xa1,
|
||||
0x96, 0x5d, 0x02, 0xfc, 0x18, 0xca, 0xea, 0x8c, 0x88, 0x3d, 0xbb, 0x53, 0xea, 0xb9, 0x43, 0xb3,
|
||||
0xc3, 0xab, 0x50, 0x1d, 0x53, 0x22, 0x98, 0x8a, 0x38, 0x3a, 0x92, 0xed, 0xf1, 0x0b, 0x58, 0x4a,
|
||||
0x58, 0x27, 0x7c, 0x2a, 0x03, 0x1e, 0xb2, 0xc0, 0x2b, 0x69, 0x4a, 0x33, 0x81, 0xbf, 0x36, 0x28,
|
||||
0x7e, 0x06, 0x8b, 0xe9, 0xa1, 0x13, 0xa6, 0x4c, 0x72, 0x35, 0xad, 0x91, 0x82, 0x07, 0xca, 0xa3,
|
||||
0x67, 0x00, 0x64, 0x2a, 0xf9, 0xc9, 0x98, 0x92, 0x0b, 0xaa, 0xbf, 0xb0, 0x74, 0x16, 0x35, 0x85,
|
||||
0xef, 0x2b, 0xb8, 0xfb, 0xbb, 0x0d, 0xa0, 0x44, 0x6f, 0x9f, 0x13, 0x16, 0x50, 0xfc, 0x89, 0xb9,
|
||||
0x0b, 0x8e, 0xbe, 0x0b, 0x8f, 0x8b, 0x77, 0x3b, 0x61, 0xdc, 0xbb, 0x0e, 0x2f, 0xa0, 0xc2, 0xb8,
|
||||
0x4f, 0x4f, 0x42, 0xdf, 0x98, 0xd2, 0x54, 0xc1, 0xdb, 0xb7, 0x4f, 0xcb, 0x07, 0xdc, 0xa7, 0x7b,
|
||||
0x3b, 0xc3, 0xb2, 0x0a, 0xef, 0xf9, 0xd8, 0xcb, 0x47, 0x9a, 0x3c, 0x34, 0xd9, 0x30, 0x57, 0xc1,
|
||||
0x09, 0x7d, 0x33, 0x08, 0x30, 0xa7, 0x9d, 0xbd, 0x9d, 0xa1, 0x13, 0xfa, 0xdd, 0x09, 0xa0, 0xbc,
|
||||
0xf8, 0x61, 0xc8, 0x82, 0x71, 0x2e, 0xd2, 0xfe, 0x3f, 0x22, 0x9d, 0xf7, 0x89, 0xec, 0xfe, 0x61,
|
||||
0x43, 0x23, 0xcf, 0x73, 0xdc, 0xc7, 0x5b, 0x00, 0x52, 0x10, 0x16, 0x87, 0x32, 0xe4, 0xcc, 0x54,
|
||||
0x5c, 0x7b, 0xa0, 0x62, 0xc6, 0x49, 0x3f, 0xe6, 0xfc, 0x14, 0xfe, 0x1c, 0x2a, 0x23, 0xcd, 0x4a,
|
||||
0x26, 0x5e, 0x78, 0xa7, 0xe6, 0x5b, 0x4b, 0xaf, 0xad, 0xa1, 0x17, 0x3d, 0x2b, 0xcd, 0x78, 0xb6,
|
||||
0xbe, 0x0b, 0xb5, 0xec, 0x31, 0xc7, 0x4b, 0x50, 0xd7, 0x9b, 0x03, 0x2e, 0x26, 0x64, 0x8c, 0x2c,
|
||||
0xbc, 0x02, 0x4b, 0x1a, 0xc8, 0xf3, 0x23, 0x1b, 0x3f, 0x82, 0xe5, 0x39, 0xf0, 0xb8, 0x8f, 0x9c,
|
||||
0xf5, 0xbf, 0x4a, 0x50, 0x2f, 0xbc, 0x75, 0x18, 0xa0, 0x3c, 0x88, 0x83, 0xdd, 0x69, 0x84, 0x2c,
|
||||
0x5c, 0x87, 0xca, 0x20, 0x0e, 0xb6, 0x28, 0x91, 0xc8, 0x36, 0x9b, 0xd7, 0x82, 0x47, 0xc8, 0x31,
|
||||
0xac, 0xcd, 0x28, 0x42, 0x25, 0xdc, 0x04, 0x48, 0xd6, 0x43, 0x1a, 0x47, 0xc8, 0x35, 0xc4, 0x63,
|
||||
0x2e, 0x29, 0x5a, 0x50, 0xda, 0xcc, 0x46, 0x47, 0xcb, 0x26, 0xaa, 0x5e, 0x0f, 0x54, 0xc1, 0x08,
|
||||
0x1a, 0xaa, 0x18, 0x25, 0x42, 0x9e, 0xaa, 0x2a, 0x55, 0xdc, 0x02, 0x54, 0x44, 0xf4, 0xa1, 0x1a,
|
||||
0xc6, 0xd0, 0x1c, 0xc4, 0xc1, 0x1b, 0x26, 0x28, 0x19, 0x9d, 0x93, 0xd3, 0x31, 0x45, 0x80, 0x97,
|
||||
0x61, 0xd1, 0x24, 0x52, 0x37, 0x6e, 0x1a, 0xa3, 0xba, 0xa1, 0x6d, 0x9f, 0xd3, 0xd1, 0x0f, 0xdf,
|
||||
0x4c, 0xb9, 0x98, 0x4e, 0x50, 0x43, 0xb5, 0x3d, 0x88, 0x03, 0x3d, 0xa0, 0x33, 0x2a, 0xf6, 0x29,
|
||||
0xf1, 0xa9, 0x40, 0x8b, 0xe6, 0xf4, 0x51, 0x38, 0xa1, 0x7c, 0x2a, 0x0f, 0xf8, 0x8f, 0xa8, 0x69,
|
||||
0xc4, 0x0c, 0x29, 0xf1, 0xf5, 0x4f, 0x14, 0x2d, 0x19, 0x31, 0x19, 0xa2, 0xc5, 0x20, 0xd3, 0xef,
|
||||
0x6b, 0x41, 0x75, 0x8b, 0xcb, 0xa6, 0xaa, 0xd9, 0x6b, 0x0e, 0x36, 0x27, 0x0f, 0x25, 0x17, 0x24,
|
||||
0xa0, 0x9b, 0x51, 0x44, 0x99, 0x8f, 0x56, 0xb0, 0x07, 0xad, 0x79, 0x54, 0xf3, 0x5b, 0x6a, 0x62,
|
||||
0x33, 0x91, 0xf1, 0x15, 0x7a, 0x84, 0x9f, 0xc0, 0xca, 0x1c, 0xa8, 0xd9, 0x8f, 0x0d, 0xfb, 0x4b,
|
||||
0x2e, 0x02, 0x2a, 0x4d, 0x47, 0x4f, 0xd6, 0x7f, 0xb1, 0xa1, 0xf5, 0xd0, 0x17, 0x89, 0xd7, 0xc0,
|
||||
0x7b, 0x08, 0xdf, 0x9c, 0x4a, 0x8e, 0x2c, 0xfc, 0x21, 0x7c, 0xf0, 0x50, 0xf4, 0x2b, 0x1e, 0x32,
|
||||
0xb9, 0x37, 0x89, 0xc6, 0xe1, 0x28, 0x54, 0xd3, 0x7f, 0x1f, 0xed, 0xd5, 0xa5, 0xa1, 0x39, 0xeb,
|
||||
0x57, 0xd0, 0x9c, 0xbd, 0x87, 0xca, 0xff, 0x1c, 0xd9, 0xf4, 0x7d, 0x75, 0xe3, 0x90, 0xa5, 0xac,
|
||||
0xc8, 0xe1, 0x21, 0x9d, 0xf0, 0x0b, 0xaa, 0x23, 0xf6, 0x6c, 0xe4, 0x4d, 0xe4, 0x13, 0x99, 0x44,
|
||||
0x9c, 0xd9, 0x46, 0x36, 0x7d, 0x7f, 0x3f, 0x79, 0xee, 0x74, 0xb4, 0xb4, 0xf5, 0xfc, 0xfa, 0x5d,
|
||||
0xdb, 0xba, 0x79, 0xd7, 0xb6, 0xae, 0x6f, 0xdb, 0xf6, 0xcd, 0x6d, 0xdb, 0xfe, 0xe7, 0xb6, 0x6d,
|
||||
0xff, 0x7a, 0xd7, 0xb6, 0x7e, 0xbb, 0x6b, 0x5b, 0x37, 0x77, 0x6d, 0xeb, 0xef, 0xbb, 0xb6, 0xf5,
|
||||
0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x67, 0x2b, 0x47, 0x0c, 0x83, 0x09, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *Entry) Marshal() (dAtA []byte, err error) {
|
||||
@@ -901,6 +939,23 @@ func (m *Message) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Responses) > 0 {
|
||||
for iNdEx := len(m.Responses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.Responses[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintRaft(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x72
|
||||
}
|
||||
}
|
||||
i = encodeVarintRaft(dAtA, i, uint64(m.Vote))
|
||||
i--
|
||||
dAtA[i] = 0x68
|
||||
if m.Context != nil {
|
||||
i -= len(m.Context)
|
||||
copy(dAtA[i:], m.Context)
|
||||
@@ -919,16 +974,18 @@ func (m *Message) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x50
|
||||
{
|
||||
size, err := m.Snapshot.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
if m.Snapshot != nil {
|
||||
{
|
||||
size, err := m.Snapshot.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintRaft(dAtA, i, uint64(size))
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintRaft(dAtA, i, uint64(size))
|
||||
i--
|
||||
dAtA[i] = 0x4a
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x4a
|
||||
i = encodeVarintRaft(dAtA, i, uint64(m.Commit))
|
||||
i--
|
||||
dAtA[i] = 0x40
|
||||
@@ -1247,14 +1304,23 @@ func (m *Message) Size() (n int) {
|
||||
}
|
||||
}
|
||||
n += 1 + sovRaft(uint64(m.Commit))
|
||||
l = m.Snapshot.Size()
|
||||
n += 1 + l + sovRaft(uint64(l))
|
||||
if m.Snapshot != nil {
|
||||
l = m.Snapshot.Size()
|
||||
n += 1 + l + sovRaft(uint64(l))
|
||||
}
|
||||
n += 2
|
||||
n += 1 + sovRaft(uint64(m.RejectHint))
|
||||
if m.Context != nil {
|
||||
l = len(m.Context)
|
||||
n += 1 + l + sovRaft(uint64(l))
|
||||
}
|
||||
n += 1 + sovRaft(uint64(m.Vote))
|
||||
if len(m.Responses) > 0 {
|
||||
for _, e := range m.Responses {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovRaft(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -1957,6 +2023,9 @@ func (m *Message) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.Snapshot == nil {
|
||||
m.Snapshot = &Snapshot{}
|
||||
}
|
||||
if err := m.Snapshot.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2034,6 +2103,59 @@ func (m *Message) Unmarshal(dAtA []byte) error {
|
||||
m.Context = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 13:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Vote", wireType)
|
||||
}
|
||||
m.Vote = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaft
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Vote |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 14:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Responses", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaft
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthRaft
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthRaft
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Responses = append(m.Responses, Message{})
|
||||
if err := m.Responses[len(m.Responses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipRaft(dAtA[iNdEx:])
|
||||
Generated
Vendored
+53
-26
@@ -37,27 +37,35 @@ message Snapshot {
|
||||
}
|
||||
|
||||
// For description of different message types, see:
|
||||
// https://pkg.go.dev/go.etcd.io/etcd/raft/v3#hdr-MessageType
|
||||
// https://pkg.go.dev/go.etcd.io/raft/v3#hdr-MessageType
|
||||
enum MessageType {
|
||||
MsgHup = 0;
|
||||
MsgBeat = 1;
|
||||
MsgProp = 2;
|
||||
MsgApp = 3;
|
||||
MsgAppResp = 4;
|
||||
MsgVote = 5;
|
||||
MsgVoteResp = 6;
|
||||
MsgSnap = 7;
|
||||
MsgHeartbeat = 8;
|
||||
MsgHeartbeatResp = 9;
|
||||
MsgUnreachable = 10;
|
||||
MsgSnapStatus = 11;
|
||||
MsgCheckQuorum = 12;
|
||||
MsgTransferLeader = 13;
|
||||
MsgTimeoutNow = 14;
|
||||
MsgReadIndex = 15;
|
||||
MsgReadIndexResp = 16;
|
||||
MsgPreVote = 17;
|
||||
MsgPreVoteResp = 18;
|
||||
MsgHup = 0;
|
||||
MsgBeat = 1;
|
||||
MsgProp = 2;
|
||||
MsgApp = 3;
|
||||
MsgAppResp = 4;
|
||||
MsgVote = 5;
|
||||
MsgVoteResp = 6;
|
||||
MsgSnap = 7;
|
||||
MsgHeartbeat = 8;
|
||||
MsgHeartbeatResp = 9;
|
||||
MsgUnreachable = 10;
|
||||
MsgSnapStatus = 11;
|
||||
MsgCheckQuorum = 12;
|
||||
MsgTransferLeader = 13;
|
||||
MsgTimeoutNow = 14;
|
||||
MsgReadIndex = 15;
|
||||
MsgReadIndexResp = 16;
|
||||
MsgPreVote = 17;
|
||||
MsgPreVoteResp = 18;
|
||||
MsgStorageAppend = 19;
|
||||
MsgStorageAppendResp = 20;
|
||||
MsgStorageApply = 21;
|
||||
MsgStorageApplyResp = 22;
|
||||
MsgForgetLeader = 23;
|
||||
// NOTE: when adding new message types, remember to update the isLocalMsg and
|
||||
// isResponseMsg arrays in raft/util.go and update the corresponding tests in
|
||||
// raft/util_test.go.
|
||||
}
|
||||
|
||||
message Message {
|
||||
@@ -66,18 +74,37 @@ message Message {
|
||||
optional uint64 from = 3 [(gogoproto.nullable) = false];
|
||||
optional uint64 term = 4 [(gogoproto.nullable) = false];
|
||||
// logTerm is generally used for appending Raft logs to followers. For example,
|
||||
// (type=MsgApp,index=100,logTerm=5) means leader appends entries starting at
|
||||
// index=101, and the term of entry at index 100 is 5.
|
||||
// (type=MsgApp,index=100,logTerm=5) means the leader appends entries starting
|
||||
// at index=101, and the term of the entry at index 100 is 5.
|
||||
// (type=MsgAppResp,reject=true,index=100,logTerm=5) means follower rejects some
|
||||
// entries from its leader as it already has an entry with term 5 at index 100.
|
||||
// (type=MsgStorageAppendResp,index=100,logTerm=5) means the local node wrote
|
||||
// entries up to index=100 in stable storage, and the term of the entry at index
|
||||
// 100 was 5. This doesn't always mean that the corresponding MsgStorageAppend
|
||||
// message was the one that carried these entries, just that those entries were
|
||||
// stable at the time of processing the corresponding MsgStorageAppend.
|
||||
optional uint64 logTerm = 5 [(gogoproto.nullable) = false];
|
||||
optional uint64 index = 6 [(gogoproto.nullable) = false];
|
||||
repeated Entry entries = 7 [(gogoproto.nullable) = false];
|
||||
optional uint64 commit = 8 [(gogoproto.nullable) = false];
|
||||
optional Snapshot snapshot = 9 [(gogoproto.nullable) = false];
|
||||
// (type=MsgStorageAppend,vote=5,term=10) means the local node is voting for
|
||||
// peer 5 in term 10. For MsgStorageAppends, the term, vote, and commit fields
|
||||
// will either all be set (to facilitate the construction of a HardState) if
|
||||
// any of the fields have changed or will all be unset if none of the fields
|
||||
// have changed.
|
||||
optional uint64 vote = 13 [(gogoproto.nullable) = false];
|
||||
// snapshot is non-nil and non-empty for MsgSnap messages and nil for all other
|
||||
// message types. However, peer nodes running older binary versions may send a
|
||||
// non-nil, empty value for the snapshot field of non-MsgSnap messages. Code
|
||||
// should be prepared to handle such messages.
|
||||
optional Snapshot snapshot = 9 [(gogoproto.nullable) = true];
|
||||
optional bool reject = 10 [(gogoproto.nullable) = false];
|
||||
optional uint64 rejectHint = 11 [(gogoproto.nullable) = false];
|
||||
optional bytes context = 12;
|
||||
optional bytes context = 12 [(gogoproto.nullable) = true];
|
||||
// responses are populated by a raft node to instruct storage threads on how
|
||||
// to respond and who to respond to when the work associated with a message
|
||||
// is complete. Populated for MsgStorageAppend and MsgStorageApply messages.
|
||||
repeated Message responses = 14 [(gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message HardState {
|
||||
@@ -132,13 +159,13 @@ enum ConfChangeType {
|
||||
|
||||
message ConfChange {
|
||||
optional ConfChangeType type = 2 [(gogoproto.nullable) = false];
|
||||
optional uint64 node_id = 3 [(gogoproto.nullable) = false, (gogoproto.customname) = "NodeID" ];
|
||||
optional uint64 node_id = 3 [(gogoproto.nullable) = false, (gogoproto.customname) = "NodeID"];
|
||||
optional bytes context = 4;
|
||||
|
||||
// NB: this is used only by etcd to thread through a unique identifier.
|
||||
// Ideally it should really use the Context instead. No counterpart to
|
||||
// this field exists in ConfChangeV2.
|
||||
optional uint64 id = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "ID" ];
|
||||
optional uint64 id = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "ID"];
|
||||
}
|
||||
|
||||
// ConfChangeSingle is an individual configuration change operation. Multiple
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
This package (golang.org/x/net/http2) is the original source of truth
|
||||
of the Go HTTP/2 implementation.
|
||||
|
||||
As of Go 1.27, the source of truth has moved to the standard library
|
||||
package net/http/internal/http2.
|
||||
All new feature development should happen in that package.
|
||||
Only critical bug fixes and security fixes will be backported to x/net.
|
||||
|
||||
The x/net package contains two implementations of the HTTP/2 transport and server:
|
||||
|
||||
The original implementation (no longer the source of truth).
|
||||
|
||||
A reimplementation of the x/net/http2 APIs in terms of net/http.
|
||||
This is called "the wrapping implementation", since it wraps net/http.
|
||||
|
||||
The original implementation is used when the Go version is less than 1.27.
|
||||
|
||||
The wrapping implementation is used when the Go version is at least 1.27.
|
||||
The build tag "http2legacy" may be set to use the original implementation.
|
||||
+2
-12
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
// Transport code's client connection pooling.
|
||||
|
||||
package http2
|
||||
@@ -14,18 +16,6 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||
type ClientConnPool interface {
|
||||
// GetClientConn returns a specific HTTP/2 connection (usually
|
||||
// a TLS-TCP connection) to an HTTP/2 server. On success, the
|
||||
// returned ClientConn accounts for the upcoming RoundTrip
|
||||
// call, so the caller should not omit it. If the caller needs
|
||||
// to, ClientConn.RoundTrip can be called with a bogus
|
||||
// new(http.Request) to release the stream reservation.
|
||||
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
||||
MarkDead(*ClientConn)
|
||||
}
|
||||
|
||||
// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
|
||||
// implementations which can close their idle connections.
|
||||
type clientConnPoolIdleCloser interface {
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.roundTrip(req)
|
||||
}
|
||||
|
||||
// SetDoNotReuse marks cc as not reusable for future HTTP requests.
|
||||
func (cc *ClientConn) SetDoNotReuse() {
|
||||
cc.setDoNotReuse()
|
||||
}
|
||||
|
||||
// CanTakeNewRequest reports whether the connection can take a new request,
|
||||
// meaning it has not been closed or received or sent a GOAWAY.
|
||||
//
|
||||
// If the caller is going to immediately make a new request on this
|
||||
// connection, use ReserveNewRequest instead.
|
||||
func (cc *ClientConn) CanTakeNewRequest() bool {
|
||||
return cc.canTakeNewRequest()
|
||||
}
|
||||
|
||||
// ReserveNewRequest is like CanTakeNewRequest but also reserves a
|
||||
// concurrent stream in cc. The reservation is decremented on the
|
||||
// next call to RoundTrip.
|
||||
func (cc *ClientConn) ReserveNewRequest() bool {
|
||||
return cc.reserveNewRequest()
|
||||
}
|
||||
|
||||
// State returns a snapshot of cc's state.
|
||||
func (cc *ClientConn) State() ClientConnState {
|
||||
return cc.state()
|
||||
}
|
||||
|
||||
// Shutdown gracefully closes the client connection, waiting for running streams to complete.
|
||||
func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
||||
return cc.shutdown(ctx)
|
||||
}
|
||||
|
||||
// Close closes the client connection immediately.
|
||||
//
|
||||
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
||||
func (cc *ClientConn) Close() error {
|
||||
return cc.close()
|
||||
}
|
||||
|
||||
// Ping sends a PING frame to the server and waits for the ack.
|
||||
func (cc *ClientConn) Ping(ctx context.Context) error {
|
||||
return cc.ping(ctx)
|
||||
}
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
|
||||
+3
-10
@@ -6,7 +6,6 @@ package hpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// headerFieldTable implements a list of HeaderFields.
|
||||
@@ -55,16 +54,10 @@ func (t *headerFieldTable) len() int {
|
||||
|
||||
// addEntry adds a new entry.
|
||||
func (t *headerFieldTable) addEntry(f HeaderField) {
|
||||
// Prevent f from escaping to the heap.
|
||||
f2 := HeaderField{
|
||||
Name: strings.Clone(f.Name),
|
||||
Value: strings.Clone(f.Value),
|
||||
Sensitive: f.Sensitive,
|
||||
}
|
||||
id := uint64(t.len()) + t.evictCount + 1
|
||||
t.byName[f2.Name] = id
|
||||
t.byNameValue[pairNameValue{f2.Name, f2.Value}] = id
|
||||
t.ents = append(t.ents, f2)
|
||||
t.byName[f.Name] = id
|
||||
t.byNameValue[pairNameValue{f.Name, f.Value}] = id
|
||||
t.ents = append(t.ents, f)
|
||||
}
|
||||
|
||||
// evictOldest evicts the n oldest entries in the table.
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ func (s SettingID) String() string {
|
||||
}
|
||||
|
||||
// validWireHeaderFieldName reports whether v is a valid header field
|
||||
// name (key). See httpguts.ValidHeaderName for the base rules.
|
||||
// name (key). See httpguts.ValidHeaderFieldName for the base rules.
|
||||
//
|
||||
// Further, http2 says:
|
||||
//
|
||||
|
||||
+9
-205
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
// TODO: turn off the serve goroutine when idle, so
|
||||
// an idle conn only has the readFrames goroutine active. (which could
|
||||
// also be optimized probably to pin less memory in crypto/tls). This
|
||||
@@ -88,98 +90,6 @@ var (
|
||||
testHookOnPanic func(sc *serverConn, panicVal interface{}) (rePanic bool)
|
||||
)
|
||||
|
||||
// Server is an HTTP/2 server.
|
||||
type Server struct {
|
||||
// MaxHandlers limits the number of http.Handler ServeHTTP goroutines
|
||||
// which may run at a time over all connections.
|
||||
// Negative or zero no limit.
|
||||
// TODO: implement
|
||||
MaxHandlers int
|
||||
|
||||
// MaxConcurrentStreams optionally specifies the number of
|
||||
// concurrent streams that each client may have open at a
|
||||
// time. This is unrelated to the number of http.Handler goroutines
|
||||
// which may be active globally, which is MaxHandlers.
|
||||
// If zero, MaxConcurrentStreams defaults to at least 100, per
|
||||
// the HTTP/2 spec's recommendations.
|
||||
MaxConcurrentStreams uint32
|
||||
|
||||
// MaxDecoderHeaderTableSize optionally specifies the http2
|
||||
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
||||
// informs the remote endpoint of the maximum size of the header compression
|
||||
// table used to decode header blocks, in octets. If zero, the default value
|
||||
// of 4096 is used.
|
||||
MaxDecoderHeaderTableSize uint32
|
||||
|
||||
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
||||
// header compression table used for encoding request headers. Received
|
||||
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
||||
// the default value of 4096 is used.
|
||||
MaxEncoderHeaderTableSize uint32
|
||||
|
||||
// MaxReadFrameSize optionally specifies the largest frame
|
||||
// this server is willing to read. A valid value is between
|
||||
// 16k and 16M, inclusive. If zero or otherwise invalid, a
|
||||
// default value is used.
|
||||
MaxReadFrameSize uint32
|
||||
|
||||
// PermitProhibitedCipherSuites, if true, permits the use of
|
||||
// cipher suites prohibited by the HTTP/2 spec.
|
||||
PermitProhibitedCipherSuites bool
|
||||
|
||||
// IdleTimeout specifies how long until idle clients should be
|
||||
// closed with a GOAWAY frame. PING frames are not considered
|
||||
// activity for the purposes of IdleTimeout.
|
||||
// If zero or negative, there is no timeout.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// ReadIdleTimeout is the timeout after which a health check using a ping
|
||||
// frame will be carried out if no frame is received on the connection.
|
||||
// If zero, no health check is performed.
|
||||
ReadIdleTimeout time.Duration
|
||||
|
||||
// PingTimeout is the timeout after which the connection will be closed
|
||||
// if a response to a ping is not received.
|
||||
// If zero, a default of 15 seconds is used.
|
||||
PingTimeout time.Duration
|
||||
|
||||
// WriteByteTimeout is the timeout after which a connection will be
|
||||
// closed if no data can be written to it. The timeout begins when data is
|
||||
// available to write, and is extended whenever any bytes are written.
|
||||
// If zero or negative, there is no timeout.
|
||||
WriteByteTimeout time.Duration
|
||||
|
||||
// MaxUploadBufferPerConnection is the size of the initial flow
|
||||
// control window for each connections. The HTTP/2 spec does not
|
||||
// allow this to be smaller than 65535 or larger than 2^32-1.
|
||||
// If the value is outside this range, a default value will be
|
||||
// used instead.
|
||||
MaxUploadBufferPerConnection int32
|
||||
|
||||
// MaxUploadBufferPerStream is the size of the initial flow control
|
||||
// window for each stream. The HTTP/2 spec does not allow this to
|
||||
// be larger than 2^32-1. If the value is zero or larger than the
|
||||
// maximum, a default value will be used instead.
|
||||
MaxUploadBufferPerStream int32
|
||||
|
||||
// NewWriteScheduler constructs a write scheduler for a connection.
|
||||
// If nil, a default scheduler is chosen.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
NewWriteScheduler func() WriteScheduler
|
||||
|
||||
// CountError, if non-nil, is called on HTTP/2 server errors.
|
||||
// It's intended to increment a metric for monitoring, such
|
||||
// as an expvar or Prometheus metric.
|
||||
// The errType consists of only ASCII word characters.
|
||||
CountError func(errType string)
|
||||
|
||||
// Internal state. This is a pointer (rather than embedded directly)
|
||||
// so that we don't embed a Mutex in this struct, which will make the
|
||||
// struct non-copyable, which might break some callers.
|
||||
state *serverInternalState
|
||||
}
|
||||
|
||||
type serverInternalState struct {
|
||||
mu sync.Mutex
|
||||
activeConns map[*serverConn]struct{}
|
||||
@@ -187,6 +97,9 @@ type serverInternalState struct {
|
||||
// Pool of error channels. This is per-Server rather than global
|
||||
// because channels can't be reused across synctest bubbles.
|
||||
errChanPool sync.Pool
|
||||
|
||||
// Used in tests.
|
||||
testNewConn func(*serverConn)
|
||||
}
|
||||
|
||||
func (s *serverInternalState) registerConn(sc *serverConn) {
|
||||
@@ -239,12 +152,7 @@ func (s *serverInternalState) putErrChan(ch chan error) {
|
||||
s.errChanPool.Put(ch)
|
||||
}
|
||||
|
||||
// ConfigureServer adds HTTP/2 support to a net/http Server.
|
||||
//
|
||||
// The configuration conf may be nil.
|
||||
//
|
||||
// ConfigureServer must be called before s begins serving.
|
||||
func ConfigureServer(s *http.Server, conf *Server) error {
|
||||
func configureServer(s *http.Server, conf *Server) error {
|
||||
if s == nil {
|
||||
panic("nil *http.Server")
|
||||
}
|
||||
@@ -349,83 +257,6 @@ func ConfigureServer(s *http.Server, conf *Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeConnOpts are options for the Server.ServeConn method.
|
||||
type ServeConnOpts struct {
|
||||
// Context is the base context to use.
|
||||
// If nil, context.Background is used.
|
||||
Context context.Context
|
||||
|
||||
// BaseConfig optionally sets the base configuration
|
||||
// for values. If nil, defaults are used.
|
||||
BaseConfig *http.Server
|
||||
|
||||
// Handler specifies which handler to use for processing
|
||||
// requests. If nil, BaseConfig.Handler is used. If BaseConfig
|
||||
// or BaseConfig.Handler is nil, http.DefaultServeMux is used.
|
||||
Handler http.Handler
|
||||
|
||||
// UpgradeRequest is an initial request received on a connection
|
||||
// undergoing an h2c upgrade. The request body must have been
|
||||
// completely read from the connection before calling ServeConn,
|
||||
// and the 101 Switching Protocols response written.
|
||||
UpgradeRequest *http.Request
|
||||
|
||||
// Settings is the decoded contents of the HTTP2-Settings header
|
||||
// in an h2c upgrade request.
|
||||
Settings []byte
|
||||
|
||||
// SawClientPreface is set if the HTTP/2 connection preface
|
||||
// has already been read from the connection.
|
||||
SawClientPreface bool
|
||||
}
|
||||
|
||||
func (o *ServeConnOpts) context() context.Context {
|
||||
if o != nil && o.Context != nil {
|
||||
return o.Context
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (o *ServeConnOpts) baseConfig() *http.Server {
|
||||
if o != nil && o.BaseConfig != nil {
|
||||
return o.BaseConfig
|
||||
}
|
||||
return new(http.Server)
|
||||
}
|
||||
|
||||
func (o *ServeConnOpts) handler() http.Handler {
|
||||
if o != nil {
|
||||
if o.Handler != nil {
|
||||
return o.Handler
|
||||
}
|
||||
if o.BaseConfig != nil && o.BaseConfig.Handler != nil {
|
||||
return o.BaseConfig.Handler
|
||||
}
|
||||
}
|
||||
return http.DefaultServeMux
|
||||
}
|
||||
|
||||
// ServeConn serves HTTP/2 requests on the provided connection and
|
||||
// blocks until the connection is no longer readable.
|
||||
//
|
||||
// ServeConn starts speaking HTTP/2 assuming that c has not had any
|
||||
// reads or writes. It writes its initial settings frame and expects
|
||||
// to be able to read the preface and settings frame from the
|
||||
// client. If c has a ConnectionState method like a *tls.Conn, the
|
||||
// ConnectionState is used to verify the TLS ciphersuite and to set
|
||||
// the Request.TLS field in Handlers.
|
||||
//
|
||||
// ServeConn does not support h2c by itself. Any h2c support must be
|
||||
// implemented in terms of providing a suitably-behaving net.Conn.
|
||||
//
|
||||
// The opts parameter is optional. If nil, default values are used.
|
||||
func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
||||
if opts == nil {
|
||||
opts = &ServeConnOpts{}
|
||||
}
|
||||
s.serveConn(c, opts, nil)
|
||||
}
|
||||
|
||||
func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) {
|
||||
baseCtx, cancel := serverConnBaseContext(c, opts)
|
||||
defer cancel()
|
||||
@@ -461,6 +292,9 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
|
||||
if newf != nil {
|
||||
newf(sc)
|
||||
}
|
||||
if s.state != nil && s.state.testNewConn != nil {
|
||||
s.state.testNewConn(sc)
|
||||
}
|
||||
|
||||
s.state.registerConn(sc)
|
||||
defer s.state.unregisterConn(sc)
|
||||
@@ -570,15 +404,6 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
|
||||
sc.serve(conf)
|
||||
}
|
||||
|
||||
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) {
|
||||
ctx, cancel = context.WithCancel(opts.context())
|
||||
ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr())
|
||||
if hs := opts.baseConfig(); hs != nil {
|
||||
ctx = context.WithValue(ctx, http.ServerContextKey, hs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *serverConn) rejectConn(err ErrCode, debug string) {
|
||||
sc.vlogf("http2: server rejecting conn: %v, %s", err, debug)
|
||||
// ignoring errors. hanging up anyway.
|
||||
@@ -2832,21 +2657,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// TrailerPrefix is a magic prefix for ResponseWriter.Header map keys
|
||||
// that, if present, signals that the map entry is actually for
|
||||
// the response trailers, and not the response headers. The prefix
|
||||
// is stripped after the ServeHTTP call finishes and the values are
|
||||
// sent in the trailers.
|
||||
//
|
||||
// This mechanism is intended only for trailers that are not known
|
||||
// prior to the headers being written. If the set of trailers is fixed
|
||||
// or known before the header is written, the normal Go trailers mechanism
|
||||
// is preferred:
|
||||
//
|
||||
// https://golang.org/pkg/net/http/#ResponseWriter
|
||||
// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
||||
const TrailerPrefix = "Trailer:"
|
||||
|
||||
// promoteUndeclaredTrailers permits http.Handlers to set trailers
|
||||
// after the header has already been flushed. Because the Go
|
||||
// ResponseWriter interface has no way to set Trailers (only the
|
||||
@@ -3123,12 +2933,6 @@ func (w *responseWriter) handlerDone() {
|
||||
responseWriterStatePool.Put(rws)
|
||||
}
|
||||
|
||||
// Push errors.
|
||||
var (
|
||||
ErrRecursivePush = errors.New("http2: recursive push not allowed")
|
||||
ErrPushLimitReached = errors.New("http2: push would exceed peer's SETTINGS_MAX_CONCURRENT_STREAMS")
|
||||
)
|
||||
|
||||
var _ http.Pusher = (*responseWriter)(nil)
|
||||
|
||||
func (w *responseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TrailerPrefix is a magic prefix for ResponseWriter.Header map keys
|
||||
// that, if present, signals that the map entry is actually for
|
||||
// the response trailers, and not the response headers. The prefix
|
||||
// is stripped after the ServeHTTP call finishes and the values are
|
||||
// sent in the trailers.
|
||||
//
|
||||
// This mechanism is intended only for trailers that are not known
|
||||
// prior to the headers being written. If the set of trailers is fixed
|
||||
// or known before the header is written, the normal Go trailers mechanism
|
||||
// is preferred:
|
||||
//
|
||||
// https://golang.org/pkg/net/http/#ResponseWriter
|
||||
// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
||||
const TrailerPrefix = "Trailer:"
|
||||
|
||||
// Push errors.
|
||||
var (
|
||||
ErrRecursivePush = errors.New("http2: recursive push not allowed")
|
||||
ErrPushLimitReached = errors.New("http2: push would exceed peer's SETTINGS_MAX_CONCURRENT_STREAMS")
|
||||
)
|
||||
|
||||
// ConfigureServer adds HTTP/2 support to a net/http Server.
|
||||
//
|
||||
// The configuration conf may be nil.
|
||||
//
|
||||
// ConfigureServer must be called before s begins serving.
|
||||
func ConfigureServer(s *http.Server, conf *Server) error {
|
||||
return configureServer(s, conf)
|
||||
}
|
||||
|
||||
// Server is an HTTP/2 server.
|
||||
type Server struct {
|
||||
// MaxHandlers limits the number of http.Handler ServeHTTP goroutines
|
||||
// which may run at a time over all connections.
|
||||
// Negative or zero no limit.
|
||||
// TODO: implement
|
||||
MaxHandlers int
|
||||
|
||||
// MaxConcurrentStreams optionally specifies the number of
|
||||
// concurrent streams that each client may have open at a
|
||||
// time. This is unrelated to the number of http.Handler goroutines
|
||||
// which may be active globally, which is MaxHandlers.
|
||||
// If zero, MaxConcurrentStreams defaults to at least 100, per
|
||||
// the HTTP/2 spec's recommendations.
|
||||
MaxConcurrentStreams uint32
|
||||
|
||||
// MaxDecoderHeaderTableSize optionally specifies the http2
|
||||
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
||||
// informs the remote endpoint of the maximum size of the header compression
|
||||
// table used to decode header blocks, in octets. If zero, the default value
|
||||
// of 4096 is used.
|
||||
MaxDecoderHeaderTableSize uint32
|
||||
|
||||
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
||||
// header compression table used for encoding request headers. Received
|
||||
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
||||
// the default value of 4096 is used.
|
||||
MaxEncoderHeaderTableSize uint32
|
||||
|
||||
// MaxReadFrameSize optionally specifies the largest frame
|
||||
// this server is willing to read. A valid value is between
|
||||
// 16k and 16M, inclusive. If zero or otherwise invalid, a
|
||||
// default value is used.
|
||||
MaxReadFrameSize uint32
|
||||
|
||||
// PermitProhibitedCipherSuites, if true, permits the use of
|
||||
// cipher suites prohibited by the HTTP/2 spec.
|
||||
PermitProhibitedCipherSuites bool
|
||||
|
||||
// IdleTimeout specifies how long until idle clients should be
|
||||
// closed with a GOAWAY frame. PING frames are not considered
|
||||
// activity for the purposes of IdleTimeout.
|
||||
// If zero or negative, there is no timeout.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// ReadIdleTimeout is the timeout after which a health check using a ping
|
||||
// frame will be carried out if no frame is received on the connection.
|
||||
// If zero, no health check is performed.
|
||||
ReadIdleTimeout time.Duration
|
||||
|
||||
// PingTimeout is the timeout after which the connection will be closed
|
||||
// if a response to a ping is not received.
|
||||
// If zero, a default of 15 seconds is used.
|
||||
PingTimeout time.Duration
|
||||
|
||||
// WriteByteTimeout is the timeout after which a connection will be
|
||||
// closed if no data can be written to it. The timeout begins when data is
|
||||
// available to write, and is extended whenever any bytes are written.
|
||||
// If zero or negative, there is no timeout.
|
||||
WriteByteTimeout time.Duration
|
||||
|
||||
// MaxUploadBufferPerConnection is the size of the initial flow
|
||||
// control window for each connections. The HTTP/2 spec does not
|
||||
// allow this to be smaller than 65535 or larger than 2^32-1.
|
||||
// If the value is outside this range, a default value will be
|
||||
// used instead.
|
||||
MaxUploadBufferPerConnection int32
|
||||
|
||||
// MaxUploadBufferPerStream is the size of the initial flow control
|
||||
// window for each stream. The HTTP/2 spec does not allow this to
|
||||
// be larger than 2^32-1. If the value is zero or larger than the
|
||||
// maximum, a default value will be used instead.
|
||||
MaxUploadBufferPerStream int32
|
||||
|
||||
// NewWriteScheduler constructs a write scheduler for a connection.
|
||||
// If nil, a default scheduler is chosen.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
NewWriteScheduler func() WriteScheduler
|
||||
|
||||
// CountError, if non-nil, is called on HTTP/2 server errors.
|
||||
// It's intended to increment a metric for monitoring, such
|
||||
// as an expvar or Prometheus metric.
|
||||
// The errType consists of only ASCII word characters.
|
||||
CountError func(errType string)
|
||||
|
||||
// Internal state. This is a pointer (rather than embedded directly)
|
||||
// so that we don't embed a Mutex in this struct, which will make the
|
||||
// struct non-copyable, which might break some callers.
|
||||
state *serverInternalState
|
||||
}
|
||||
|
||||
// ServeConnOpts are options for the Server.ServeConn method.
|
||||
type ServeConnOpts struct {
|
||||
// Context is the base context to use.
|
||||
// If nil, context.Background is used.
|
||||
Context context.Context
|
||||
|
||||
// BaseConfig optionally sets the base configuration
|
||||
// for values. If nil, defaults are used.
|
||||
BaseConfig *http.Server
|
||||
|
||||
// Handler specifies which handler to use for processing
|
||||
// requests. If nil, BaseConfig.Handler is used. If BaseConfig
|
||||
// or BaseConfig.Handler is nil, http.DefaultServeMux is used.
|
||||
Handler http.Handler
|
||||
|
||||
// UpgradeRequest is an initial request received on a connection
|
||||
// undergoing an h2c upgrade. The request body must have been
|
||||
// completely read from the connection before calling ServeConn,
|
||||
// and the 101 Switching Protocols response written.
|
||||
UpgradeRequest *http.Request
|
||||
|
||||
// Settings is the decoded contents of the HTTP2-Settings header
|
||||
// in an h2c upgrade request.
|
||||
Settings []byte
|
||||
|
||||
// SawClientPreface is set if the HTTP/2 connection preface
|
||||
// has already been read from the connection.
|
||||
SawClientPreface bool
|
||||
}
|
||||
|
||||
// ServeConn serves HTTP/2 requests on the provided connection and
|
||||
// blocks until the connection is no longer readable.
|
||||
//
|
||||
// ServeConn starts speaking HTTP/2 assuming that c has not had any
|
||||
// reads or writes. It writes its initial settings frame and expects
|
||||
// to be able to read the preface and settings frame from the
|
||||
// client. If c has a ConnectionState method like a *tls.Conn, the
|
||||
// ConnectionState is used to verify the TLS ciphersuite and to set
|
||||
// the Request.TLS field in Handlers.
|
||||
//
|
||||
// ServeConn does not support h2c by itself. Any h2c support must be
|
||||
// implemented in terms of providing a suitably-behaving net.Conn.
|
||||
//
|
||||
// The opts parameter is optional. If nil, default values are used.
|
||||
func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
||||
if opts == nil {
|
||||
opts = &ServeConnOpts{}
|
||||
}
|
||||
s.serveConn(c, opts, nil)
|
||||
}
|
||||
|
||||
func (o *ServeConnOpts) context() context.Context {
|
||||
if o != nil && o.Context != nil {
|
||||
return o.Context
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (o *ServeConnOpts) baseConfig() *http.Server {
|
||||
if o != nil && o.BaseConfig != nil {
|
||||
return o.BaseConfig
|
||||
}
|
||||
return new(http.Server)
|
||||
}
|
||||
|
||||
func (o *ServeConnOpts) handler() http.Handler {
|
||||
if o != nil {
|
||||
if o.Handler != nil {
|
||||
return o.Handler
|
||||
}
|
||||
if o.BaseConfig != nil && o.BaseConfig.Handler != nil {
|
||||
return o.BaseConfig.Handler
|
||||
}
|
||||
}
|
||||
return http.DefaultServeMux
|
||||
}
|
||||
|
||||
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) {
|
||||
ctx, cancel = context.WithCancel(opts.context())
|
||||
ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr())
|
||||
if hs := opts.baseConfig(); hs != nil {
|
||||
ctx = context.WithValue(ctx, http.ServerContextKey, hs)
|
||||
}
|
||||
return
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.27 && !http2legacy
|
||||
|
||||
// Server wrapping a net/http.Server.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type serverInternalState struct {
|
||||
s1 *http.Server
|
||||
initOnce sync.Once
|
||||
serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)
|
||||
}
|
||||
|
||||
func configureServer(s *http.Server, conf *Server) error {
|
||||
if s == nil {
|
||||
panic("nil *http.Server")
|
||||
}
|
||||
if conf == nil {
|
||||
conf = new(Server)
|
||||
}
|
||||
if conf.state != nil {
|
||||
// This isn't a panic in the pre-wrapping implementation,
|
||||
// but calling ConfigureServer twice with the same http2.Server
|
||||
// overwrites internal state on the server.
|
||||
// Make the error explicit and early here.
|
||||
panic("ConfigureServer may be called only once per Server")
|
||||
}
|
||||
if h1, h2 := s, conf; h2.IdleTimeout == 0 {
|
||||
if h1.IdleTimeout != 0 {
|
||||
h2.IdleTimeout = h1.IdleTimeout
|
||||
} else {
|
||||
h2.IdleTimeout = h1.ReadTimeout
|
||||
}
|
||||
}
|
||||
conf.state = &serverInternalState{
|
||||
s1: s,
|
||||
}
|
||||
sconfig := &serverConfig{s: conf}
|
||||
if err := s.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil {
|
||||
panic("http2: net/http does not support this version of x/net/http2")
|
||||
}
|
||||
conf.state.serveConnFunc = sconfig.serveConnFunc
|
||||
return nil
|
||||
}
|
||||
|
||||
type serverConfig struct {
|
||||
s *Server
|
||||
serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)
|
||||
}
|
||||
|
||||
func (*serverConfig) Accept() (net.Conn, error) {
|
||||
return nil, errors.New("unexpected call to Accept")
|
||||
}
|
||||
func (*serverConfig) Close() error {
|
||||
return nil
|
||||
}
|
||||
func (*serverConfig) Addr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serverConfig) ServeConnFunc(f func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)) {
|
||||
s.serveConnFunc = f
|
||||
}
|
||||
|
||||
func (s *serverConfig) HTTP2Config() http.HTTP2Config {
|
||||
return http.HTTP2Config{
|
||||
MaxConcurrentStreams: int(s.s.MaxConcurrentStreams),
|
||||
MaxDecoderHeaderTableSize: int(s.s.MaxDecoderHeaderTableSize),
|
||||
MaxEncoderHeaderTableSize: int(s.s.MaxEncoderHeaderTableSize),
|
||||
MaxReadFrameSize: int(s.s.MaxReadFrameSize),
|
||||
PermitProhibitedCipherSuites: s.s.PermitProhibitedCipherSuites,
|
||||
MaxReceiveBufferPerConnection: int(s.s.MaxUploadBufferPerConnection),
|
||||
MaxReceiveBufferPerStream: int(s.s.MaxUploadBufferPerStream),
|
||||
SendPingTimeout: s.s.ReadIdleTimeout,
|
||||
PingTimeout: s.s.PingTimeout,
|
||||
WriteByteTimeout: s.s.WriteByteTimeout,
|
||||
CountError: s.s.CountError,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serverConfig) IdleTimeout() time.Duration {
|
||||
return s.s.IdleTimeout
|
||||
}
|
||||
|
||||
type serverConn struct{}
|
||||
|
||||
func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, _ func(*serverConn)) {
|
||||
var serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)
|
||||
switch {
|
||||
case opts.BaseConfig != nil:
|
||||
// The user has provided us with an http.Server to take configuration from.
|
||||
//
|
||||
// We can't send our request to opts.BaseConfig, because an http.Server can
|
||||
// only be associated with a single http2.Server and the user might
|
||||
// use this one with several http.Servers.
|
||||
//
|
||||
// We can't send our request to s.state.s1, because it doesn't contain
|
||||
// the right configuration.
|
||||
//
|
||||
// So create a one-off copy of opts.BaseConfig and use it.
|
||||
h1 := &http.Server{
|
||||
TLSConfig: opts.BaseConfig.TLSConfig,
|
||||
ReadTimeout: opts.BaseConfig.ReadTimeout,
|
||||
ReadHeaderTimeout: opts.BaseConfig.ReadHeaderTimeout,
|
||||
WriteTimeout: opts.BaseConfig.WriteTimeout,
|
||||
IdleTimeout: opts.BaseConfig.IdleTimeout,
|
||||
MaxHeaderBytes: opts.BaseConfig.MaxHeaderBytes,
|
||||
ConnState: opts.BaseConfig.ConnState,
|
||||
ErrorLog: opts.BaseConfig.ErrorLog,
|
||||
BaseContext: opts.BaseConfig.BaseContext,
|
||||
ConnContext: opts.BaseConfig.ConnContext,
|
||||
HTTP2: opts.BaseConfig.HTTP2,
|
||||
}
|
||||
sconfig := &serverConfig{s: s}
|
||||
if err := h1.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil {
|
||||
panic("http2: net/http does not support this version of x/net/http2")
|
||||
}
|
||||
serveConnFunc = sconfig.serveConnFunc
|
||||
case s.state != nil:
|
||||
serveConnFunc = s.state.serveConnFunc
|
||||
default:
|
||||
// Strange-but-true: Server has no concurrency-safe way to initialize
|
||||
// its internal state, so historically ServeConn just doesn't use any
|
||||
// persistent state if you don't call ConfigureServer first.
|
||||
//
|
||||
// If ConfigureServer hasn't been called, create a one-off http.Server
|
||||
// for the connection, since we don't have any way to keep one around for reuse.
|
||||
h1 := &http.Server{}
|
||||
sconfig := &serverConfig{s: s}
|
||||
if err := h1.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil {
|
||||
panic("http2: net/http does not support this version of x/net/http2")
|
||||
}
|
||||
serveConnFunc = sconfig.serveConnFunc
|
||||
}
|
||||
|
||||
ctx, cancel := serverConnBaseContext(c, opts)
|
||||
defer cancel()
|
||||
serveConnFunc(ctx, c, opts.handler(), opts.SawClientPreface, opts.UpgradeRequest, opts.Settings)
|
||||
|
||||
}
|
||||
|
||||
// FrameWriteRequest is a request to write a frame.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
type FrameWriteRequest struct {
|
||||
// Ideally we'd define this in writesched_common.go,
|
||||
// to avoid duplicating an exported symbol across two files,
|
||||
// but the changes required to make this work are fairly large.
|
||||
}
|
||||
|
||||
func (wr FrameWriteRequest) StreamID() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (wr FrameWriteRequest) DataSize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (wr FrameWriteRequest) Consume(n int32) (FrameWriteRequest, FrameWriteRequest, int) {
|
||||
return FrameWriteRequest{}, FrameWriteRequest{}, 0
|
||||
}
|
||||
|
||||
func (wr FrameWriteRequest) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// NewPriorityWriteScheduler is deprecated.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
func NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler {
|
||||
return unsupportedWriteScheduler{}
|
||||
}
|
||||
|
||||
// NewRandomWriteScheduler is deprecated.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
func NewRandomWriteScheduler() WriteScheduler {
|
||||
return unsupportedWriteScheduler{}
|
||||
}
|
||||
|
||||
type unsupportedWriteScheduler struct{}
|
||||
|
||||
func (unsupportedWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {}
|
||||
func (unsupportedWriteScheduler) CloseStream(streamID uint32) {}
|
||||
func (unsupportedWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {}
|
||||
func (unsupportedWriteScheduler) Push(wr FrameWriteRequest) {}
|
||||
func (unsupportedWriteScheduler) Pop() (wr FrameWriteRequest, ok bool) {
|
||||
return FrameWriteRequest{}, false
|
||||
}
|
||||
+28
-425
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
// Transport code.
|
||||
|
||||
package http2
|
||||
@@ -21,20 +23,17 @@ import (
|
||||
"log"
|
||||
"math"
|
||||
"math/bits"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/net/idna"
|
||||
"golang.org/x/net/internal/httpcommon"
|
||||
)
|
||||
|
||||
@@ -60,123 +59,7 @@ const (
|
||||
defaultMaxConcurrentStreams = 1000
|
||||
)
|
||||
|
||||
// Transport is an HTTP/2 Transport.
|
||||
//
|
||||
// A Transport internally caches connections to servers. It is safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
type Transport struct {
|
||||
// DialTLSContext specifies an optional dial function with context for
|
||||
// creating TLS connections for requests.
|
||||
//
|
||||
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
||||
//
|
||||
// If the returned net.Conn has a ConnectionState method like tls.Conn,
|
||||
// it will be used to set http.Response.TLS.
|
||||
DialTLSContext func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error)
|
||||
|
||||
// DialTLS specifies an optional dial function for creating
|
||||
// TLS connections for requests.
|
||||
//
|
||||
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
||||
//
|
||||
// Deprecated: Use DialTLSContext instead, which allows the transport
|
||||
// to cancel dials as soon as they are no longer needed.
|
||||
// If both are set, DialTLSContext takes priority.
|
||||
DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error)
|
||||
|
||||
// TLSClientConfig specifies the TLS configuration to use with
|
||||
// tls.Client. If nil, the default configuration is used.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// ConnPool optionally specifies an alternate connection pool to use.
|
||||
// If nil, the default is used.
|
||||
ConnPool ClientConnPool
|
||||
|
||||
// DisableCompression, if true, prevents the Transport from
|
||||
// requesting compression with an "Accept-Encoding: gzip"
|
||||
// request header when the Request contains no existing
|
||||
// Accept-Encoding value. If the Transport requests gzip on
|
||||
// its own and gets a gzipped response, it's transparently
|
||||
// decoded in the Response.Body. However, if the user
|
||||
// explicitly requested gzip it is not automatically
|
||||
// uncompressed.
|
||||
DisableCompression bool
|
||||
|
||||
// AllowHTTP, if true, permits HTTP/2 requests using the insecure,
|
||||
// plain-text "http" scheme. Note that this does not enable h2c support.
|
||||
AllowHTTP bool
|
||||
|
||||
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
||||
// send in the initial settings frame. It is how many bytes
|
||||
// of response headers are allowed. Unlike the http2 spec, zero here
|
||||
// means to use a default limit (currently 10MB). If you actually
|
||||
// want to advertise an unlimited value to the peer, Transport
|
||||
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
||||
// to mean no limit.
|
||||
MaxHeaderListSize uint32
|
||||
|
||||
// MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the
|
||||
// initial settings frame. It is the size in bytes of the largest frame
|
||||
// payload that the sender is willing to receive. If 0, no setting is
|
||||
// sent, and the value is provided by the peer, which should be 16384
|
||||
// according to the spec:
|
||||
// https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2.
|
||||
// Values are bounded in the range 16k to 16M.
|
||||
MaxReadFrameSize uint32
|
||||
|
||||
// MaxDecoderHeaderTableSize optionally specifies the http2
|
||||
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
||||
// informs the remote endpoint of the maximum size of the header compression
|
||||
// table used to decode header blocks, in octets. If zero, the default value
|
||||
// of 4096 is used.
|
||||
MaxDecoderHeaderTableSize uint32
|
||||
|
||||
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
||||
// header compression table used for encoding request headers. Received
|
||||
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
||||
// the default value of 4096 is used.
|
||||
MaxEncoderHeaderTableSize uint32
|
||||
|
||||
// StrictMaxConcurrentStreams controls whether the server's
|
||||
// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
|
||||
// globally. If false, new TCP connections are created to the
|
||||
// server as needed to keep each under the per-connection
|
||||
// SETTINGS_MAX_CONCURRENT_STREAMS limit. If true, the
|
||||
// server's SETTINGS_MAX_CONCURRENT_STREAMS is interpreted as
|
||||
// a global limit and callers of RoundTrip block when needed,
|
||||
// waiting for their turn.
|
||||
StrictMaxConcurrentStreams bool
|
||||
|
||||
// IdleConnTimeout is the maximum amount of time an idle
|
||||
// (keep-alive) connection will remain idle before closing
|
||||
// itself.
|
||||
// Zero means no limit.
|
||||
IdleConnTimeout time.Duration
|
||||
|
||||
// ReadIdleTimeout is the timeout after which a health check using ping
|
||||
// frame will be carried out if no frame is received on the connection.
|
||||
// Note that a ping response will is considered a received frame, so if
|
||||
// there is no other traffic on the connection, the health check will
|
||||
// be performed every ReadIdleTimeout interval.
|
||||
// If zero, no health check is performed.
|
||||
ReadIdleTimeout time.Duration
|
||||
|
||||
// PingTimeout is the timeout after which the connection will be closed
|
||||
// if a response to Ping is not received.
|
||||
// Defaults to 15s.
|
||||
PingTimeout time.Duration
|
||||
|
||||
// WriteByteTimeout is the timeout after which the connection will be
|
||||
// closed no data can be written to it. The timeout begins when data is
|
||||
// available to write, and is extended whenever any bytes are written.
|
||||
WriteByteTimeout time.Duration
|
||||
|
||||
// CountError, if non-nil, is called on HTTP/2 transport errors.
|
||||
// It's intended to increment a metric for monitoring, such
|
||||
// as an expvar or Prometheus metric.
|
||||
// The errType consists of only ASCII word characters.
|
||||
CountError func(errType string)
|
||||
|
||||
type transportInternal struct {
|
||||
// t1, if non-nil, is the standard library Transport using
|
||||
// this transport. Its settings are used (but not its
|
||||
// RoundTrip method, etc).
|
||||
@@ -217,27 +100,18 @@ func (t *Transport) disableCompression() bool {
|
||||
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
|
||||
}
|
||||
|
||||
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||
// It returns an error if t1 has already been HTTP/2-enabled.
|
||||
//
|
||||
// Use ConfigureTransports instead to configure the HTTP/2 Transport.
|
||||
func ConfigureTransport(t1 *http.Transport) error {
|
||||
_, err := ConfigureTransports(t1)
|
||||
func configureTransport(t1 *http.Transport) error {
|
||||
_, err := configureTransports(t1)
|
||||
return err
|
||||
}
|
||||
|
||||
// ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||
// It returns a new HTTP/2 Transport for further configuration.
|
||||
// It returns an error if t1 has already been HTTP/2-enabled.
|
||||
func ConfigureTransports(t1 *http.Transport) (*Transport, error) {
|
||||
return configureTransports(t1)
|
||||
}
|
||||
|
||||
func configureTransports(t1 *http.Transport) (*Transport, error) {
|
||||
connPool := new(clientConnPool)
|
||||
t2 := &Transport{
|
||||
ConnPool: noDialClientConnPool{connPool},
|
||||
t1: t1,
|
||||
transportInternal: transportInternal{
|
||||
t1: t1,
|
||||
},
|
||||
}
|
||||
connPool.t = t2
|
||||
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
|
||||
@@ -525,68 +399,7 @@ func (sew stickyErrWriter) Write(p []byte) (n int, err error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// noCachedConnError is the concrete type of ErrNoCachedConn, which
|
||||
// needs to be detected by net/http regardless of whether it's its
|
||||
// bundled version (in h2_bundle.go with a rewritten type name) or
|
||||
// from a user's x/net/http2. As such, as it has a unique method name
|
||||
// (IsHTTP2NoCachedConnError) that net/http sniffs for via func
|
||||
// isNoCachedConnError.
|
||||
type noCachedConnError struct{}
|
||||
|
||||
func (noCachedConnError) IsHTTP2NoCachedConnError() {}
|
||||
func (noCachedConnError) Error() string { return "http2: no cached connection was available" }
|
||||
|
||||
// isNoCachedConnError reports whether err is of type noCachedConnError
|
||||
// or its equivalent renamed type in net/http2's h2_bundle.go. Both types
|
||||
// may coexist in the same running program.
|
||||
func isNoCachedConnError(err error) bool {
|
||||
_, ok := err.(interface{ IsHTTP2NoCachedConnError() })
|
||||
return ok
|
||||
}
|
||||
|
||||
var ErrNoCachedConn error = noCachedConnError{}
|
||||
|
||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||
type RoundTripOpt struct {
|
||||
// OnlyCachedConn controls whether RoundTripOpt may
|
||||
// create a new TCP connection. If set true and
|
||||
// no cached connection is available, RoundTripOpt
|
||||
// will return ErrNoCachedConn.
|
||||
OnlyCachedConn bool
|
||||
|
||||
allowHTTP bool // allow http:// URLs
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.RoundTripOpt(req, RoundTripOpt{})
|
||||
}
|
||||
|
||||
// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
|
||||
// and returns a host:port. The port 443 is added if needed.
|
||||
func authorityAddr(scheme string, authority string) (addr string) {
|
||||
host, port, err := net.SplitHostPort(authority)
|
||||
if err != nil { // authority didn't have a port
|
||||
host = authority
|
||||
port = ""
|
||||
}
|
||||
if port == "" { // authority's port was empty
|
||||
port = "443"
|
||||
if scheme == "http" {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
if a, err := idna.ToASCII(host); err == nil {
|
||||
host = a
|
||||
}
|
||||
// IPv6 address literal, without a port:
|
||||
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||
return host + ":" + port
|
||||
}
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
// RoundTripOpt is like RoundTrip, but takes options.
|
||||
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
switch req.URL.Scheme {
|
||||
case "https":
|
||||
// Always okay.
|
||||
@@ -597,126 +410,15 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
|
||||
default:
|
||||
return nil, errors.New("http2: unsupported scheme")
|
||||
}
|
||||
|
||||
addr := authorityAddr(req.URL.Scheme, req.URL.Host)
|
||||
for retry := 0; ; retry++ {
|
||||
cc, err := t.connPool().GetClientConn(req, addr)
|
||||
if err != nil {
|
||||
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
|
||||
return nil, err
|
||||
}
|
||||
reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1)
|
||||
traceGotConn(req, cc, reused)
|
||||
res, err := cc.RoundTrip(req)
|
||||
if err != nil && retry <= 6 {
|
||||
roundTripErr := err
|
||||
if req, err = shouldRetryRequest(req, err); err == nil {
|
||||
// After the first retry, do exponential backoff with 10% jitter.
|
||||
if retry == 0 {
|
||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||
continue
|
||||
}
|
||||
backoff := float64(uint(1) << (uint(retry) - 1))
|
||||
backoff += backoff * (0.1 * mathrand.Float64())
|
||||
d := time.Second * time.Duration(backoff)
|
||||
tm := time.NewTimer(d)
|
||||
select {
|
||||
case <-tm.C:
|
||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||
continue
|
||||
case <-req.Context().Done():
|
||||
tm.Stop()
|
||||
err = req.Context().Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == errClientConnNotEstablished {
|
||||
// This ClientConn was created recently,
|
||||
// this is the first request to use it,
|
||||
// and the connection is closed and not usable.
|
||||
//
|
||||
// In this state, cc.idleTimer will remove the conn from the pool
|
||||
// when it fires. Stop the timer and remove it here so future requests
|
||||
// won't try to use this connection.
|
||||
//
|
||||
// If the timer has already fired and we're racing it, the redundant
|
||||
// call to MarkDead is harmless.
|
||||
if cc.idleTimer != nil {
|
||||
cc.idleTimer.Stop()
|
||||
}
|
||||
t.connPool().MarkDead(cc)
|
||||
}
|
||||
if err != nil {
|
||||
t.vlogf("RoundTrip failure: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
return t.roundTripViaPool(req, opt, t.connPool())
|
||||
}
|
||||
|
||||
// CloseIdleConnections closes any connections which were previously
|
||||
// connected from previous requests but are now sitting idle.
|
||||
// It does not interrupt any connections currently in use.
|
||||
func (t *Transport) CloseIdleConnections() {
|
||||
func (t *Transport) closeIdleConnections() {
|
||||
if cp, ok := t.connPool().(clientConnPoolIdleCloser); ok {
|
||||
cp.closeIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errClientConnClosed = errors.New("http2: client conn is closed")
|
||||
errClientConnUnusable = errors.New("http2: client conn not usable")
|
||||
errClientConnNotEstablished = errors.New("http2: client conn could not be established")
|
||||
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
||||
errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close")
|
||||
)
|
||||
|
||||
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
||||
// response headers. It is always called with a non-nil error.
|
||||
// It returns either a request to retry (either the same request, or a
|
||||
// modified clone), or an error if the request can't be replayed.
|
||||
func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) {
|
||||
if !canRetryError(err) {
|
||||
return nil, err
|
||||
}
|
||||
// If the Body is nil (or http.NoBody), it's safe to reuse
|
||||
// this request and its Body.
|
||||
if req.Body == nil || req.Body == http.NoBody {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// If the request body can be reset back to its original
|
||||
// state via the optional req.GetBody, do that.
|
||||
if req.GetBody != nil {
|
||||
body, err := req.GetBody()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newReq := *req
|
||||
newReq.Body = body
|
||||
return &newReq, nil
|
||||
}
|
||||
|
||||
// The Request.Body can't reset back to the beginning, but we
|
||||
// don't seem to have started to read from it yet, so reuse
|
||||
// the request directly.
|
||||
if err == errClientConnUnusable {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", err)
|
||||
}
|
||||
|
||||
func canRetryError(err error) bool {
|
||||
if err == errClientConnUnusable || err == errClientConnGotGoAway {
|
||||
return true
|
||||
}
|
||||
if se, ok := err.(StreamError); ok {
|
||||
return se.Code == ErrCodeRefusedStream
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
@@ -743,27 +445,6 @@ func (t *Transport) newTLSConfig(host string) *tls.Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (t *Transport) dialTLS(ctx context.Context, network, addr string, tlsCfg *tls.Config) (net.Conn, error) {
|
||||
if t.DialTLSContext != nil {
|
||||
return t.DialTLSContext(ctx, network, addr, tlsCfg)
|
||||
} else if t.DialTLS != nil {
|
||||
return t.DialTLS(network, addr, tlsCfg)
|
||||
}
|
||||
|
||||
tlsCn, err := t.dialTLSWithContext(ctx, network, addr, tlsCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := tlsCn.ConnectionState()
|
||||
if p := state.NegotiatedProtocol; p != NextProtoTLS {
|
||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, NextProtoTLS)
|
||||
}
|
||||
if !state.NegotiatedProtocolIsMutual {
|
||||
return nil, errors.New("http2: could not negotiate protocol mutually")
|
||||
}
|
||||
return tlsCn, nil
|
||||
}
|
||||
|
||||
// disableKeepAlives reports whether connections should be closed as
|
||||
// soon as possible after handling the first request.
|
||||
func (t *Transport) disableKeepAlives() bool {
|
||||
@@ -777,7 +458,7 @@ func (t *Transport) expectContinueTimeout() time.Duration {
|
||||
return t.t1.ExpectContinueTimeout
|
||||
}
|
||||
|
||||
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
||||
func (t *Transport) newUserClientConn(c net.Conn) (*ClientConn, error) {
|
||||
return t.newClientConn(c, t.disableKeepAlives(), nil)
|
||||
}
|
||||
|
||||
@@ -890,8 +571,7 @@ func (cc *ClientConn) healthCheck() {
|
||||
}
|
||||
}
|
||||
|
||||
// SetDoNotReuse marks cc as not reusable for future HTTP requests.
|
||||
func (cc *ClientConn) SetDoNotReuse() {
|
||||
func (cc *ClientConn) setDoNotReuse() {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
cc.doNotReuse = true
|
||||
@@ -932,21 +612,13 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
|
||||
}
|
||||
}
|
||||
|
||||
// CanTakeNewRequest reports whether the connection can take a new request,
|
||||
// meaning it has not been closed or received or sent a GOAWAY.
|
||||
//
|
||||
// If the caller is going to immediately make a new request on this
|
||||
// connection, use ReserveNewRequest instead.
|
||||
func (cc *ClientConn) CanTakeNewRequest() bool {
|
||||
func (cc *ClientConn) canTakeNewRequest() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
return cc.canTakeNewRequestLocked()
|
||||
}
|
||||
|
||||
// ReserveNewRequest is like CanTakeNewRequest but also reserves a
|
||||
// concurrent stream in cc. The reservation is decremented on the
|
||||
// next call to RoundTrip.
|
||||
func (cc *ClientConn) ReserveNewRequest() bool {
|
||||
func (cc *ClientConn) reserveNewRequest() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if st := cc.idleStateLocked(); !st.canTakeNewRequest {
|
||||
@@ -956,41 +628,7 @@ func (cc *ClientConn) ReserveNewRequest() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ClientConnState describes the state of a ClientConn.
|
||||
type ClientConnState struct {
|
||||
// Closed is whether the connection is closed.
|
||||
Closed bool
|
||||
|
||||
// Closing is whether the connection is in the process of
|
||||
// closing. It may be closing due to shutdown, being a
|
||||
// single-use connection, being marked as DoNotReuse, or
|
||||
// having received a GOAWAY frame.
|
||||
Closing bool
|
||||
|
||||
// StreamsActive is how many streams are active.
|
||||
StreamsActive int
|
||||
|
||||
// StreamsReserved is how many streams have been reserved via
|
||||
// ClientConn.ReserveNewRequest.
|
||||
StreamsReserved int
|
||||
|
||||
// StreamsPending is how many requests have been sent in excess
|
||||
// of the peer's advertised MaxConcurrentStreams setting and
|
||||
// are waiting for other streams to complete.
|
||||
StreamsPending int
|
||||
|
||||
// MaxConcurrentStreams is how many concurrent streams the
|
||||
// peer advertised as acceptable. Zero means no SETTINGS
|
||||
// frame has been received yet.
|
||||
MaxConcurrentStreams uint32
|
||||
|
||||
// LastIdle, if non-zero, is when the connection last
|
||||
// transitioned to idle state.
|
||||
LastIdle time.Time
|
||||
}
|
||||
|
||||
// State returns a snapshot of cc's state.
|
||||
func (cc *ClientConn) State() ClientConnState {
|
||||
func (cc *ClientConn) state() ClientConnState {
|
||||
cc.wmu.Lock()
|
||||
maxConcurrent := cc.maxConcurrentStreams
|
||||
if !cc.seenSettings {
|
||||
@@ -1161,6 +799,12 @@ func (cc *ClientConn) closeIfIdle() {
|
||||
cc.closeConn()
|
||||
}
|
||||
|
||||
func (cc *ClientConn) stopIdleTimer() {
|
||||
if cc.idleTimer != nil {
|
||||
cc.idleTimer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *ClientConn) isDoNotReuseAndIdle() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
@@ -1169,8 +813,7 @@ func (cc *ClientConn) isDoNotReuseAndIdle() bool {
|
||||
|
||||
var shutdownEnterWaitStateHook = func() {}
|
||||
|
||||
// Shutdown gracefully closes the client connection, waiting for running streams to complete.
|
||||
func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
||||
func (cc *ClientConn) shutdown(ctx context.Context) error {
|
||||
if err := cc.sendGoAway(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1244,10 +887,7 @@ func (cc *ClientConn) closeForError(err error) {
|
||||
cc.closeConn()
|
||||
}
|
||||
|
||||
// Close closes the client connection immediately.
|
||||
//
|
||||
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
||||
func (cc *ClientConn) Close() error {
|
||||
func (cc *ClientConn) close() error {
|
||||
cc.closeForError(errClientConnForceClosed)
|
||||
return nil
|
||||
}
|
||||
@@ -1301,11 +941,11 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.roundTrip(req, nil)
|
||||
func (cc *ClientConn) roundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.internalRoundTrip(req, nil)
|
||||
}
|
||||
|
||||
func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
|
||||
func (cc *ClientConn) internalRoundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
|
||||
ctx := req.Context()
|
||||
cs := &clientStream{
|
||||
cc: cc,
|
||||
@@ -2125,19 +1765,6 @@ func (cc *ClientConn) readLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// GoAwayError is returned by the Transport when the server closes the
|
||||
// TCP connection after sending a GOAWAY frame.
|
||||
type GoAwayError struct {
|
||||
LastStreamID uint32
|
||||
ErrCode ErrCode
|
||||
DebugData string
|
||||
}
|
||||
|
||||
func (e GoAwayError) Error() string {
|
||||
return fmt.Sprintf("http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%q",
|
||||
e.LastStreamID, e.ErrCode, e.DebugData)
|
||||
}
|
||||
|
||||
func isEOFOrNetReadError(err error) bool {
|
||||
if err == io.EOF {
|
||||
return true
|
||||
@@ -2978,7 +2605,7 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
|
||||
}
|
||||
|
||||
// Ping sends a PING frame to the server and waits for the ack.
|
||||
func (cc *ClientConn) Ping(ctx context.Context) error {
|
||||
func (cc *ClientConn) ping(ctx context.Context) error {
|
||||
c := make(chan struct{})
|
||||
// Generate a random payload
|
||||
var p [8]byte
|
||||
@@ -3092,16 +2719,6 @@ func (cc *ClientConn) vlogf(format string, args ...interface{}) {
|
||||
cc.t.vlogf(format, args...)
|
||||
}
|
||||
|
||||
func (t *Transport) vlogf(format string, args ...interface{}) {
|
||||
if VerboseLogs {
|
||||
t.logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) logf(format string, args ...interface{}) {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
|
||||
var noBody io.ReadCloser = noBodyReader{}
|
||||
|
||||
type noBodyReader struct{}
|
||||
@@ -3417,17 +3034,3 @@ func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.M
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
|
||||
// connection.
|
||||
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
|
||||
dialer := &tls.Dialer{
|
||||
Config: cfg,
|
||||
}
|
||||
cn, err := dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
|
||||
return tlsCn, nil
|
||||
}
|
||||
|
||||
+447
@@ -0,0 +1,447 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||
// It returns an error if t1 has already been HTTP/2-enabled.
|
||||
//
|
||||
// Use ConfigureTransports instead to configure the HTTP/2 Transport.
|
||||
func ConfigureTransport(t1 *http.Transport) error {
|
||||
return configureTransport(t1)
|
||||
}
|
||||
|
||||
// ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||
// It returns a new HTTP/2 Transport for further configuration.
|
||||
// It returns an error if t1 has already been HTTP/2-enabled.
|
||||
func ConfigureTransports(t1 *http.Transport) (*Transport, error) {
|
||||
return configureTransports(t1)
|
||||
}
|
||||
|
||||
// Transport is an HTTP/2 Transport.
|
||||
//
|
||||
// A Transport internally caches connections to servers. It is safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
type Transport struct {
|
||||
// DialTLSContext specifies an optional dial function with context for
|
||||
// creating TLS connections for requests.
|
||||
//
|
||||
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
||||
//
|
||||
// If the returned net.Conn has a ConnectionState method like tls.Conn,
|
||||
// it will be used to set http.Response.TLS.
|
||||
DialTLSContext func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error)
|
||||
|
||||
// DialTLS specifies an optional dial function for creating
|
||||
// TLS connections for requests.
|
||||
//
|
||||
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
||||
//
|
||||
// Deprecated: Use DialTLSContext instead, which allows the transport
|
||||
// to cancel dials as soon as they are no longer needed.
|
||||
// If both are set, DialTLSContext takes priority.
|
||||
DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error)
|
||||
|
||||
// TLSClientConfig specifies the TLS configuration to use with
|
||||
// tls.Client. If nil, the default configuration is used.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// ConnPool optionally specifies an alternate connection pool to use.
|
||||
// If nil, the default is used.
|
||||
ConnPool ClientConnPool
|
||||
|
||||
// DisableCompression, if true, prevents the Transport from
|
||||
// requesting compression with an "Accept-Encoding: gzip"
|
||||
// request header when the Request contains no existing
|
||||
// Accept-Encoding value. If the Transport requests gzip on
|
||||
// its own and gets a gzipped response, it's transparently
|
||||
// decoded in the Response.Body. However, if the user
|
||||
// explicitly requested gzip it is not automatically
|
||||
// uncompressed.
|
||||
DisableCompression bool
|
||||
|
||||
// AllowHTTP, if true, permits HTTP/2 requests using the insecure,
|
||||
// plain-text "http" scheme. Note that this does not enable h2c support.
|
||||
AllowHTTP bool
|
||||
|
||||
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
||||
// send in the initial settings frame. It is how many bytes
|
||||
// of response headers are allowed. Unlike the http2 spec, zero here
|
||||
// means to use a default limit (currently 10MB). If you actually
|
||||
// want to advertise an unlimited value to the peer, Transport
|
||||
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
||||
// to mean no limit.
|
||||
MaxHeaderListSize uint32
|
||||
|
||||
// MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the
|
||||
// initial settings frame. It is the size in bytes of the largest frame
|
||||
// payload that the sender is willing to receive. If 0, no setting is
|
||||
// sent, and the value is provided by the peer, which should be 16384
|
||||
// according to the spec:
|
||||
// https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2.
|
||||
// Values are bounded in the range 16k to 16M.
|
||||
MaxReadFrameSize uint32
|
||||
|
||||
// MaxDecoderHeaderTableSize optionally specifies the http2
|
||||
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
||||
// informs the remote endpoint of the maximum size of the header compression
|
||||
// table used to decode header blocks, in octets. If zero, the default value
|
||||
// of 4096 is used.
|
||||
MaxDecoderHeaderTableSize uint32
|
||||
|
||||
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
||||
// header compression table used for encoding request headers. Received
|
||||
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
||||
// the default value of 4096 is used.
|
||||
MaxEncoderHeaderTableSize uint32
|
||||
|
||||
// StrictMaxConcurrentStreams controls whether the server's
|
||||
// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
|
||||
// globally. If false, new TCP connections are created to the
|
||||
// server as needed to keep each under the per-connection
|
||||
// SETTINGS_MAX_CONCURRENT_STREAMS limit. If true, the
|
||||
// server's SETTINGS_MAX_CONCURRENT_STREAMS is interpreted as
|
||||
// a global limit and callers of RoundTrip block when needed,
|
||||
// waiting for their turn.
|
||||
StrictMaxConcurrentStreams bool
|
||||
|
||||
// IdleConnTimeout is the maximum amount of time an idle
|
||||
// (keep-alive) connection will remain idle before closing
|
||||
// itself.
|
||||
// Zero means no limit.
|
||||
IdleConnTimeout time.Duration
|
||||
|
||||
// ReadIdleTimeout is the timeout after which a health check using ping
|
||||
// frame will be carried out if no frame is received on the connection.
|
||||
// Note that a ping response will is considered a received frame, so if
|
||||
// there is no other traffic on the connection, the health check will
|
||||
// be performed every ReadIdleTimeout interval.
|
||||
// If zero, no health check is performed.
|
||||
ReadIdleTimeout time.Duration
|
||||
|
||||
// PingTimeout is the timeout after which the connection will be closed
|
||||
// if a response to Ping is not received.
|
||||
// Defaults to 15s.
|
||||
PingTimeout time.Duration
|
||||
|
||||
// WriteByteTimeout is the timeout after which the connection will be
|
||||
// closed no data can be written to it. The timeout begins when data is
|
||||
// available to write, and is extended whenever any bytes are written.
|
||||
WriteByteTimeout time.Duration
|
||||
|
||||
// CountError, if non-nil, is called on HTTP/2 transport errors.
|
||||
// It's intended to increment a metric for monitoring, such
|
||||
// as an expvar or Prometheus metric.
|
||||
// The errType consists of only ASCII word characters.
|
||||
CountError func(errType string)
|
||||
|
||||
// Internal state, differs between wrapped and non-wrapped implementations.
|
||||
transportInternal
|
||||
}
|
||||
|
||||
var (
|
||||
errClientConnClosed = errors.New("http2: client conn is closed")
|
||||
errClientConnNotEstablished = errors.New("http2: client conn could not be established")
|
||||
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
||||
errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close")
|
||||
errClientConnUnusable = errors.New("http2: client conn not usable")
|
||||
)
|
||||
|
||||
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||
type ClientConnPool interface {
|
||||
// GetClientConn returns a specific HTTP/2 connection (usually
|
||||
// a TLS-TCP connection) to an HTTP/2 server. On success, the
|
||||
// returned ClientConn accounts for the upcoming RoundTrip
|
||||
// call, so the caller should not omit it. If the caller needs
|
||||
// to, ClientConn.RoundTrip can be called with a bogus
|
||||
// new(http.Request) to release the stream reservation.
|
||||
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
||||
MarkDead(*ClientConn)
|
||||
}
|
||||
|
||||
// ClientConnState describes the state of a ClientConn.
|
||||
type ClientConnState struct {
|
||||
// Closed is whether the connection is closed.
|
||||
Closed bool
|
||||
|
||||
// Closing is whether the connection is in the process of
|
||||
// closing. It may be closing due to shutdown, being a
|
||||
// single-use connection, being marked as DoNotReuse, or
|
||||
// having received a GOAWAY frame.
|
||||
Closing bool
|
||||
|
||||
// StreamsActive is how many streams are active.
|
||||
StreamsActive int
|
||||
|
||||
// StreamsReserved is how many streams have been reserved via
|
||||
// ClientConn.ReserveNewRequest.
|
||||
StreamsReserved int
|
||||
|
||||
// StreamsPending is how many requests have been sent in excess
|
||||
// of the peer's advertised MaxConcurrentStreams setting and
|
||||
// are waiting for other streams to complete.
|
||||
StreamsPending int
|
||||
|
||||
// MaxConcurrentStreams is how many concurrent streams the
|
||||
// peer advertised as acceptable. Zero means no SETTINGS
|
||||
// frame has been received yet.
|
||||
MaxConcurrentStreams uint32
|
||||
|
||||
// LastIdle, if non-zero, is when the connection last
|
||||
// transitioned to idle state.
|
||||
LastIdle time.Time
|
||||
}
|
||||
|
||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||
type RoundTripOpt struct {
|
||||
// OnlyCachedConn controls whether RoundTripOpt may
|
||||
// create a new TCP connection. If set true and
|
||||
// no cached connection is available, RoundTripOpt
|
||||
// will return ErrNoCachedConn.
|
||||
|
||||
// OnlyCachedConn was broken in https://go.dev/cl/16699.
|
||||
OnlyCachedConn bool
|
||||
|
||||
allowHTTP bool // allow http:// URLs
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.RoundTripOpt(req, RoundTripOpt{})
|
||||
}
|
||||
|
||||
// RoundTripOpt is like RoundTrip, but takes options.
|
||||
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
return t.roundTripOpt(req, opt)
|
||||
}
|
||||
|
||||
// CloseIdleConnections closes any connections which were previously
|
||||
// connected from previous requests but are now sitting idle.
|
||||
// It does not interrupt any connections currently in use.
|
||||
func (t *Transport) CloseIdleConnections() {
|
||||
t.closeIdleConnections()
|
||||
}
|
||||
|
||||
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
||||
return t.newUserClientConn(c)
|
||||
}
|
||||
|
||||
// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
|
||||
// and returns a host:port. The port 443 is added if needed.
|
||||
func authorityAddr(scheme string, authority string) (addr string) {
|
||||
host, port, err := net.SplitHostPort(authority)
|
||||
if err != nil { // authority didn't have a port
|
||||
host = authority
|
||||
port = ""
|
||||
}
|
||||
if port == "" { // authority's port was empty
|
||||
port = "443"
|
||||
if scheme == "http" {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
if a, err := idna.ToASCII(host); err == nil {
|
||||
host = a
|
||||
}
|
||||
// IPv6 address literal, without a port:
|
||||
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||
return host + ":" + port
|
||||
}
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
func (t *Transport) roundTripViaPool(req *http.Request, opt RoundTripOpt, pool ClientConnPool) (*http.Response, error) {
|
||||
addr := authorityAddr(req.URL.Scheme, req.URL.Host)
|
||||
for retry := 0; ; retry++ {
|
||||
cc, err := pool.GetClientConn(req, addr)
|
||||
if err != nil {
|
||||
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
|
||||
return nil, err
|
||||
}
|
||||
reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1)
|
||||
traceGotConn(req, cc, reused)
|
||||
res, err := cc.RoundTrip(req)
|
||||
if err != nil && retry <= 6 {
|
||||
roundTripErr := err
|
||||
if req, err = shouldRetryRequest(req, err); err == nil {
|
||||
// After the first retry, do exponential backoff with 10% jitter.
|
||||
if retry == 0 {
|
||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||
continue
|
||||
}
|
||||
backoff := float64(uint(1) << (uint(retry) - 1))
|
||||
backoff += backoff * (0.1 * mathrand.Float64())
|
||||
d := time.Second * time.Duration(backoff)
|
||||
tm := time.NewTimer(d)
|
||||
select {
|
||||
case <-tm.C:
|
||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||
continue
|
||||
case <-req.Context().Done():
|
||||
tm.Stop()
|
||||
err = req.Context().Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == errClientConnNotEstablished {
|
||||
// This ClientConn was created recently,
|
||||
// this is the first request to use it,
|
||||
// and the connection is closed and not usable.
|
||||
//
|
||||
// In this state, cc.idleTimer will remove the conn from the pool
|
||||
// when it fires. Stop the timer and remove it here so future requests
|
||||
// won't try to use this connection.
|
||||
//
|
||||
// If the timer has already fired and we're racing it, the redundant
|
||||
// call to MarkDead is harmless.
|
||||
cc.stopIdleTimer()
|
||||
pool.MarkDead(cc)
|
||||
}
|
||||
if err != nil {
|
||||
t.vlogf("RoundTrip failure: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
||||
// response headers. It is always called with a non-nil error.
|
||||
// It returns either a request to retry (either the same request, or a
|
||||
// modified clone), or an error if the request can't be replayed.
|
||||
func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) {
|
||||
if !canRetryError(err) {
|
||||
return nil, err
|
||||
}
|
||||
// If the Body is nil (or http.NoBody), it's safe to reuse
|
||||
// this request and its Body.
|
||||
if req.Body == nil || req.Body == http.NoBody {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// If the request body can be reset back to its original
|
||||
// state via the optional req.GetBody, do that.
|
||||
if req.GetBody != nil {
|
||||
body, err := req.GetBody()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newReq := *req
|
||||
newReq.Body = body
|
||||
return &newReq, nil
|
||||
}
|
||||
|
||||
// The Request.Body can't reset back to the beginning, but we
|
||||
// don't seem to have started to read from it yet, so reuse
|
||||
// the request directly.
|
||||
if err == errClientConnUnusable {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", err)
|
||||
}
|
||||
|
||||
func canRetryError(err error) bool {
|
||||
if err == errClientConnUnusable || err == errClientConnGotGoAway {
|
||||
return true
|
||||
}
|
||||
if se, ok := err.(StreamError); ok {
|
||||
return se.Code == ErrCodeRefusedStream
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Transport) vlogf(format string, args ...interface{}) {
|
||||
if VerboseLogs {
|
||||
t.logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) logf(format string, args ...interface{}) {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
|
||||
func (t *Transport) dialTLS(ctx context.Context, network, addr string, tlsCfg *tls.Config) (net.Conn, error) {
|
||||
if t.DialTLSContext != nil {
|
||||
return t.DialTLSContext(ctx, network, addr, tlsCfg)
|
||||
} else if t.DialTLS != nil {
|
||||
return t.DialTLS(network, addr, tlsCfg)
|
||||
}
|
||||
|
||||
tlsCn, err := t.dialTLSWithContext(ctx, network, addr, tlsCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := tlsCn.ConnectionState()
|
||||
if p := state.NegotiatedProtocol; p != NextProtoTLS {
|
||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, NextProtoTLS)
|
||||
}
|
||||
if !state.NegotiatedProtocolIsMutual {
|
||||
return nil, errors.New("http2: could not negotiate protocol mutually")
|
||||
}
|
||||
return tlsCn, nil
|
||||
}
|
||||
|
||||
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
|
||||
// connection.
|
||||
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
|
||||
dialer := &tls.Dialer{
|
||||
Config: cfg,
|
||||
}
|
||||
cn, err := dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
|
||||
return tlsCn, nil
|
||||
}
|
||||
|
||||
// GoAwayError is returned by the Transport when the server closes the
|
||||
// TCP connection after sending a GOAWAY frame.
|
||||
type GoAwayError struct {
|
||||
LastStreamID uint32
|
||||
ErrCode ErrCode
|
||||
DebugData string
|
||||
}
|
||||
|
||||
func (e GoAwayError) Error() string {
|
||||
return fmt.Sprintf("http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%q",
|
||||
e.LastStreamID, e.ErrCode, e.DebugData)
|
||||
}
|
||||
|
||||
// noCachedConnError is the concrete type of ErrNoCachedConn, which
|
||||
// needs to be detected by net/http regardless of whether it's its
|
||||
// bundled version (in h2_bundle.go with a rewritten type name) or
|
||||
// from a user's x/net/http2. As such, as it has a unique method name
|
||||
// (IsHTTP2NoCachedConnError) that net/http sniffs for via func
|
||||
// isNoCachedConnError.
|
||||
type noCachedConnError struct{}
|
||||
|
||||
func (noCachedConnError) IsHTTP2NoCachedConnError() {}
|
||||
func (noCachedConnError) Error() string { return "http2: no cached connection was available" }
|
||||
|
||||
// isNoCachedConnError reports whether err is of type noCachedConnError
|
||||
// or its equivalent renamed type in net/http2's h2_bundle.go. Both types
|
||||
// may coexist in the same running program.
|
||||
func isNoCachedConnError(err error) bool {
|
||||
_, ok := err.(interface{ IsHTTP2NoCachedConnError() })
|
||||
return ok
|
||||
}
|
||||
|
||||
var ErrNoCachedConn error = noCachedConnError{}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.27 && !http2legacy
|
||||
|
||||
// Transport wrapping a net/http.Transport.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func configureTransport(t1 *http.Transport) error {
|
||||
// ConfigureTransport is a no-op: The http.Transport already supports HTTP/2.
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureTransports(t1 *http.Transport) (*Transport, error) {
|
||||
// ConfigureTransport returns an http2.Transport with a configuration
|
||||
// linked to the http.Transport's.
|
||||
tr2 := &Transport{}
|
||||
tr2.configure(t1)
|
||||
return tr2, nil
|
||||
}
|
||||
|
||||
// transportConfig is passed to net/http.Transport.RegisterProtocol("http/2", config).
|
||||
// It provides the net/http.Transport with access to the configuration in the
|
||||
// x/net/http2.Transport.
|
||||
type transportConfig struct {
|
||||
t *Transport
|
||||
}
|
||||
|
||||
// Registered is called by net/http.Transport.RegisterProtocol,
|
||||
// to let us know that it understands the registration mechanism we're using.
|
||||
func (t transportConfig) Registered(t1 *http.Transport) {
|
||||
t.t.t1 = t1
|
||||
}
|
||||
|
||||
func (t transportConfig) DisableCompression() bool {
|
||||
return t.t.DisableCompression
|
||||
}
|
||||
|
||||
func (t transportConfig) MaxHeaderListSize() int64 {
|
||||
return int64(t.t.MaxHeaderListSize)
|
||||
}
|
||||
|
||||
func (t transportConfig) IdleConnTimeout() time.Duration {
|
||||
return t.t.IdleConnTimeout
|
||||
}
|
||||
|
||||
func (t transportConfig) HTTP2Config() http.HTTP2Config {
|
||||
return http.HTTP2Config{
|
||||
StrictMaxConcurrentRequests: t.t.StrictMaxConcurrentStreams,
|
||||
MaxDecoderHeaderTableSize: int(t.t.MaxDecoderHeaderTableSize),
|
||||
MaxEncoderHeaderTableSize: int(t.t.MaxEncoderHeaderTableSize),
|
||||
MaxReadFrameSize: int(t.t.MaxReadFrameSize),
|
||||
SendPingTimeout: t.t.ReadIdleTimeout,
|
||||
PingTimeout: t.t.PingTimeout,
|
||||
WriteByteTimeout: t.t.WriteByteTimeout,
|
||||
CountError: t.t.CountError,
|
||||
}
|
||||
}
|
||||
|
||||
// ExternalRoundTrip reports whether the Transport wants to take control of the RoundTrip call.
|
||||
// If the user hasn't configured a custom connection pool, we leave the RoundTrip up to net/http.
|
||||
func (t transportConfig) ExternalRoundTrip() bool {
|
||||
return t.t.ConnPool != nil
|
||||
}
|
||||
|
||||
// RoundTrip is used when the http.Transport is passing control of the full
|
||||
// RoundTrip to us--connection pooling, retries, etc.
|
||||
//
|
||||
// This is only used when the http2.Transport has a user-provided ConnPool.
|
||||
// Any other time, net/http handles everything.
|
||||
func (t transportConfig) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if t.t.ConnPool == nil {
|
||||
return nil, http.ErrSkipAltProtocol
|
||||
}
|
||||
return t.t.RoundTrip(req)
|
||||
}
|
||||
|
||||
// netConnContextKey passes a net.Conn to http.Transport.NewClientConn.
|
||||
// See http2.Transport.NewClientConn.
|
||||
type netConnContextKey struct{}
|
||||
|
||||
// ConnFromContext lets the http.Transport fetch a net.Conn out of a context
|
||||
// passed to NewClientConn. See http2.Transport.NewClientConn.
|
||||
func (t transportConfig) ConnFromContext(ctx context.Context) net.Conn {
|
||||
nc, _ := ctx.Value(netConnContextKey{}).(net.Conn)
|
||||
return nc
|
||||
}
|
||||
|
||||
// http2TransportContextKey marks a RoundTrip as needing its dial handled by the http2.Transport.
|
||||
// We set this for http2.RoundTrip calls, where the historical behavior is to use the
|
||||
// http2.Transport's dialer.
|
||||
type http2TransportContextKey struct{}
|
||||
|
||||
// DialFromContext dials a new connection using the http2.Transport's DialTLS/DialTLSContext.
|
||||
func (t transportConfig) DialFromContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
if ctx.Value(http2TransportContextKey{}) == nil {
|
||||
// We're being called from a RoundTrip that did not start with an http2.Transport.
|
||||
// Use the http.Transport's dialer.
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
|
||||
tlsConf := t.t.TLSClientConfig
|
||||
if tlsConf == nil {
|
||||
tlsConf = &tls.Config{}
|
||||
} else {
|
||||
tlsConf = tlsConf.Clone()
|
||||
}
|
||||
if !slices.Contains(tlsConf.NextProtos, "h2") {
|
||||
tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...)
|
||||
}
|
||||
if tlsConf.ServerName == "" {
|
||||
host, _, err := net.SplitHostPort(address)
|
||||
if err == nil {
|
||||
tlsConf.ServerName = host
|
||||
}
|
||||
}
|
||||
return t.t.dialTLS(ctx, network, address, tlsConf)
|
||||
}
|
||||
|
||||
type transportInternal struct {
|
||||
initOnce sync.Once
|
||||
t1 *http.Transport
|
||||
}
|
||||
|
||||
func (t *Transport) init() {
|
||||
t.initOnce.Do(func() {
|
||||
if t.t1 != nil {
|
||||
return
|
||||
}
|
||||
t1 := &http.Transport{}
|
||||
t.configure(t1)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Transport) configure(t1 *http.Transport) {
|
||||
t1.RegisterProtocol("http/2", transportConfig{t})
|
||||
// tr2.t1 is set by transportConfig.Registered.
|
||||
if t.t1 != t1 {
|
||||
panic("http2: net/http does not support this version of x/net/http2")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
t.init()
|
||||
|
||||
if req.URL.Scheme == "http" && !t.AllowHTTP {
|
||||
return nil, errors.New("http2: unencrypted HTTP/2 not enabled")
|
||||
}
|
||||
|
||||
// When the Transport has a user-provided connection pool (unusual, deprecated),
|
||||
// we need to handle picking a connection, retrys, etc.
|
||||
if t.ConnPool != nil {
|
||||
return t.roundTripViaPool(req, opt, t.ConnPool)
|
||||
}
|
||||
|
||||
// Setting this context key lets net/http know that if it is necessary to dial
|
||||
// a new connection, we should handle the net.Dial.
|
||||
//
|
||||
// Both http.Transport and http2.Transport allow the user to provide a custom
|
||||
// dial function, and historically you only get the dial function from the
|
||||
// Transport you're calling RoundTrip on.
|
||||
ctx := context.WithValue(req.Context(), http2TransportContextKey{}, t)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
return t.t1.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (t *Transport) closeIdleConnections() {
|
||||
t.init()
|
||||
t.t1.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (t *Transport) newUserClientConn(c net.Conn) (*ClientConn, error) {
|
||||
// http.Transport's NewClientConn doesn't provide a supported way to create
|
||||
// a connection from a net.Conn. (This might be useful to add in the future?)
|
||||
// We're going to craftily sneak one in via the context key, with the
|
||||
// scheme of "http/2" telling NewClientConn to look for it.
|
||||
ctx := context.WithValue(context.Background(), netConnContextKey{}, c)
|
||||
|
||||
nhcc, err := t.t1.NewClientConn(ctx, "http/2", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc := &ClientConn{cc: nhcc, tr: t, tconn: c}
|
||||
nhcc.SetStateHook(cc.stateHook)
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// ClientConn is the state of a single HTTP/2 client connection to an
|
||||
// HTTP/2 server.
|
||||
type ClientConn struct {
|
||||
cc *http.ClientConn
|
||||
tconn net.Conn
|
||||
tr *Transport
|
||||
doNotReuse bool
|
||||
|
||||
mu sync.Mutex
|
||||
closing bool
|
||||
closed bool
|
||||
roundTrips int
|
||||
reserved int
|
||||
starting int
|
||||
pending int
|
||||
maxConcurrent int
|
||||
lastIdle time.Time
|
||||
shutdownc chan struct{}
|
||||
|
||||
atomicReused uint32 // whether conn is being reused; atomic
|
||||
}
|
||||
|
||||
func (cc *ClientConn) roundTrip(req *http.Request) (*http.Response, error) {
|
||||
err := func() error {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if cc.doNotReuse {
|
||||
return errClientConnUnusable
|
||||
}
|
||||
cc.roundTrips++
|
||||
if cc.reserved > 0 {
|
||||
// We've already reserved a concurrency slot for this request.
|
||||
cc.reserved--
|
||||
} else if cc.cc.Reserve() != nil {
|
||||
// We don't seem to have an available concurrency slot,
|
||||
// so bump the pending count (requests waiting for a slot).
|
||||
cc.pending++
|
||||
}
|
||||
// ClientConn.Shutdown will not shut down the conn while
|
||||
// cc.starting > 0 or cc.cc.InFlight() > 0.
|
||||
//
|
||||
// The starting state covers the gap between us deciding to
|
||||
// start sending the request, and actually sending it.
|
||||
cc.starting++
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := cc.cc.RoundTrip(req)
|
||||
cc.mu.Lock()
|
||||
cc.starting--
|
||||
if cc.pending > 0 {
|
||||
// A request completing frees up a concurrency slot for
|
||||
// a pending request to start.
|
||||
cc.pending--
|
||||
}
|
||||
cc.updateStateLocked()
|
||||
cc.mu.Unlock()
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (cc *ClientConn) canTakeNewRequest() bool {
|
||||
return cc.cc.Available() > 0 && !cc.doNotReuse
|
||||
}
|
||||
|
||||
func (cc *ClientConn) close() error {
|
||||
return cc.cc.Close()
|
||||
}
|
||||
|
||||
func (cc *ClientConn) ping(ctx context.Context) error {
|
||||
// Ask net/http to ping its connection by sending a request with a method of ":ping".
|
||||
_, err := cc.cc.RoundTrip((&http.Request{
|
||||
Method: ":ping",
|
||||
}).WithContext(ctx))
|
||||
return err
|
||||
}
|
||||
|
||||
func (cc *ClientConn) reserveNewRequest() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if cc.doNotReuse {
|
||||
return false
|
||||
}
|
||||
if err := cc.cc.Reserve(); err != nil {
|
||||
return false
|
||||
}
|
||||
cc.reserved++
|
||||
return true
|
||||
}
|
||||
|
||||
func (cc *ClientConn) setDoNotReuse() {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
cc.doNotReuse = true
|
||||
cc.closing = true
|
||||
}
|
||||
|
||||
func (cc *ClientConn) shutdown(ctx context.Context) error {
|
||||
cc.mu.Lock()
|
||||
inFlight := cc.cc.InFlight() + cc.starting
|
||||
if inFlight > 0 && cc.shutdownc == nil {
|
||||
cc.shutdownc = make(chan struct{})
|
||||
}
|
||||
shutdownc := cc.shutdownc
|
||||
cc.mu.Unlock()
|
||||
if shutdownc != nil {
|
||||
// Wait for in-flight requests to finish.
|
||||
select {
|
||||
case <-shutdownc:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
cc.cc.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ClientConn) state() ClientConnState {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
cc.updateStateLocked()
|
||||
return ClientConnState{
|
||||
Closed: cc.closed,
|
||||
Closing: cc.closing,
|
||||
StreamsActive: cc.cc.InFlight() - cc.reserved,
|
||||
StreamsReserved: cc.reserved,
|
||||
StreamsPending: cc.pending,
|
||||
MaxConcurrentStreams: uint32(min(int64(cc.maxConcurrent), math.MaxUint32)),
|
||||
LastIdle: cc.lastIdle,
|
||||
}
|
||||
}
|
||||
|
||||
// stateHook is the http.ClientConn's state hook.
|
||||
func (cc *ClientConn) stateHook(*http.ClientConn) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
cc.updateStateLocked()
|
||||
}
|
||||
|
||||
func (cc *ClientConn) updateStateLocked() {
|
||||
if cc.cc.Err() != nil && !cc.closed {
|
||||
cc.closing = true
|
||||
cc.closed = true
|
||||
if cc.tr.ConnPool != nil {
|
||||
// Do the ConnPool update in another goroutine,
|
||||
// to avoid holding the conn mutex while it runs.
|
||||
go cc.tr.ConnPool.MarkDead(cc)
|
||||
}
|
||||
}
|
||||
if cc.cc.InFlight() == 0 && cc.roundTrips > 0 && cc.starting == 0 {
|
||||
cc.lastIdle = time.Now()
|
||||
}
|
||||
if !cc.closed {
|
||||
// This is slightly racy (a request could start or finish in between
|
||||
// the Available and InFlight calls), but the best we can do given that
|
||||
// the net/http ClientConn API doesn't expose the conn's max concurrency.
|
||||
cc.maxConcurrent = cc.cc.Available() + cc.cc.InFlight()
|
||||
}
|
||||
if cc.shutdownc != nil && cc.cc.InFlight()+cc.starting == 0 {
|
||||
close(cc.shutdownc)
|
||||
cc.shutdownc = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *ClientConn) stopIdleTimer() {}
|
||||
|
||||
// traceGotConn is (when http2legacy is not enabled) only used for tracing
|
||||
// connections acquired while using a user-provided ClientConnPool.
|
||||
func traceGotConn(req *http.Request, cc *ClientConn, reused bool) {
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
if trace == nil || trace.GotConn == nil {
|
||||
return
|
||||
}
|
||||
ci := httptrace.GotConnInfo{Conn: cc.tconn}
|
||||
ci.Reused = reused
|
||||
trace.GotConn(ci)
|
||||
}
|
||||
+2
-44
@@ -2,54 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WriteScheduler is the interface implemented by HTTP/2 write schedulers.
|
||||
// Methods are never called concurrently.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
type WriteScheduler interface {
|
||||
// OpenStream opens a new stream in the write scheduler.
|
||||
// It is illegal to call this with streamID=0 or with a streamID that is
|
||||
// already open -- the call may panic.
|
||||
OpenStream(streamID uint32, options OpenStreamOptions)
|
||||
|
||||
// CloseStream closes a stream in the write scheduler. Any frames queued on
|
||||
// this stream should be discarded. It is illegal to call this on a stream
|
||||
// that is not open -- the call may panic.
|
||||
CloseStream(streamID uint32)
|
||||
|
||||
// AdjustStream adjusts the priority of the given stream. This may be called
|
||||
// on a stream that has not yet been opened or has been closed. Note that
|
||||
// RFC 7540 allows PRIORITY frames to be sent on streams in any state. See:
|
||||
// https://tools.ietf.org/html/rfc7540#section-5.1
|
||||
AdjustStream(streamID uint32, priority PriorityParam)
|
||||
|
||||
// Push queues a frame in the scheduler. In most cases, this will not be
|
||||
// called with wr.StreamID()!=0 unless that stream is currently open. The one
|
||||
// exception is RST_STREAM frames, which may be sent on idle or closed streams.
|
||||
Push(wr FrameWriteRequest)
|
||||
|
||||
// Pop dequeues the next frame to write. Returns false if no frames can
|
||||
// be written. Frames with a given wr.StreamID() are Pop'd in the same
|
||||
// order they are Push'd, except RST_STREAM frames. No frames should be
|
||||
// discarded except by CloseStream.
|
||||
Pop() (wr FrameWriteRequest, ok bool)
|
||||
}
|
||||
|
||||
// OpenStreamOptions specifies extra options for WriteScheduler.OpenStream.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
type OpenStreamOptions struct {
|
||||
// PusherID is zero if the stream was initiated by the client. Otherwise,
|
||||
// PusherID names the stream that pushed the newly opened stream.
|
||||
PusherID uint32
|
||||
// priority is used to set the priority of the newly opened stream.
|
||||
priority PriorityParam
|
||||
}
|
||||
|
||||
// FrameWriteRequest is a request to write a frame.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
// WriteScheduler is the interface implemented by HTTP/2 write schedulers.
|
||||
// Methods are never called concurrently.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
type WriteScheduler interface {
|
||||
// OpenStream opens a new stream in the write scheduler.
|
||||
// It is illegal to call this with streamID=0 or with a streamID that is
|
||||
// already open -- the call may panic.
|
||||
OpenStream(streamID uint32, options OpenStreamOptions)
|
||||
|
||||
// CloseStream closes a stream in the write scheduler. Any frames queued on
|
||||
// this stream should be discarded. It is illegal to call this on a stream
|
||||
// that is not open -- the call may panic.
|
||||
CloseStream(streamID uint32)
|
||||
|
||||
// AdjustStream adjusts the priority of the given stream. This may be called
|
||||
// on a stream that has not yet been opened or has been closed. Note that
|
||||
// RFC 7540 allows PRIORITY frames to be sent on streams in any state. See:
|
||||
// https://tools.ietf.org/html/rfc7540#section-5.1
|
||||
AdjustStream(streamID uint32, priority PriorityParam)
|
||||
|
||||
// Push queues a frame in the scheduler. In most cases, this will not be
|
||||
// called with wr.StreamID()!=0 unless that stream is currently open. The one
|
||||
// exception is RST_STREAM frames, which may be sent on idle or closed streams.
|
||||
Push(wr FrameWriteRequest)
|
||||
|
||||
// Pop dequeues the next frame to write. Returns false if no frames can
|
||||
// be written. Frames with a given wr.StreamID() are Pop'd in the same
|
||||
// order they are Push'd, except RST_STREAM frames. No frames should be
|
||||
// discarded except by CloseStream.
|
||||
Pop() (wr FrameWriteRequest, ok bool)
|
||||
}
|
||||
|
||||
// OpenStreamOptions specifies extra options for WriteScheduler.OpenStream.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
type OpenStreamOptions struct {
|
||||
// PusherID is zero if the stream was initiated by the client. Otherwise,
|
||||
// PusherID names the stream that pushed the newly opened stream.
|
||||
PusherID uint32
|
||||
// priority is used to set the priority of the newly opened stream.
|
||||
priority PriorityParam
|
||||
}
|
||||
|
||||
// PriorityWriteSchedulerConfig configures a priorityWriteScheduler.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
type PriorityWriteSchedulerConfig struct {
|
||||
// MaxClosedNodesInTree controls the maximum number of closed streams to
|
||||
// retain in the priority tree. Setting this to zero saves a small amount
|
||||
// of memory at the cost of performance.
|
||||
//
|
||||
// See RFC 7540, Section 5.3.4:
|
||||
// "It is possible for a stream to become closed while prioritization
|
||||
// information ... is in transit. ... This potentially creates suboptimal
|
||||
// prioritization, since the stream could be given a priority that is
|
||||
// different from what is intended. To avoid these problems, an endpoint
|
||||
// SHOULD retain stream prioritization state for a period after streams
|
||||
// become closed. The longer state is retained, the lower the chance that
|
||||
// streams are assigned incorrect or default priority values."
|
||||
MaxClosedNodesInTree int
|
||||
|
||||
// MaxIdleNodesInTree controls the maximum number of idle streams to
|
||||
// retain in the priority tree. Setting this to zero saves a small amount
|
||||
// of memory at the cost of performance.
|
||||
//
|
||||
// See RFC 7540, Section 5.3.4:
|
||||
// Similarly, streams that are in the "idle" state can be assigned
|
||||
// priority or become a parent of other streams. This allows for the
|
||||
// creation of a grouping node in the dependency tree, which enables
|
||||
// more flexible expressions of priority. Idle streams begin with a
|
||||
// default priority (Section 5.3.5).
|
||||
MaxIdleNodesInTree int
|
||||
|
||||
// ThrottleOutOfOrderWrites enables write throttling to help ensure that
|
||||
// data is delivered in priority order. This works around a race where
|
||||
// stream B depends on stream A and both streams are about to call Write
|
||||
// to queue DATA frames. If B wins the race, a naive scheduler would eagerly
|
||||
// write as much data from B as possible, but this is suboptimal because A
|
||||
// is a higher-priority stream. With throttling enabled, we write a small
|
||||
// amount of data from B to minimize the amount of bandwidth that B can
|
||||
// steal from A.
|
||||
ThrottleOutOfOrderWrites bool
|
||||
}
|
||||
+2
-41
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
@@ -13,47 +15,6 @@ import (
|
||||
// RFC 7540, Section 5.3.5: the default weight is 16.
|
||||
const priorityDefaultWeightRFC7540 = 15 // 16 = 15 + 1
|
||||
|
||||
// PriorityWriteSchedulerConfig configures a priorityWriteScheduler.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
type PriorityWriteSchedulerConfig struct {
|
||||
// MaxClosedNodesInTree controls the maximum number of closed streams to
|
||||
// retain in the priority tree. Setting this to zero saves a small amount
|
||||
// of memory at the cost of performance.
|
||||
//
|
||||
// See RFC 7540, Section 5.3.4:
|
||||
// "It is possible for a stream to become closed while prioritization
|
||||
// information ... is in transit. ... This potentially creates suboptimal
|
||||
// prioritization, since the stream could be given a priority that is
|
||||
// different from what is intended. To avoid these problems, an endpoint
|
||||
// SHOULD retain stream prioritization state for a period after streams
|
||||
// become closed. The longer state is retained, the lower the chance that
|
||||
// streams are assigned incorrect or default priority values."
|
||||
MaxClosedNodesInTree int
|
||||
|
||||
// MaxIdleNodesInTree controls the maximum number of idle streams to
|
||||
// retain in the priority tree. Setting this to zero saves a small amount
|
||||
// of memory at the cost of performance.
|
||||
//
|
||||
// See RFC 7540, Section 5.3.4:
|
||||
// Similarly, streams that are in the "idle" state can be assigned
|
||||
// priority or become a parent of other streams. This allows for the
|
||||
// creation of a grouping node in the dependency tree, which enables
|
||||
// more flexible expressions of priority. Idle streams begin with a
|
||||
// default priority (Section 5.3.5).
|
||||
MaxIdleNodesInTree int
|
||||
|
||||
// ThrottleOutOfOrderWrites enables write throttling to help ensure that
|
||||
// data is delivered in priority order. This works around a race where
|
||||
// stream B depends on stream A and both streams are about to call Write
|
||||
// to queue DATA frames. If B wins the race, a naive scheduler would eagerly
|
||||
// write as much data from B as possible, but this is suboptimal because A
|
||||
// is a higher-priority stream. With throttling enabled, we write a small
|
||||
// amount of data from B to minimize the amount of bandwidth that B can
|
||||
// steal from A.
|
||||
ThrottleOutOfOrderWrites bool
|
||||
}
|
||||
|
||||
// NewPriorityWriteScheduler constructs a WriteScheduler that schedules
|
||||
// frames by following HTTP/2 priorities as described in RFC 7540 Section 5.3.
|
||||
// If cfg is nil, default options are used.
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
import "math"
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
|
||||
package idna
|
||||
|
||||
// Transitional processing is disabled by default in Go 1.18.
|
||||
// https://golang.org/issue/47510
|
||||
const transitionalLookup = false
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user