mirror of
https://github.com/moby/moby.git
synced 2026-01-14 17:36:01 +00:00
Compare commits
173 Commits
copilot/fi
...
26.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3025fab86d | ||
|
|
2af1a28b19 | ||
|
|
6e95db3aa6 | ||
|
|
3a8eb7efe5 | ||
|
|
f5f15cef17 | ||
|
|
b6c3f5483b | ||
|
|
88c68b1226 | ||
|
|
faf6a54607 | ||
|
|
bdcc51c004 | ||
|
|
9c6669ca38 | ||
|
|
5205ea9f8e | ||
|
|
64a40af0ef | ||
|
|
f7c3505c82 | ||
|
|
3e0c4ba425 | ||
|
|
2a322a86ec | ||
|
|
89b3062abd | ||
|
|
e39ef953eb | ||
|
|
70ed458f9b | ||
|
|
3bda2f839e | ||
|
|
81b93d3e05 | ||
|
|
28e8a23afd | ||
|
|
d8a1415724 | ||
|
|
4d9560d6df | ||
|
|
357886087c | ||
|
|
37f2a4135a | ||
|
|
30ff73f25a | ||
|
|
30f2e54a95 | ||
|
|
f2bc077921 | ||
|
|
9b7903b291 | ||
|
|
53ecbc1e26 | ||
|
|
b61235e50a | ||
|
|
b3a92aedd5 | ||
|
|
ed9dba83fd | ||
|
|
d8674ab4d9 | ||
|
|
ef804f5954 | ||
|
|
95807d26b8 | ||
|
|
d960588787 | ||
|
|
936bfb792c | ||
|
|
590e249fb9 | ||
|
|
eaac976b86 | ||
|
|
01ba6c607d | ||
|
|
4c146e4945 | ||
|
|
5b2bd7a42c | ||
|
|
277b75e5c2 | ||
|
|
2e72912cdb | ||
|
|
8b58088b87 | ||
|
|
e462220e0e | ||
|
|
578c65d309 | ||
|
|
328131f6d9 | ||
|
|
8d9bd34f6d | ||
|
|
4dc89c2ba5 | ||
|
|
792b33ddad | ||
|
|
6ea1289acc | ||
|
|
254ff38eb1 | ||
|
|
c20fcb4d2c | ||
|
|
b097b8c18e | ||
|
|
0f69c67a27 | ||
|
|
8d5c53e1d4 | ||
|
|
9c662344a1 | ||
|
|
3fb5329499 | ||
|
|
4c81a932c0 | ||
|
|
d333ed926e | ||
|
|
c4c2efb855 | ||
|
|
ba6dcc61d2 | ||
|
|
e5b0f306c9 | ||
|
|
9e12578690 | ||
|
|
85daff009a | ||
|
|
0f4b73cd74 | ||
|
|
e581b23691 | ||
|
|
20bd79ac97 | ||
|
|
1b47203d9a | ||
|
|
237840446b | ||
|
|
eac73050d7 | ||
|
|
7a9644db86 | ||
|
|
de54a079af | ||
|
|
834ded3aee | ||
|
|
50ebafca50 | ||
|
|
c2ae605ee5 | ||
|
|
047fc79ee1 | ||
|
|
7464a5c496 | ||
|
|
d72ea00242 | ||
|
|
93b315efa5 | ||
|
|
a9ce67fb88 | ||
|
|
16c0d67f4d | ||
|
|
683a13108e | ||
|
|
74666f41d7 | ||
|
|
c363a59c2d | ||
|
|
6656e12aaa | ||
|
|
58a2b0e872 | ||
|
|
3a5a738a55 | ||
|
|
86885803ff | ||
|
|
3fe917f56a | ||
|
|
1b71859bb5 | ||
|
|
fe4cd44867 | ||
|
|
b5235a6c4a | ||
|
|
fbdfaeeedc | ||
|
|
b8835f4bba | ||
|
|
486d776e49 | ||
|
|
1b0bc01b70 | ||
|
|
f20597e377 | ||
|
|
12cdef7182 | ||
|
|
7ca5a37fbe | ||
|
|
2256402711 | ||
|
|
c7c462a7b6 | ||
|
|
d4afed04d6 | ||
|
|
0ea0289c77 | ||
|
|
77dcf9e04b | ||
|
|
5e22bc1b27 | ||
|
|
41446f1992 | ||
|
|
355f08e80f | ||
|
|
9a16ce3de0 | ||
|
|
ba83de48c9 | ||
|
|
715612aabb | ||
|
|
1be121e241 | ||
|
|
06cfa32468 | ||
|
|
13693101bf | ||
|
|
0732215719 | ||
|
|
b1754d1e1c | ||
|
|
411e817ddf | ||
|
|
9cc85eaef1 | ||
|
|
820cab90bc | ||
|
|
6bc49067a6 | ||
|
|
6fbdce4b94 | ||
|
|
f5334644ec | ||
|
|
c1d4587d76 | ||
|
|
d6428049a5 | ||
|
|
daba2462f5 | ||
|
|
de5c9cf0b9 | ||
|
|
c62dcf8ab1 | ||
|
|
17315a20ee | ||
|
|
cbd94183ab | ||
|
|
fb9f72aeb6 | ||
|
|
3115daaa91 | ||
|
|
2861734174 | ||
|
|
9c95aea306 | ||
|
|
3e09e197a7 | ||
|
|
65b679ac9c | ||
|
|
12072173df | ||
|
|
92f00d41ba | ||
|
|
a046857fbf | ||
|
|
476d9314d0 | ||
|
|
4021fa775e | ||
|
|
0fd5efe64a | ||
|
|
1017246d24 | ||
|
|
fbfa1bcc96 | ||
|
|
e12db89eef | ||
|
|
5838467405 | ||
|
|
1033f561af | ||
|
|
0fa6816991 | ||
|
|
eaad6f503d | ||
|
|
f679e1d4dc | ||
|
|
fda7b48363 | ||
|
|
258a372230 | ||
|
|
dbec963cee | ||
|
|
e35c6f5625 | ||
|
|
364e2d28ce | ||
|
|
06e19ec444 | ||
|
|
8e96db1c32 | ||
|
|
c21fe3efa3 | ||
|
|
86af4eddb3 | ||
|
|
73511cdee0 | ||
|
|
9326cda7bf | ||
|
|
76fcf9a8e0 | ||
|
|
ef1912d8b6 | ||
|
|
10739af81a | ||
|
|
ac2de55998 | ||
|
|
9a2b531127 | ||
|
|
2f5bbbe16b | ||
|
|
40618081f1 | ||
|
|
21da192ae4 | ||
|
|
2c91196921 | ||
|
|
a9a8787c93 | ||
|
|
c9689eccf5 |
@@ -1,6 +1,4 @@
|
||||
/.git
|
||||
|
||||
# build artifacts
|
||||
/bundles/
|
||||
/cmd/dockerd/winresources/winres.json
|
||||
/cmd/dockerd/*.syso
|
||||
.git
|
||||
bundles/
|
||||
cli/winresources/**/winres.json
|
||||
cli/winresources/**/*.syso
|
||||
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1 +1,3 @@
|
||||
Dockerfile* linguist-language=Dockerfile
|
||||
vendor.mod linguist-language=Go-Module
|
||||
vendor.sum linguist-language=Go-Checksums
|
||||
|
||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -19,14 +19,11 @@ Please provide the following information:
|
||||
|
||||
**- How to verify it**
|
||||
|
||||
**- Human readable description for the release notes**
|
||||
**- Description for the changelog**
|
||||
<!--
|
||||
Write a short (one line) summary that describes the changes in this
|
||||
pull request for inclusion in the changelog.
|
||||
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.
|
||||
It must be placed inside the below triple backticks section:
|
||||
-->
|
||||
```markdown changelog
|
||||
|
||||
|
||||
224
.github/copilot-instructions.md
vendored
224
.github/copilot-instructions.md
vendored
@@ -1,224 +0,0 @@
|
||||
# Moby Project Development Guide
|
||||
|
||||
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
||||
|
||||
## Critical Build and Test Environment Constraints
|
||||
|
||||
**IMPORTANT LIMITATIONS**: The development environment has significant constraints that prevent full Docker-based development container usage:
|
||||
- Docker container builds fail due to SSL certificate verification issues when downloading external dependencies
|
||||
- Integration tests require Docker daemon access which may not be available
|
||||
- Some validation tools (shfmt, gotestsum, golangci-lint) are not installed in the base environment
|
||||
- Privileged operations (chown, mount, network namespaces) are restricted
|
||||
|
||||
**DO NOT attempt the following**:
|
||||
- `make build` or `make shell` (Docker container build will fail)
|
||||
- Full integration test suites without Docker daemon access
|
||||
- Commands requiring privileged operations or Docker-in-Docker
|
||||
|
||||
## Working Effectively
|
||||
|
||||
### Bootstrap and Basic Build (WORKING METHODS)
|
||||
```bash
|
||||
# Navigate to repository
|
||||
cd /path/to/moby
|
||||
|
||||
# Download Go dependencies - takes 5-10 seconds
|
||||
go mod download
|
||||
|
||||
# Build main binaries directly with Go (RECOMMENDED)
|
||||
time go build ./cmd/dockerd # ~6-7 seconds, produces dockerd binary
|
||||
time go build ./cmd/docker-proxy # ~0.3 seconds, produces docker-proxy binary
|
||||
|
||||
# Verify binaries work
|
||||
./dockerd --version # Should show version info
|
||||
```
|
||||
|
||||
### Unit Testing
|
||||
```bash
|
||||
# Run unit tests for specific packages (FASTEST - ~1 second)
|
||||
time go test -short ./pkg/...
|
||||
|
||||
# Run with specific test directory
|
||||
TESTDIRS='./pkg/...' go test -short $TESTDIRS
|
||||
|
||||
# Run specific tests with timeout
|
||||
go test -short -timeout=5m ./pkg/authorization
|
||||
|
||||
# NEVER CANCEL: Full unit test suite can take 4+ minutes
|
||||
# Note: Many tests fail due to environment constraints (expected)
|
||||
time go test -short ./... # Takes ~4 minutes, set timeout to 10+ minutes
|
||||
```
|
||||
|
||||
### Validation and Linting
|
||||
```bash
|
||||
# Format checking (takes ~2 seconds, mostly clean except vendor/)
|
||||
gofmt -l .
|
||||
|
||||
# Go vet validation (takes ~1 second per package)
|
||||
go vet ./cmd/dockerd
|
||||
go vet ./pkg/...
|
||||
|
||||
# YAML linting (works but shows many style violations - takes ~40 seconds)
|
||||
yamllint . # NEVER CANCEL: Set timeout to 60+ minutes
|
||||
|
||||
# Check imports and basic structure
|
||||
go list -f '{{ join .Deps "\n" }}' ./cmd/dockerd | head -10
|
||||
```
|
||||
|
||||
## Manual Validation Scenarios
|
||||
|
||||
**ALWAYS test these scenarios after making changes**:
|
||||
|
||||
1. **Binary Build Validation**:
|
||||
```bash
|
||||
# Clean previous builds
|
||||
rm -f dockerd docker-proxy
|
||||
|
||||
# Build and verify
|
||||
go build ./cmd/dockerd && ./dockerd --version
|
||||
go build ./cmd/docker-proxy
|
||||
```
|
||||
|
||||
2. **Unit Test Validation** (for code in pkg/):
|
||||
```bash
|
||||
# Test specific package you modified
|
||||
go test -v ./pkg/[modified-package]
|
||||
|
||||
# Test all pkg packages (limited but fast)
|
||||
go test -short ./pkg/...
|
||||
```
|
||||
|
||||
3. **Code Quality Validation**:
|
||||
```bash
|
||||
# Check formatting
|
||||
gofmt -l . | grep -v vendor | head -5
|
||||
|
||||
# Verify imports work
|
||||
go list ./cmd/dockerd
|
||||
go list ./pkg/...
|
||||
```
|
||||
|
||||
## Repository Structure and Navigation
|
||||
|
||||
### Key Directories
|
||||
- `cmd/dockerd/` - Main Docker daemon binary
|
||||
- `cmd/docker-proxy/` - Docker proxy binary
|
||||
- `daemon/` - Core daemon implementation
|
||||
- `api/` - API definitions and client code
|
||||
- `client/` - Docker client library
|
||||
- `pkg/` - Shared packages (safest for testing)
|
||||
- `integration/` - Integration tests (require Docker daemon)
|
||||
- `hack/` - Build scripts and validation tools
|
||||
- `.github/workflows/` - CI/CD pipeline definitions
|
||||
|
||||
### Build System
|
||||
- `Makefile` - Main build interface (Docker-based, mostly non-functional in this environment)
|
||||
- `hack/make.sh` - Build script (requires Docker container)
|
||||
- `hack/test/unit` - Unit test script (requires gotestsum)
|
||||
- `hack/validate/` - Validation scripts (many require special tools)
|
||||
|
||||
### Go Modules
|
||||
```bash
|
||||
# Main module
|
||||
cat go.mod # Shows github.com/moby/moby/v2
|
||||
|
||||
# Submodules
|
||||
ls */go.mod # api/go.mod, client/go.mod, man/go.mod
|
||||
```
|
||||
|
||||
## Common Validation Tasks
|
||||
|
||||
### Pre-commit Validation (USE THESE)
|
||||
```bash
|
||||
# Quick validation workflow (~10 seconds total)
|
||||
gofmt -l . | grep -v vendor | head -5 # Check formatting
|
||||
go vet ./cmd/dockerd # Vet main package
|
||||
go build ./cmd/dockerd # Ensure it builds
|
||||
go test -short ./pkg/... # Test shared packages
|
||||
|
||||
# Extended validation (~4 minutes total)
|
||||
go test -short ./... # NEVER CANCEL: Set timeout to 10+ minutes
|
||||
```
|
||||
|
||||
### Code Changes Validation
|
||||
```bash
|
||||
# After modifying daemon code
|
||||
go build ./cmd/dockerd && ./dockerd --version
|
||||
|
||||
# After modifying pkg code
|
||||
go test -v ./pkg/[modified-package]
|
||||
|
||||
# After modifying client code
|
||||
cd client && go test ./...
|
||||
cd ..
|
||||
|
||||
# After modifying API definitions
|
||||
cd api && go test ./...
|
||||
cd ..
|
||||
```
|
||||
|
||||
## Timing Expectations and Timeouts
|
||||
|
||||
**CRITICAL**: Always set appropriate timeouts and NEVER CANCEL these operations:
|
||||
|
||||
- `go mod download`: 5-10 seconds (rarely needed, cached)
|
||||
- `go build ./cmd/dockerd`: 6-7 seconds (NEVER CANCEL - set 2+ minute timeout)
|
||||
- `go build ./cmd/docker-proxy`: 0.3 seconds
|
||||
- `go test -short ./pkg/...`: 1 second (cached), 5-10 seconds (first run)
|
||||
- `go test -short ./...`: 4+ minutes (NEVER CANCEL - set 10+ minute timeout)
|
||||
- `gofmt -l .`: 2 seconds
|
||||
- `yamllint .`: 40+ seconds (NEVER CANCEL - set 60+ minute timeout)
|
||||
|
||||
**WARNING**: Do not attempt these operations (they will fail):
|
||||
- `make build`: Fails due to Docker build issues (~15+ minutes timeout, will fail)
|
||||
- `make test`: Requires Docker container
|
||||
- Integration tests: Require Docker daemon access
|
||||
|
||||
## Architectural Overview
|
||||
|
||||
Moby is a container platform written in Go with these key components:
|
||||
|
||||
- **dockerd**: Main daemon process managing containers, images, networks
|
||||
- **docker-proxy**: Network proxy for container port mapping
|
||||
- **containerd**: Container runtime (external dependency)
|
||||
- **runc**: Low-level container runtime (external dependency)
|
||||
|
||||
### API Structure
|
||||
- REST API defined in `api/` directory
|
||||
- Client library in `client/` directory
|
||||
- Daemon implementation in `daemon/` directory
|
||||
- Shared utilities in `pkg/` directory
|
||||
|
||||
### Testing Strategy
|
||||
- Unit tests: Test individual packages and functions
|
||||
- Integration tests: Test API interactions (require Docker daemon)
|
||||
- End-to-end tests: Test complete workflows (require full environment)
|
||||
|
||||
## Environment Setup Notes
|
||||
|
||||
This repository uses:
|
||||
- **Go 1.24.6** (verified working)
|
||||
- **Docker 28.0.4** (CLI available, daemon access limited)
|
||||
- **Module system**: Multiple Go modules (main, api, client, man)
|
||||
- **Build tags**: Uses netgo, osusergo, static_build tags
|
||||
- **Cross-compilation**: Supports multiple platforms
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Issues
|
||||
- If `go build` fails: Check Go version, run `go mod download`
|
||||
- If tests fail with permission errors: Expected in restricted environment
|
||||
- If Docker commands fail: Use Go build commands instead
|
||||
|
||||
### Test Issues
|
||||
- Network-related test failures: Expected (namespace restrictions)
|
||||
- Permission errors: Expected (privilege restrictions)
|
||||
- Docker API errors: Expected (daemon not accessible)
|
||||
|
||||
### Working Around Limitations
|
||||
- Use `go test -short` to skip long-running tests
|
||||
- Focus on `./pkg/...` tests which have fewer dependencies
|
||||
- Use `go build` directly instead of Make targets
|
||||
- Validate formatting with `gofmt` instead of full validation suite
|
||||
|
||||
Always validate your changes work with the core Go commands before submitting.
|
||||
13
.github/workflows/.dco.yml
vendored
13
.github/workflows/.dco.yml
vendored
@@ -3,24 +3,15 @@ name: .dco
|
||||
|
||||
# TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
ALPINE_VERSION: "3.22"
|
||||
ALPINE_VERSION: "3.20"
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10 # guardrails timeout for the whole job
|
||||
steps:
|
||||
-
|
||||
|
||||
36
.github/workflows/.test-prepare.yml
vendored
Normal file
36
.github/workflows/.test-prepare.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# reusable workflow
|
||||
name: .test-prepare
|
||||
|
||||
# TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
matrix:
|
||||
description: Test matrix
|
||||
value: ${{ jobs.run.outputs.matrix }}
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
outputs:
|
||||
matrix: ${{ steps.set.outputs.matrix }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Create matrix
|
||||
id: set
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let matrix = ['graphdriver'];
|
||||
if ("${{ contains(github.event.pull_request.labels.*.name, 'containerd-integration') || github.event_name != 'pull_request' }}" == "true") {
|
||||
matrix.push('snapshotter');
|
||||
}
|
||||
await core.group(`Set matrix`, async () => {
|
||||
core.info(`matrix: ${JSON.stringify(matrix)}`);
|
||||
core.setOutput('matrix', JSON.stringify(matrix));
|
||||
});
|
||||
123
.github/workflows/.test-unit.yml
vendored
123
.github/workflows/.test-unit.yml
vendored
@@ -1,123 +0,0 @@
|
||||
# reusable workflow
|
||||
name: .test-unit
|
||||
|
||||
# TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24.6"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
mode:
|
||||
- ""
|
||||
- firewalld
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
CACHE_DEV_SCOPE=dev
|
||||
if [[ "${{ matrix.mode }}" == *"firewalld"* ]]; then
|
||||
echo "FIREWALLD=true" >> $GITHUB_ENV
|
||||
CACHE_DEV_SCOPE="${CACHE_DEV_SCOPE}firewalld"
|
||||
fi
|
||||
echo "CACHE_DEV_SCOPE=${CACHE_DEV_SCOPE}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
dev.cache-from=type=gha,scope=${{ env.CACHE_DEV_SCOPE }}
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
make -o build test-unit
|
||||
-
|
||||
name: Prepare reports
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p bundles /tmp/reports
|
||||
find bundles -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
|
||||
tar -xzf /tmp/reports.tar.gz -C /tmp/reports
|
||||
sudo chown -R $(id -u):$(id -g) /tmp/reports
|
||||
tree -nh /tmp/reports
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
directory: ./bundles
|
||||
env_vars: RUNNER_OS
|
||||
flags: unit
|
||||
token: ${{ secrets.CODECOV_TOKEN }} # used to upload coverage reports: https://github.com/moby/buildkit/pull/4660#issue-2142122533
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-unit--${{ matrix.mode }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
unit-report:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always()
|
||||
needs:
|
||||
- unit
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: test-reports-unit-*
|
||||
path: /tmp/reports
|
||||
-
|
||||
name: Install teststat
|
||||
run: |
|
||||
go install github.com/vearutop/teststat@${{ env.TESTSTAT_VERSION }}
|
||||
-
|
||||
name: Create summary
|
||||
run: |
|
||||
find /tmp/reports -type f -name '*-go-test-report.json' -exec teststat -markdown {} \+ >> $GITHUB_STEP_SUMMARY
|
||||
274
.github/workflows/.test.yml
vendored
274
.github/workflows/.test.yml
vendored
@@ -3,15 +3,6 @@ name: .test
|
||||
|
||||
# TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -21,19 +12,96 @@ on:
|
||||
default: "graphdriver"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24.6"
|
||||
GO_VERSION: "1.22.10"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
ITG_CLI_MATRIX_SIZE: 6
|
||||
DOCKER_EXPERIMENTAL: 1
|
||||
DOCKER_GRAPHDRIVER: ${{ inputs.storage == 'snapshotter' && 'overlayfs' || 'overlay2' }}
|
||||
TEST_INTEGRATION_USE_GRAPHDRIVER: ${{ inputs.storage == 'graphdriver' && '1' || '' }}
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
TEST_INTEGRATION_USE_SNAPSHOTTER: ${{ inputs.storage == 'snapshotter' && '1' || '' }}
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
dev.cache-from=type=gha,scope=dev
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
make -o build test-unit
|
||||
-
|
||||
name: Prepare reports
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p bundles /tmp/reports
|
||||
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
|
||||
tar -xzf /tmp/reports.tar.gz -C /tmp/reports
|
||||
sudo chown -R $(id -u):$(id -g) /tmp/reports
|
||||
tree -nh /tmp/reports
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
directory: ./bundles
|
||||
env_vars: RUNNER_OS
|
||||
flags: unit
|
||||
token: ${{ secrets.CODECOV_TOKEN }} # used to upload coverage reports: https://github.com/moby/buildkit/pull/4660#issue-2142122533
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-unit-${{ inputs.storage }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
unit-report:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always()
|
||||
needs:
|
||||
- unit
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: test-reports-unit-${{ inputs.storage }}
|
||||
path: /tmp/reports
|
||||
-
|
||||
name: Install teststat
|
||||
run: |
|
||||
go install github.com/vearutop/teststat@${{ env.TESTSTAT_VERSION }}
|
||||
-
|
||||
name: Create summary
|
||||
run: |
|
||||
find /tmp/reports -type f -name '*-go-test-report.json' -exec teststat -markdown {} \+ >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
docker-py:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
@@ -49,13 +117,9 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
@@ -63,7 +127,7 @@ jobs:
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
make TEST_SKIP_INTEGRATION_CLI=1 -o build test-docker-py
|
||||
make -o build test-docker-py
|
||||
-
|
||||
name: Prepare reports
|
||||
if: always()
|
||||
@@ -90,7 +154,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
integration-flaky:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
@@ -103,13 +167,9 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
@@ -121,52 +181,21 @@ jobs:
|
||||
env:
|
||||
TEST_SKIP_INTEGRATION_CLI: 1
|
||||
|
||||
integration-prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
outputs:
|
||||
includes: ${{ steps.set.outputs.includes }}
|
||||
steps:
|
||||
-
|
||||
name: Create matrix includes
|
||||
id: set
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let includes = [
|
||||
{ os: 'ubuntu-22.04', mode: '' },
|
||||
{ os: 'ubuntu-22.04', mode: 'rootless' },
|
||||
{ os: 'ubuntu-22.04', mode: 'systemd' },
|
||||
{ os: 'ubuntu-24.04', mode: '' },
|
||||
// { os: 'ubuntu-24.04', mode: 'rootless' }, // FIXME: https://github.com/moby/moby/pull/49579#issuecomment-2698622223
|
||||
{ os: 'ubuntu-24.04', mode: 'systemd' },
|
||||
// { os: 'ubuntu-24.04', mode: 'rootless-systemd' }, // FIXME: https://github.com/moby/moby/issues/44084
|
||||
];
|
||||
if ("${{ inputs.storage }}" == "snapshotter") {
|
||||
includes.push({ os: 'ubuntu-24.04', mode: 'iptables+firewalld' });
|
||||
includes.push({ os: 'ubuntu-24.04', mode: 'nftables' });
|
||||
includes.push({ os: 'ubuntu-24.04', mode: 'nftables+firewalld' });
|
||||
}
|
||||
await core.group(`Set matrix`, async () => {
|
||||
core.info(`matrix: ${JSON.stringify(includes)}`);
|
||||
core.setOutput('includes', JSON.stringify(includes));
|
||||
});
|
||||
-
|
||||
name: Show matrix
|
||||
run: |
|
||||
echo ${{ steps.set.outputs.includes }}
|
||||
|
||||
integration:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
needs:
|
||||
- integration-prepare
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.integration-prepare.outputs.includes) }}
|
||||
os:
|
||||
- ubuntu-20.04
|
||||
- ubuntu-22.04
|
||||
mode:
|
||||
- ""
|
||||
- rootless
|
||||
- systemd
|
||||
#- rootless-systemd FIXME: https://github.com/moby/moby/issues/44084
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@@ -188,24 +217,13 @@ jobs:
|
||||
echo "SYSTEMD=true" >> $GITHUB_ENV
|
||||
CACHE_DEV_SCOPE="${CACHE_DEV_SCOPE}systemd"
|
||||
fi
|
||||
if [[ "${{ matrix.mode }}" == *"firewalld"* ]]; then
|
||||
echo "FIREWALLD=true" >> $GITHUB_ENV
|
||||
CACHE_DEV_SCOPE="${CACHE_DEV_SCOPE}firewalld"
|
||||
fi
|
||||
if [[ "${{ matrix.mode }}" == *"nftables"* ]]; then
|
||||
echo "DOCKER_FIREWALL_BACKEND=nftables" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "CACHE_DEV_SCOPE=${CACHE_DEV_SCOPE}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
@@ -258,7 +276,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
integration-report:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always()
|
||||
@@ -270,7 +288,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -288,11 +305,11 @@ jobs:
|
||||
find /tmp/reports -type f -name '*-go-test-report.json' -exec teststat -markdown {} \+ >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
integration-cli-prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
outputs:
|
||||
matrix: ${{ steps.set.outputs.matrix }}
|
||||
matrix: ${{ steps.tests.outputs.matrix }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@@ -302,13 +319,12 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Install gotestlist
|
||||
run:
|
||||
go install github.com/crazy-max/gotestlist/cmd/gotestlist@${{ env.GOTESTLIST_VERSION }}
|
||||
-
|
||||
name: Create test matrix
|
||||
name: Create matrix
|
||||
id: tests
|
||||
working-directory: ./integration-cli
|
||||
run: |
|
||||
@@ -320,77 +336,20 @@ jobs:
|
||||
matrix="$(gotestlist -d ${{ env.ITG_CLI_MATRIX_SIZE }} -o "./..." -o "DockerSwarmSuite" -o "DockerNetworkSuite|DockerExternalVolumeSuite" ./...)"
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Create gha matrix
|
||||
id: set
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let matrix = {
|
||||
test: ${{ steps.tests.outputs.matrix }},
|
||||
include: [],
|
||||
};
|
||||
// For some reasons, GHA doesn't combine a dynamically defined
|
||||
// 'include' with other matrix variables that aren't part of the
|
||||
// include items.
|
||||
// Moreover, since the goal is to run only relevant tests with
|
||||
// firewalld/nftables enabled to minimize the number of CI jobs, we
|
||||
// statically define the list of test suites that we want to run.
|
||||
if ("${{ inputs.storage }}" == "snapshotter") {
|
||||
matrix.include.push({
|
||||
'mode': 'iptables+firewalld',
|
||||
'test': 'DockerCLINetworkSuite|DockerCLIPortSuite|DockerDaemonSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'iptables+firewalld',
|
||||
'test': 'DockerSwarmSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'iptables+firewalld',
|
||||
'test': 'DockerNetworkSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables',
|
||||
'test': 'DockerCLINetworkSuite|DockerCLIPortSuite|DockerDaemonSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables',
|
||||
'test': 'DockerSwarmSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables',
|
||||
'test': 'DockerNetworkSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables+firewalld',
|
||||
'test': 'DockerCLINetworkSuite|DockerCLIPortSuite|DockerDaemonSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables+firewalld',
|
||||
'test': 'DockerSwarmSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables+firewalld',
|
||||
'test': 'DockerNetworkSuite'
|
||||
});
|
||||
}
|
||||
await core.group(`Set matrix`, async () => {
|
||||
core.info(`matrix: ${JSON.stringify(matrix)}`);
|
||||
core.setOutput('matrix', JSON.stringify(matrix));
|
||||
});
|
||||
-
|
||||
name: Show final gha matrix
|
||||
name: Show matrix
|
||||
run: |
|
||||
echo ${{ steps.set.outputs.matrix }}
|
||||
echo ${{ steps.tests.outputs.matrix }}
|
||||
|
||||
integration-cli:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
needs:
|
||||
- integration-cli-prepare
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.integration-cli-prepare.outputs.matrix) }}
|
||||
matrix:
|
||||
test: ${{ fromJson(needs.integration-cli-prepare.outputs.matrix) }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@@ -401,28 +360,12 @@ jobs:
|
||||
-
|
||||
name: Set up tracing
|
||||
uses: ./.github/actions/setup-tracing
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
CACHE_DEV_SCOPE=dev
|
||||
if [[ "${{ matrix.mode }}" == *"firewalld"* ]]; then
|
||||
echo "FIREWALLD=true" >> $GITHUB_ENV
|
||||
CACHE_DEV_SCOPE="${CACHE_DEV_SCOPE}firewalld"
|
||||
fi
|
||||
if [[ "${{ matrix.mode }}" == *"nftables"* ]]; then
|
||||
echo "DOCKER_FIREWALL_BACKEND=nftables" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "CACHE_DEV_SCOPE=${CACHE_DEV_SCOPE}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
@@ -469,12 +412,12 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-${{ env.TESTREPORTS_NAME }}
|
||||
name: test-reports-integration-cli-${{ inputs.storage }}-${{ env.TESTREPORTS_NAME }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
integration-cli-report:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always()
|
||||
@@ -486,13 +429,12 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/reports
|
||||
pattern: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-*
|
||||
pattern: test-reports-integration-cli-${{ inputs.storage }}-*
|
||||
merge-multiple: true
|
||||
-
|
||||
name: Install teststat
|
||||
|
||||
86
.github/workflows/.windows.yml
vendored
86
.github/workflows/.windows.yml
vendored
@@ -3,15 +3,6 @@ name: .windows
|
||||
|
||||
# TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -28,12 +19,12 @@ on:
|
||||
default: false
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24.6"
|
||||
GO_VERSION: "1.22.10"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore
|
||||
WINDOWS_BASE_TAG_2019: ltsc2019
|
||||
WINDOWS_BASE_TAG_2022: ltsc2022
|
||||
WINDOWS_BASE_TAG_2025: ltsc2025
|
||||
TEST_IMAGE_NAME: moby:test
|
||||
TEST_CTN_NAME: moby
|
||||
DOCKER_BUILDKIT: 0
|
||||
@@ -65,8 +56,8 @@ jobs:
|
||||
run: |
|
||||
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build"
|
||||
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod"
|
||||
If ("${{ inputs.os }}" -eq "windows-2025") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
If ("${{ inputs.os }}" -eq "windows-2019") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2019 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
} ElseIf ("${{ inputs.os }}" -eq "windows-2022") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
@@ -92,6 +83,7 @@ jobs:
|
||||
& docker build `
|
||||
--build-arg WINDOWS_BASE_IMAGE `
|
||||
--build-arg WINDOWS_BASE_IMAGE_TAG `
|
||||
--build-arg GO_VERSION `
|
||||
-t ${{ env.TEST_IMAGE_NAME }} `
|
||||
-f Dockerfile.windows .
|
||||
-
|
||||
@@ -144,8 +136,8 @@ jobs:
|
||||
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build"
|
||||
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod"
|
||||
New-Item -ItemType "directory" -Path "bundles"
|
||||
If ("${{ inputs.os }}" -eq "windows-2025") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
If ("${{ inputs.os }}" -eq "windows-2019") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2019 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
} ElseIf ("${{ inputs.os }}" -eq "windows-2022") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
@@ -171,6 +163,7 @@ jobs:
|
||||
& docker build `
|
||||
--build-arg WINDOWS_BASE_IMAGE `
|
||||
--build-arg WINDOWS_BASE_IMAGE_TAG `
|
||||
--build-arg GO_VERSION `
|
||||
-t ${{ env.TEST_IMAGE_NAME }} `
|
||||
-f Dockerfile.windows .
|
||||
-
|
||||
@@ -212,7 +205,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -242,7 +234,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Install gotestlist
|
||||
run:
|
||||
@@ -319,8 +310,8 @@ jobs:
|
||||
name: Init
|
||||
run: |
|
||||
New-Item -ItemType "directory" -Path "bundles"
|
||||
If ("${{ inputs.os }}" -eq "windows-2025") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
If ("${{ inputs.os }}" -eq "windows-2019") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2019 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
} ElseIf ("${{ inputs.os }}" -eq "windows-2022") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
@@ -347,12 +338,33 @@ jobs:
|
||||
$ErrorActionPreference = "Stop"
|
||||
Write-Host "Service removed"
|
||||
}
|
||||
-
|
||||
name: Starting containerd
|
||||
if: matrix.runtime == 'containerd'
|
||||
run: |
|
||||
Write-Host "Generating config"
|
||||
& "${{ env.BIN_OUT }}\containerd.exe" config default | Out-File "$env:TEMP\ctn.toml" -Encoding ascii
|
||||
Write-Host "Creating service"
|
||||
New-Item -ItemType Directory "$env:TEMP\ctn-root" -ErrorAction SilentlyContinue | Out-Null
|
||||
New-Item -ItemType Directory "$env:TEMP\ctn-state" -ErrorAction SilentlyContinue | Out-Null
|
||||
Start-Process -Wait "${{ env.BIN_OUT }}\containerd.exe" `
|
||||
-ArgumentList "--log-level=debug", `
|
||||
"--config=$env:TEMP\ctn.toml", `
|
||||
"--address=\\.\pipe\containerd-containerd", `
|
||||
"--root=$env:TEMP\ctn-root", `
|
||||
"--state=$env:TEMP\ctn-state", `
|
||||
"--log-file=$env:TEMP\ctn.log", `
|
||||
"--register-service"
|
||||
Write-Host "Starting service"
|
||||
Start-Service -Name containerd
|
||||
Start-Sleep -Seconds 5
|
||||
Write-Host "Service started successfully!"
|
||||
-
|
||||
name: Starting test daemon
|
||||
run: |
|
||||
Write-Host "Creating service"
|
||||
If ("${{ matrix.runtime }}" -eq "containerd") {
|
||||
$runtimeArg="--default-runtime=io.containerd.runhcs.v1"
|
||||
$runtimeArg="--containerd=\\.\pipe\containerd-containerd"
|
||||
echo "DOCKER_WINDOWS_CONTAINERD_RUNTIME=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
New-Item -ItemType Directory "$env:TEMP\moby-root" -ErrorAction SilentlyContinue | Out-Null
|
||||
@@ -364,10 +376,10 @@ jobs:
|
||||
"--exec-root=$env:TEMP\moby-exec", `
|
||||
"--pidfile=$env:TEMP\docker.pid", `
|
||||
"--register-service"
|
||||
If ("${{ inputs.storage }}" -eq "graphdriver") {
|
||||
If ("${{ inputs.storage }}" -eq "snapshotter") {
|
||||
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
|
||||
& reg add "HKLM\SYSTEM\CurrentControlSet\Services\docker" /v Environment /t REG_MULTI_SZ /s '@' /d TEST_INTEGRATION_USE_GRAPHDRIVER=1
|
||||
echo "TEST_INTEGRATION_USE_GRAPHDRIVER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
& reg add "HKLM\SYSTEM\CurrentControlSet\Services\docker" /v Environment /t REG_MULTI_SZ /s '@' /d TEST_INTEGRATION_USE_SNAPSHOTTER=1
|
||||
echo "TEST_INTEGRATION_USE_SNAPSHOTTER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
Write-Host "Starting service"
|
||||
Start-Service -Name docker
|
||||
@@ -392,17 +404,6 @@ jobs:
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
Write-Host "Test daemon started and replied!"
|
||||
If ("${{ matrix.runtime }}" -eq "containerd") {
|
||||
$containerdProcesses = Get-Process -Name containerd -ErrorAction:SilentlyContinue
|
||||
If (-not $containerdProcesses) {
|
||||
Throw "containerd process is not running"
|
||||
} else {
|
||||
foreach ($process in $containerdProcesses) {
|
||||
$processPath = (Get-Process -Id $process.Id -FileVersionInfo).FileName
|
||||
Write-Output "Running containerd instance binary Path: $($processPath)"
|
||||
}
|
||||
}
|
||||
}
|
||||
env:
|
||||
DOCKER_HOST: npipe:////./pipe/docker_engine
|
||||
-
|
||||
@@ -431,7 +432,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Test integration
|
||||
if: matrix.test == './...'
|
||||
@@ -439,6 +439,7 @@ jobs:
|
||||
.\hack\make.ps1 -TestIntegration
|
||||
env:
|
||||
DOCKER_HOST: npipe:////./pipe/docker_engine
|
||||
GO111MODULE: "off"
|
||||
TEST_CLIENT_BINARY: ${{ env.BIN_OUT }}\docker
|
||||
-
|
||||
name: Test integration-cli
|
||||
@@ -447,6 +448,7 @@ jobs:
|
||||
.\hack\make.ps1 -TestIntegrationCli
|
||||
env:
|
||||
DOCKER_HOST: npipe:////./pipe/docker_engine
|
||||
GO111MODULE: "off"
|
||||
TEST_CLIENT_BINARY: ${{ env.BIN_OUT }}\docker
|
||||
INTEGRATION_TESTRUN: ${{ matrix.test }}
|
||||
-
|
||||
@@ -465,6 +467,19 @@ jobs:
|
||||
& "${{ env.BIN_OUT }}\docker" info
|
||||
env:
|
||||
DOCKER_HOST: npipe:////./pipe/docker_engine
|
||||
-
|
||||
name: Stop containerd
|
||||
if: always() && matrix.runtime == 'containerd'
|
||||
run: |
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
Stop-Service -Force -Name containerd
|
||||
$ErrorActionPreference = "Stop"
|
||||
-
|
||||
name: Containerd logs
|
||||
if: always() && matrix.runtime == 'containerd'
|
||||
run: |
|
||||
Copy-Item "$env:TEMP\ctn.log" -Destination ".\bundles\containerd.log"
|
||||
Get-Content "$env:TEMP\ctn.log" | Out-Host
|
||||
-
|
||||
name: Stop daemon
|
||||
if: always()
|
||||
@@ -523,7 +538,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
15
.github/workflows/arm64.yml
vendored
15
.github/workflows/arm64.yml
vendored
@@ -19,11 +19,10 @@ on:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24.6"
|
||||
GO_VERSION: "1.22.10"
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
DESTDIR: ./build
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
@@ -35,7 +34,7 @@ jobs:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-22.04-arm
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
@@ -68,7 +67,7 @@ jobs:
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||
|
||||
build-dev:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-22.04-arm
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
@@ -91,7 +90,7 @@ jobs:
|
||||
*.output=type=cacheonly
|
||||
|
||||
test-unit:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-22.04-arm
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- build-dev
|
||||
@@ -147,7 +146,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
test-unit-report:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always()
|
||||
@@ -176,7 +175,7 @@ jobs:
|
||||
find /tmp/reports -type f -name '*-go-test-report.json' -exec teststat -markdown {} \+ >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
test-integration:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-22.04-arm
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
needs:
|
||||
@@ -246,7 +245,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
test-integration-report:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always()
|
||||
|
||||
56
.github/workflows/bin-image.yml
vendored
56
.github/workflows/bin-image.yml
vendored
@@ -1,14 +1,5 @@
|
||||
name: bin-image
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -19,7 +10,6 @@ on:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
@@ -31,8 +21,6 @@ env:
|
||||
PLATFORM: Moby Engine - Nightly
|
||||
PRODUCT: moby-bin
|
||||
PACKAGER_NAME: The Moby Project
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
@@ -40,7 +28,7 @@ jobs:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
outputs:
|
||||
platforms: ${{ steps.platforms.outputs.matrix }}
|
||||
@@ -58,21 +46,20 @@ jobs:
|
||||
### versioning strategy
|
||||
## push semver tag v23.0.0
|
||||
# moby/moby-bin:23.0.0
|
||||
# moby/moby-bin:23.0
|
||||
# moby/moby-bin:23
|
||||
# moby/moby-bin:latest
|
||||
## push semver prerelease tag v23.0.0-beta.1
|
||||
## push semver prelease tag v23.0.0-beta.1
|
||||
# moby/moby-bin:23.0.0-beta.1
|
||||
## push on master
|
||||
# moby/moby-bin:master
|
||||
## push on 28.x branch
|
||||
# moby/moby-bin:28.x
|
||||
## push on 23.0 branch
|
||||
# moby/moby-bin:23.0
|
||||
## any push
|
||||
# moby/moby-bin:sha-ad132f5
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
-
|
||||
name: Rename meta bake definition file
|
||||
# see https://github.com/docker/metadata-action/issues/381#issuecomment-1918607161
|
||||
@@ -94,8 +81,8 @@ jobs:
|
||||
echo "matrix=$(docker buildx bake bin-image-cross --print | jq -cr '.target."bin-image-cross".platforms')" >>${GITHUB_OUTPUT}
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
- prepare
|
||||
@@ -105,16 +92,16 @@ jobs:
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Download meta bake definition
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -127,10 +114,6 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request' && github.repository == 'moby/moby'
|
||||
@@ -141,9 +124,8 @@ jobs:
|
||||
-
|
||||
name: Build
|
||||
id: bake
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
source: .
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
/tmp/bake-meta.json
|
||||
@@ -170,8 +152,8 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 40 # guardrails timeout for the whole job
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- build
|
||||
if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && github.event_name != 'pull_request' && github.repository == 'moby/moby'
|
||||
@@ -192,10 +174,6 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
|
||||
250
.github/workflows/buildkit.yml
vendored
250
.github/workflows/buildkit.yml
vendored
@@ -1,14 +1,5 @@
|
||||
name: buildkit
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -19,35 +10,31 @@ on:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24.6"
|
||||
GO_VERSION: "1.22.10"
|
||||
DESTDIR: ./build
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-24.04
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: binary
|
||||
-
|
||||
@@ -59,11 +46,11 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
test-linux:
|
||||
runs-on: ubuntu-24.04
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- build-linux
|
||||
- build
|
||||
env:
|
||||
TEST_IMAGE_BUILD: "0"
|
||||
TEST_IMAGE_ID: "buildkit-tests"
|
||||
@@ -106,7 +93,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: BuildKit ref
|
||||
run: |
|
||||
@@ -125,10 +111,6 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Download binary artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -144,9 +126,8 @@ jobs:
|
||||
docker info
|
||||
-
|
||||
name: Build test image
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
source: .
|
||||
workdir: ./buildkit
|
||||
targets: integration-tests
|
||||
set: |
|
||||
@@ -162,212 +143,3 @@ jobs:
|
||||
TESTPKGS: "./${{ matrix.pkg }}"
|
||||
TESTFLAGS: "-v --parallel=1 --timeout=30m --run=//worker=${{ matrix.worker }}$"
|
||||
working-directory: buildkit
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 120
|
||||
needs:
|
||||
- validate-dco
|
||||
env:
|
||||
GOPATH: ${{ github.workspace }}\go
|
||||
GOBIN: ${{ github.workspace }}\go\bin
|
||||
BIN_OUT: ${{ github.workspace }}\out
|
||||
WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore
|
||||
WINDOWS_BASE_TAG_2022: ltsc2022
|
||||
TEST_IMAGE_NAME: moby:test
|
||||
TEST_CTN_NAME: moby
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/docker
|
||||
|
||||
- name: Env
|
||||
run: |
|
||||
Get-ChildItem Env: | Out-String
|
||||
- name: Moby - Init
|
||||
run: |
|
||||
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build"
|
||||
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod"
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
~\go\pkg\mod
|
||||
${{ github.workspace }}\go-build
|
||||
${{ env.GOPATH }}\pkg\mod
|
||||
key: ${{ inputs.os }}-${{ github.job }}-${{ hashFiles('**/vendor.sum') }}
|
||||
restore-keys: |
|
||||
${{ inputs.os }}-${{ github.job }}-
|
||||
|
||||
- name: Docker info
|
||||
run: |
|
||||
docker info
|
||||
|
||||
- name: Build base image
|
||||
run: |
|
||||
& docker build `
|
||||
--build-arg WINDOWS_BASE_IMAGE `
|
||||
--build-arg WINDOWS_BASE_IMAGE_TAG `
|
||||
-t ${{ env.TEST_IMAGE_NAME }} `
|
||||
-f Dockerfile.windows .
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
& docker run --name ${{ env.TEST_CTN_NAME }} -e "DOCKER_GITCOMMIT=${{ github.sha }}" `
|
||||
-v "${{ github.workspace }}\go-build:C:\Users\ContainerAdministrator\AppData\Local\go-build" `
|
||||
-v "${{ github.workspace }}\go\pkg\mod:C:\gopath\pkg\mod" `
|
||||
${{ env.TEST_IMAGE_NAME }} hack\make.ps1 -Daemon -Client
|
||||
go install github.com/distribution/distribution/v3/cmd/registry@latest
|
||||
|
||||
- name: Checkout BuildKit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: moby/buildkit
|
||||
ref: master
|
||||
path: buildkit
|
||||
|
||||
- name: Add buildctl to binaries
|
||||
run: |
|
||||
go install ./cmd/buildctl
|
||||
working-directory: buildkit
|
||||
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
New-Item -ItemType "directory" -Path "${{ env.BIN_OUT }}"
|
||||
docker cp "${{ env.TEST_CTN_NAME }}`:c`:\gopath\src\github.com\docker\docker\bundles\docker.exe" ${{ env.BIN_OUT }}\
|
||||
docker cp "${{ env.TEST_CTN_NAME }}`:c`:\gopath\src\github.com\docker\docker\bundles\dockerd.exe" ${{ env.BIN_OUT }}\
|
||||
docker cp "${{ env.TEST_CTN_NAME }}`:c`:\gopath\bin\gotestsum.exe" ${{ env.BIN_OUT }}\
|
||||
docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd.exe" ${{ env.BIN_OUT }}\
|
||||
docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd-shim-runhcs-v1.exe" ${{ env.BIN_OUT }}\
|
||||
cp ${{ env.GOPATH }}\bin\registry.exe ${{ env.BIN_OUT }}
|
||||
cp ${{ env.GOPATH }}\bin\buildctl.exe ${{ env.BIN_OUT }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-windows
|
||||
path: ${{ env.BIN_OUT }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 2
|
||||
|
||||
test-windows:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- build-windows
|
||||
env:
|
||||
TEST_IMAGE_BUILD: "0"
|
||||
TEST_IMAGE_ID: "buildkit-tests"
|
||||
GOPATH: ${{ github.workspace }}\go
|
||||
GOBIN: ${{ github.workspace }}\go\bin
|
||||
BIN_OUT: ${{ github.workspace }}\out
|
||||
TESTFLAGS: "-v --timeout=90m"
|
||||
TEST_DOCKERD: "1"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
worker:
|
||||
- dockerd-containerd
|
||||
pkg:
|
||||
- ./client#1-4
|
||||
- ./client#2-4
|
||||
- ./client#3-4
|
||||
- ./client#4-4
|
||||
- ./cmd/buildctl
|
||||
- ./frontend
|
||||
- ./frontend/dockerfile#1-12
|
||||
- ./frontend/dockerfile#2-12
|
||||
- ./frontend/dockerfile#3-12
|
||||
- ./frontend/dockerfile#4-12
|
||||
- ./frontend/dockerfile#5-12
|
||||
- ./frontend/dockerfile#6-12
|
||||
- ./frontend/dockerfile#7-12
|
||||
- ./frontend/dockerfile#8-12
|
||||
- ./frontend/dockerfile#9-12
|
||||
- ./frontend/dockerfile#10-12
|
||||
- ./frontend/dockerfile#11-12
|
||||
- ./frontend/dockerfile#12-12
|
||||
steps:
|
||||
- name: Prepare
|
||||
shell: bash
|
||||
run: |
|
||||
disabledFeatures="cache_backend_azblob,cache_backend_s3"
|
||||
if [ "${{ matrix.worker }}" = "dockerd" ]; then
|
||||
disabledFeatures="${disabledFeatures},merge_diff"
|
||||
fi
|
||||
echo "BUILDKIT_TEST_DISABLE_FEATURES=${disabledFeatures}" >> $GITHUB_ENV
|
||||
- name: Expose GitHub Runtime
|
||||
uses: crazy-max/ghaction-github-runtime@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: moby
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
- name: BuildKit ref
|
||||
shell: bash
|
||||
run: |
|
||||
echo "$(./hack/buildkit-ref)" >> $GITHUB_ENV
|
||||
working-directory: moby
|
||||
- name: Checkout BuildKit ${{ env.BUILDKIT_REF }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.BUILDKIT_REPO }}
|
||||
ref: ${{ env.BUILDKIT_REF }}
|
||||
path: buildkit
|
||||
|
||||
- name: Download Moby artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-windows
|
||||
path: ${{ env.BIN_OUT }}
|
||||
|
||||
- name: Add binaries to PATH
|
||||
run: |
|
||||
ls ${{ env.BIN_OUT }}
|
||||
Write-Output "${{ env.BIN_OUT }}" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Test Prep
|
||||
shell: bash
|
||||
run: |
|
||||
TESTPKG=$(echo "${{ matrix.pkg }}" | awk '-F#' '{print $1}')
|
||||
echo "TESTPKG=$TESTPKG" >> $GITHUB_ENV
|
||||
echo "TEST_REPORT_NAME=${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
testFlags="${{ env.TESTFLAGS }}"
|
||||
testSlice=$(echo "${{ matrix.pkg }}" | awk '-F#' '{print $2}')
|
||||
testSliceOffset=""
|
||||
if [ -n "$testSlice" ]; then
|
||||
testSliceOffset="slice=$testSlice/"
|
||||
fi
|
||||
if [ -n "${{ matrix.worker }}" ]; then
|
||||
testFlags="${testFlags} --run=TestIntegration/$testSliceOffset.*/worker=${{ matrix.worker }}"
|
||||
fi
|
||||
echo "TESTFLAGS=${testFlags}" >> $GITHUB_ENV
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ./bin/testreports
|
||||
gotestsum \
|
||||
--jsonfile="./bin/testreports/go-test-report-${{ env.TEST_REPORT_NAME }}.json" \
|
||||
--junitfile="./bin/testreports/junit-report-${{ env.TEST_REPORT_NAME }}.xml" \
|
||||
--packages="${{ env.TESTPKG }}" \
|
||||
-- \
|
||||
"-mod=vendor" \
|
||||
"-coverprofile" "./bin/testreports/coverage-${{ env.TEST_REPORT_NAME }}.txt" \
|
||||
"-covermode" "atomic" ${{ env.TESTFLAGS }}
|
||||
working-directory: buildkit
|
||||
|
||||
88
.github/workflows/ci.yml
vendored
88
.github/workflows/ci.yml
vendored
@@ -1,14 +1,5 @@
|
||||
name: ci
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -19,20 +10,17 @@ on:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DESTDIR: ./build
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
@@ -43,16 +31,17 @@ jobs:
|
||||
- binary
|
||||
- dynbinary
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
-
|
||||
@@ -87,7 +76,7 @@ jobs:
|
||||
echo ${{ steps.platforms.outputs.matrix }}
|
||||
|
||||
cross:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
@@ -97,6 +86,11 @@ jobs:
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare-cross.outputs.matrix) }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
@@ -105,13 +99,9 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: all
|
||||
set: |
|
||||
@@ -124,53 +114,3 @@ jobs:
|
||||
name: Check artifacts
|
||||
run: |
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||
|
||||
govulncheck:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
permissions:
|
||||
# required to write sarif report
|
||||
security-events: write
|
||||
# required to check out the repository
|
||||
contents: read
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Run
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
targets: govulncheck
|
||||
env:
|
||||
GOVULNCHECK_FORMAT: sarif
|
||||
-
|
||||
name: Upload SARIF report
|
||||
if: ${{ github.event_name != 'pull_request' && github.repository == 'moby/moby' }}
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: ${{ env.DESTDIR }}/govulncheck.out
|
||||
|
||||
build-dind:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- validate-dco
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dind image
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
targets: dind
|
||||
set: |
|
||||
*.output=type=cacheonly
|
||||
|
||||
62
.github/workflows/codeql.yml
vendored
62
.github/workflows/codeql.yml
vendored
@@ -1,62 +0,0 @@
|
||||
name: codeql
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: ["master"]
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)
|
||||
# │ │ │ │ │
|
||||
# │ │ │ │ │
|
||||
# │ │ │ │ │
|
||||
# * * * * *
|
||||
- cron: '0 9 * * 4'
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:go"
|
||||
55
.github/workflows/test.yml
vendored
55
.github/workflows/test.yml
vendored
@@ -1,14 +1,5 @@
|
||||
name: test
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -19,22 +10,19 @@ on:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24.6"
|
||||
GO_VERSION: "1.22.10"
|
||||
GIT_PAGER: "cat"
|
||||
PAGER: "cat"
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
build-dev:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
@@ -51,16 +39,15 @@ jobs:
|
||||
if [ "${{ matrix.mode }}" = "systemd" ]; then
|
||||
echo "SYSTEMD=true" >> $GITHUB_ENV
|
||||
fi
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
@@ -83,15 +70,8 @@ jobs:
|
||||
with:
|
||||
storage: ${{ matrix.storage }}
|
||||
|
||||
test-unit:
|
||||
needs:
|
||||
- build-dev
|
||||
- validate-dco
|
||||
uses: ./.github/workflows/.test-unit.yml
|
||||
secrets: inherit
|
||||
|
||||
validate-prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
@@ -113,7 +93,7 @@ jobs:
|
||||
echo ${{ steps.scripts.outputs.matrix }}
|
||||
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-prepare
|
||||
@@ -134,13 +114,9 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: dev
|
||||
set: |
|
||||
@@ -151,7 +127,7 @@ jobs:
|
||||
make -o build validate-${{ matrix.script }}
|
||||
|
||||
smoke-prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- validate-dco
|
||||
@@ -173,7 +149,7 @@ jobs:
|
||||
echo ${{ steps.platforms.outputs.matrix }}
|
||||
|
||||
smoke:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
needs:
|
||||
- smoke-prepare
|
||||
@@ -182,6 +158,9 @@ jobs:
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.smoke-prepare.outputs.matrix) }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
@@ -193,13 +172,9 @@ jobs:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: binary-smoketest
|
||||
set: |
|
||||
|
||||
68
.github/workflows/validate-pr.yml
vendored
68
.github/workflows/validate-pr.yml
vendored
@@ -1,41 +1,27 @@
|
||||
name: validate-pr
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, labeled, unlabeled, synchronize]
|
||||
types: [opened, edited, labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
check-labels:
|
||||
runs-on: ubuntu-24.04
|
||||
check-area-label:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
steps:
|
||||
- name: Missing `area/` label
|
||||
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
|
||||
if: contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
|
||||
run: |
|
||||
echo "::error::Every PR with an 'impact/*' label should also have an 'area/*' label"
|
||||
exit 1
|
||||
- name: Missing `kind/` label
|
||||
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'kind/')
|
||||
run: |
|
||||
echo "::error::Every PR with an 'impact/*' label should also have a 'kind/*' label"
|
||||
exit 1
|
||||
- name: OK
|
||||
run: exit 0
|
||||
|
||||
check-changelog:
|
||||
runs-on: ubuntu-24.04
|
||||
if: contains(join(github.event.pull_request.labels.*.name, ','), 'impact/')
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
env:
|
||||
HAS_IMPACT_LABEL: ${{ contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') }}
|
||||
PR_BODY: |
|
||||
${{ github.event.pull_request.body }}
|
||||
steps:
|
||||
@@ -47,30 +33,22 @@ jobs:
|
||||
# Strip empty lines
|
||||
desc=$(echo "$block" | awk NF)
|
||||
|
||||
if [ "$HAS_IMPACT_LABEL" = "true" ]; then
|
||||
if [ -z "$desc" ]; then
|
||||
echo "::error::Changelog section is empty. Please provide a description for the changelog."
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$desc" ]; then
|
||||
echo "::error::Changelog section is empty. Please provide a description for the changelog."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
len=$(echo -n "$desc" | wc -c)
|
||||
if [[ $len -le 6 ]]; then
|
||||
echo "::error::Description looks too short: $desc"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [ -n "$desc" ]; then
|
||||
echo "::error::PR has a changelog description, but no changelog label"
|
||||
echo "::error::Please add the relevant 'impact/' label to the PR or remove the changelog description"
|
||||
exit 1
|
||||
fi
|
||||
len=$(echo -n "$desc" | wc -c)
|
||||
if [[ $len -le 6 ]]; then
|
||||
echo "::error::Description looks too short: $desc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "This PR will be included in the release notes with the following note:"
|
||||
echo "$desc"
|
||||
|
||||
check-pr-branch:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
@@ -78,16 +56,10 @@ jobs:
|
||||
# Backports or PR that target a release branch directly should mention the target branch in the title, for example:
|
||||
# [X.Y backport] Some change that needs backporting to X.Y
|
||||
# [X.Y] Change directly targeting the X.Y branch
|
||||
- name: Check release branch
|
||||
- name: Get branch from PR title
|
||||
id: title_branch
|
||||
run: |
|
||||
# get the intended major version prefix ("[27.1 backport]" -> "27.") from the PR title.
|
||||
[[ "$PR_TITLE" =~ ^\[([0-9]*\.)[^]]*\] ]] && branch="${BASH_REMATCH[1]}"
|
||||
run: echo "$PR_TITLE" | sed -n 's/^\[\([0-9]*\.[0-9]*\)[^]]*\].*/branch=\1/p' >> $GITHUB_OUTPUT
|
||||
|
||||
# get major version prefix from the release branch ("27.x -> "27.")
|
||||
[[ "$GITHUB_BASE_REF" =~ ^([0-9]*\.) ]] && target_branch="${BASH_REMATCH[1]}" || target_branch="$GITHUB_BASE_REF"
|
||||
|
||||
if [[ "$target_branch" != "$branch" ]] && ! [[ "$GITHUB_BASE_REF" == "master" && "$branch" == "" ]]; then
|
||||
echo "::error::PR is opened against the $GITHUB_BASE_REF branch, but its title suggests otherwise."
|
||||
exit 1
|
||||
fi
|
||||
- name: Check release branch
|
||||
if: github.event.pull_request.base.ref != steps.title_branch.outputs.branch && !(github.event.pull_request.base.ref == 'master' && steps.title_branch.outputs.branch == '')
|
||||
run: echo "::error::PR title suggests targetting the ${{ steps.title_branch.outputs.branch }} branch, but is opened against ${{ github.event.pull_request.base.ref }}" && exit 1
|
||||
|
||||
33
.github/workflows/windows-2019.yml
vendored
Normal file
33
.github/workflows/windows-2019.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: windows-2019
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
test-prepare:
|
||||
uses: ./.github/workflows/.test-prepare.yml
|
||||
needs:
|
||||
- validate-dco
|
||||
|
||||
run:
|
||||
needs:
|
||||
- test-prepare
|
||||
uses: ./.github/workflows/.windows.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
storage: ${{ fromJson(needs.test-prepare.outputs.matrix) }}
|
||||
with:
|
||||
os: windows-2019
|
||||
storage: ${{ matrix.storage }}
|
||||
send_coverage: false
|
||||
27
.github/workflows/windows-2022.yml
vendored
27
.github/workflows/windows-2022.yml
vendored
@@ -1,36 +1,35 @@
|
||||
name: windows-2022
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
test-prepare:
|
||||
uses: ./.github/workflows/.test-prepare.yml
|
||||
needs:
|
||||
- validate-dco
|
||||
|
||||
run:
|
||||
needs:
|
||||
- test-prepare
|
||||
uses: ./.github/workflows/.windows.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
storage:
|
||||
- graphdriver
|
||||
- snapshotter
|
||||
storage: ${{ fromJson(needs.test-prepare.outputs.matrix) }}
|
||||
with:
|
||||
os: windows-2022
|
||||
storage: ${{ matrix.storage }}
|
||||
|
||||
41
.github/workflows/windows-2025.yml
vendored
41
.github/workflows/windows-2025.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: windows-2025
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
run:
|
||||
uses: ./.github/workflows/.windows.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
storage:
|
||||
- graphdriver
|
||||
- snapshotter
|
||||
with:
|
||||
os: windows-2025
|
||||
storage: ${{ matrix.storage }}
|
||||
send_coverage: false
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -14,9 +14,10 @@ thumbs.db
|
||||
.editorconfig
|
||||
|
||||
# build artifacts
|
||||
/bundles/
|
||||
/cmd/dockerd/winresources/winres.json
|
||||
/cmd/dockerd/*.syso
|
||||
bundles/
|
||||
cli/winresources/*/*.syso
|
||||
cli/winresources/*/winres.json
|
||||
contrib/builder/rpm/*/changelog
|
||||
|
||||
# ci artifacts
|
||||
*.exe
|
||||
|
||||
455
.golangci.yml
455
.golangci.yml
@@ -1,357 +1,144 @@
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
# prevent golangci-lint from deducting the go version to lint for through go.mod,
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
go: "1.24.6"
|
||||
concurrency: 2
|
||||
# Only supported with go modules enabled (build flag -mod=vendor only valid when using modules)
|
||||
# modules-download-mode: vendor
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint # Detects "[]any" used as argument for variadic "func(...any)".
|
||||
- copyloopvar # Detects places where loop variables are copied.
|
||||
- depguard
|
||||
- dogsled # Detects assignments with too many blank identifiers.
|
||||
- dupword # Detects duplicate words.
|
||||
- durationcheck # Detect cases where two time.Duration values are being multiplied in possibly erroneous ways.
|
||||
- errorlint # Detects code that will cause problems with the error wrapping scheme introduced in Go 1.13.
|
||||
- errchkjson # Detects unsupported types passed to json encoding functions and reports if checks for the returned error can be omitted.
|
||||
- exhaustive # Detects missing options in enum switch statements.
|
||||
- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions.
|
||||
- fatcontext # Detects nested contexts in loops and function literals.
|
||||
- forbidigo
|
||||
- gocheckcompilerdirectives # Detects invalid go compiler directive comments (//go:).
|
||||
- gocritic # Detects for bugs, performance and style issues.
|
||||
- gosec # Detects security problems.
|
||||
- dupword # Checks for duplicate words in the source code.
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- iface # Detects incorrect use of interfaces. Currently only used for "identical" interfaces in the same package.
|
||||
- importas
|
||||
- ineffassign
|
||||
- makezero # Finds slice declarations with non-zero initial length.
|
||||
- mirror # Detects wrong mirror patterns of bytes/strings usage.
|
||||
- misspell # Detects commonly misspelled English words in comments.
|
||||
- nakedret # Detects uses of naked returns.
|
||||
- nilnesserr # Detects returning nil errors. It combines the features of nilness and nilerr,
|
||||
- nosprintfhostport # Detects misuse of Sprintf to construct a host with port in a URL.
|
||||
- reassign # Detects reassigning a top-level variable in another package.
|
||||
- revive # Metalinter; drop-in replacement for golint.
|
||||
- spancheck # Detects mistakes with OpenTelemetry/Census spans.
|
||||
- misspell
|
||||
- revive
|
||||
- staticcheck
|
||||
- thelper
|
||||
- unconvert # Detects unnecessary type conversions.
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- usestdlibvars # Detects the possibility to use variables/constants from the Go standard library.
|
||||
- wastedassign # Detects wasted assignment statements.
|
||||
|
||||
disable:
|
||||
- errcheck
|
||||
- spancheck # FIXME
|
||||
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: "github.com/stretchr/testify/assert"
|
||||
desc: Use "gotest.tools/v3/assert" instead
|
||||
- pkg: "github.com/stretchr/testify/require"
|
||||
desc: Use "gotest.tools/v3/assert" instead
|
||||
- pkg: "github.com/stretchr/testify/suite"
|
||||
desc: Do not use
|
||||
- pkg: "github.com/containerd/containerd/pkg/userns"
|
||||
desc: Use github.com/moby/sys/userns instead.
|
||||
- pkg: "github.com/tonistiigi/fsutil"
|
||||
desc: The fsutil module does not have a stable API, so we should not have a direct dependency unless necessary.
|
||||
- pkg: "github.com/hashicorp/go-multierror"
|
||||
desc: "Use errors.Join instead"
|
||||
run:
|
||||
concurrency: 2
|
||||
modules-download-mode: vendor
|
||||
|
||||
dupword:
|
||||
ignore:
|
||||
- "true" # some tests use this as expected output
|
||||
- "false" # some tests use this as expected output
|
||||
- "root" # for tests using "ls" output with files owned by "root:root"
|
||||
skip-dirs:
|
||||
- docs
|
||||
|
||||
errorlint:
|
||||
# Check whether fmt.Errorf uses the %w verb for formatting errors.
|
||||
# See the https://github.com/polyfloyd/go-errorlint for caveats.
|
||||
errorf: false
|
||||
# Check for plain type assertions and type switches.
|
||||
asserts: false
|
||||
linters-settings:
|
||||
dupword:
|
||||
ignore:
|
||||
- "true" # some tests use this as expected output
|
||||
- "false" # some tests use this as expected output
|
||||
- "root" # for tests using "ls" output with files owned by "root:root"
|
||||
importas:
|
||||
# Do not allow unaliased imports of aliased packages.
|
||||
no-unaliased: true
|
||||
|
||||
exhaustive:
|
||||
# Program elements to check for exhaustiveness.
|
||||
# Default: [ switch ]
|
||||
check:
|
||||
- switch
|
||||
# - map # TODO(thaJeztah): also enable for maps
|
||||
# Presence of "default" case in switch statements satisfies exhaustiveness,
|
||||
# even if all enum members are not listed.
|
||||
# Default: false
|
||||
#
|
||||
# TODO(thaJeztah): consider not allowing this to catch new values being added (and falling through to "default")
|
||||
default-signifies-exhaustive: true
|
||||
alias:
|
||||
# Enforce alias to prevent it accidentally being used instead of our
|
||||
# own errdefs package (or vice-versa).
|
||||
- pkg: github.com/containerd/errdefs
|
||||
alias: cerrdefs
|
||||
- pkg: github.com/opencontainers/image-spec/specs-go/v1
|
||||
alias: ocispec
|
||||
|
||||
forbidigo:
|
||||
forbid:
|
||||
- pkg: ^sync/atomic$
|
||||
pattern: ^atomic\.(Add|CompareAndSwap|Load|Store|Swap).
|
||||
msg: Go 1.19 atomic types should be used instead.
|
||||
- pkg: ^regexp$
|
||||
pattern: ^regexp\.MustCompile
|
||||
msg: Use daemon/internal/lazyregexp.New instead.
|
||||
- pkg: github.com/vishvananda/netlink$
|
||||
pattern: ^netlink\.(Handle\.)?(AddrList|BridgeVlanList|ChainList|ClassList|ConntrackTableList|ConntrackDeleteFilter$|ConntrackDeleteFilters|DevLinkGetDeviceList|DevLinkGetAllPortList|DevlinkGetDeviceParams|FilterList|FouList|GenlFamilyList|GTPPDPList|LinkByName|LinkByAlias|LinkList|LinkSubscribeWithOptions|NeighList$|NeighProxyList|NeighListExecute|NeighSubscribeWithOptions|LinkGetProtinfo|QdiscList|RdmaLinkList|RdmaLinkByName|RdmaLinkDel|RouteList|RouteListFilteredIter|RuleListFiltered$|RouteSubscribeWithOptions|RuleList$|RuleListFiltered|SocketGet|SocketDiagTCPInfo|SocketDiagTCP|SocketDiagUDPInfo|SocketDiagUDP|UnixSocketDiagInfo|UnixSocketDiag|VDPAGetDevConfigList|VDPAGetDevList|VDPAGetMGMTDevList|XfrmPolicyList|XfrmStateList)
|
||||
msg: Use internal nlwrap package for EINTR handling.
|
||||
- pkg: github.com/moby/moby/v2/internal/nlwrap$
|
||||
pattern: ^nlwrap.Handle.(BridgeVlanList|ChainList|ClassList|ConntrackDeleteFilter$|DevLinkGetDeviceList|DevLinkGetAllPortList|DevlinkGetDeviceParams|FilterList|FouList|GenlFamilyList|GTPPDPList|LinkByAlias|LinkSubscribeWithOptions|NeighList$|NeighProxyList|NeighListExecute|NeighSubscribeWithOptions|LinkGetProtinfo|QdiscList|RdmaLinkList|RdmaLinkByName|RdmaLinkDel|RouteListFilteredIter|RuleListFiltered$|RouteSubscribeWithOptions|RuleList$|RuleListFiltered|SocketGet|SocketDiagTCPInfo|SocketDiagTCP|SocketDiagUDPInfo|SocketDiagUDP|UnixSocketDiagInfo|UnixSocketDiag|VDPAGetDevConfigList|VDPAGetDevList|VDPAGetMGMTDevList)
|
||||
msg: Add a wrapper to nlwrap.Handle for EINTR handling and update the list in .golangci.yml.
|
||||
analyze-types: true
|
||||
govet:
|
||||
check-shadowing: false
|
||||
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
- appendCombine
|
||||
- assignOp
|
||||
- builtinShadow
|
||||
- builtinShadowDecl
|
||||
- captLocal
|
||||
- commentedOutCode
|
||||
- deferInLoop
|
||||
- dupImport
|
||||
- dupSubExpr
|
||||
- elseif
|
||||
- emptyFallthrough
|
||||
- equalFold
|
||||
- evalOrder
|
||||
- exitAfterDefer
|
||||
- exposedSyncMutex
|
||||
- filepathJoin
|
||||
- hexLiteral
|
||||
- hugeParam
|
||||
- ifElseChain
|
||||
- importShadow
|
||||
- indexAlloc
|
||||
- methodExprCall
|
||||
- nestingReduce
|
||||
- nilValReturn
|
||||
- octalLiteral
|
||||
- paramTypeCombine
|
||||
- preferStringWriter
|
||||
- ptrToRefParam
|
||||
- rangeValCopy
|
||||
- redundantSprint
|
||||
- regexpMust
|
||||
- regexpSimplify
|
||||
- singleCaseSwitch
|
||||
- sloppyReassign
|
||||
- stringXbytes
|
||||
- typeAssertChain
|
||||
- typeDefFirst
|
||||
- typeUnparen
|
||||
- uncheckedInlineErr
|
||||
- unlambda
|
||||
- unnamedResult
|
||||
- unnecessaryDefer
|
||||
- unslice
|
||||
- valSwap
|
||||
- whyNoLint
|
||||
enable-all: true
|
||||
gosec:
|
||||
excludes:
|
||||
- G115 # FIXME temporarily suppress 'G115: integer overflow conversion': it produces many hits, some of which may be false positives, and need to be looked at; see https://github.com/moby/moby/issues/48358
|
||||
|
||||
gosec:
|
||||
excludes:
|
||||
- G104 # G104: Errors unhandled; (TODO: reduce unhandled errors, or explicitly ignore)
|
||||
- G115 # G115: integer overflow conversion; (TODO: verify these: https://github.com/moby/moby/issues/48358)
|
||||
- G204 # G204: Subprocess launched with variable; too many false positives.
|
||||
- G301 # G301: Expect directory permissions to be 0750 or less (also EXC0009); too restrictive
|
||||
- G302 # G302: Expect file permissions to be 0600 or less (also EXC0009); too restrictive
|
||||
- G304 # G304: Potential file inclusion via variable.
|
||||
- G306 # G306: Expect WriteFile permissions to be 0600 or less (too restrictive; also flags "0o644" permissions)
|
||||
- G307 # G307: Deferring unsafe method "*os.File" on type "Close" (also EXC0008); (TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close")
|
||||
- G504 # G504: Blocklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386); (only affects go < 1.6.3)
|
||||
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment # TODO: evaluate which ones should be updated.
|
||||
|
||||
importas:
|
||||
# Do not allow unaliased imports of aliased packages.
|
||||
no-unaliased: true
|
||||
|
||||
alias:
|
||||
# Enforce alias to prevent it accidentally being used instead of our
|
||||
# own errdefs package (or vice-versa).
|
||||
- pkg: github.com/containerd/errdefs
|
||||
alias: cerrdefs
|
||||
- pkg: github.com/containerd/containerd/images
|
||||
alias: c8dimages
|
||||
- pkg: github.com/opencontainers/image-spec/specs-go/v1
|
||||
alias: ocispec
|
||||
- pkg: go.etcd.io/bbolt
|
||||
alias: bolt
|
||||
# Enforce that gotest.tools/v3/assert/cmp is always aliased as "is"
|
||||
- pkg: gotest.tools/v3/assert/cmp
|
||||
alias: is
|
||||
|
||||
nakedret:
|
||||
# Disallow naked returns if func has more lines of code than this setting.
|
||||
# Default: 30
|
||||
max-func-lines: 0
|
||||
|
||||
revive:
|
||||
# Only listed rules are applied
|
||||
# https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md
|
||||
rules:
|
||||
- name: increment-decrement
|
||||
# FIXME make sure all packages have a description. Currently, there's many packages without.
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
- name: redefines-builtin-id
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- preserve-scope
|
||||
- name: use-any
|
||||
- name: use-errors-new
|
||||
- name: var-declaration
|
||||
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008
|
||||
- -ST1000 # Incorrect or missing package comment; https://staticcheck.dev/docs/checks/#ST1000
|
||||
- -ST1003 # Poorly chosen identifier; https://staticcheck.dev/docs/checks/#ST1003
|
||||
- -ST1005 # Incorrectly formatted error string; https://staticcheck.dev/docs/checks/#ST1005
|
||||
|
||||
spancheck:
|
||||
# Default: ["end"]
|
||||
checks:
|
||||
- end # check that `span.End()` is called
|
||||
- record-error # check that `span.RecordError(err)` is called when an error is returned
|
||||
- set-status # check that `span.SetStatus(codes.Error, msg)` is called when an error is returned
|
||||
|
||||
thelper:
|
||||
test:
|
||||
# Check *testing.T is first param (or after context.Context) of helper function.
|
||||
first: false
|
||||
# Check t.Helper() begins helper function.
|
||||
begin: false
|
||||
benchmark:
|
||||
# Check *testing.B is first param (or after context.Context) of helper function.
|
||||
first: false
|
||||
# Check b.Helper() begins helper function.
|
||||
begin: false
|
||||
tb:
|
||||
# Check *testing.TB is first param (or after context.Context) of helper function.
|
||||
first: false
|
||||
# Check *testing.TB param has name tb.
|
||||
name: false
|
||||
# Check tb.Helper() begins helper function.
|
||||
begin: false
|
||||
fuzz:
|
||||
# Check *testing.F is first param (or after context.Context) of helper function.
|
||||
first: false
|
||||
# Check f.Helper() begins helper function.
|
||||
begin: false
|
||||
|
||||
usestdlibvars:
|
||||
# Suggest the use of http.MethodXX.
|
||||
http-method: true
|
||||
# Suggest the use of http.StatusXX.
|
||||
http-status-code: true
|
||||
|
||||
exclusions:
|
||||
depguard:
|
||||
rules:
|
||||
# We prefer to use an "linters.exclusions.rules" so that new "default" exclusions are not
|
||||
# automatically inherited. We can decide whether or not to follow upstream
|
||||
# defaults when updating golang-ci-lint versions.
|
||||
# Unfortunately, this means we have to copy the whole exclusion pattern, as
|
||||
# (unlike the "include" option), the "exclude" option does not take exclusion
|
||||
# ID's.
|
||||
#
|
||||
# These exclusion patterns are copied from the default excludes at:
|
||||
# https://github.com/golangci/golangci-lint/blob/v1.61.0/pkg/config/issues.go#L11-L104
|
||||
#
|
||||
# The default list of exclusions can be found at:
|
||||
# https://golangci-lint.run/usage/false-positives/#default-exclusions
|
||||
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
|
||||
- text: "G404: Use of weak random number generator"
|
||||
path: _test\.go
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
# Suppress golint complaining about generated types in api/types/
|
||||
- text: "type name will be used as (container|volume)\\.(Container|Volume).* by other packages, and that stutters; consider calling this"
|
||||
path: "api/types/(volume|container)/"
|
||||
linters:
|
||||
- revive
|
||||
|
||||
# FIXME: ignoring unused assigns to ctx for now; too many hits in libnetwork/xxx functions that setup traces
|
||||
- text: "assigned to ctx, but never used afterwards"
|
||||
linters:
|
||||
- wastedassign
|
||||
|
||||
- text: "ineffectual assignment to ctx"
|
||||
source: "ctx[, ].*=.*\\(ctx[,)]"
|
||||
linters:
|
||||
- ineffassign
|
||||
|
||||
- text: "SA4006: this value of ctx is never used"
|
||||
source: "ctx[, ].*=.*\\(ctx[,)]"
|
||||
linters:
|
||||
- staticcheck
|
||||
|
||||
# Ignore "nested context in function literal (fatcontext)" as we intentionally set up tracing on a base-context for tests.
|
||||
# FIXME(thaJeztah): see if there's a more iodiomatic way to do this.
|
||||
- text: 'nested context in function literal'
|
||||
path: '((main|check)_(linux_|)test\.go)|testutil/helpers\.go'
|
||||
linters:
|
||||
- fatcontext
|
||||
|
||||
- text: '^shadow: declaration of "(ctx|err|ok)" shadows declaration'
|
||||
linters:
|
||||
- govet
|
||||
- text: '^shadow: declaration of "(out)" shadows declaration'
|
||||
path: _test\.go
|
||||
linters:
|
||||
- govet
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: _test\.go
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "daemon/internal/lazyregexp"
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "internal/testutils"
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "libnetwork/cmd/networkdb-test/dbclient"
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "registry/"
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
# Log a warning if an exclusion rule is unused.
|
||||
# Default: false
|
||||
warn-unused: true
|
||||
|
||||
main:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
|
||||
- pkg: "github.com/stretchr/testify/assert"
|
||||
desc: Use "gotest.tools/v3/assert" instead
|
||||
- pkg: "github.com/stretchr/testify/require"
|
||||
desc: Use "gotest.tools/v3/assert" instead
|
||||
- pkg: "github.com/stretchr/testify/suite"
|
||||
desc: Do not use
|
||||
- pkg: github.com/containerd/containerd/errdefs
|
||||
desc: The errdefs package has moved to a separate module, https://github.com/containerd/errdefs
|
||||
- pkg: github.com/containerd/containerd/log
|
||||
desc: The logs package has moved to a separate module, https://github.com/containerd/log
|
||||
revive:
|
||||
rules:
|
||||
# FIXME make sure all packages have a description. Currently, there's many packages without.
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
issues:
|
||||
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
|
||||
exclude-use-default: false
|
||||
|
||||
exclude-rules:
|
||||
# We prefer to use an "exclude-list" so that new "default" exclusions are not
|
||||
# automatically inherited. We can decide whether or not to follow upstream
|
||||
# defaults when updating golang-ci-lint versions.
|
||||
# Unfortunately, this means we have to copy the whole exclusion pattern, as
|
||||
# (unlike the "include" option), the "exclude" option does not take exclusion
|
||||
# ID's.
|
||||
#
|
||||
# These exclusion patterns are copied from the default excluses at:
|
||||
# https://github.com/golangci/golangci-lint/blob/v1.46.2/pkg/config/issues.go#L10-L104
|
||||
|
||||
# EXC0001
|
||||
- text: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked"
|
||||
linters:
|
||||
- errcheck
|
||||
# EXC0006
|
||||
- text: "Use of unsafe calls should be audited"
|
||||
linters:
|
||||
- gosec
|
||||
# EXC0007
|
||||
- text: "Subprocess launch(ed with variable|ing should be audited)"
|
||||
linters:
|
||||
- gosec
|
||||
# EXC0008
|
||||
# TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close" (gosec)
|
||||
- text: "(G104|G307)"
|
||||
linters:
|
||||
- gosec
|
||||
# EXC0009
|
||||
- text: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)"
|
||||
linters:
|
||||
- gosec
|
||||
# EXC0010
|
||||
- text: "Potential file inclusion via variable"
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
# Looks like the match in "EXC0007" above doesn't catch this one
|
||||
# TODO: consider upstreaming this to golangci-lint's default exclusion rules
|
||||
- text: "G204: Subprocess launched with a potential tainted input or cmd arguments"
|
||||
linters:
|
||||
- gosec
|
||||
# Looks like the match in "EXC0009" above doesn't catch this one
|
||||
# TODO: consider upstreaming this to golangci-lint's default exclusion rules
|
||||
- text: "G306: Expect WriteFile permissions to be 0600 or less"
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
- gosec
|
||||
|
||||
# Suppress golint complaining about generated types in api/types/
|
||||
- text: "type name will be used as (container|volume)\\.(Container|Volume).* by other packages, and that stutters; consider calling this"
|
||||
path: "api/types/(volume|container)/"
|
||||
linters:
|
||||
- revive
|
||||
# FIXME temporarily suppress these (see https://github.com/gotestyourself/gotest.tools/issues/272)
|
||||
- text: "SA1019: (assert|cmp|is)\\.ErrorType is deprecated"
|
||||
linters:
|
||||
- staticcheck
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-issues-per-linter: 0
|
||||
|
||||
|
||||
30
.mailmap
30
.mailmap
@@ -7,7 +7,6 @@
|
||||
#
|
||||
# For an explanation of this file format, consult gitmailmap(5).
|
||||
|
||||
Aaron Yoshitake <airandfingers@gmail.com>
|
||||
Aaron L. Xu <liker.xu@foxmail.com>
|
||||
Aaron L. Xu <liker.xu@foxmail.com> <likexu@harmonycloud.cn>
|
||||
Aaron Lehmann <alehmann@netflix.com>
|
||||
@@ -31,11 +30,9 @@ Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.akihiro@lab.ntt.co.jp>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.kyoto@gmail.com>
|
||||
Akshay Moghe <akshay.moghe@gmail.com>
|
||||
Alano Terblanche <alano.terblanche@docker.com>
|
||||
Alano Terblanche <alano.terblanche@docker.com> <18033717+Benehiko@users.noreply.github.com>
|
||||
Albin Kerouanton <albinker@gmail.com>
|
||||
Albin Kerouanton <albinker@gmail.com> <557933+akerouanton@users.noreply.github.com>
|
||||
Albin Kerouanton <albinker@gmail.com> <albin@akerouanton.name>
|
||||
Albin Kerouanton <albinker@gmail.com> <557933+akerouanton@users.noreply.github.com>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
|
||||
Aleksa Sarai <asarai@suse.de> <cyphar@cyphar.com>
|
||||
@@ -62,8 +59,6 @@ Allen Sun <allensun.shl@alibaba-inc.com> <allen.sun@daocloud.io>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com> <shlallen1990@gmail.com>
|
||||
Anca Iordache <anca.iordache@docker.com>
|
||||
Andrea Denisse Gómez <crypto.andrea@protonmail.ch>
|
||||
Andrew Baxter <423qpsxzhh8k3h@s.rendaw.me>
|
||||
Andrew Baxter <423qpsxzhh8k3h@s.rendaw.me> andrew <>
|
||||
Andrew Kim <taeyeonkim90@gmail.com>
|
||||
Andrew Kim <taeyeonkim90@gmail.com> <akim01@fortinet.com>
|
||||
Andrew Weiss <andrew.weiss@docker.com> <andrew.weiss@microsoft.com>
|
||||
@@ -94,9 +89,6 @@ Arnaud Rebillout <arnaud.rebillout@collabora.com>
|
||||
Arnaud Rebillout <arnaud.rebillout@collabora.com> <elboulangero@gmail.com>
|
||||
Arthur Gautier <baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
|
||||
Artur Meyster <arthurfbi@yahoo.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com> <55906459+austinvazquez@users.noreply.github.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com> <macedonv@amazon.com>
|
||||
Avi Miller <avi.miller@oracle.com> <avi.miller@gmail.com>
|
||||
Ben Bonnefoy <frenchben@docker.com>
|
||||
Ben Golub <ben.golub@dotcloud.com>
|
||||
@@ -127,7 +119,6 @@ Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.local>
|
||||
Brian Goff <cpuguy83@gmail.com> <brian.goff@microsoft.com>
|
||||
Brian Goff <cpuguy83@gmail.com> <cpuguy@hey.com>
|
||||
Calvin Liu <flycalvin@qq.com>
|
||||
Cameron Sparr <gh@sparr.email>
|
||||
Carlos de Paula <me@carlosedp.com>
|
||||
Chander Govindarajan <chandergovind@gmail.com>
|
||||
@@ -139,8 +130,6 @@ Chen Mingjie <chenmingjie0828@163.com>
|
||||
Chen Qiu <cheney-90@hotmail.com>
|
||||
Chen Qiu <cheney-90@hotmail.com> <21321229@zju.edu.cn>
|
||||
Chengfei Shang <cfshang@alauda.io>
|
||||
Chengyu Zhu <hudson@cyzhu.com>
|
||||
Chentianze <cmoman@126.com>
|
||||
Chris Dias <cdias@microsoft.com>
|
||||
Chris McKinnel <chris.mckinnel@tangentlabs.co.uk>
|
||||
Chris Price <cprice@mirantis.com>
|
||||
@@ -149,8 +138,6 @@ Chris Telfer <ctelfer@docker.com>
|
||||
Chris Telfer <ctelfer@docker.com> <ctelfer@users.noreply.github.com>
|
||||
Christopher Biscardi <biscarch@sketcht.com>
|
||||
Christopher Latham <sudosurootdev@gmail.com>
|
||||
Christopher Petito <chrisjpetito@gmail.com>
|
||||
Christopher Petito <chrisjpetito@gmail.com> <47751006+krissetto@users.noreply.github.com>
|
||||
Christy Norman <christy@linux.vnet.ibm.com>
|
||||
Chun Chen <ramichen@tencent.com> <chenchun.feed@gmail.com>
|
||||
Corbin Coleman <corbin.coleman@docker.com>
|
||||
@@ -354,8 +341,6 @@ John Howard <github@lowenna.com> <john.howard@microsoft.com>
|
||||
John Howard <github@lowenna.com> <john@lowenna.com>
|
||||
John Stephens <johnstep@docker.com> <johnstep@users.noreply.github.com>
|
||||
Jon Surrell <jon.surrell@gmail.com> <jon.surrell@automattic.com>
|
||||
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
||||
Jonathan A. Sternberg <jonathansternberg@gmail.com> <jonathan.sternberg@docker.com>
|
||||
Jonathan Choy <jonathan.j.choy@gmail.com>
|
||||
Jonathan Choy <jonathan.j.choy@gmail.com> <oni@tetsujinlabs.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
@@ -498,20 +483,19 @@ Mikael Davranche <mikael.davranche@corp.ovh.com> <mikael.davranche@corp.ovh.net>
|
||||
Mike Casas <mkcsas0@gmail.com> <mikecasas@users.noreply.github.com>
|
||||
Mike Goelzer <mike.goelzer@docker.com> <mgoelzer@docker.com>
|
||||
Milas Bowman <devnull@milas.dev>
|
||||
Milas Bowman <devnull@milas.dev> <milas.bowman@docker.com>
|
||||
Milas Bowman <devnull@milas.dev> <milasb@gmail.com>
|
||||
Milas Bowman <devnull@milas.dev> <milas.bowman@docker.com>
|
||||
Milind Chawre <milindchawre@gmail.com>
|
||||
Misty Stanley-Jones <misty@docker.com> <misty@apache.org>
|
||||
Mohammad Banikazemi <MBanikazemi@gmail.com>
|
||||
Mohammad Banikazemi <MBanikazemi@gmail.com> <mb@us.ibm.com>
|
||||
Mohd Sadiq <mohdsadiq058@gmail.com> <42430865+msadiq058@users.noreply.github.com>
|
||||
Mohd Sadiq <mohdsadiq058@gmail.com> <mohdsadiq058@gmail.com>
|
||||
Mohd Sadiq <mohdsadiq058@gmail.com> <42430865+msadiq058@users.noreply.github.com>
|
||||
Mohit Soni <mosoni@ebay.com> <mohitsoni1989@gmail.com>
|
||||
Moorthy RS <rsmoorthy@gmail.com> <rsmoorthy@users.noreply.github.com>
|
||||
Moysés Borges <moysesb@gmail.com>
|
||||
Moysés Borges <moysesb@gmail.com> <moyses.furtado@wplex.com.br>
|
||||
mrfly <mr.wrfly@gmail.com> <wrfly@users.noreply.github.com>
|
||||
Myeongjoon Kim <kimmj8409@gmail.com>
|
||||
Nace Oroz <orkica@gmail.com>
|
||||
Natasha Jarus <linuxmercedes@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathan.leclaire@gmail.com>
|
||||
@@ -531,11 +515,8 @@ Olli Janatuinen <olli.janatuinen@gmail.com> <olljanat@users.noreply.github.com>
|
||||
Onur Filiz <onur.filiz@microsoft.com>
|
||||
Onur Filiz <onur.filiz@microsoft.com> <ofiliz@users.noreply.github.com>
|
||||
Ouyang Liduo <oyld0210@163.com>
|
||||
Patrick St. laurent <patrick@saint-laurent.us>
|
||||
Patrick Stapleton <github@gdi2290.com>
|
||||
Paul Liljenberg <liljenberg.paul@gmail.com> <letters@paulnotcom.se>
|
||||
Paweł Gronowski <pawel.gronowski@docker.com>
|
||||
Paweł Gronowski <pawel.gronowski@docker.com> <me@woland.xyz>
|
||||
Pavel Tikhomirov <ptikhomirov@virtuozzo.com> <ptikhomirov@parallels.com>
|
||||
Pawel Konczalski <mail@konczalski.de>
|
||||
Peter Choi <phkchoi89@gmail.com> <reikani@Peters-MacBook-Pro.local>
|
||||
@@ -557,21 +538,16 @@ Qin TianHuan <tianhuan@bingotree.cn>
|
||||
Ray Tsang <rayt@google.com> <saturnism@users.noreply.github.com>
|
||||
Renaud Gaubert <rgaubert@nvidia.com> <renaud.gaubert@gmail.com>
|
||||
Richard Scothern <richard.scothern@gmail.com>
|
||||
Rob Murray <rob.murray@docker.com>
|
||||
Rob Murray <rob.murray@docker.com> <148866618+robmry@users.noreply.github.com>
|
||||
Robert Terhaar <rterhaar@atlanticdynamic.com> <robbyt@users.noreply.github.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
|
||||
Roberto Muñoz Fernández <robertomf@gmail.com> <roberto.munoz.fernandez.contractor@bbva.com>
|
||||
Robin Thoni <robin@rthoni.com>
|
||||
Rodrigo Campos <rodrigoca@microsoft.com>
|
||||
Rodrigo Campos <rodrigoca@microsoft.com> <rodrigo@kinvolk.io>
|
||||
Roman Dudin <katrmr@gmail.com> <decadent@users.noreply.github.com>
|
||||
Rong Zhang <rongzhang@alauda.io>
|
||||
Rongxiang Song <tinysong1226@gmail.com>
|
||||
Rony Weng <ronyweng@synology.com>
|
||||
Ross Boucher <rboucher@gmail.com>
|
||||
Rui Cao <ruicao@alauda.io>
|
||||
Rui JingAn <quiterace@gmail.com>
|
||||
Runshen Zhu <runshen.zhu@gmail.com>
|
||||
Ryan Stelly <ryan.stelly@live.com>
|
||||
Ryoga Saito <contact@proelbtn.com>
|
||||
|
||||
64
AUTHORS
64
AUTHORS
@@ -2,10 +2,7 @@
|
||||
# This file lists all contributors to the repository.
|
||||
# See hack/generate-authors.sh to make modifications.
|
||||
|
||||
17neverends <ionianrise@gmail.com>
|
||||
7sunarni <710720732@qq.com>
|
||||
Aanand Prasad <aanand.prasad@gmail.com>
|
||||
Aarni Koskela <akx@iki.fi>
|
||||
Aaron Davidson <aaron@databricks.com>
|
||||
Aaron Feng <aaron.feng@gmail.com>
|
||||
Aaron Hnatiw <aaron@griddio.com>
|
||||
@@ -13,8 +10,6 @@ Aaron Huslage <huslage@gmail.com>
|
||||
Aaron L. Xu <liker.xu@foxmail.com>
|
||||
Aaron Lehmann <alehmann@netflix.com>
|
||||
Aaron Welch <welch@packet.net>
|
||||
Aaron Yoshitake <airandfingers@gmail.com>
|
||||
Abdur Rehman <abdur_rehman@mentor.com>
|
||||
Abel Muiño <amuino@gmail.com>
|
||||
Abhijeet Kasurde <akasurde@redhat.com>
|
||||
Abhinandan Prativadi <aprativadi@gmail.com>
|
||||
@@ -28,11 +23,9 @@ Adam Avilla <aavilla@yp.com>
|
||||
Adam Dobrawy <naczelnik@jawnosc.tk>
|
||||
Adam Eijdenberg <adam.eijdenberg@gmail.com>
|
||||
Adam Kunk <adam.kunk@tiaa-cref.org>
|
||||
Adam Lamers <adam.lamers@wmsdev.pl>
|
||||
Adam Miller <admiller@redhat.com>
|
||||
Adam Mills <adam@armills.info>
|
||||
Adam Pointer <adam.pointer@skybettingandgaming.com>
|
||||
Adam Simon <adamsimon85100@gmail.com>
|
||||
Adam Singer <financeCoding@gmail.com>
|
||||
Adam Thornton <adam.thornton@maryville.com>
|
||||
Adam Walz <adam@adamwalz.net>
|
||||
@@ -69,7 +62,6 @@ alambike <alambike@gmail.com>
|
||||
Alan Hoyle <alan@alanhoyle.com>
|
||||
Alan Scherger <flyinprogrammer@gmail.com>
|
||||
Alan Thompson <cloojure@gmail.com>
|
||||
Alano Terblanche <alano.terblanche@docker.com>
|
||||
Albert Callarisa <shark234@gmail.com>
|
||||
Albert Zhang <zhgwenming@gmail.com>
|
||||
Albin Kerouanton <albinker@gmail.com>
|
||||
@@ -125,7 +117,6 @@ amangoel <amangoel@gmail.com>
|
||||
Amen Belayneh <amenbelayneh@gmail.com>
|
||||
Ameya Gawde <agawde@mirantis.com>
|
||||
Amir Goldstein <amir73il@aquasec.com>
|
||||
AmirBuddy <badinlu.amirhossein@gmail.com>
|
||||
Amit Bakshi <ambakshi@gmail.com>
|
||||
Amit Krishnan <amit.krishnan@oracle.com>
|
||||
Amit Shukla <amit.shukla@docker.com>
|
||||
@@ -150,7 +141,6 @@ Andreas Tiefenthaler <at@an-ti.eu>
|
||||
Andrei Gherzan <andrei@resin.io>
|
||||
Andrei Ushakov <aushakov@netflix.com>
|
||||
Andrei Vagin <avagin@gmail.com>
|
||||
Andrew Baxter <423qpsxzhh8k3h@s.rendaw.me>
|
||||
Andrew C. Bodine <acbodine@us.ibm.com>
|
||||
Andrew Clay Shafer <andrewcshafer@gmail.com>
|
||||
Andrew Duckworth <grillopress@gmail.com>
|
||||
@@ -175,7 +165,6 @@ Andrey Kolomentsev <andrey.kolomentsev@docker.com>
|
||||
Andrey Petrov <andrey.petrov@shazow.net>
|
||||
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
||||
André Martins <aanm90@gmail.com>
|
||||
Andrés Maldonado <maldonado@codelutin.com>
|
||||
Andy Chambers <anchambers@paypal.com>
|
||||
andy diller <dillera@gmail.com>
|
||||
Andy Goldstein <agoldste@redhat.com>
|
||||
@@ -190,7 +179,6 @@ Anes Hasicic <anes.hasicic@gmail.com>
|
||||
Angel Velazquez <angelcar@amazon.com>
|
||||
Anil Belur <askb23@gmail.com>
|
||||
Anil Madhavapeddy <anil@recoil.org>
|
||||
Anirudh Aithal <aithal@amazon.com>
|
||||
Ankit Jain <ajatkj@yahoo.co.in>
|
||||
Ankush Agarwal <ankushagarwal11@gmail.com>
|
||||
Anonmily <michelle@michelleliu.io>
|
||||
@@ -205,7 +193,6 @@ Anton Löfgren <anton.lofgren@gmail.com>
|
||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||
Anton Polonskiy <anton.polonskiy@gmail.com>
|
||||
Anton Tiurin <noxiouz@yandex.ru>
|
||||
Antonio Aguilar <antonio@zoftko.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com>
|
||||
Antonis Kalipetis <akalipetis@gmail.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
@@ -228,13 +215,13 @@ Artur Meyster <arthurfbi@yahoo.com>
|
||||
Arun Gupta <arun.gupta@gmail.com>
|
||||
Asad Saeeduddin <masaeedu@gmail.com>
|
||||
Asbjørn Enge <asbjorn@hanafjedle.net>
|
||||
Ashly Mathew <ashly.mathew@sap.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com>
|
||||
Austin Vazquez <macedonv@amazon.com>
|
||||
averagehuman <averagehuman@users.noreply.github.com>
|
||||
Avi Das <andas222@gmail.com>
|
||||
Avi Kivity <avi@scylladb.com>
|
||||
Avi Miller <avi.miller@oracle.com>
|
||||
Avi Vaid <avaid1996@gmail.com>
|
||||
ayoshitake <airandfingers@gmail.com>
|
||||
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
||||
Bao Yonglei <baoyonglei@huawei.com>
|
||||
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
||||
@@ -295,7 +282,6 @@ Brandon Liu <bdon@bdon.org>
|
||||
Brandon Philips <brandon.philips@coreos.com>
|
||||
Brandon Rhodes <brandon@rhodesmill.org>
|
||||
Brendan Dixon <brendand@microsoft.com>
|
||||
Brendon Smith <bws@bws.bio>
|
||||
Brennan Kinney <5098581+polarathene@users.noreply.github.com>
|
||||
Brent Salisbury <brent.salisbury@docker.com>
|
||||
Brett Higgins <brhiggins@arbor.net>
|
||||
@@ -330,7 +316,6 @@ Burke Libbey <burke@libbey.me>
|
||||
Byung Kang <byung.kang.ctr@amrdec.army.mil>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Calen Pennington <cale@edx.org>
|
||||
Calvin Liu <flycalvin@qq.com>
|
||||
Cameron Boehmer <cameron.boehmer@gmail.com>
|
||||
Cameron Sparr <gh@sparr.email>
|
||||
Cameron Spear <cameronspear@gmail.com>
|
||||
@@ -350,14 +335,12 @@ Casey Bisson <casey.bisson@joyent.com>
|
||||
Catalin Pirvu <pirvu.catalin94@gmail.com>
|
||||
Ce Gao <ce.gao@outlook.com>
|
||||
Cedric Davies <cedricda@microsoft.com>
|
||||
Cesar Talledo <cesar.talledo@docker.com>
|
||||
Cezar Sa Espinola <cezarsa@gmail.com>
|
||||
Chad Swenson <chadswen@gmail.com>
|
||||
Chance Zibolski <chance.zibolski@gmail.com>
|
||||
Chander Govindarajan <chandergovind@gmail.com>
|
||||
Chanhun Jeong <keyolk@gmail.com>
|
||||
Chao Wang <wangchao.fnst@cn.fujitsu.com>
|
||||
Charity Kathure <ckathure@microsoft.com>
|
||||
Charles Chan <charleswhchan@users.noreply.github.com>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Charles Law <claw@conduce.com>
|
||||
@@ -379,8 +362,6 @@ Chen Qiu <cheney-90@hotmail.com>
|
||||
Cheng-mean Liu <soccerl@microsoft.com>
|
||||
Chengfei Shang <cfshang@alauda.io>
|
||||
Chengguang Xu <cgxu519@gmx.com>
|
||||
Chengyu Zhu <hudson@cyzhu.com>
|
||||
Chentianze <cmoman@126.com>
|
||||
Chenyang Yan <memory.yancy@gmail.com>
|
||||
chenyuzhu <chenyuzhi@oschina.cn>
|
||||
Chetan Birajdar <birajdar.chetan@gmail.com>
|
||||
@@ -428,7 +409,6 @@ Christopher Crone <christopher.crone@docker.com>
|
||||
Christopher Currie <codemonkey+github@gmail.com>
|
||||
Christopher Jones <tophj@linux.vnet.ibm.com>
|
||||
Christopher Latham <sudosurootdev@gmail.com>
|
||||
Christopher Petito <chrisjpetito@gmail.com>
|
||||
Christopher Rigor <crigor@gmail.com>
|
||||
Christy Norman <christy@linux.vnet.ibm.com>
|
||||
Chun Chen <ramichen@tencent.com>
|
||||
@@ -494,7 +474,6 @@ Daniel Farrell <dfarrell@redhat.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Daniel Gasienica <daniel@gasienica.ch>
|
||||
Daniel Grunwell <mwgrunny@gmail.com>
|
||||
Daniel Guns <danbguns@gmail.com>
|
||||
Daniel Helfand <helfand.4@gmail.com>
|
||||
Daniel Hiltgen <daniel.hiltgen@docker.com>
|
||||
Daniel J Walsh <dwalsh@redhat.com>
|
||||
@@ -778,7 +757,6 @@ Frank Macreery <frank@macreery.com>
|
||||
Frank Rosquin <frank.rosquin+github@gmail.com>
|
||||
Frank Villaro-Dixon <frank.villarodixon@merkle.com>
|
||||
Frank Yang <yyb196@gmail.com>
|
||||
François Scala <github@arcenik.net>
|
||||
Fred Lifton <fred.lifton@docker.com>
|
||||
Frederick F. Kautz IV <fkautz@redhat.com>
|
||||
Frederico F. de Oliveira <FreddieOliveira@users.noreply.github.com>
|
||||
@@ -799,7 +777,6 @@ Gabriel L. Somlo <gsomlo@gmail.com>
|
||||
Gabriel Linder <linder.gabriel@gmail.com>
|
||||
Gabriel Monroy <gabriel@opdemand.com>
|
||||
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
|
||||
Gabriel Tomitsuka <gabriel@tomitsuka.com>
|
||||
Gaetan de Villele <gdevillele@gmail.com>
|
||||
Galen Sampson <galen.sampson@gmail.com>
|
||||
Gang Qiao <qiaohai8866@gmail.com>
|
||||
@@ -814,9 +791,7 @@ GennadySpb <lipenkov@gmail.com>
|
||||
Geoff Levand <geoff@infradead.org>
|
||||
Geoffrey Bachelet <grosfrais@gmail.com>
|
||||
Geon Kim <geon0250@gmail.com>
|
||||
George Adams <georgeadams1995@gmail.com>
|
||||
George Kontridze <george@bugsnag.com>
|
||||
George Ma <mayangang@outlook.com>
|
||||
George MacRorie <gmacr31@gmail.com>
|
||||
George Xie <georgexsh@gmail.com>
|
||||
Georgi Hristozov <georgi@forkbomb.nl>
|
||||
@@ -843,7 +818,6 @@ Gopikannan Venugopalsamy <gopikannan.venugopalsamy@gmail.com>
|
||||
Gosuke Miyashita <gosukenator@gmail.com>
|
||||
Gou Rao <gou@portworx.com>
|
||||
Govinda Fichtner <govinda.fichtner@googlemail.com>
|
||||
Grace Choi <grace.54109@gmail.com>
|
||||
Grant Millar <rid@cylo.io>
|
||||
Grant Reaber <grant.reaber@gmail.com>
|
||||
Graydon Hoare <graydon@pobox.com>
|
||||
@@ -939,7 +913,6 @@ Illo Abdulrahim <abdulrahim.illo@nokia.com>
|
||||
Ilya Dmitrichenko <errordeveloper@gmail.com>
|
||||
Ilya Gusev <mail@igusev.ru>
|
||||
Ilya Khlopotov <ilya.khlopotov@gmail.com>
|
||||
imalasong <2879499479@qq.com>
|
||||
imre Fitos <imre.fitos+github@gmail.com>
|
||||
inglesp <peter.inglesby@gmail.com>
|
||||
Ingo Gottwald <in.gottwald@gmail.com>
|
||||
@@ -957,7 +930,6 @@ J Bruni <joaohbruni@yahoo.com.br>
|
||||
J. Nunn <jbnunn@gmail.com>
|
||||
Jack Danger Canty <jackdanger@squareup.com>
|
||||
Jack Laxson <jackjrabbit@gmail.com>
|
||||
Jack Walker <90711509+j2walker@users.noreply.github.com>
|
||||
Jacob Atzen <jacob@jacobatzen.dk>
|
||||
Jacob Edelman <edelman.jd@gmail.com>
|
||||
Jacob Tomlinson <jacob@tom.linson.uk>
|
||||
@@ -984,7 +956,6 @@ James Nugent <james@jen20.com>
|
||||
James Sanders <james3sanders@gmail.com>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
James Watkins-Harvey <jwatkins@progi-media.com>
|
||||
Jameson Hyde <jameson.hyde@docker.com>
|
||||
Jamie Hannaford <jamie@limetree.org>
|
||||
Jamshid Afshar <jafshar@yahoo.com>
|
||||
Jan Breig <git@pygos.space>
|
||||
@@ -1018,7 +989,6 @@ Jason Shepherd <jason@jasonshepherd.net>
|
||||
Jason Smith <jasonrichardsmith@gmail.com>
|
||||
Jason Sommer <jsdirv@gmail.com>
|
||||
Jason Stangroome <jason@codeassassin.com>
|
||||
Jasper Siepkes <siepkes@serviceplanet.nl>
|
||||
Javier Bassi <javierbassi@gmail.com>
|
||||
jaxgeller <jacksongeller@gmail.com>
|
||||
Jay <teguhwpurwanto@gmail.com>
|
||||
@@ -1083,16 +1053,13 @@ Jim Perrin <jperrin@centos.org>
|
||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||
Jimmy Puckett <jimmy.puckett@spinen.com>
|
||||
Jimmy Song <rootsongjc@gmail.com>
|
||||
jinjiadu <jinjiadu@aliyun.com>
|
||||
Jinsoo Park <cellpjs@gmail.com>
|
||||
Jintao Zhang <zhangjintao9020@gmail.com>
|
||||
Jiri Appl <jiria@microsoft.com>
|
||||
Jiri Popelka <jpopelka@redhat.com>
|
||||
Jiuyue Ma <majiuyue@huawei.com>
|
||||
Jiří Župka <jzupka@redhat.com>
|
||||
jjimbo137 <115816493+jjimbo137@users.noreply.github.com>
|
||||
Joakim Roubert <joakim.roubert@axis.com>
|
||||
Joan Grau <grautxo.dev@proton.me>
|
||||
Joao Fernandes <joao.fernandes@docker.com>
|
||||
Joao Trindade <trindade.joao@gmail.com>
|
||||
Joe Beda <joe.github@bedafamily.com>
|
||||
@@ -1133,7 +1100,6 @@ Jon Johnson <jonjohnson@google.com>
|
||||
Jon Surrell <jon.surrell@gmail.com>
|
||||
Jon Wedaman <jweede@gmail.com>
|
||||
Jonas Dohse <jonas@dohse.ch>
|
||||
Jonas Geiler <git@jonasgeiler.com>
|
||||
Jonas Heinrich <Jonas@JonasHeinrich.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
Jonathan A. Schweder <jonathanschweder@gmail.com>
|
||||
@@ -1177,7 +1143,6 @@ Josiah Kiehl <jkiehl@riotgames.com>
|
||||
José Tomás Albornoz <jojo@eljojo.net>
|
||||
Joyce Jang <mail@joycejang.com>
|
||||
JP <jpellerin@leapfrogonline.com>
|
||||
JSchltggr <jschltggr@gmail.com>
|
||||
Julian Taylor <jtaylor.debian@googlemail.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Julien Bisconti <veggiemonk@users.noreply.github.com>
|
||||
@@ -1212,7 +1177,6 @@ K. Heller <pestophagous@gmail.com>
|
||||
Kai Blin <kai@samba.org>
|
||||
Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
|
||||
Kaijie Chen <chen@kaijie.org>
|
||||
Kaita Nakamura <kaita.nakamura0830@gmail.com>
|
||||
Kamil Domański <kamil@domanski.co>
|
||||
Kamjar Gerami <kami.gerami@gmail.com>
|
||||
Kanstantsin Shautsou <kanstantsin.sha@gmail.com>
|
||||
@@ -1287,7 +1251,6 @@ Krasi Georgiev <krasi@vip-consult.solutions>
|
||||
Krasimir Georgiev <support@vip-consult.co.uk>
|
||||
Kris-Mikael Krister <krismikael@protonmail.com>
|
||||
Kristian Haugene <kristian.haugene@capgemini.com>
|
||||
Kristian Heljas <kristian@kristian.ee>
|
||||
Kristina Zabunova <triara.xiii@gmail.com>
|
||||
Krystian Wojcicki <kwojcicki@sympatico.ca>
|
||||
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
|
||||
@@ -1304,7 +1267,6 @@ Lakshan Perera <lakshan@laktek.com>
|
||||
Lalatendu Mohanty <lmohanty@redhat.com>
|
||||
Lance Chen <cyen0312@gmail.com>
|
||||
Lance Kinley <lkinley@loyaltymethods.com>
|
||||
Lars Andringa <l.s.andringa@rug.nl>
|
||||
Lars Butler <Lars.Butler@gmail.com>
|
||||
Lars Kellogg-Stedman <lars@redhat.com>
|
||||
Lars R. Damerow <lars@pixar.com>
|
||||
@@ -1314,7 +1276,6 @@ Laura Brehm <laurabrehm@hey.com>
|
||||
Laura Frank <ljfrank@gmail.com>
|
||||
Laurent Bernaille <laurent.bernaille@datadoghq.com>
|
||||
Laurent Erignoux <lerignoux@gmail.com>
|
||||
Laurent Goderre <laurent.goderre@docker.com>
|
||||
Laurie Voss <github@seldo.com>
|
||||
Leandro Motta Barros <lmb@stackedboxes.org>
|
||||
Leandro Siqueira <leandro.siqueira@gmail.com>
|
||||
@@ -1395,7 +1356,6 @@ Madhan Raj Mookkandy <MadhanRaj.Mookkandy@microsoft.com>
|
||||
Madhav Puri <madhav.puri@gmail.com>
|
||||
Madhu Venugopal <mavenugo@gmail.com>
|
||||
Mageee <fangpuyi@foxmail.com>
|
||||
maggie44 <64841595+maggie44@users.noreply.github.com>
|
||||
Mahesh Tiyyagura <tmahesh@gmail.com>
|
||||
malnick <malnick@gmail..com>
|
||||
Malte Janduda <mail@janduda.net>
|
||||
@@ -1489,7 +1449,6 @@ Matthias Kühnle <git.nivoc@neverbox.com>
|
||||
Matthias Rampke <mr@soundcloud.com>
|
||||
Matthieu Fronton <m@tthieu.fr>
|
||||
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
|
||||
Matthieu MOREL <matthieu.morel35@gmail.com>
|
||||
Mattias Jernberg <nostrad@gmail.com>
|
||||
Mauricio Garavaglia <mauricio@medallia.com>
|
||||
mauriyouth <mauriyouth@gmail.com>
|
||||
@@ -1607,7 +1566,6 @@ Muayyad Alsadi <alsadi@gmail.com>
|
||||
Muhammad Zohaib Aslam <zohaibse011@gmail.com>
|
||||
Mustafa Akın <mustafa91@gmail.com>
|
||||
Muthukumar R <muthur@gmail.com>
|
||||
Myeongjoon Kim <kimmj8409@gmail.com>
|
||||
Máximo Cuadros <mcuadros@gmail.com>
|
||||
Médi-Rémi Hashim <medimatrix@users.noreply.github.com>
|
||||
Nace Oroz <orkica@gmail.com>
|
||||
@@ -1622,7 +1580,6 @@ Natasha Jarus <linuxmercedes@gmail.com>
|
||||
Nate Brennand <nate.brennand@clever.com>
|
||||
Nate Eagleson <nate@nateeag.com>
|
||||
Nate Jones <nate@endot.org>
|
||||
Nathan Baulch <nathan.baulch@gmail.com>
|
||||
Nathan Carlson <carl4403@umn.edu>
|
||||
Nathan Herald <me@nathanherald.com>
|
||||
Nathan Hsieh <hsieh.nathan@gmail.com>
|
||||
@@ -1685,7 +1642,6 @@ Nuutti Kotivuori <naked@iki.fi>
|
||||
nzwsch <hi@nzwsch.com>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
objectified <objectified@gmail.com>
|
||||
Octol1ttle <l1ttleofficial@outlook.com>
|
||||
Odin Ugedal <odin@ugedal.com>
|
||||
Oguz Bilgic <fisyonet@gmail.com>
|
||||
Oh Jinkyun <tintypemolly@gmail.com>
|
||||
@@ -1717,10 +1673,8 @@ Patrick Böänziger <patrick.baenziger@bsi-software.com>
|
||||
Patrick Devine <patrick.devine@docker.com>
|
||||
Patrick Haas <patrickhaas@google.com>
|
||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||
Patrick St. laurent <patrick@saint-laurent.us>
|
||||
Patrick Stapleton <github@gdi2290.com>
|
||||
Patrik Cyvoct <patrik@ptrk.io>
|
||||
Patrik Leifert <patrikleifert@hotmail.com>
|
||||
pattichen <craftsbear@gmail.com>
|
||||
Paul "TBBle" Hampson <Paul.Hampson@Pobox.com>
|
||||
Paul <paul9869@gmail.com>
|
||||
@@ -1795,7 +1749,6 @@ Pierre Carrier <pierre@meteor.com>
|
||||
Pierre Dal-Pra <dalpra.pierre@gmail.com>
|
||||
Pierre Wacrenier <pierre.wacrenier@gmail.com>
|
||||
Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
||||
pinglanlu <pinglanlu@outlook.com>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
Piotr Karbowski <piotr.karbowski@protonmail.ch>
|
||||
Porjo <porjo38@yahoo.com.au>
|
||||
@@ -1823,7 +1776,6 @@ Quentin Tayssier <qtayssier@gmail.com>
|
||||
r0n22 <cameron.regan@gmail.com>
|
||||
Rachit Sharma <rachitsharma613@gmail.com>
|
||||
Radostin Stoyanov <rstoyanov1@gmail.com>
|
||||
Rafael Fernández López <ereslibre@ereslibre.es>
|
||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
||||
Rafe Colton <rafael.colton@gmail.com>
|
||||
Raghavendra K T <raghavendra.kt@linux.vnet.ibm.com>
|
||||
@@ -1879,7 +1831,6 @@ Robert Obryk <robryk@gmail.com>
|
||||
Robert Schneider <mail@shakeme.info>
|
||||
Robert Shade <robert.shade@gmail.com>
|
||||
Robert Stern <lexandro2000@gmail.com>
|
||||
Robert Sturla <robertsturla@outlook.com>
|
||||
Robert Terhaar <rterhaar@atlanticdynamic.com>
|
||||
Robert Wallis <smilingrob@gmail.com>
|
||||
Robert Wang <robert@arctic.tw>
|
||||
@@ -1891,7 +1842,7 @@ Robin Speekenbrink <robin@kingsquare.nl>
|
||||
Robin Thoni <robin@rthoni.com>
|
||||
robpc <rpcann@gmail.com>
|
||||
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||
Rodrigo Campos <rodrigoca@microsoft.com>
|
||||
Rodrigo Campos <rodrigo@kinvolk.io>
|
||||
Rodrigo Vaz <rodrigo.vaz@gmail.com>
|
||||
Roel Van Nyen <roel.vannyen@gmail.com>
|
||||
Roger Peppe <rogpeppe@gmail.com>
|
||||
@@ -1927,7 +1878,6 @@ Royce Remer <royceremer@gmail.com>
|
||||
Rozhnov Alexandr <nox73@ya.ru>
|
||||
Rudolph Gottesheim <r.gottesheim@loot.at>
|
||||
Rui Cao <ruicao@alauda.io>
|
||||
Rui JingAn <quiterace@gmail.com>
|
||||
Rui Lopes <rgl@ruilopes.com>
|
||||
Ruilin Li <liruilin4@huawei.com>
|
||||
Runshen Zhu <runshen.zhu@gmail.com>
|
||||
@@ -2030,7 +1980,6 @@ Sevki Hasirci <s@sevki.org>
|
||||
Shane Canon <scanon@lbl.gov>
|
||||
Shane da Silva <shane@dasilva.io>
|
||||
Shaun Kaasten <shaunk@gmail.com>
|
||||
Shaun Thompson <shaun.thompson@docker.com>
|
||||
shaunol <shaunol@gmail.com>
|
||||
Shawn Landden <shawn@churchofgit.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
@@ -2049,7 +1998,6 @@ Shijun Qin <qinshijun16@mails.ucas.ac.cn>
|
||||
Shishir Mahajan <shishir.mahajan@redhat.com>
|
||||
Shoubhik Bose <sbose78@gmail.com>
|
||||
Shourya Sarcar <shourya.sarcar@gmail.com>
|
||||
Shreenidhi Shedi <shreenidhi.shedi@broadcom.com>
|
||||
Shu-Wai Chow <shu-wai.chow@seattlechildrens.org>
|
||||
shuai-z <zs.broccoli@gmail.com>
|
||||
Shukui Yang <yangshukui@huawei.com>
|
||||
@@ -2137,7 +2085,6 @@ Sébastien Stormacq <sebsto@users.noreply.github.com>
|
||||
Sören Tempel <soeren+git@soeren-tempel.net>
|
||||
Tabakhase <mail@tabakhase.com>
|
||||
Tadej Janež <tadej.j@nez.si>
|
||||
Tadeusz Dudkiewicz <tadeusz.dudkiewicz@rtbhouse.com>
|
||||
Takuto Sato <tockn.jp@gmail.com>
|
||||
tang0th <tang0th@gmx.com>
|
||||
Tangi Colin <tangicolin@gmail.com>
|
||||
@@ -2145,7 +2092,6 @@ Tatsuki Sugiura <sugi@nemui.org>
|
||||
Tatsushi Inagaki <e29253@jp.ibm.com>
|
||||
Taylan Isikdemir <taylani@google.com>
|
||||
Taylor Jones <monitorjbl@gmail.com>
|
||||
tcpdumppy <847462026@qq.com>
|
||||
Ted M. Young <tedyoung@gmail.com>
|
||||
Tehmasp Chaudhri <tehmasp@gmail.com>
|
||||
Tejaswini Duggaraju <naduggar@microsoft.com>
|
||||
@@ -2238,7 +2184,6 @@ Tomek Mańko <tomek.manko@railgun-solutions.com>
|
||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||
Tomoya Tabuchi <t@tomoyat1.com>
|
||||
Tomáš Hrčka <thrcka@redhat.com>
|
||||
Tomáš Virtus <nechtom@gmail.com>
|
||||
tonic <tonicbupt@gmail.com>
|
||||
Tonny Xu <tonny.xu@gmail.com>
|
||||
Tony Abboud <tdabboud@hotmail.com>
|
||||
@@ -2283,7 +2228,6 @@ Victor I. Wood <viw@t2am.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Marmol <vmarmol@google.com>
|
||||
Victor Palma <palma.victor@gmail.com>
|
||||
Victor Toni <victor.toni@gmail.com>
|
||||
Victor Vieux <victor.vieux@docker.com>
|
||||
Victoria Bialas <victoria.bialas@docker.com>
|
||||
Vijaya Kumar K <vijayak@caviumnetworks.com>
|
||||
@@ -2335,7 +2279,6 @@ Wassim Dhif <wassimdhif@gmail.com>
|
||||
Wataru Ishida <ishida.wataru@lab.ntt.co.jp>
|
||||
Wayne Chang <wayne@neverfear.org>
|
||||
Wayne Song <wsong@docker.com>
|
||||
weebney <weebney@gmail.com>
|
||||
Weerasak Chongnguluam <singpor@gmail.com>
|
||||
Wei Fu <fuweid89@gmail.com>
|
||||
Wei Wu <wuwei4455@gmail.com>
|
||||
@@ -2430,7 +2373,6 @@ You-Sheng Yang (楊有勝) <vicamo@gmail.com>
|
||||
youcai <omegacoleman@gmail.com>
|
||||
Youcef YEKHLEF <yyekhlef@gmail.com>
|
||||
Youfu Zhang <zhangyoufu@gmail.com>
|
||||
YR Chen <stevapple@icloud.com>
|
||||
Yu Changchun <yuchangchun1@huawei.com>
|
||||
Yu Chengxia <yuchengxia@huawei.com>
|
||||
Yu Peng <yu.peng36@zte.com.cn>
|
||||
|
||||
@@ -83,39 +83,6 @@ contributions, see [the advanced contribution
|
||||
section](https://docs.docker.com/opensource/workflow/advanced-contributing/) in
|
||||
the contributors guide.
|
||||
|
||||
### Where to put your changes
|
||||
|
||||
You can make changes to any Go package within Moby outside of the vendor directory. There are no
|
||||
restrictions on packages but a few guidelines to follow for deciding on making these changes.
|
||||
When adding new packages, first consider putting them in an internal directory to prevent
|
||||
unintended importing from other modules. Code changes should either go under `api`, `client`,
|
||||
or `daemon` modules, or one of the integration test directories.
|
||||
|
||||
Try to put a new package under the appropriate directories. The root directory is reserved for
|
||||
configuration and build files, no source files will be accepted in the root.
|
||||
|
||||
- `api` - All types shared by client and daemon along with swagger definitions.
|
||||
- `client` - All Go files for the docker client
|
||||
- `contrib` - Files, configurations, and packages related to external tools or libraries
|
||||
- `daemon` - All Go files and packages for building the daemon
|
||||
- `docs` - All Moby technical documentation using markdown
|
||||
- `hack` - All scripts used for testing, development, and CI
|
||||
- `integration` - Testing the integration of the API, client, and daemon
|
||||
- `integration-cli` - Deprecated integration tests of the docker cli with the daemon, no new tests allowed
|
||||
- `pkg` - Legacy Go packages used externally, no new packages should be added here
|
||||
- `project` - All files related to Moby project governance
|
||||
- `vendor` - Autogenerated vendor files from `make vendor` command, do not manually edit files here
|
||||
|
||||
The daemon module has many subpackages. Consider putting new packages under one of these
|
||||
directories.
|
||||
|
||||
- `daemon/cmd` - All Go main packages and the packages used only for that main package
|
||||
- `daemon/internal` - All utility packages used by daemon and not intended for external use
|
||||
- `daemon/man`- All Moby reference manuals used for the `man` command
|
||||
- `daemon/plugins` - All included daemon plugins which are intended to be registered via init
|
||||
- `daemon/pkg` - All libraries used by daemon and for integration testing
|
||||
- `daemon/version` - Version package with the current daemon version
|
||||
|
||||
### Connect with other Moby Project contributors
|
||||
|
||||
<table class="tg">
|
||||
|
||||
220
Dockerfile
220
Dockerfile
@@ -1,46 +1,32 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
ARG GO_VERSION=1.24.6
|
||||
ARG GO_VERSION=1.22.10
|
||||
ARG BASE_DEBIAN_DISTRO="bookworm"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
ARG XX_VERSION=1.6.1
|
||||
|
||||
# VPNKIT_VERSION is the version of the vpnkit binary which is used as a fallback
|
||||
# network driver for rootless.
|
||||
ARG VPNKIT_VERSION=0.6.0
|
||||
ARG VPNKIT_VERSION=0.5.0
|
||||
|
||||
# DOCKERCLI_VERSION is the version of the CLI to install in the dev-container.
|
||||
ARG DOCKERCLI_VERSION=v28.3.2
|
||||
ARG DOCKERCLI_REPOSITORY="https://github.com/docker/cli.git"
|
||||
|
||||
ARG DOCKERCLI_VERSION=v26.0.0
|
||||
# cli version used for integration-cli tests
|
||||
ARG DOCKERCLI_INTEGRATION_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION=v18.06.3-ce
|
||||
|
||||
# BUILDX_VERSION is the version of buildx to install in the dev container.
|
||||
ARG BUILDX_VERSION=0.25.0
|
||||
|
||||
# COMPOSE_VERSION is the version of compose to install in the dev container.
|
||||
ARG COMPOSE_VERSION=v2.38.2
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION=v17.06.2-ce
|
||||
ARG BUILDX_VERSION=0.13.1
|
||||
ARG COMPOSE_VERSION=v2.25.0
|
||||
|
||||
ARG SYSTEMD="false"
|
||||
ARG FIREWALLD="false"
|
||||
ARG DOCKER_STATIC=1
|
||||
|
||||
# REGISTRY_VERSION specifies the version of the registry to download from
|
||||
# https://hub.docker.com/r/distribution/distribution. This version of
|
||||
# the registry is used to test schema 2 manifests. Generally, the version
|
||||
# specified here should match a current release.
|
||||
ARG REGISTRY_VERSION=3.0.0
|
||||
ARG REGISTRY_VERSION=2.8.3
|
||||
|
||||
# delve is currently only supported on linux/amd64 and linux/arm64;
|
||||
# https://github.com/go-delve/delve/blob/v1.25.0/pkg/proc/native/support_sentinel.go#L1
|
||||
# https://github.com/go-delve/delve/blob/v1.25.0/pkg/proc/native/support_sentinel_linux.go#L1
|
||||
#
|
||||
# ppc64le support was added in v1.21.1, but is still experimental, and requires
|
||||
# the "-tags exp.linuxppc64le" build-tag to be set:
|
||||
# https://github.com/go-delve/delve/commit/71f12207175a1cc09668f856340d8a543c87dcca
|
||||
ARG DELVE_SUPPORTED=${TARGETPLATFORM#linux/amd64} DELVE_SUPPORTED=${DELVE_SUPPORTED#linux/arm64} DELVE_SUPPORTED=${DELVE_SUPPORTED#linux/ppc64le}
|
||||
# https://github.com/go-delve/delve/blob/v1.8.1/pkg/proc/native/support_sentinel.go#L1-L6
|
||||
ARG DELVE_SUPPORTED=${TARGETPLATFORM#linux/amd64} DELVE_SUPPORTED=${DELVE_SUPPORTED#linux/arm64}
|
||||
ARG DELVE_SUPPORTED=${DELVE_SUPPORTED:+"unsupported"}
|
||||
ARG DELVE_SUPPORTED=${DELVE_SUPPORTED:-"supported"}
|
||||
|
||||
@@ -57,13 +43,9 @@ COPY --from=build-dummy /build /build
|
||||
# base
|
||||
FROM --platform=$BUILDPLATFORM ${GOLANG_IMAGE} AS base
|
||||
COPY --from=xx / /
|
||||
# Disable collecting local telemetry, as collected by Go and Delve;
|
||||
#
|
||||
# - https://github.com/go-delve/delve/blob/v1.24.1/CHANGELOG.md#1231-2024-09-23
|
||||
# - https://go.dev/doc/telemetry#background
|
||||
RUN go telemetry off && [ "$(go telemetry)" = "off" ] || { echo "Failed to disable Go telemetry"; exit 1; }
|
||||
RUN echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y file
|
||||
ENV GO111MODULE=off
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
FROM base AS criu
|
||||
@@ -77,22 +59,62 @@ RUN --mount=type=cache,sharing=locked,id=moby-criu-aptlib,target=/var/lib/apt \
|
||||
&& /build/criu --version
|
||||
|
||||
# registry
|
||||
FROM distribution/distribution:$REGISTRY_VERSION AS registry
|
||||
RUN mkdir /build && mv /bin/registry /build/registry
|
||||
FROM base AS registry-src
|
||||
WORKDIR /usr/src/registry
|
||||
RUN git init . && git remote add origin "https://github.com/distribution/distribution.git"
|
||||
|
||||
FROM base AS registry
|
||||
WORKDIR /go/src/github.com/docker/distribution
|
||||
|
||||
# REGISTRY_VERSION_SCHEMA1 specifies the version of the registry to build and
|
||||
# install from the https://github.com/docker/distribution repository. This is
|
||||
# an older (pre v2.3.0) version of the registry that only supports schema1
|
||||
# manifests. This version of the registry is not working on arm64, so installation
|
||||
# is skipped on that architecture.
|
||||
ARG REGISTRY_VERSION_SCHEMA1=v2.1.0
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=from=registry-src,src=/usr/src/registry,rw \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=registry-build-$TARGETPLATFORM \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=tmpfs,target=/go/src <<EOT
|
||||
set -ex
|
||||
export GOPATH="/go/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH"
|
||||
# Make the /build directory no matter what so that it doesn't fail on arm64 or
|
||||
# any other platform where we don't build this registry
|
||||
mkdir /build
|
||||
case $TARGETPLATFORM in
|
||||
linux/amd64|linux/arm/v7|linux/ppc64le|linux/s390x)
|
||||
git fetch -q --depth 1 origin "${REGISTRY_VERSION_SCHEMA1}" +refs/tags/*:refs/tags/*
|
||||
git checkout -q FETCH_HEAD
|
||||
CGO_ENABLED=0 xx-go build -o /build/registry-v2-schema1 -v ./cmd/registry
|
||||
xx-verify /build/registry-v2-schema1
|
||||
;;
|
||||
esac
|
||||
EOT
|
||||
|
||||
FROM distribution/distribution:$REGISTRY_VERSION AS registry-v2
|
||||
RUN mkdir /build && mv /bin/registry /build/registry-v2
|
||||
|
||||
# go-swagger
|
||||
FROM base AS swagger-src
|
||||
WORKDIR /usr/src/swagger
|
||||
# Currently uses a fork from https://github.com/kolyshkin/go-swagger/tree/golang-1.13-fix
|
||||
# TODO: move to under moby/ or fix upstream go-swagger to work for us.
|
||||
RUN git init . && git remote add origin "https://github.com/kolyshkin/go-swagger.git"
|
||||
# GO_SWAGGER_COMMIT specifies the version of the go-swagger binary to build and
|
||||
# install. Go-swagger is used in CI for validating swagger.yaml in hack/validate/swagger-gen
|
||||
ARG GO_SWAGGER_COMMIT=c56166c036004ba7a3a321e5951ba472b9ae298c
|
||||
RUN git fetch -q --depth 1 origin "${GO_SWAGGER_COMMIT}" && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS swagger
|
||||
WORKDIR /go/src/github.com/go-swagger/go-swagger
|
||||
ARG TARGETPLATFORM
|
||||
# GO_SWAGGER_VERSION specifies the version of the go-swagger binary to install.
|
||||
# Go-swagger is used in CI for generating types from swagger.yaml in
|
||||
# hack/validate/swagger-gen
|
||||
ARG GO_SWAGGER_VERSION=v0.32.3
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build,id=swagger-build-$TARGETPLATFORM \
|
||||
RUN --mount=from=swagger-src,src=/usr/src/swagger,rw \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=swagger-build-$TARGETPLATFORM \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=tmpfs,target=/go/src/ <<EOT
|
||||
set -e
|
||||
GOBIN=/build xx-go install "github.com/go-swagger/go-swagger/cmd/swagger@${GO_SWAGGER_VERSION}"
|
||||
xx-go build -o /build/swagger ./cmd/swagger
|
||||
xx-verify /build/swagger
|
||||
EOT
|
||||
|
||||
@@ -115,9 +137,7 @@ RUN /download-frozen-image-v2.sh /build \
|
||||
busybox:glibc@sha256:1f81263701cddf6402afe9f33fca0266d9fff379e59b1748f33d3072da71ee85 \
|
||||
debian:bookworm-slim@sha256:2bc5c236e9b262645a323e9088dfa3bb1ecb16cc75811daf40a23a824d665be9 \
|
||||
hello-world:latest@sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9 \
|
||||
arm32v7/hello-world:latest@sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1 \
|
||||
hello-world:amd64@sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 \
|
||||
hello-world:arm64@sha256:963612c5503f3f1674f315c67089dee577d8cc6afc18565e0b4183ae355fb343
|
||||
arm32v7/hello-world:latest@sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1
|
||||
|
||||
# delve
|
||||
FROM base AS delve-src
|
||||
@@ -127,7 +147,7 @@ RUN git init . && git remote add origin "https://github.com/go-delve/delve.git"
|
||||
# from the https://github.com/go-delve/delve repository.
|
||||
# It can be used to run Docker with a possibility of
|
||||
# attaching debugger to it.
|
||||
ARG DELVE_VERSION=v1.25.0
|
||||
ARG DELVE_VERSION=v1.23.0
|
||||
RUN git fetch -q --depth 1 origin "${DELVE_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS delve-supported
|
||||
@@ -137,19 +157,32 @@ RUN --mount=from=delve-src,src=/usr/src/delve,rw \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=delve-build-$TARGETPLATFORM \
|
||||
--mount=type=cache,target=/go/pkg/mod <<EOT
|
||||
set -e
|
||||
xx-go build -o /build/dlv ./cmd/dlv
|
||||
GO111MODULE=on xx-go build -o /build/dlv ./cmd/dlv
|
||||
xx-verify /build/dlv
|
||||
EOT
|
||||
|
||||
FROM binary-dummy AS delve-unsupported
|
||||
FROM delve-${DELVE_SUPPORTED} AS delve
|
||||
|
||||
FROM base AS tomll
|
||||
# GOTOML_VERSION specifies the version of the tomll binary to build and install
|
||||
# from the https://github.com/pelletier/go-toml repository. This binary is used
|
||||
# in CI in the hack/validate/toml script.
|
||||
#
|
||||
# When updating this version, consider updating the github.com/pelletier/go-toml
|
||||
# dependency in vendor.mod accordingly.
|
||||
ARG GOTOML_VERSION=v1.8.1
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build/ GO111MODULE=on go install "github.com/pelletier/go-toml/cmd/tomll@${GOTOML_VERSION}" \
|
||||
&& /build/tomll --help
|
||||
|
||||
FROM base AS gowinres
|
||||
# GOWINRES_VERSION defines go-winres tool version
|
||||
ARG GOWINRES_VERSION=v0.3.1
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build/ go install "github.com/tc-hib/go-winres@${GOWINRES_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "github.com/tc-hib/go-winres@${GOWINRES_VERSION}" \
|
||||
&& /build/go-winres --help
|
||||
|
||||
# containerd
|
||||
@@ -159,8 +192,11 @@ RUN git init . && git remote add origin "https://github.com/containerd/container
|
||||
# CONTAINERD_VERSION is used to build containerd binaries, and used for the
|
||||
# integration tests. The distributed docker .deb and .rpm packages depend on a
|
||||
# separate (containerd.io) package, which may be a different version as is
|
||||
# specified here.
|
||||
ARG CONTAINERD_VERSION=v1.7.28
|
||||
# specified here. The containerd golang package is also pinned in vendor.mod.
|
||||
# When updating the binary version you may also need to update the vendor
|
||||
# version to pick up bug fixes or new APIs, however, usually the Go packages
|
||||
# are built from a commit from the master branch.
|
||||
ARG CONTAINERD_VERSION=v1.7.18
|
||||
RUN git fetch -q --depth 1 origin "${CONTAINERD_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS containerd-build
|
||||
@@ -170,6 +206,8 @@ RUN --mount=type=cache,sharing=locked,id=moby-containerd-aptlib,target=/var/lib/
|
||||
--mount=type=cache,sharing=locked,id=moby-containerd-aptcache,target=/var/cache/apt \
|
||||
apt-get update && xx-apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libbtrfs-dev \
|
||||
libsecret-1-dev \
|
||||
pkg-config
|
||||
ARG DOCKER_STATIC
|
||||
RUN --mount=from=containerd-src,src=/usr/src/containerd,rw \
|
||||
@@ -191,31 +229,30 @@ FROM binary-dummy AS containerd-windows
|
||||
FROM containerd-${TARGETOS} AS containerd
|
||||
|
||||
FROM base AS golangci_lint
|
||||
ARG GOLANGCI_LINT_VERSION=v2.1.5
|
||||
ARG GOLANGCI_LINT_VERSION=v1.60.2
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build/ go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" \
|
||||
&& /build/golangci-lint --version
|
||||
|
||||
FROM base AS gotestsum
|
||||
# GOTESTSUM_VERSION is the version of gotest.tools/gotestsum to install.
|
||||
ARG GOTESTSUM_VERSION=v1.12.3
|
||||
ARG GOTESTSUM_VERSION=v1.8.2
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build/ go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
|
||||
&& /build/gotestsum --version
|
||||
|
||||
FROM base AS shfmt
|
||||
ARG SHFMT_VERSION=v3.8.0
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build/ go install "mvdan.cc/sh/v3/cmd/shfmt@${SHFMT_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "mvdan.cc/sh/v3/cmd/shfmt@${SHFMT_VERSION}" \
|
||||
&& /build/shfmt --version
|
||||
|
||||
FROM base AS gopls
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build/ go install "golang.org/x/tools/gopls@latest" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "golang.org/x/tools/gopls@latest" \
|
||||
&& /build/gopls version
|
||||
|
||||
FROM base AS dockercli
|
||||
@@ -228,8 +265,7 @@ RUN --mount=source=hack/dockerfile/cli.sh,target=/download-or-build-cli.sh \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=dockercli-build-$TARGETPLATFORM \
|
||||
rm -f ./.git/*.lock \
|
||||
&& /download-or-build-cli.sh ${DOCKERCLI_VERSION} ${DOCKERCLI_REPOSITORY} /build \
|
||||
&& /build/docker --version \
|
||||
&& /build/docker completion bash >/completion.bash
|
||||
&& /build/docker --version
|
||||
|
||||
FROM base AS dockercli-integration
|
||||
WORKDIR /go/src/github.com/docker/cli
|
||||
@@ -249,8 +285,9 @@ WORKDIR /usr/src/runc
|
||||
RUN git init . && git remote add origin "https://github.com/opencontainers/runc.git"
|
||||
# RUNC_VERSION should match the version that is used by the containerd version
|
||||
# that is used. If you need to update runc, open a pull request in the containerd
|
||||
# project first, and update both after that is merged.
|
||||
ARG RUNC_VERSION=v1.3.0
|
||||
# project first, and update both after that is merged. When updating RUNC_VERSION,
|
||||
# consider updating runc in vendor.mod accordingly.
|
||||
ARG RUNC_VERSION=v1.1.14
|
||||
RUN git fetch -q --depth 1 origin "${RUNC_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS runc-build
|
||||
@@ -259,6 +296,7 @@ ARG TARGETPLATFORM
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-runc-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-runc-aptcache,target=/var/cache/apt \
|
||||
apt-get update && xx-apt-get install -y --no-install-recommends \
|
||||
dpkg-dev \
|
||||
gcc \
|
||||
libc6-dev \
|
||||
libseccomp-dev \
|
||||
@@ -317,8 +355,8 @@ FROM tini-${TARGETOS} AS tini
|
||||
FROM base AS rootlesskit-src
|
||||
WORKDIR /usr/src/rootlesskit
|
||||
RUN git init . && git remote add origin "https://github.com/rootless-containers/rootlesskit.git"
|
||||
# When updating, also update go.mod and hack/dockerfile/install/rootlesskit.installer accordingly.
|
||||
ARG ROOTLESSKIT_VERSION=v2.3.4
|
||||
# When updating, also update vendor.mod and hack/dockerfile/install/rootlesskit.installer accordingly.
|
||||
ARG ROOTLESSKIT_VERSION=v2.0.2
|
||||
RUN git fetch -q --depth 1 origin "${ROOTLESSKIT_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS rootlesskit-build
|
||||
@@ -330,6 +368,7 @@ RUN --mount=type=cache,sharing=locked,id=moby-rootlesskit-aptlib,target=/var/lib
|
||||
gcc \
|
||||
libc6-dev \
|
||||
pkg-config
|
||||
ENV GO111MODULE=on
|
||||
ARG DOCKER_STATIC
|
||||
RUN --mount=from=rootlesskit-src,src=/usr/src/rootlesskit,rw \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
@@ -338,6 +377,8 @@ RUN --mount=from=rootlesskit-src,src=/usr/src/rootlesskit,rw \
|
||||
export CGO_ENABLED=$([ "$DOCKER_STATIC" = "1" ] && echo "0" || echo "1")
|
||||
xx-go build -o /build/rootlesskit -ldflags="$([ "$DOCKER_STATIC" != "1" ] && echo "-linkmode=external")" ./cmd/rootlesskit
|
||||
xx-verify $([ "$DOCKER_STATIC" = "1" ] && echo "--static") /build/rootlesskit
|
||||
xx-go build -o /build/rootlesskit-docker-proxy -ldflags="$([ "$DOCKER_STATIC" != "1" ] && echo "-linkmode=external")" ./cmd/rootlesskit-docker-proxy
|
||||
xx-verify $([ "$DOCKER_STATIC" = "1" ] && echo "--static") /build/rootlesskit-docker-proxy
|
||||
EOT
|
||||
COPY --link ./contrib/dockerd-rootless.sh /build/
|
||||
COPY --link ./contrib/dockerd-rootless-setuptool.sh /build/
|
||||
@@ -347,8 +388,7 @@ FROM binary-dummy AS rootlesskit-windows
|
||||
FROM rootlesskit-${TARGETOS} AS rootlesskit
|
||||
|
||||
FROM base AS crun
|
||||
# CRUN_VERSION is the version of crun to install in the dev-container.
|
||||
ARG CRUN_VERSION=1.21
|
||||
ARG CRUN_VERSION=1.12
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-crun-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-crun-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
@@ -360,6 +400,7 @@ RUN --mount=type=cache,sharing=locked,id=moby-crun-aptlib,target=/var/lib/apt \
|
||||
libseccomp-dev \
|
||||
libsystemd-dev \
|
||||
libtool \
|
||||
libudev-dev \
|
||||
libyajl-dev \
|
||||
python3 \
|
||||
;
|
||||
@@ -379,8 +420,8 @@ FROM scratch AS vpnkit-linux-arm
|
||||
FROM scratch AS vpnkit-linux-ppc64le
|
||||
FROM scratch AS vpnkit-linux-riscv64
|
||||
FROM scratch AS vpnkit-linux-s390x
|
||||
FROM moby/vpnkit-bin:${VPNKIT_VERSION} AS vpnkit-linux-amd64
|
||||
FROM moby/vpnkit-bin:${VPNKIT_VERSION} AS vpnkit-linux-arm64
|
||||
FROM djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit-linux-amd64
|
||||
FROM djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit-linux-arm64
|
||||
FROM vpnkit-linux-${TARGETARCH} AS vpnkit-linux
|
||||
FROM vpnkit-${TARGETOS} AS vpnkit
|
||||
|
||||
@@ -412,16 +453,18 @@ FROM binary-dummy AS containerutil-linux
|
||||
FROM containerutil-build AS containerutil-windows-amd64
|
||||
FROM containerutil-windows-${TARGETARCH} AS containerutil-windows
|
||||
FROM containerutil-${TARGETOS} AS containerutil
|
||||
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
|
||||
FROM docker/compose-bin:${COMPOSE_VERSION} AS compose
|
||||
FROM docker/buildx-bin:${BUILDX_VERSION} as buildx
|
||||
FROM docker/compose-bin:${COMPOSE_VERSION} as compose
|
||||
|
||||
FROM base AS dev-systemd-false
|
||||
COPY --link --from=frozen-images /build/ /docker-frozen-images
|
||||
COPY --link --from=swagger /build/ /usr/local/bin/
|
||||
COPY --link --from=delve /build/ /usr/local/bin/
|
||||
COPY --link --from=tomll /build/ /usr/local/bin/
|
||||
COPY --link --from=gowinres /build/ /usr/local/bin/
|
||||
COPY --link --from=tini /build/ /usr/local/bin/
|
||||
COPY --link --from=registry /build/ /usr/local/bin/
|
||||
COPY --link --from=registry-v2 /build/ /usr/local/bin/
|
||||
|
||||
# Skip the CRIU stage for now, as the opensuse package repository is sometimes
|
||||
# unstable, and we're currently not using it in CI.
|
||||
@@ -461,23 +504,16 @@ RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
systemd-sysv
|
||||
ENTRYPOINT ["hack/dind-systemd"]
|
||||
|
||||
FROM dev-systemd-${SYSTEMD} AS dev-firewalld-false
|
||||
|
||||
FROM dev-systemd-true AS dev-firewalld-true
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-dev-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
firewalld
|
||||
|
||||
FROM dev-firewalld-${FIREWALLD} AS dev-base
|
||||
FROM dev-systemd-${SYSTEMD} AS dev-base
|
||||
RUN groupadd -r docker
|
||||
RUN useradd --create-home --gid docker unprivilegeduser \
|
||||
&& mkdir -p /home/unprivilegeduser/.local/share/docker \
|
||||
&& chown -R unprivilegeduser /home/unprivilegeduser
|
||||
# Let us use a .bashrc file
|
||||
RUN ln -sfv /go/src/github.com/docker/docker/.bashrc ~/.bashrc
|
||||
# Activate bash completion
|
||||
# Activate bash completion and include Docker's completion if mounted with DOCKER_BASH_COMPLETION_PATH
|
||||
RUN echo "source /usr/share/bash-completion/bash_completion" >> /etc/bash.bashrc
|
||||
RUN ln -s /usr/local/completion/bash/docker /etc/bash_completion.d/docker
|
||||
RUN ldconfig
|
||||
# Set dev environment as safe git directory to prevent "dubious ownership" errors
|
||||
# when bind-mounting the source into the dev-container. See https://github.com/moby/moby/pull/44930
|
||||
@@ -493,16 +529,13 @@ RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
inetutils-ping \
|
||||
iproute2 \
|
||||
iptables \
|
||||
nftables \
|
||||
jq \
|
||||
libcap2-bin \
|
||||
libnet1 \
|
||||
libnl-3-200 \
|
||||
libprotobuf-c1 \
|
||||
libyajl2 \
|
||||
nano \
|
||||
net-tools \
|
||||
netcat-openbsd \
|
||||
patch \
|
||||
pigz \
|
||||
sudo \
|
||||
@@ -515,37 +548,49 @@ RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
xz-utils \
|
||||
zip \
|
||||
zstd
|
||||
# Switch to use iptables instead of nftables (to match the CI hosts)
|
||||
# TODO use some kind of runtime auto-detection instead if/when nftables is supported (https://github.com/moby/moby/issues/26824)
|
||||
RUN update-alternatives --set iptables /usr/sbin/iptables-legacy || true \
|
||||
&& update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true \
|
||||
&& update-alternatives --set arptables /usr/sbin/arptables-legacy || true
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-dev-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install --no-install-recommends -y \
|
||||
gcc \
|
||||
pkg-config \
|
||||
dpkg-dev \
|
||||
libapparmor-dev \
|
||||
libseccomp-dev \
|
||||
libsecret-1-dev \
|
||||
libsystemd-dev \
|
||||
libudev-dev \
|
||||
yamllint
|
||||
COPY --link --from=dockercli /build/ /usr/local/cli
|
||||
COPY --link --from=dockercli /completion.bash /etc/bash_completion.d/docker
|
||||
COPY --link --from=dockercli-integration /build/ /usr/local/cli-integration
|
||||
|
||||
FROM base AS build
|
||||
COPY --from=gowinres /build/ /usr/local/bin/
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV GO111MODULE=off
|
||||
ENV CGO_ENABLED=1
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-build-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-build-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install --no-install-recommends -y \
|
||||
clang \
|
||||
lld \
|
||||
llvm \
|
||||
icoutils
|
||||
llvm
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-build-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-build-aptcache,target=/var/cache/apt \
|
||||
xx-apt-get install --no-install-recommends -y \
|
||||
dpkg-dev \
|
||||
gcc \
|
||||
libapparmor-dev \
|
||||
libc6-dev \
|
||||
libseccomp-dev \
|
||||
libsecret-1-dev \
|
||||
libsystemd-dev \
|
||||
libudev-dev \
|
||||
pkg-config
|
||||
ARG DOCKER_BUILDTAGS
|
||||
ARG DOCKER_DEBUG
|
||||
@@ -567,13 +612,15 @@ RUN <<EOT
|
||||
fi
|
||||
EOT
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=type=tmpfs,target=cli/winresources/dockerd \
|
||||
--mount=type=tmpfs,target=cli/winresources/docker-proxy \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=moby-build-$TARGETPLATFORM <<EOT
|
||||
set -e
|
||||
target=$([ "$DOCKER_STATIC" = "1" ] && echo "binary" || echo "dynbinary")
|
||||
xx-go --wrap
|
||||
PKG_CONFIG=$(xx-go env PKG_CONFIG) ./hack/make.sh $target
|
||||
xx-verify $([ "$DOCKER_STATIC" = "1" ] && echo "--static") /tmp/bundles/${target}-daemon/dockerd$([ "$(xx-info os)" = "windows" ] && echo ".exe")
|
||||
[ "$(xx-info os)" != "linux" ] || xx-verify $([ "$DOCKER_STATIC" = "1" ] && echo "--static") /tmp/bundles/${target}-daemon/docker-proxy
|
||||
xx-verify $([ "$DOCKER_STATIC" = "1" ] && echo "--static") /tmp/bundles/${target}-daemon/docker-proxy$([ "$(xx-info os)" = "windows" ] && echo ".exe")
|
||||
mkdir /build
|
||||
mv /tmp/bundles/${target}-daemon/* /build/
|
||||
EOT
|
||||
@@ -601,7 +648,7 @@ COPY --link --from=build /build /
|
||||
# smoke tests
|
||||
# usage:
|
||||
# > docker buildx bake binary-smoketest
|
||||
FROM base AS smoketest
|
||||
FROM --platform=$TARGETPLATFORM base AS smoketest
|
||||
WORKDIR /usr/local/bin
|
||||
COPY --from=build /build .
|
||||
RUN <<EOT
|
||||
@@ -617,15 +664,6 @@ FROM dev-base AS devcontainer
|
||||
COPY --link . .
|
||||
COPY --link --from=gopls /build/ /usr/local/bin/
|
||||
|
||||
# usage:
|
||||
# > docker buildx bake dind
|
||||
# > docker run -d --restart always --privileged --name devdind -p 12375:2375 docker-dind --debug --host=tcp://0.0.0.0:2375 --tlsverify=false
|
||||
FROM docker:dind AS dind
|
||||
COPY --link --from=dockercli /build/docker /usr/local/bin/
|
||||
COPY --link --from=buildx /buildx /usr/local/libexec/docker/cli-plugins/docker-buildx
|
||||
COPY --link --from=compose /docker-compose /usr/local/libexec/docker/cli-plugins/docker-compose
|
||||
COPY --link --from=all / /usr/local/bin/
|
||||
|
||||
# usage:
|
||||
# > make shell
|
||||
# > SYSTEMD=true make shell
|
||||
|
||||
@@ -5,22 +5,24 @@
|
||||
|
||||
# This represents the bare minimum required to build and test Docker.
|
||||
|
||||
ARG GO_VERSION=1.24.6
|
||||
ARG GO_VERSION=1.22.10
|
||||
|
||||
ARG BASE_DEBIAN_DISTRO="bookworm"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
|
||||
FROM ${GOLANG_IMAGE}
|
||||
ENV GO111MODULE=off
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
# Compile and runtime deps
|
||||
# https://github.com/moby/moby/blob/master/project/PACKAGERS.md#build-dependencies
|
||||
# https://github.com/moby/moby/blob/master/project/PACKAGERS.md#runtime-dependencies
|
||||
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#build-dependencies
|
||||
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#runtime-dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
cmake \
|
||||
git \
|
||||
libapparmor-dev \
|
||||
libseccomp-dev \
|
||||
ca-certificates \
|
||||
e2fsprogs \
|
||||
@@ -34,10 +36,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
vim-common \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install runc, containerd, and tini
|
||||
# Install runc, containerd, tini and docker-proxy
|
||||
# Please edit hack/dockerfile/install/<name>.installer to update them.
|
||||
COPY hack/dockerfile/install hack/dockerfile/install
|
||||
RUN set -e; for i in runc containerd tini dockercli; \
|
||||
RUN for i in runc containerd tini proxy dockercli; \
|
||||
do hack/dockerfile/install/install.sh $i; \
|
||||
done
|
||||
ENV PATH=/usr/local/cli:$PATH
|
||||
|
||||
@@ -161,14 +161,10 @@ FROM ${WINDOWS_BASE_IMAGE}:${WINDOWS_BASE_IMAGE_TAG}
|
||||
# Use PowerShell as the default shell
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
ARG GO_VERSION=1.24.6
|
||||
|
||||
# GOTESTSUM_VERSION is the version of gotest.tools/gotestsum to install.
|
||||
ARG GOTESTSUM_VERSION=v1.12.3
|
||||
|
||||
# GOWINRES_VERSION is the version of go-winres to install.
|
||||
ARG GOWINRES_VERSION=v0.3.3
|
||||
ARG CONTAINERD_VERSION=v1.7.28
|
||||
ARG GO_VERSION=1.22.10
|
||||
ARG GOTESTSUM_VERSION=v1.8.2
|
||||
ARG GOWINRES_VERSION=v0.3.1
|
||||
ARG CONTAINERD_VERSION=v1.7.18
|
||||
|
||||
# Environment variable notes:
|
||||
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
@@ -178,6 +174,7 @@ ENV GO_VERSION=${GO_VERSION} `
|
||||
CONTAINERD_VERSION=${CONTAINERD_VERSION} `
|
||||
GIT_VERSION=2.11.1 `
|
||||
GOPATH=C:\gopath `
|
||||
GO111MODULE=off `
|
||||
GOTOOLCHAIN=local `
|
||||
FROM_DOCKERFILE=1 `
|
||||
GOTESTSUM_VERSION=${GOTESTSUM_VERSION} `
|
||||
@@ -277,11 +274,13 @@ RUN `
|
||||
|
||||
RUN `
|
||||
Function Install-GoTestSum() { `
|
||||
$Env:GO111MODULE = 'on'; `
|
||||
$tmpGobin = "${Env:GOBIN_TMP}"; `
|
||||
$Env:GOBIN = """${Env:GOPATH}`\bin"""; `
|
||||
Write-Host "INFO: Installing gotestsum version $Env:GOTESTSUM_VERSION in $Env:GOBIN"; `
|
||||
&go install "gotest.tools/gotestsum@${Env:GOTESTSUM_VERSION}"; `
|
||||
$Env:GOBIN = "${tmpGobin}"; `
|
||||
$Env:GO111MODULE = 'off'; `
|
||||
if ($LASTEXITCODE -ne 0) { `
|
||||
Throw '"gotestsum install failed..."'; `
|
||||
} `
|
||||
@@ -291,11 +290,13 @@ RUN `
|
||||
|
||||
RUN `
|
||||
Function Install-GoWinres() { `
|
||||
$Env:GO111MODULE = 'on'; `
|
||||
$tmpGobin = "${Env:GOBIN_TMP}"; `
|
||||
$Env:GOBIN = """${Env:GOPATH}`\bin"""; `
|
||||
Write-Host "INFO: Installing go-winres version $Env:GOWINRES_VERSION in $Env:GOBIN"; `
|
||||
&go install "github.com/tc-hib/go-winres@${Env:GOWINRES_VERSION}"; `
|
||||
$Env:GOBIN = "${tmpGobin}"; `
|
||||
$Env:GO111MODULE = 'off'; `
|
||||
if ($LASTEXITCODE -ne 0) { `
|
||||
Throw '"go-winres install failed..."'; `
|
||||
} `
|
||||
|
||||
610
MAINTAINERS
610
MAINTAINERS
@@ -1,33 +1,585 @@
|
||||
# Moby maintainers file
|
||||
#
|
||||
# See project/GOVERNANCE.md for committer versus reviewer roles
|
||||
# This file describes the maintainer groups within the moby/moby project.
|
||||
# More detail on Moby project governance is available in the
|
||||
# project/GOVERNANCE.md file found in this repository.
|
||||
#
|
||||
# COMMITTERS
|
||||
# GitHub ID, Name, Email address, GPG fingerprint
|
||||
"akerouanton","Albin Kerouanton","albinker@gmail.com"
|
||||
"AkihiroSuda","Akihiro Suda","akihiro.suda.cz@hco.ntt.co.jp"
|
||||
"austinvazquez","Austin Vazquez","austin.vazquez.dev@gmail.com"
|
||||
"corhere","Cory Snider","csnider@mirantis.com"
|
||||
"cpuguy83","Brian Goff","cpuguy83@gmail.com"
|
||||
"robmry","Rob Murray","rob.murray@docker.com"
|
||||
"thaJeztah","Sebastiaan van Stijn","github@gone.nl"
|
||||
"tianon","Tianon Gravi","admwiggin@gmail.com"
|
||||
"tonistiigi","Tõnis Tiigi","tonis@docker.com"
|
||||
"vvoland","Paweł Gronowski","pawel.gronowski@docker.com"
|
||||
# It is structured to be consumable by both humans and programs.
|
||||
# To extract its contents programmatically, use any TOML-compliant
|
||||
# parser.
|
||||
#
|
||||
# REVIEWERS
|
||||
# GitHub ID, Name, Email address, GPG fingerprint
|
||||
"coolljt0725","Lei Jitang","leijitang@huawei.com"
|
||||
"crazy-max","Kevin Alvarez","contact@crazymax.dev"
|
||||
"dmcgowan","Derek McGowan","derek@mcgstyle.net"
|
||||
"estesp","Phil Estes","estesp@linux.vnet.ibm.com"
|
||||
"justincormack","Justin Cormack","justin.cormack@docker.com"
|
||||
"kolyshkin","Kir Kolyshkin","kolyshkin@gmail.com"
|
||||
"laurazard","Laura Brehm","laurabrehm@hey.com"
|
||||
"neersighted","Bjorn Neergaard","bjorn@neersighted.com"
|
||||
"rumpl","Djordje Lukic","djordje.lukic@docker.com"
|
||||
"samuelkarp","Samuel Karp","me@samuelkarp.com"
|
||||
"stevvooe","Stephen Day","stephen.day@docker.com"
|
||||
"thompson-shaun","Shaun Thompson","shaun.thompson@docker.com"
|
||||
"tiborvass","Tibor Vass","tibor@docker.com"
|
||||
"unclejack","Cristian Staretu","cristian.staretu@gmail.com"
|
||||
# TODO(estesp): This file should not necessarily depend on docker/opensource
|
||||
# This file is compiled into the MAINTAINERS file in docker/opensource.
|
||||
#
|
||||
[Org]
|
||||
|
||||
[Org."Core maintainers"]
|
||||
|
||||
# The Core maintainers are the ghostbusters of the project: when there's a problem others
|
||||
# can't solve, they show up and fix it with bizarre devices and weaponry.
|
||||
# They have final say on technical implementation and coding style.
|
||||
# They are ultimately responsible for quality in all its forms: usability polish,
|
||||
# bugfixes, performance, stability, etc. When ownership can cleanly be passed to
|
||||
# a subsystem, they are responsible for doing so and holding the
|
||||
# subsystem maintainers accountable. If ownership is unclear, they are the de facto owners.
|
||||
|
||||
people = [
|
||||
"akerouanton",
|
||||
"akihirosuda",
|
||||
"anusha",
|
||||
"coolljt0725",
|
||||
"corhere",
|
||||
"cpuguy83",
|
||||
"crazy-max",
|
||||
"estesp",
|
||||
"johnstep",
|
||||
"justincormack",
|
||||
"kolyshkin",
|
||||
"laurazard",
|
||||
"mhbauer",
|
||||
"neersighted",
|
||||
"rumpl",
|
||||
"runcom",
|
||||
"samuelkarp",
|
||||
"stevvooe",
|
||||
"thajeztah",
|
||||
"tianon",
|
||||
"tibor",
|
||||
"tonistiigi",
|
||||
"unclejack",
|
||||
"vdemeester",
|
||||
"vieux",
|
||||
"vvoland",
|
||||
"yongtang"
|
||||
]
|
||||
|
||||
[Org.Curators]
|
||||
|
||||
# The curators help ensure that incoming issues and pull requests are properly triaged and
|
||||
# that our various contribution and reviewing processes are respected. With their knowledge of
|
||||
# the repository activity, they can also guide contributors to relevant material or
|
||||
# discussions.
|
||||
#
|
||||
# They are neither code nor docs reviewers, so they are never expected to merge. They can
|
||||
# however:
|
||||
# - close an issue or pull request when it's an exact duplicate
|
||||
# - close an issue or pull request when it's inappropriate or off-topic
|
||||
|
||||
people = [
|
||||
"alexellis",
|
||||
"andrewhsu",
|
||||
"bsousaa",
|
||||
"dmcgowan",
|
||||
"fntlnz",
|
||||
"gianarb",
|
||||
"olljanat",
|
||||
"programmerq",
|
||||
"ripcurld",
|
||||
"robmry",
|
||||
"sam-thibault",
|
||||
"samwhited",
|
||||
"thajeztah"
|
||||
]
|
||||
|
||||
[Org.Alumni]
|
||||
|
||||
# This list contains maintainers that are no longer active on the project.
|
||||
# It is thanks to these people that the project has become what it is today.
|
||||
# Thank you!
|
||||
|
||||
people = [
|
||||
# Aaron Lehmann was a maintainer for swarmkit, the registry, and the engine,
|
||||
# and contributed many improvements, features, and bugfixes in those areas,
|
||||
# among which "automated service rollbacks", templated secrets and configs,
|
||||
# and resumable image layer downloads.
|
||||
"aaronlehmann",
|
||||
|
||||
# Harald Albers is the mastermind behind the bash completion scripts for the
|
||||
# Docker CLI. The completion scripts moved to the Docker CLI repository, so
|
||||
# you can now find him perform his magic in the https://github.com/docker/cli repository.
|
||||
"albers",
|
||||
|
||||
# Andrea Luzzardi started contributing to the Docker codebase in the "dotCloud"
|
||||
# era, even before it was called "Docker". He is one of the architects of both
|
||||
# Swarm and SwarmKit, and its integration into the Docker engine.
|
||||
"aluzzardi",
|
||||
|
||||
# David Calavera contributed many features to Docker, such as an improved
|
||||
# event system, dynamic configuration reloading, volume plugins, fancy
|
||||
# new templating options, and an external client credential store. As a
|
||||
# maintainer, David was release captain for Docker 1.8, and competing
|
||||
# with Jess Frazelle to be "top dream killer".
|
||||
# David is now doing amazing stuff as CTO for https://www.netlify.com,
|
||||
# and tweets as @calavera.
|
||||
"calavera",
|
||||
|
||||
# Michael Crosby was "chief maintainer" of the Docker project.
|
||||
# During his time as a maintainer, Michael contributed to many
|
||||
# milestones of the project; he was release captain of Docker v1.0.0,
|
||||
# started the development of "libcontainer" (what later became runc)
|
||||
# and containerd, as well as demoing cool hacks such as live migrating
|
||||
# a game server container with checkpoint/restore.
|
||||
#
|
||||
# Michael is currently a maintainer of containerd, but you may see
|
||||
# him around in other projects on GitHub.
|
||||
"crosbymichael",
|
||||
|
||||
# Before becoming a maintainer, Daniel Nephin was a core contributor
|
||||
# to "Fig" (now known as Docker Compose). As a maintainer for both the
|
||||
# Engine and Docker CLI, Daniel contributed many features, among which
|
||||
# the `docker stack` commands, allowing users to deploy their Docker
|
||||
# Compose projects as a Swarm service.
|
||||
"dnephin",
|
||||
|
||||
# Doug Davis contributed many features and fixes for the classic builder,
|
||||
# such as "wildcard" copy, the dockerignore file, custom paths/names
|
||||
# for the Dockerfile, as well as enhancements to the API and documentation.
|
||||
# Follow Doug on Twitter, where he tweets as @duginabox.
|
||||
"duglin",
|
||||
|
||||
# As a maintainer, Erik was responsible for the "builder", and
|
||||
# started the first designs for the new networking model in
|
||||
# Docker. Erik is now working on all kinds of plugins for Docker
|
||||
# (https://github.com/contiv) and various open source projects
|
||||
# in his own repository https://github.com/erikh. You may
|
||||
# still stumble into him in our issue tracker, or on IRC.
|
||||
"erikh",
|
||||
|
||||
# Evan Hazlett is the creator of the Shipyard and Interlock open source projects,
|
||||
# and the author of "Orca", which became the foundation of Docker Universal Control
|
||||
# Plane (UCP). As a maintainer, Evan helped integrating SwarmKit (secrets, tasks)
|
||||
# into the Docker engine.
|
||||
"ehazlett",
|
||||
|
||||
# Arnaud Porterie (AKA "icecrime") was in charge of maintaining the maintainers.
|
||||
# As a maintainer, he made life easier for contributors to the Docker open-source
|
||||
# projects, bringing order in the chaos by designing a triage- and review workflow
|
||||
# using labels (see https://icecrime.net/technology/a-structured-approach-to-labeling/),
|
||||
# and automating the hell out of things with his buddies GordonTheTurtle and Poule
|
||||
# (a chicken!).
|
||||
#
|
||||
# A lesser-known fact is that he created the first commit in the libnetwork repository
|
||||
# even though he didn't know anything about it. Some say, he's now selling stuff on
|
||||
# the internet ;-)
|
||||
"icecrime",
|
||||
|
||||
# After a false start with his first PR being rejected, James Turnbull became a frequent
|
||||
# contributor to the documentation, and became a docs maintainer on December 5, 2013. As
|
||||
# a maintainer, James lifted the docs to a higher standard, and introduced the community
|
||||
# guidelines ("three strikes"). James is currently changing the world as CTO of https://www.empatico.org,
|
||||
# meanwhile authoring various books that are worth checking out. You can find him on Twitter,
|
||||
# rambling as @kartar, and although no longer active as a maintainer, he's always "game" to
|
||||
# help out reviewing docs PRs, so you may still see him around in the repository.
|
||||
"jamtur01",
|
||||
|
||||
# Jessica Frazelle, also known as the "Keyser Söze of containers",
|
||||
# runs *everything* in containers. She started contributing to
|
||||
# Docker with a (fun fun) change involving both iptables and regular
|
||||
# expressions (coz, YOLO!) on July 10, 2014
|
||||
# https://github.com/docker/docker/pull/6950/commits/f3a68ffa390fb851115c77783fa4031f1d3b2995.
|
||||
# Jess was Release Captain for Docker 1.4, 1.6 and 1.7, and contributed
|
||||
# many features and improvement, among which "seccomp profiles" (making
|
||||
# containers a lot more secure). Besides being a maintainer, she
|
||||
# set up the CI infrastructure for the project, giving everyone
|
||||
# something to shout at if a PR failed ("noooo Janky!").
|
||||
# Be sure you don't miss her talks at a conference near you (a must-see),
|
||||
# read her blog at https://blog.jessfraz.com (a must-read), and
|
||||
# check out her open source projects on GitHub https://github.com/jessfraz (a must-try).
|
||||
"jessfraz",
|
||||
|
||||
# As a maintainer, John Howard managed to make the impossible possible;
|
||||
# to run Docker on Windows. After facing many challenges, teaching
|
||||
# fellow-maintainers that 'Windows is not Linux', and many changes in
|
||||
# Windows Server to facilitate containers, native Windows containers
|
||||
# saw the light of day in 2015.
|
||||
#
|
||||
# John is now enjoying life without containers: playing piano, painting,
|
||||
# and walking his dogs, but you may occasionally see him drop by on GitHub.
|
||||
"lowenna",
|
||||
|
||||
# Alexander Morozov contributed many features to Docker, worked on the premise of
|
||||
# what later became containerd (and worked on that too), and made a "stupid" Go
|
||||
# vendor tool specifically for docker/docker needs: vndr (https://github.com/LK4D4/vndr).
|
||||
# Not many know that Alexander is a master negotiator, being able to change course
|
||||
# of action with a single "Nope, we're not gonna do that".
|
||||
"lk4d4",
|
||||
|
||||
# Madhu Venugopal was part of the SocketPlane team that joined Docker.
|
||||
# As a maintainer, he was working with Jana for the Container Network
|
||||
# Model (CNM) implemented through libnetwork, and the "routing mesh" powering
|
||||
# Swarm mode networking.
|
||||
"mavenugo",
|
||||
|
||||
# As a maintainer, Kenfe-Mickaël Laventure worked on the container runtime,
|
||||
# integrating containerd 1.0 with the daemon, and adding support for custom
|
||||
# OCI runtimes, as well as implementing the `docker prune` subcommands,
|
||||
# which was a welcome feature to be added. You can keep up with Mickaél on
|
||||
# Twitter (@kmlaventure).
|
||||
"mlaventure",
|
||||
|
||||
# As a docs maintainer, Mary Anthony contributed greatly to the Docker
|
||||
# docs. She wrote the Docker Contributor Guide and Getting Started
|
||||
# Guides. She helped create a doc build system independent of
|
||||
# docker/docker project, and implemented a new docs.docker.com theme and
|
||||
# nav for 2015 Dockercon. Fun fact: the most inherited layer in DockerHub
|
||||
# public repositories was originally referenced in
|
||||
# maryatdocker/docker-whale back in May 2015.
|
||||
"moxiegirl",
|
||||
|
||||
# Jana Radhakrishnan was part of the SocketPlane team that joined Docker.
|
||||
# As a maintainer, he was the lead architect for the Container Network
|
||||
# Model (CNM) implemented through libnetwork, and the "routing mesh" powering
|
||||
# Swarm mode networking.
|
||||
#
|
||||
# Jana started new adventures in networking, but you can find him tweeting as @mrjana,
|
||||
# coding on GitHub https://github.com/mrjana, and he may be hiding on the Docker Community
|
||||
# slack channel :-)
|
||||
"mrjana",
|
||||
|
||||
# Sven Dowideit became a well known person in the Docker ecosphere, building
|
||||
# boot2docker, and became a regular contributor to the project, starting as
|
||||
# early as October 2013 (https://github.com/docker/docker/pull/2119), to become
|
||||
# a maintainer less than two months later (https://github.com/docker/docker/pull/3061).
|
||||
#
|
||||
# As a maintainer, Sven took on the task to convert the documentation from
|
||||
# ReStructuredText to Markdown, migrate to Hugo for generating the docs, and
|
||||
# writing tooling for building, testing, and publishing them.
|
||||
#
|
||||
# If you're not in the occasion to visit "the Australian office", you
|
||||
# can keep up with Sven on Twitter (@SvenDowideit), his blog http://fosiki.com,
|
||||
# and of course on GitHub.
|
||||
"sven",
|
||||
|
||||
# Vincent "vbatts!" Batts made his first contribution to the project
|
||||
# in November 2013, to become a maintainer a few months later, on
|
||||
# May 10, 2014 (https://github.com/docker/docker/commit/d6e666a87a01a5634c250358a94c814bf26cb778).
|
||||
# As a maintainer, Vincent made important contributions to core elements
|
||||
# of Docker, such as "distribution" (tarsum) and graphdrivers (btrfs, devicemapper).
|
||||
# He also contributed the "tar-split" library, an important element
|
||||
# for the content-addressable store.
|
||||
# Vincent is currently a member of the Open Containers Initiative
|
||||
# Technical Oversight Board (TOB), besides his work at Red Hat and
|
||||
# Project Atomic. You can still find him regularly hanging out in
|
||||
# our repository and the #docker-dev and #docker-maintainers IRC channels
|
||||
# for a chat, as he's always a lot of fun.
|
||||
"vbatts",
|
||||
|
||||
# Vishnu became a maintainer to help out on the daemon codebase and
|
||||
# libcontainer integration. He's currently involved in the
|
||||
# Open Containers Initiative, working on the specifications,
|
||||
# besides his work on cAdvisor and Kubernetes for Google.
|
||||
"vishh"
|
||||
]
|
||||
|
||||
[people]
|
||||
|
||||
# A reference list of all people associated with the project.
|
||||
# All other sections should refer to people by their canonical key
|
||||
# in the people section.
|
||||
|
||||
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||
|
||||
[people.aaronlehmann]
|
||||
Name = "Aaron Lehmann"
|
||||
Email = "aaron.lehmann@docker.com"
|
||||
GitHub = "aaronlehmann"
|
||||
|
||||
[people.akerouanton]
|
||||
Name = "Albin Kerouanton"
|
||||
Email = "albinker@gmail.com"
|
||||
GitHub = "akerouanton"
|
||||
|
||||
[people.alexellis]
|
||||
Name = "Alex Ellis"
|
||||
Email = "alexellis2@gmail.com"
|
||||
GitHub = "alexellis"
|
||||
|
||||
[people.akihirosuda]
|
||||
Name = "Akihiro Suda"
|
||||
Email = "akihiro.suda.cz@hco.ntt.co.jp"
|
||||
GitHub = "AkihiroSuda"
|
||||
|
||||
[people.aluzzardi]
|
||||
Name = "Andrea Luzzardi"
|
||||
Email = "al@docker.com"
|
||||
GitHub = "aluzzardi"
|
||||
|
||||
[people.albers]
|
||||
Name = "Harald Albers"
|
||||
Email = "github@albersweb.de"
|
||||
GitHub = "albers"
|
||||
|
||||
[people.andrewhsu]
|
||||
Name = "Andrew Hsu"
|
||||
Email = "andrewhsu@docker.com"
|
||||
GitHub = "andrewhsu"
|
||||
|
||||
[people.anusha]
|
||||
Name = "Anusha Ragunathan"
|
||||
Email = "anusha@docker.com"
|
||||
GitHub = "anusha-ragunathan"
|
||||
|
||||
[people.bsousaa]
|
||||
Name = "Bruno de Sousa"
|
||||
Email = "bruno.sousa@docker.com"
|
||||
GitHub = "bsousaa"
|
||||
|
||||
[people.calavera]
|
||||
Name = "David Calavera"
|
||||
Email = "david.calavera@gmail.com"
|
||||
GitHub = "calavera"
|
||||
|
||||
[people.coolljt0725]
|
||||
Name = "Lei Jitang"
|
||||
Email = "leijitang@huawei.com"
|
||||
GitHub = "coolljt0725"
|
||||
|
||||
[people.corhere]
|
||||
Name = "Cory Snider"
|
||||
Email = "csnider@mirantis.com"
|
||||
GitHub = "corhere"
|
||||
|
||||
[people.cpuguy83]
|
||||
Name = "Brian Goff"
|
||||
Email = "cpuguy83@gmail.com"
|
||||
GitHub = "cpuguy83"
|
||||
|
||||
[people.crazy-max]
|
||||
Name = "Kevin Alvarez"
|
||||
Email = "contact@crazymax.dev"
|
||||
GitHub = "crazy-max"
|
||||
|
||||
[people.crosbymichael]
|
||||
Name = "Michael Crosby"
|
||||
Email = "crosbymichael@gmail.com"
|
||||
GitHub = "crosbymichael"
|
||||
|
||||
[people.dnephin]
|
||||
Name = "Daniel Nephin"
|
||||
Email = "dnephin@gmail.com"
|
||||
GitHub = "dnephin"
|
||||
|
||||
[people.dmcgowan]
|
||||
Name = "Derek McGowan"
|
||||
Email = "derek@mcgstyle.net"
|
||||
GitHub = "dmcgowan"
|
||||
|
||||
[people.duglin]
|
||||
Name = "Doug Davis"
|
||||
Email = "dug@us.ibm.com"
|
||||
GitHub = "duglin"
|
||||
|
||||
[people.ehazlett]
|
||||
Name = "Evan Hazlett"
|
||||
Email = "ejhazlett@gmail.com"
|
||||
GitHub = "ehazlett"
|
||||
|
||||
[people.erikh]
|
||||
Name = "Erik Hollensbe"
|
||||
Email = "erik@docker.com"
|
||||
GitHub = "erikh"
|
||||
|
||||
[people.estesp]
|
||||
Name = "Phil Estes"
|
||||
Email = "estesp@linux.vnet.ibm.com"
|
||||
GitHub = "estesp"
|
||||
|
||||
[people.fntlnz]
|
||||
Name = "Lorenzo Fontana"
|
||||
Email = "fontanalorenz@gmail.com"
|
||||
GitHub = "fntlnz"
|
||||
|
||||
[people.gianarb]
|
||||
Name = "Gianluca Arbezzano"
|
||||
Email = "ga@thumpflow.com"
|
||||
GitHub = "gianarb"
|
||||
|
||||
[people.icecrime]
|
||||
Name = "Arnaud Porterie"
|
||||
Email = "icecrime@gmail.com"
|
||||
GitHub = "icecrime"
|
||||
|
||||
[people.jamtur01]
|
||||
Name = "James Turnbull"
|
||||
Email = "james@lovedthanlost.net"
|
||||
GitHub = "jamtur01"
|
||||
|
||||
[people.jessfraz]
|
||||
Name = "Jessie Frazelle"
|
||||
Email = "jess@linux.com"
|
||||
GitHub = "jessfraz"
|
||||
|
||||
[people.johnstep]
|
||||
Name = "John Stephens"
|
||||
Email = "johnstep@docker.com"
|
||||
GitHub = "johnstep"
|
||||
|
||||
[people.justincormack]
|
||||
Name = "Justin Cormack"
|
||||
Email = "justin.cormack@docker.com"
|
||||
GitHub = "justincormack"
|
||||
|
||||
[people.kolyshkin]
|
||||
Name = "Kir Kolyshkin"
|
||||
Email = "kolyshkin@gmail.com"
|
||||
GitHub = "kolyshkin"
|
||||
|
||||
[people.laurazard]
|
||||
Name = "Laura Brehm"
|
||||
Email = "laura.brehm@docker.com"
|
||||
GitHub = "laurazard"
|
||||
|
||||
[people.lk4d4]
|
||||
Name = "Alexander Morozov"
|
||||
Email = "lk4d4@docker.com"
|
||||
GitHub = "lk4d4"
|
||||
|
||||
[people.lowenna]
|
||||
Name = "John Howard"
|
||||
Email = "github@lowenna.com"
|
||||
GitHub = "lowenna"
|
||||
|
||||
[people.mavenugo]
|
||||
Name = "Madhu Venugopal"
|
||||
Email = "madhu@docker.com"
|
||||
GitHub = "mavenugo"
|
||||
|
||||
[people.mhbauer]
|
||||
Name = "Morgan Bauer"
|
||||
Email = "mbauer@us.ibm.com"
|
||||
GitHub = "mhbauer"
|
||||
|
||||
[people.mlaventure]
|
||||
Name = "Kenfe-Mickaël Laventure"
|
||||
Email = "mickael.laventure@gmail.com"
|
||||
GitHub = "mlaventure"
|
||||
|
||||
[people.moxiegirl]
|
||||
Name = "Mary Anthony"
|
||||
Email = "mary.anthony@docker.com"
|
||||
GitHub = "moxiegirl"
|
||||
|
||||
[people.mrjana]
|
||||
Name = "Jana Radhakrishnan"
|
||||
Email = "mrjana@docker.com"
|
||||
GitHub = "mrjana"
|
||||
|
||||
[people.neersighted]
|
||||
Name = "Bjorn Neergaard"
|
||||
Email = "bjorn@neersighted.com"
|
||||
GitHub = "neersighted"
|
||||
|
||||
[people.olljanat]
|
||||
Name = "Olli Janatuinen"
|
||||
Email = "olli.janatuinen@gmail.com"
|
||||
GitHub = "olljanat"
|
||||
|
||||
[people.programmerq]
|
||||
Name = "Jeff Anderson"
|
||||
Email = "jeff@docker.com"
|
||||
GitHub = "programmerq"
|
||||
|
||||
[people.robmry]
|
||||
Name = "Rob Murray"
|
||||
Email = "rob.murray@docker.com"
|
||||
GitHub = "robmry"
|
||||
|
||||
[people.ripcurld]
|
||||
Name = "Boaz Shuster"
|
||||
Email = "ripcurld.github@gmail.com"
|
||||
GitHub = "ripcurld"
|
||||
|
||||
[people.rumpl]
|
||||
Name = "Djordje Lukic"
|
||||
Email = "djordje.lukic@docker.com"
|
||||
GitHub = "rumpl"
|
||||
|
||||
[people.runcom]
|
||||
Name = "Antonio Murdaca"
|
||||
Email = "runcom@redhat.com"
|
||||
GitHub = "runcom"
|
||||
|
||||
[people.sam-thibault]
|
||||
Name = "Sam Thibault"
|
||||
Email = "sam.thibault@docker.com"
|
||||
GitHub = "sam-thibault"
|
||||
|
||||
[people.samuelkarp]
|
||||
Name = "Samuel Karp"
|
||||
Email = "me@samuelkarp.com"
|
||||
GitHub = "samuelkarp"
|
||||
|
||||
[people.samwhited]
|
||||
Name = "Sam Whited"
|
||||
Email = "sam@samwhited.com"
|
||||
GitHub = "samwhited"
|
||||
|
||||
[people.shykes]
|
||||
Name = "Solomon Hykes"
|
||||
Email = "solomon@docker.com"
|
||||
GitHub = "shykes"
|
||||
|
||||
[people.stevvooe]
|
||||
Name = "Stephen Day"
|
||||
Email = "stephen.day@docker.com"
|
||||
GitHub = "stevvooe"
|
||||
|
||||
[people.sven]
|
||||
Name = "Sven Dowideit"
|
||||
Email = "SvenDowideit@home.org.au"
|
||||
GitHub = "SvenDowideit"
|
||||
|
||||
[people.thajeztah]
|
||||
Name = "Sebastiaan van Stijn"
|
||||
Email = "github@gone.nl"
|
||||
GitHub = "thaJeztah"
|
||||
|
||||
[people.tianon]
|
||||
Name = "Tianon Gravi"
|
||||
Email = "admwiggin@gmail.com"
|
||||
GitHub = "tianon"
|
||||
|
||||
[people.tibor]
|
||||
Name = "Tibor Vass"
|
||||
Email = "tibor@docker.com"
|
||||
GitHub = "tiborvass"
|
||||
|
||||
[people.tonistiigi]
|
||||
Name = "Tõnis Tiigi"
|
||||
Email = "tonis@docker.com"
|
||||
GitHub = "tonistiigi"
|
||||
|
||||
[people.unclejack]
|
||||
Name = "Cristian Staretu"
|
||||
Email = "cristian.staretu@gmail.com"
|
||||
GitHub = "unclejack"
|
||||
|
||||
[people.vbatts]
|
||||
Name = "Vincent Batts"
|
||||
Email = "vbatts@redhat.com"
|
||||
GitHub = "vbatts"
|
||||
|
||||
[people.vdemeester]
|
||||
Name = "Vincent Demeester"
|
||||
Email = "vincent@sbr.pm"
|
||||
GitHub = "vdemeester"
|
||||
|
||||
[people.vieux]
|
||||
Name = "Victor Vieux"
|
||||
Email = "vieux@docker.com"
|
||||
GitHub = "vieux"
|
||||
|
||||
[people.vishh]
|
||||
Name = "Vishnu Kannan"
|
||||
Email = "vishnuk@google.com"
|
||||
GitHub = "vishh"
|
||||
|
||||
[people.vvoland]
|
||||
Name = "Paweł Gronowski"
|
||||
Email = "pawel.gronowski@docker.com"
|
||||
GitHub = "vvoland"
|
||||
|
||||
[people.yongtang]
|
||||
Name = "Yong Tang"
|
||||
Email = "yong.tang.github@outlook.com"
|
||||
GitHub = "yongtang"
|
||||
|
||||
58
Makefile
58
Makefile
@@ -1,6 +1,12 @@
|
||||
.PHONY: all binary dynbinary build cross help install manpages run shell test test-docker-py test-integration test-unit validate validate-% win
|
||||
|
||||
DOCKER ?= docker
|
||||
BUILDX ?= $(DOCKER) buildx
|
||||
|
||||
# set the graph driver as the current graphdriver if not set
|
||||
DOCKER_GRAPHDRIVER := $(if $(DOCKER_GRAPHDRIVER),$(DOCKER_GRAPHDRIVER),$(shell docker info -f '{{ .Driver }}' 2>&1))
|
||||
export DOCKER_GRAPHDRIVER
|
||||
|
||||
DOCKER_GITCOMMIT := $(shell git rev-parse HEAD)
|
||||
export DOCKER_GITCOMMIT
|
||||
|
||||
@@ -31,6 +37,7 @@ DOCKER_ENVS := \
|
||||
-e DOCKER_BUILD_OPTS \
|
||||
-e DOCKER_BUILD_PKGS \
|
||||
-e DOCKER_BUILDKIT \
|
||||
-e DOCKER_BASH_COMPLETION_PATH \
|
||||
-e DOCKER_CLI_PATH \
|
||||
-e DOCKERCLI_VERSION \
|
||||
-e DOCKERCLI_REPOSITORY \
|
||||
@@ -38,7 +45,6 @@ DOCKER_ENVS := \
|
||||
-e DOCKERCLI_INTEGRATION_REPOSITORY \
|
||||
-e DOCKER_DEBUG \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_FIREWALL_BACKEND \
|
||||
-e DOCKER_GITCOMMIT \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e DOCKER_LDFLAGS \
|
||||
@@ -50,14 +56,14 @@ DOCKER_ENVS := \
|
||||
-e DOCKER_USERLANDPROXY \
|
||||
-e DOCKERD_ARGS \
|
||||
-e DELVE_PORT \
|
||||
-e FIREWALLD \
|
||||
-e GITHUB_ACTIONS \
|
||||
-e TEST_FORCE_VALIDATE \
|
||||
-e TEST_INTEGRATION_DIR \
|
||||
-e TEST_INTEGRATION_USE_GRAPHDRIVER \
|
||||
-e TEST_INTEGRATION_USE_SNAPSHOTTER \
|
||||
-e TEST_INTEGRATION_FAIL_FAST \
|
||||
-e TEST_SKIP_INTEGRATION \
|
||||
-e TEST_SKIP_INTEGRATION_CLI \
|
||||
-e TEST_IGNORE_CGROUP_CHECK \
|
||||
-e TESTCOVERAGE \
|
||||
-e TESTDEBUG \
|
||||
-e TESTDIRS \
|
||||
@@ -84,11 +90,11 @@ DOCKER_ENVS := \
|
||||
# to allow `make BIND_DIR=. shell` or `make BIND_DIR= test`
|
||||
# (default to no bind mount if DOCKER_HOST is set)
|
||||
# note: BINDDIR is supported for backwards-compatibility here
|
||||
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,.))
|
||||
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
|
||||
|
||||
# DOCKER_MOUNT can be overridden, but use at your own risk!
|
||||
# DOCKER_MOUNT can be overriden, but use at your own risk!
|
||||
ifndef DOCKER_MOUNT
|
||||
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
|
||||
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
|
||||
DOCKER_MOUNT := $(if $(DOCKER_BINDDIR_MOUNT_OPTS),$(DOCKER_MOUNT):$(DOCKER_BINDDIR_MOUNT_OPTS),$(DOCKER_MOUNT))
|
||||
|
||||
# This allows the test suite to be able to run without worrying about the underlying fs used by the container running the daemon (e.g. aufs-on-aufs), so long as the host running the container is running a supported fs.
|
||||
@@ -98,14 +104,8 @@ DOCKER_MOUNT := $(if $(DOCKER_MOUNT),$(DOCKER_MOUNT),-v /go/src/github.com/docke
|
||||
|
||||
DOCKER_MOUNT_CACHE := -v docker-dev-cache:/root/.cache -v docker-mod-cache:/go/pkg/mod/
|
||||
DOCKER_MOUNT_CLI := $(if $(DOCKER_CLI_PATH),-v $(shell dirname $(DOCKER_CLI_PATH)):/usr/local/cli,)
|
||||
|
||||
ifdef BIND_GIT
|
||||
# Gets the common .git directory (even from inside a git worktree)
|
||||
GITDIR := $(shell realpath $(shell git rev-parse --git-common-dir))
|
||||
MOUNT_GITDIR := $(if $(GITDIR),-v "$(GITDIR):$(GITDIR)")
|
||||
endif
|
||||
|
||||
DOCKER_MOUNT := $(DOCKER_MOUNT) $(DOCKER_MOUNT_CACHE) $(DOCKER_MOUNT_CLI) $(DOCKER_MOUNT_BASH_COMPLETION) $(MOUNT_GITDIR)
|
||||
DOCKER_MOUNT_BASH_COMPLETION := $(if $(DOCKER_BASH_COMPLETION_PATH),-v $(shell dirname $(DOCKER_BASH_COMPLETION_PATH)):/usr/local/completion/bash,)
|
||||
DOCKER_MOUNT := $(DOCKER_MOUNT) $(DOCKER_MOUNT_CACHE) $(DOCKER_MOUNT_CLI) $(DOCKER_MOUNT_BASH_COMPLETION)
|
||||
endif # ifndef DOCKER_MOUNT
|
||||
|
||||
# This allows to set the docker-dev container name
|
||||
@@ -150,9 +150,6 @@ DOCKER_BUILD_ARGS += --build-arg=DOCKERCLI_INTEGRATION_REPOSITORY
|
||||
ifdef DOCKER_SYSTEMD
|
||||
DOCKER_BUILD_ARGS += --build-arg=SYSTEMD=true
|
||||
endif
|
||||
ifdef FIREWALLD
|
||||
DOCKER_BUILD_ARGS += --build-arg=FIREWALLD=true
|
||||
endif
|
||||
|
||||
BUILD_OPTS := ${DOCKER_BUILD_ARGS} ${DOCKER_BUILD_OPTS}
|
||||
BUILD_CMD := $(BUILDX) build
|
||||
@@ -160,19 +157,15 @@ BAKE_CMD := $(BUILDX) bake
|
||||
|
||||
default: binary
|
||||
|
||||
.PHONY: all
|
||||
all: build ## validate all checks, build linux binaries, run all tests,\ncross build non-linux binaries, and generate archives
|
||||
$(DOCKER_RUN_DOCKER) bash -c 'hack/validate/default && hack/make.sh'
|
||||
|
||||
.PHONY: binary
|
||||
binary: bundles ## build statically linked linux binaries
|
||||
$(BAKE_CMD) binary
|
||||
|
||||
.PHONY: dynbinary
|
||||
dynbinary: bundles ## build dynamically linked linux binaries
|
||||
$(BAKE_CMD) dynbinary
|
||||
|
||||
.PHONY: cross
|
||||
cross: bundles ## cross build the binaries
|
||||
$(BAKE_CMD) binary-cross
|
||||
|
||||
@@ -186,15 +179,12 @@ clean: clean-cache
|
||||
clean-cache: ## remove the docker volumes that are used for caching in the dev-container
|
||||
docker volume rm -f docker-dev-cache docker-mod-cache
|
||||
|
||||
.PHONY: help
|
||||
help: ## this help
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {gsub("\\\\n",sprintf("\n%22c",""), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
.PHONY: install
|
||||
install: ## install the linux binaries
|
||||
KEEPBUNDLE=1 hack/make.sh install-binary
|
||||
|
||||
.PHONY: run
|
||||
run: build ## run the docker daemon in a container
|
||||
$(DOCKER_RUN_DOCKER) sh -c "KEEPBUNDLE=1 hack/make.sh install-binary run"
|
||||
|
||||
@@ -204,25 +194,20 @@ build: shell_target := --target=dev-base
|
||||
else
|
||||
build: shell_target := --target=dev
|
||||
endif
|
||||
build: validate-bind-dir bundles
|
||||
build: bundles
|
||||
$(BUILD_CMD) $(BUILD_OPTS) $(shell_target) --load -t "$(DOCKER_IMAGE)" .
|
||||
|
||||
.PHONY: shell
|
||||
shell: build ## start a shell inside the build env
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
.PHONY: test
|
||||
test: build test-unit ## run the unit, integration and docker-py tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-integration test-docker-py
|
||||
|
||||
.PHONY: test-docker-py
|
||||
test-docker-py: build ## run the docker-py tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-docker-py
|
||||
|
||||
.PHONY: test-integration-cli
|
||||
test-integration-cli: test-integration ## (DEPRECATED) use test-integration
|
||||
|
||||
.PHONY: test-integration
|
||||
ifneq ($(and $(TEST_SKIP_INTEGRATION),$(TEST_SKIP_INTEGRATION_CLI)),)
|
||||
test-integration:
|
||||
@echo Both integrations suites skipped per environment variables
|
||||
@@ -231,29 +216,23 @@ test-integration: build ## run the integration tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-integration
|
||||
endif
|
||||
|
||||
.PHONY: test-integration-flaky
|
||||
test-integration-flaky: build ## run the stress test for all new integration tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-integration-flaky
|
||||
|
||||
.PHONY: test-unit
|
||||
test-unit: build ## run the unit tests
|
||||
$(DOCKER_RUN_DOCKER) hack/test/unit
|
||||
|
||||
.PHONY: validate
|
||||
validate: build ## validate DCO, Seccomp profile generation, gofmt,\n./pkg/ isolation, golint, tests, tomls, go vet and vendor
|
||||
$(DOCKER_RUN_DOCKER) hack/validate/all
|
||||
|
||||
.PHONY: validate-generate-files
|
||||
validate-generate-files:
|
||||
$(BUILD_CMD) --target "validate" \
|
||||
--output "type=cacheonly" \
|
||||
--file "./hack/dockerfiles/generate-files.Dockerfile" .
|
||||
|
||||
.PHONY: validate-%
|
||||
validate-%: build ## validate specific check
|
||||
$(DOCKER_RUN_DOCKER) hack/validate/$*
|
||||
|
||||
.PHONY: win
|
||||
win: bundles ## cross build the binary for windows
|
||||
$(BAKE_CMD) --set *.platform=windows/amd64 binary
|
||||
|
||||
@@ -285,10 +264,3 @@ generate-files:
|
||||
--file "./hack/dockerfiles/generate-files.Dockerfile" .
|
||||
cp -R "$($@_TMP_OUT)"/. .
|
||||
rm -rf "$($@_TMP_OUT)"/*
|
||||
|
||||
.PHONY: validate-bind-dir
|
||||
validate-bind-dir:
|
||||
@case "$(BIND_DIR)" in \
|
||||
".."*|"/"*) echo "Make needs to be run from the project-root directory, with BIND_DIR set to \".\" or a subdir"; \
|
||||
exit 1 ;; \
|
||||
esac
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
The Moby Project
|
||||
================
|
||||
|
||||
[](https://pkg.go.dev/github.com/moby/moby/v2)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/moby/moby/v2)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/moby/moby)
|
||||
[](https://www.bestpractices.dev/projects/10989)
|
||||
|
||||
|
||||

|
||||
|
||||
Moby is an open-source project created by Docker to enable and accelerate software containerization.
|
||||
@@ -39,7 +32,7 @@ New projects can be added if they fit with the community goals. Docker is commit
|
||||
However, other projects are also encouraged to use Moby as an upstream, and to reuse the components in diverse ways, and all these uses will be treated in the same way. External maintainers and contributors are welcomed.
|
||||
|
||||
The Moby project is not intended as a location for support or feature requests for Docker products, but as a place for contributors to work on open source code, fix bugs, and make the code more useful.
|
||||
The releases are supported by the maintainers, community and users, on a best efforts basis only. For customers who want enterprise or commercial support, [Docker Desktop](https://www.docker.com/products/docker-desktop/) and [Mirantis Container Runtime](https://www.mirantis.com/software/mirantis-container-runtime/) are the appropriate products for these use cases.
|
||||
The releases are supported by the maintainers, community and users, on a best efforts basis only, and are not intended for customers who want enterprise or commercial support; Docker EE is the appropriate product for these use cases.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -113,5 +113,5 @@ We see gRPC as the natural communication layer between decoupled components.
|
||||
|
||||
In addition to pushing out large components into other projects, much of the
|
||||
internal code structure, and in particular the
|
||||
["Daemon"](https://pkg.go.dev/github.com/moby/moby/v2/daemon#Daemon) object,
|
||||
["Daemon"](https://godoc.org/github.com/docker/docker/daemon#Daemon) object,
|
||||
should be split into smaller, more manageable, and more testable components.
|
||||
|
||||
43
SECURITY.md
43
SECURITY.md
@@ -1,42 +1,9 @@
|
||||
# Security Policy
|
||||
# Reporting security issues
|
||||
|
||||
The maintainers of the Moby project take security seriously. If you discover
|
||||
a security issue, please bring it to their attention right away!
|
||||
The Moby maintainers take security seriously. If you discover a security issue, please bring it to their attention right away!
|
||||
|
||||
## Reporting a Vulnerability
|
||||
### Reporting a Vulnerability
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately
|
||||
to [security@docker.com](mailto:security@docker.com).
|
||||
Please **DO NOT** file a public issue, instead send your report privately to security@docker.com.
|
||||
|
||||
Reporter(s) can expect a response within 72 hours, acknowledging the issue was
|
||||
received.
|
||||
|
||||
## Review Process
|
||||
|
||||
After receiving the report, an initial triage and technical analysis is
|
||||
performed to confirm the report and determine its scope. We may request
|
||||
additional information in this stage of the process.
|
||||
|
||||
Once a reviewer has confirmed the relevance of the report, a draft security
|
||||
advisory will be created on GitHub. The draft advisory will be used to discuss
|
||||
the issue with maintainers, the reporter(s), and where applicable, other
|
||||
affected parties under embargo.
|
||||
|
||||
If the vulnerability is accepted, a timeline for developing a patch, public
|
||||
disclosure, and patch release will be determined. If there is an embargo period
|
||||
on public disclosure before the patch release, the reporter(s) are expected to
|
||||
participate in the discussion of the timeline and abide by agreed upon dates
|
||||
for public disclosure.
|
||||
|
||||
## Accreditation
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you,
|
||||
although we will keep your name confidential if you request it. We also like to
|
||||
send gifts - if you're into swag, make sure to let us know. We do not currently
|
||||
offer a paid security bounty program at this time.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
This project uses long-lived branches to maintain releases. Refer to
|
||||
[BRANCHES-AND-TAGS.md](project/BRANCHES-AND-TAGS.md) in the default branch to
|
||||
learn about the current maintenance status of each branch.
|
||||
Security reports are greatly appreciated and we will publicly thank you for it, although we keep your name confidential if you request it. We also like to send gifts—if you're into schwag, make sure to let us know. We currently do not offer a paid security bounty program, but are not ruling it out in the future.
|
||||
|
||||
35
TESTING.md
35
TESTING.md
@@ -8,11 +8,11 @@ questions you may have as an aspiring Moby contributor.
|
||||
Moby has two test suites (and one legacy test suite):
|
||||
|
||||
* Unit tests - use standard `go test` and
|
||||
[gotest.tools/v3/assert](https://pkg.go.dev/gotest.tools/v3/assert) assertions. They are located in
|
||||
[gotest.tools/assert](https://godoc.org/gotest.tools/assert) assertions. They are located in
|
||||
the package they test. Unit tests should be fast and test only their own
|
||||
package.
|
||||
* API integration tests - use standard `go test` and
|
||||
[gotest.tools/v3/assert](https://pkg.go.dev/gotest.tools/v3/assert) assertions. They are located in
|
||||
[gotest.tools/assert](https://godoc.org/gotest.tools/assert) assertions. They are located in
|
||||
`./integration/<component>` directories, where `component` is: container,
|
||||
image, volume, etc. These tests perform HTTP requests to an API endpoint and
|
||||
check the HTTP response and daemon state after the call.
|
||||
@@ -57,28 +57,17 @@ Instead, implement new tests under `integration/`.
|
||||
### Integration tests environment considerations
|
||||
|
||||
When adding new tests or modifying existing tests under `integration/`, testing
|
||||
environment should be properly considered. [`skip.If`](https://pkg.go.dev/gotest.tools/v3/skip#If) from
|
||||
[gotest.tools/v3/skip](https://pkg.go.dev/gotest.tools/v3/skip) can be used to make the
|
||||
environment should be properly considered. `skip.If` from
|
||||
[gotest.tools/skip](https://godoc.org/gotest.tools/skip) can be used to make the
|
||||
test run conditionally. Full testing environment conditions can be found at
|
||||
[environment.go](https://github.com/moby/moby/blob/311b2c87e125c6d4198014369e313135cf928a8a/testutil/environment/environment.go)
|
||||
[environment.go](https://github.com/moby/moby/blob/6b6eeed03b963a27085ea670f40cd5ff8a61f32e/testutil/environment/environment.go)
|
||||
|
||||
Here is a quick example. If the test needs to interact with a docker daemon on
|
||||
the same host, the following condition should be checked within the test code
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
func TestSomething(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRemoteDaemon(), "test requires a local daemon")
|
||||
|
||||
// your integration test code
|
||||
}
|
||||
skip.If(t, testEnv.IsRemoteDaemon())
|
||||
// your integration test code
|
||||
```
|
||||
|
||||
If a remote daemon is detected, the test will be skipped.
|
||||
@@ -89,11 +78,11 @@ If a remote daemon is detected, the test will be skipped.
|
||||
|
||||
To run the unit test suite:
|
||||
|
||||
```bash
|
||||
```
|
||||
make test-unit
|
||||
```
|
||||
|
||||
or `hack/test/unit` from inside a `make shell` container or properly
|
||||
or `hack/test/unit` from inside a `BINDDIR=. make shell` container or properly
|
||||
configured environment.
|
||||
|
||||
The following environment variables may be used to run a subset of tests:
|
||||
@@ -106,7 +95,7 @@ The following environment variables may be used to run a subset of tests:
|
||||
|
||||
To run the integration test suite:
|
||||
|
||||
```bash
|
||||
```
|
||||
make test-integration
|
||||
```
|
||||
|
||||
@@ -132,6 +121,6 @@ automatically set the other above mentioned environment variables accordingly.
|
||||
You can change a version of golang used for building stuff that is being tested
|
||||
by setting `GO_VERSION` variable, for example:
|
||||
|
||||
```bash
|
||||
make GO_VERSION=1.24.6 test
|
||||
```
|
||||
make GO_VERSION=1.12.8 test
|
||||
```
|
||||
|
||||
46
VENDORING.md
Normal file
46
VENDORING.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Vendoring policies
|
||||
|
||||
This document outlines recommended Vendoring policies for Docker repositories.
|
||||
(Example, libnetwork is a Docker repo and logrus is not.)
|
||||
|
||||
## Vendoring using tags
|
||||
|
||||
Commit ID based vendoring provides little/no information about the updates
|
||||
vendored. To fix this, vendors will now require that repositories use annotated
|
||||
tags along with commit ids to snapshot commits. Annotated tags by themselves
|
||||
are not sufficient, since the same tag can be force updated to reference
|
||||
different commits.
|
||||
|
||||
Each tag should:
|
||||
- Follow Semantic Versioning rules (refer to section on "Semantic Versioning")
|
||||
- Have a corresponding entry in the change tracking document.
|
||||
|
||||
Each repo should:
|
||||
- Have a change tracking document between tags/releases. Ex: CHANGELOG.md,
|
||||
github releases file.
|
||||
|
||||
The goal here is for consuming repos to be able to use the tag version and
|
||||
changelog updates to determine whether the vendoring will cause any breaking or
|
||||
backward incompatible changes. This also means that repos can specify having
|
||||
dependency on a package of a specific version or greater up to the next major
|
||||
release, without encountering breaking changes.
|
||||
|
||||
## Semantic Versioning
|
||||
Annotated version tags should follow [Semantic Versioning](http://semver.org) policies:
|
||||
|
||||
"Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
1. MAJOR version when you make incompatible API changes,
|
||||
2. MINOR version when you add functionality in a backwards-compatible manner, and
|
||||
3. PATCH version when you make backwards-compatible bug fixes.
|
||||
|
||||
Additional labels for pre-release and build metadata are available as extensions
|
||||
to the MAJOR.MINOR.PATCH format."
|
||||
|
||||
## Vendoring cadence
|
||||
In order to avoid huge vendoring changes, it is recommended to have a regular
|
||||
cadence for vendoring updates. e.g. monthly.
|
||||
|
||||
## Pre-merge vendoring tests
|
||||
All related repos will be vendored into docker/docker.
|
||||
CI on docker/docker should catch any breaking changes involving multiple repos.
|
||||
@@ -1,18 +1,12 @@
|
||||
# Engine API
|
||||
|
||||
[](https://pkg.go.dev/github.com/moby/moby/api)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/moby/moby/api)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/moby/moby)
|
||||
[](https://www.bestpractices.dev/projects/10989)
|
||||
|
||||
# Working on the Engine API
|
||||
|
||||
The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon.
|
||||
|
||||
It consists of various components in this repository:
|
||||
|
||||
- `api/swagger.yaml` A Swagger definition of the API.
|
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/moby/moby/issues/27919) for progress on this.
|
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this.
|
||||
- `cli/` The command-line client.
|
||||
- `client/` The Go client used by the command-line client. It can also be used by third-party Go programs.
|
||||
- `daemon/` The daemon, which serves the API.
|
||||
|
||||
@@ -27,7 +21,6 @@ The API is defined by the [Swagger](http://swagger.io/specification/) definition
|
||||
## Updating the API documentation
|
||||
|
||||
The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, edit this file to represent the change in the documentation.
|
||||
Documentation for each API version can be found in the [docs directory](docs/README.md), which also provides a [CHANGELOG.md](docs/CHANGELOG.md).
|
||||
|
||||
The file is split into two main sections:
|
||||
|
||||
@@ -36,7 +29,7 @@ The file is split into two main sections:
|
||||
|
||||
To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section.
|
||||
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/moby/moby/issues/27919).
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919).
|
||||
|
||||
`swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful when making edits to ensure you are doing the right thing.
|
||||
|
||||
@@ -46,4 +39,4 @@ When you make edits to `swagger.yaml`, you may want to check the generated API d
|
||||
|
||||
Run `make swagger-docs` and a preview will be running at `http://localhost:9000`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation.
|
||||
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docs](https://github.com/docker/docs).
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io).
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package api
|
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const (
|
||||
// DefaultVersion of the current REST API.
|
||||
DefaultVersion = "1.52"
|
||||
DefaultVersion = "1.45"
|
||||
|
||||
// MinSupportedAPIVersion is the minimum API version that can be supported
|
||||
// by the API server, specified as "major.minor". Note that the daemon
|
||||
// may be configured with a different minimum API version, as returned
|
||||
// in [github.com/moby/moby/api/types.Version.MinAPIVersion].
|
||||
// in [github.com/docker/docker/api/types.Version.MinAPIVersion].
|
||||
//
|
||||
// API requests for API versions lower than the configured version produce
|
||||
// an error.
|
||||
MinSupportedAPIVersion = "1.24"
|
||||
|
||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||
// command to specify that no base image is to be used.
|
||||
NoBaseImageSpecifier = "scratch"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
||||
# API Documentation
|
||||
|
||||
This directory contains versioned documents for each version of the API
|
||||
specification supported by this module. While this module provides support
|
||||
for older API versions, support should be considered "best-effort", especially
|
||||
for very old versions. Users are recommended to use the latest API versions,
|
||||
and only rely on older API versions for compatibility with older clients.
|
||||
|
||||
Newer API versions tend to be backward-compatible with older versions,
|
||||
with some exceptions where features were deprecated. For an overview
|
||||
of changes for each version, refer to [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
The latest version of the API specification can be found [at the root directory
|
||||
of this module](../swagger.yaml) which may contain unreleased changes.
|
||||
|
||||
For API version v1.24, documentation is only available in markdown
|
||||
format, for later versions [Swagger (OpenAPI) v2.0](https://swagger.io/specification/v2/)
|
||||
specifications can be found in this directory. The Moby project itself
|
||||
primarily uses these swagger files to produce the API documentation;
|
||||
while we attempt to make these files match the actual implementation,
|
||||
the OpenAPI 2.0 specification has limitations that prevent us from
|
||||
expressing all options provided. There may be discrepancies (for which
|
||||
we welcome contributions). If you find bugs, or discrepancies, please
|
||||
open a ticket (or pull request).
|
||||
|
||||
|
||||
12779
api/docs/v1.46.yaml
12779
api/docs/v1.46.yaml
File diff suppressed because it is too large
Load Diff
12929
api/docs/v1.47.yaml
12929
api/docs/v1.47.yaml
File diff suppressed because it is too large
Load Diff
13548
api/docs/v1.48.yaml
13548
api/docs/v1.48.yaml
File diff suppressed because it is too large
Load Diff
13548
api/docs/v1.49.yaml
13548
api/docs/v1.49.yaml
File diff suppressed because it is too large
Load Diff
13444
api/docs/v1.50.yaml
13444
api/docs/v1.50.yaml
File diff suppressed because it is too large
Load Diff
13443
api/docs/v1.51.yaml
13443
api/docs/v1.51.yaml
File diff suppressed because it is too large
Load Diff
13428
api/docs/v1.52.yaml
13428
api/docs/v1.52.yaml
File diff suppressed because it is too large
Load Diff
15
api/go.mod
15
api/go.mod
@@ -1,15 +0,0 @@
|
||||
module github.com/moby/moby/api
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
golang.org/x/time v0.11.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
pgregory.net/rapid v1.2.0
|
||||
)
|
||||
18
api/go.sum
18
api/go.sum
@@ -1,18 +0,0 @@
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
@@ -1,93 +0,0 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Progress represents the progress of a transfer.
|
||||
type Progress struct {
|
||||
ID string
|
||||
|
||||
// Progress contains a Message or...
|
||||
Message string
|
||||
|
||||
// ...progress of an action
|
||||
Action string
|
||||
Current int64
|
||||
Total int64
|
||||
|
||||
// If true, don't show xB/yB
|
||||
HideCounts bool
|
||||
// If not empty, use units instead of bytes for counts
|
||||
Units string
|
||||
|
||||
// Aux contains extra information not presented to the user, such as
|
||||
// digests for push signing.
|
||||
Aux any
|
||||
|
||||
LastUpdate bool
|
||||
}
|
||||
|
||||
// Output is an interface for writing progress information. It's
|
||||
// like a writer for progress, but we don't call it Writer because
|
||||
// that would be confusing next to ProgressReader (also, because it
|
||||
// doesn't implement the io.Writer interface).
|
||||
type Output interface {
|
||||
WriteProgress(Progress) error
|
||||
}
|
||||
|
||||
type chanOutput chan<- Progress
|
||||
|
||||
func (out chanOutput) WriteProgress(p Progress) error {
|
||||
// FIXME: workaround for panic in #37735
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
out <- p
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChanOutput returns an Output that writes progress updates to the
|
||||
// supplied channel.
|
||||
func ChanOutput(progressChan chan<- Progress) Output {
|
||||
return chanOutput(progressChan)
|
||||
}
|
||||
|
||||
type discardOutput struct{}
|
||||
|
||||
func (discardOutput) WriteProgress(Progress) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiscardOutput returns an Output that discards progress
|
||||
func DiscardOutput() Output {
|
||||
return discardOutput{}
|
||||
}
|
||||
|
||||
// Update is a convenience function to write a progress update to the channel.
|
||||
func Update(out Output, id, action string) {
|
||||
out.WriteProgress(Progress{ID: id, Action: action})
|
||||
}
|
||||
|
||||
// Updatef is a convenience function to write a printf-formatted progress update
|
||||
// to the channel.
|
||||
func Updatef(out Output, id, format string, a ...any) {
|
||||
Update(out, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Message is a convenience function to write a progress message to the channel.
|
||||
func Message(out Output, id, message string) {
|
||||
out.WriteProgress(Progress{ID: id, Message: message})
|
||||
}
|
||||
|
||||
// Messagef is a convenience function to write a printf-formatted progress
|
||||
// message to the channel.
|
||||
func Messagef(out Output, id, format string, a ...any) {
|
||||
Message(out, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Aux sends auxiliary information over a progress interface, which will not be
|
||||
// formatted for the UI. This is used for things such as push signing.
|
||||
func Aux(out Output, a any) {
|
||||
out.WriteProgress(Progress{Aux: a})
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// Reader is a Reader with progress bar.
|
||||
type Reader struct {
|
||||
in io.ReadCloser // Stream to read from
|
||||
out Output // Where to send progress bar to
|
||||
size int64
|
||||
current int64
|
||||
lastUpdate int64
|
||||
id string
|
||||
action string
|
||||
rateLimiter *rate.Limiter
|
||||
}
|
||||
|
||||
// NewProgressReader creates a new ProgressReader.
|
||||
func NewProgressReader(in io.ReadCloser, out Output, size int64, id, action string) *Reader {
|
||||
return &Reader{
|
||||
in: in,
|
||||
out: out,
|
||||
size: size,
|
||||
id: id,
|
||||
action: action,
|
||||
rateLimiter: rate.NewLimiter(rate.Every(100*time.Millisecond), 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Reader) Read(buf []byte) (int, error) {
|
||||
read, err := p.in.Read(buf)
|
||||
p.current += int64(read)
|
||||
updateEvery := int64(1024 * 512) // 512kB
|
||||
if p.size > 0 {
|
||||
// Update progress for every 1% read if 1% < 512kB
|
||||
if increment := int64(0.01 * float64(p.size)); increment < updateEvery {
|
||||
updateEvery = increment
|
||||
}
|
||||
}
|
||||
if p.current-p.lastUpdate > updateEvery || err != nil {
|
||||
p.updateProgress(err != nil && read == 0)
|
||||
p.lastUpdate = p.current
|
||||
}
|
||||
|
||||
return read, err
|
||||
}
|
||||
|
||||
// Close closes the progress reader and its underlying reader.
|
||||
func (p *Reader) Close() error {
|
||||
if p.current < p.size {
|
||||
// print a full progress bar when closing prematurely
|
||||
p.current = p.size
|
||||
p.updateProgress(false)
|
||||
}
|
||||
return p.in.Close()
|
||||
}
|
||||
|
||||
func (p *Reader) updateProgress(last bool) {
|
||||
if last || p.current == p.size || p.rateLimiter.Allow() {
|
||||
p.out.WriteProgress(Progress{ID: p.id, Action: p.action, Current: p.current, Total: p.size, LastUpdate: last})
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
package stdcopy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// StdType is the type of standard stream
|
||||
// a writer can multiplex to.
|
||||
type StdType byte
|
||||
|
||||
const (
|
||||
Stdin StdType = 0 // Stdin represents standard input stream. It is present for completeness and should NOT be used. When reading the stream with [StdCopy] it is output on [Stdout].
|
||||
Stdout StdType = 1 // Stdout represents standard output stream.
|
||||
Stderr StdType = 2 // Stderr represents standard error steam.
|
||||
Systemerr StdType = 3 // Systemerr represents errors originating from the system. When reading the stream with [StdCopy] it is returned as an error.
|
||||
)
|
||||
|
||||
const (
|
||||
stdWriterPrefixLen = 8
|
||||
stdWriterFdIndex = 0
|
||||
stdWriterSizeIndex = 4
|
||||
|
||||
startingBufLen = 32*1024 + stdWriterPrefixLen + 1
|
||||
)
|
||||
|
||||
var bufPool = &sync.Pool{New: func() any { return bytes.NewBuffer(nil) }}
|
||||
|
||||
// stdWriter is wrapper of io.Writer with extra customized info.
|
||||
type stdWriter struct {
|
||||
io.Writer
|
||||
prefix byte
|
||||
}
|
||||
|
||||
// Write sends the buffer to the underlying writer.
|
||||
// It inserts the prefix header before the buffer,
|
||||
// so [StdCopy] knows where to multiplex the output.
|
||||
//
|
||||
// It implements [io.Writer].
|
||||
func (w *stdWriter) Write(p []byte) (int, error) {
|
||||
if w == nil || w.Writer == nil {
|
||||
return 0, errors.New("writer not instantiated")
|
||||
}
|
||||
if p == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix}
|
||||
binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p)))
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Write(header[:])
|
||||
buf.Write(p)
|
||||
|
||||
n, err := w.Writer.Write(buf.Bytes())
|
||||
n -= stdWriterPrefixLen
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewStdWriter instantiates a new writer using a custom format to multiplex
|
||||
// multiple streams to a single writer. All messages written using this writer
|
||||
// are encapsulated using a custom format, and written to the underlying
|
||||
// stream "w".
|
||||
//
|
||||
// Writers created through NewStdWriter allow for multiple write streams
|
||||
// (e.g., stdout ([Stdout]) and stderr ([Stderr]) to be multiplexed into a
|
||||
// single connection. "streamType" indicates the type of stream to encapsulate,
|
||||
// commonly, [Stdout] or [Stderr]. The [Systemerr] stream can be used to
|
||||
// include server-side errors in the stream. Information on this stream
|
||||
// is returned as an error by [StdCopy] and terminates processing the
|
||||
// stream.
|
||||
//
|
||||
// The [Stdin] stream is present for completeness and should generally
|
||||
// NOT be used. It is output on [Stdout] when reading the stream with
|
||||
// [StdCopy].
|
||||
//
|
||||
// All streams must share the same underlying [io.Writer] to ensure proper
|
||||
// multiplexing. Each call to NewStdWriter wraps that shared writer with
|
||||
// a header indicating the target stream.
|
||||
func NewStdWriter(w io.Writer, streamType StdType) io.Writer {
|
||||
return &stdWriter{
|
||||
Writer: w,
|
||||
prefix: byte(streamType),
|
||||
}
|
||||
}
|
||||
|
||||
// StdCopy is a modified version of [io.Copy] to de-multiplex messages
|
||||
// from "multiplexedSource" and copy them to destination streams
|
||||
// "destOut" and "destErr".
|
||||
//
|
||||
// StdCopy demultiplexes "multiplexedSource", assuming that it contains
|
||||
// two streams, previously multiplexed using a writer created with
|
||||
// [NewStdWriter].
|
||||
//
|
||||
// As it reads from "multiplexedSource", StdCopy writes [Stdout] messages
|
||||
// to "destOut", and [Stderr] message to "destErr]. For backward-compatibility,
|
||||
// [Stdin] messages are output to "destOut". The [Systemerr] stream provides
|
||||
// errors produced by the daemon. It is returned as an error, and terminates
|
||||
// processing the stream.
|
||||
//
|
||||
// StdCopy it reads until it hits [io.EOF] on "multiplexedSource", after
|
||||
// which it returns a nil error. In other words: any error returned indicates
|
||||
// a real underlying error, which may be when an unknown [StdType] stream
|
||||
// is received.
|
||||
//
|
||||
// The "written" return holds the total number of bytes written to "destOut"
|
||||
// and "destErr" combined.
|
||||
func StdCopy(destOut, destErr io.Writer, multiplexedSource io.Reader) (written int64, _ error) {
|
||||
var (
|
||||
buf = make([]byte, startingBufLen)
|
||||
bufLen = len(buf)
|
||||
nr, nw int
|
||||
err error
|
||||
out io.Writer
|
||||
frameSize int
|
||||
)
|
||||
|
||||
for {
|
||||
// Make sure we have at least a full header
|
||||
for nr < stdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, err = multiplexedSource.Read(buf[nr:])
|
||||
nr += nr2
|
||||
if errors.Is(err, io.EOF) {
|
||||
if nr < stdWriterPrefixLen {
|
||||
return written, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check the first byte to know where to write
|
||||
stream := StdType(buf[stdWriterFdIndex])
|
||||
switch stream {
|
||||
case Stdin:
|
||||
fallthrough
|
||||
case Stdout:
|
||||
// Write on stdout
|
||||
out = destOut
|
||||
case Stderr:
|
||||
// Write on stderr
|
||||
out = destErr
|
||||
case Systemerr:
|
||||
// If we're on Systemerr, we won't write anywhere.
|
||||
// NB: if this code changes later, make sure you don't try to write
|
||||
// to outstream if Systemerr is the stream
|
||||
out = nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unrecognized stream: %d", stream)
|
||||
}
|
||||
|
||||
// Retrieve the size of the frame
|
||||
frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
|
||||
|
||||
// Check if the buffer is big enough to read the frame.
|
||||
// Extend it if necessary.
|
||||
if frameSize+stdWriterPrefixLen > bufLen {
|
||||
buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
|
||||
bufLen = len(buf)
|
||||
}
|
||||
|
||||
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
||||
for nr < frameSize+stdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, err = multiplexedSource.Read(buf[nr:])
|
||||
nr += nr2
|
||||
if errors.Is(err, io.EOF) {
|
||||
if nr < frameSize+stdWriterPrefixLen {
|
||||
return written, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// we might have an error from the source mixed up in our multiplexed
|
||||
// stream. if we do, return it.
|
||||
if stream == Systemerr {
|
||||
return written, fmt.Errorf("error from daemon in stream: %s", string(buf[stdWriterPrefixLen:frameSize+stdWriterPrefixLen]))
|
||||
}
|
||||
|
||||
// Write the retrieved frame (without header)
|
||||
nw, err = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If the frame has not been fully written: error
|
||||
if nw != frameSize {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
written += int64(nw)
|
||||
|
||||
// Move the rest of the buffer to the beginning
|
||||
copy(buf, buf[frameSize+stdWriterPrefixLen:])
|
||||
// Move the index
|
||||
nr -= frameSize + stdWriterPrefixLen
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package stdcopy_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/moby/moby/api/pkg/stdcopy"
|
||||
)
|
||||
|
||||
func ExampleNewStdWriter() {
|
||||
muxReader, muxStream := io.Pipe()
|
||||
defer func() { _ = muxStream.Close() }()
|
||||
|
||||
// Start demuxing before the daemon starts writing.
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
// using os.Stdout for both, otherwise output doesn't show up in the example.
|
||||
osStdout := os.Stdout
|
||||
osStderr := os.Stdout
|
||||
_, err := stdcopy.StdCopy(osStdout, osStderr, muxReader)
|
||||
done <- err
|
||||
}()
|
||||
|
||||
// daemon writing to stdout, stderr, and systemErr.
|
||||
stdout := stdcopy.NewStdWriter(muxStream, stdcopy.Stdout)
|
||||
stderr := stdcopy.NewStdWriter(muxStream, stdcopy.Stderr)
|
||||
systemErr := stdcopy.NewStdWriter(muxStream, stdcopy.Systemerr)
|
||||
|
||||
for range 10 {
|
||||
_, _ = fmt.Fprintln(stdout, "hello from stdout")
|
||||
_, _ = fmt.Fprintln(stderr, "hello from stderr")
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
_, _ = fmt.Fprintln(systemErr, errors.New("something went wrong"))
|
||||
|
||||
// Wait for the demuxer to finish.
|
||||
if err := <-done; err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// hello from stdout
|
||||
// hello from stderr
|
||||
// error from daemon in stream: something went wrong
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
// Package streamformatter provides helper functions to format a stream.
|
||||
package streamformatter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/moby/api/pkg/progress"
|
||||
"github.com/moby/moby/api/types/jsonstream"
|
||||
)
|
||||
|
||||
// jsonMessage defines a message struct. It describes
|
||||
// the created time, where it from, status, ID of the
|
||||
// message. It's used for docker events.
|
||||
//
|
||||
// It is a reduced set of [jsonmessage.JSONMessage].
|
||||
type jsonMessage struct {
|
||||
Stream string `json:"stream,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Progress *jsonstream.Progress `json:"progressDetail,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Error *jsonstream.Error `json:"errorDetail,omitempty"`
|
||||
Aux *json.RawMessage `json:"aux,omitempty"` // Aux contains out-of-band data, such as digests for push signing and image id after building.
|
||||
|
||||
// ErrorMessage contains errors encountered during the operation.
|
||||
//
|
||||
// Deprecated: this field is deprecated since docker v0.6.0 / API v1.4. Use [Error.Message] instead. This field will be omitted in a future release.
|
||||
ErrorMessage string `json:"error,omitempty"` // deprecated
|
||||
}
|
||||
|
||||
const streamNewline = "\r\n"
|
||||
|
||||
type jsonProgressFormatter struct{}
|
||||
|
||||
func appendNewline(source []byte) []byte {
|
||||
return append(source, []byte(streamNewline)...)
|
||||
}
|
||||
|
||||
// FormatStatus formats the specified objects according to the specified format (and id).
|
||||
func FormatStatus(id, format string, a ...any) []byte {
|
||||
str := fmt.Sprintf(format, a...)
|
||||
b, err := json.Marshal(&jsonMessage{ID: id, Status: str})
|
||||
if err != nil {
|
||||
return FormatError(err)
|
||||
}
|
||||
return appendNewline(b)
|
||||
}
|
||||
|
||||
// FormatError formats the error as a JSON object
|
||||
func FormatError(err error) []byte {
|
||||
jsonError, ok := err.(*jsonstream.Error)
|
||||
if !ok {
|
||||
jsonError = &jsonstream.Error{Message: err.Error()}
|
||||
}
|
||||
if b, err := json.Marshal(&jsonMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
|
||||
return appendNewline(b)
|
||||
}
|
||||
return []byte(`{"error":"format error"}` + streamNewline)
|
||||
}
|
||||
|
||||
func (sf *jsonProgressFormatter) formatStatus(id, format string, a ...any) []byte {
|
||||
return FormatStatus(id, format, a...)
|
||||
}
|
||||
|
||||
// formatProgress formats the progress information for a specified action.
|
||||
func (sf *jsonProgressFormatter) formatProgress(id, action string, progress *jsonstream.Progress, aux any) []byte {
|
||||
if progress == nil {
|
||||
progress = &jsonstream.Progress{}
|
||||
}
|
||||
var auxJSON *json.RawMessage
|
||||
if aux != nil {
|
||||
auxJSONBytes, err := json.Marshal(aux)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
auxJSON = new(json.RawMessage)
|
||||
*auxJSON = auxJSONBytes
|
||||
}
|
||||
b, err := json.Marshal(&jsonMessage{
|
||||
Status: action,
|
||||
Progress: progress,
|
||||
ID: id,
|
||||
Aux: auxJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return appendNewline(b)
|
||||
}
|
||||
|
||||
type rawProgressFormatter struct{}
|
||||
|
||||
func (sf *rawProgressFormatter) formatStatus(id, format string, a ...any) []byte {
|
||||
return []byte(fmt.Sprintf(format, a...) + streamNewline)
|
||||
}
|
||||
|
||||
func rawProgressString(p *jsonstream.Progress) string {
|
||||
if p == nil || (p.Current <= 0 && p.Total <= 0) {
|
||||
return ""
|
||||
}
|
||||
if p.Total <= 0 {
|
||||
switch p.Units {
|
||||
case "":
|
||||
return fmt.Sprintf("%8v", units.HumanSize(float64(p.Current)))
|
||||
default:
|
||||
return fmt.Sprintf("%d %s", p.Current, p.Units)
|
||||
}
|
||||
}
|
||||
|
||||
percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
|
||||
if percentage > 50 {
|
||||
percentage = 50
|
||||
}
|
||||
|
||||
numSpaces := 0
|
||||
if 50-percentage > 0 {
|
||||
numSpaces = 50 - percentage
|
||||
}
|
||||
pbBox := fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
|
||||
|
||||
var numbersBox string
|
||||
switch {
|
||||
case p.HideCounts:
|
||||
case p.Units == "": // no units, use bytes
|
||||
current := units.HumanSize(float64(p.Current))
|
||||
total := units.HumanSize(float64(p.Total))
|
||||
|
||||
numbersBox = fmt.Sprintf("%8v/%v", current, total)
|
||||
|
||||
if p.Current > p.Total {
|
||||
// remove total display if the reported current is wonky.
|
||||
numbersBox = fmt.Sprintf("%8v", current)
|
||||
}
|
||||
default:
|
||||
numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units)
|
||||
|
||||
if p.Current > p.Total {
|
||||
// remove total display if the reported current is wonky.
|
||||
numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units)
|
||||
}
|
||||
}
|
||||
|
||||
var timeLeftBox string
|
||||
if p.Current > 0 && p.Start > 0 && percentage < 50 {
|
||||
fromStart := time.Since(time.Unix(p.Start, 0))
|
||||
perEntry := fromStart / time.Duration(p.Current)
|
||||
left := time.Duration(p.Total-p.Current) * perEntry
|
||||
timeLeftBox = " " + left.Round(time.Second).String()
|
||||
}
|
||||
return pbBox + numbersBox + timeLeftBox
|
||||
}
|
||||
|
||||
func (sf *rawProgressFormatter) formatProgress(id, action string, progress *jsonstream.Progress, aux any) []byte {
|
||||
if progress == nil {
|
||||
progress = &jsonstream.Progress{}
|
||||
}
|
||||
endl := "\r"
|
||||
out := rawProgressString(progress)
|
||||
if out == "" {
|
||||
endl += "\n"
|
||||
}
|
||||
return []byte(action + " " + out + endl)
|
||||
}
|
||||
|
||||
// NewProgressOutput returns a progress.Output object that can be passed to
|
||||
// progress.NewProgressReader.
|
||||
func NewProgressOutput(out io.Writer) progress.Output {
|
||||
return &progressOutput{sf: &rawProgressFormatter{}, out: out, newLines: true}
|
||||
}
|
||||
|
||||
// NewJSONProgressOutput returns a progress.Output that formats output
|
||||
// using JSON objects
|
||||
func NewJSONProgressOutput(out io.Writer, newLines bool) progress.Output {
|
||||
return &progressOutput{sf: &jsonProgressFormatter{}, out: out, newLines: newLines}
|
||||
}
|
||||
|
||||
type formatProgress interface {
|
||||
formatStatus(id, format string, a ...any) []byte
|
||||
formatProgress(id, action string, progress *jsonstream.Progress, aux any) []byte
|
||||
}
|
||||
|
||||
type progressOutput struct {
|
||||
sf formatProgress
|
||||
out io.Writer
|
||||
newLines bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// WriteProgress formats progress information from a ProgressReader.
|
||||
func (out *progressOutput) WriteProgress(prog progress.Progress) error {
|
||||
var formatted []byte
|
||||
if prog.Message != "" {
|
||||
formatted = out.sf.formatStatus(prog.ID, prog.Message)
|
||||
} else {
|
||||
jsonProgress := jsonstream.Progress{
|
||||
Current: prog.Current,
|
||||
Total: prog.Total,
|
||||
HideCounts: prog.HideCounts,
|
||||
Units: prog.Units,
|
||||
}
|
||||
formatted = out.sf.formatProgress(prog.ID, prog.Action, &jsonProgress, prog.Aux)
|
||||
}
|
||||
|
||||
out.mu.Lock()
|
||||
defer out.mu.Unlock()
|
||||
_, err := out.out.Write(formatted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if out.newLines && prog.LastUpdate {
|
||||
_, err = out.out.Write(out.sf.formatStatus("", ""))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuxFormatter is a streamFormatter that writes aux progress messages
|
||||
type AuxFormatter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Emit emits the given interface as an aux progress message
|
||||
func (sf *AuxFormatter) Emit(id string, aux any) error {
|
||||
auxJSONBytes, err := json.Marshal(aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auxJSON := new(json.RawMessage)
|
||||
*auxJSON = auxJSONBytes
|
||||
msgJSON, err := json.Marshal(&jsonMessage{ID: id, Aux: auxJSON})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgJSON = appendNewline(msgJSON)
|
||||
n, err := sf.Writer.Write(msgJSON)
|
||||
if n != len(msgJSON) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package streamformatter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type streamWriter struct {
|
||||
io.Writer
|
||||
lineFormat func([]byte) string
|
||||
}
|
||||
|
||||
func (sw *streamWriter) Write(buf []byte) (int, error) {
|
||||
formattedBuf := sw.format(buf)
|
||||
n, err := sw.Writer.Write(formattedBuf)
|
||||
if n != len(formattedBuf) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return len(buf), err
|
||||
}
|
||||
|
||||
func (sw *streamWriter) format(buf []byte) []byte {
|
||||
msg := &jsonMessage{Stream: sw.lineFormat(buf)}
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return FormatError(err)
|
||||
}
|
||||
return appendNewline(b)
|
||||
}
|
||||
|
||||
// NewStdoutWriter returns a writer which formats the output as json message
|
||||
// representing stdout lines
|
||||
func NewStdoutWriter(out io.Writer) io.Writer {
|
||||
return &streamWriter{Writer: out, lineFormat: func(buf []byte) string {
|
||||
return string(buf)
|
||||
}}
|
||||
}
|
||||
|
||||
// NewStderrWriter returns a writer which formats the output as json message
|
||||
// representing stderr lines
|
||||
func NewStderrWriter(out io.Writer) io.Writer {
|
||||
return &streamWriter{Writer: out, lineFormat: func(buf []byte) string {
|
||||
return "\033[91m" + string(buf) + "\033[0m"
|
||||
}}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# commit to be tagged for new release
|
||||
commit = "HEAD"
|
||||
|
||||
project_name = "moby"
|
||||
github_repo = "moby/moby"
|
||||
sub_path = "api"
|
||||
ignore_deps = [ "github.com/moby/moby" ]
|
||||
|
||||
# previous release
|
||||
previous = "v28.2.2"
|
||||
|
||||
pre_release = true
|
||||
|
||||
preface = """\
|
||||
The first dedicated release for the Moby API. This release continues the 1.x
|
||||
line of API compatibility with the 52nd minor release of the 1.x API.
|
||||
"""
|
||||
130
api/server/backend/build/backend.go
Normal file
130
api/server/backend/build/backend.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package build // import "github.com/docker/docker/api/server/backend/build"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/builder"
|
||||
buildkit "github.com/docker/docker/builder/builder-next"
|
||||
daemonevents "github.com/docker/docker/daemon/events"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// ImageComponent provides an interface for working with images
|
||||
type ImageComponent interface {
|
||||
SquashImage(from string, to string) (string, error)
|
||||
TagImage(context.Context, image.ID, reference.Named) error
|
||||
}
|
||||
|
||||
// Builder defines interface for running a build
|
||||
type Builder interface {
|
||||
Build(context.Context, backend.BuildConfig) (*builder.Result, error)
|
||||
}
|
||||
|
||||
// Backend provides build functionality to the API router
|
||||
type Backend struct {
|
||||
builder Builder
|
||||
imageComponent ImageComponent
|
||||
buildkit *buildkit.Builder
|
||||
eventsService *daemonevents.Events
|
||||
}
|
||||
|
||||
// NewBackend creates a new build backend from components
|
||||
func NewBackend(components ImageComponent, builder Builder, buildkit *buildkit.Builder, es *daemonevents.Events) (*Backend, error) {
|
||||
return &Backend{imageComponent: components, builder: builder, buildkit: buildkit, eventsService: es}, nil
|
||||
}
|
||||
|
||||
// RegisterGRPC registers buildkit controller to the grpc server.
|
||||
func (b *Backend) RegisterGRPC(s *grpc.Server) {
|
||||
if b.buildkit != nil {
|
||||
b.buildkit.RegisterGRPC(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Build builds an image from a Source
|
||||
func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
|
||||
options := config.Options
|
||||
useBuildKit := options.Version == types.BuilderBuildKit
|
||||
|
||||
tags, err := sanitizeRepoAndTags(options.Tags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var build *builder.Result
|
||||
if useBuildKit {
|
||||
build, err = b.buildkit.Build(ctx, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
build, err = b.builder.Build(ctx, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if build == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
imageID := build.ImageID
|
||||
if options.Squash {
|
||||
if imageID, err = squashBuild(build, b.imageComponent); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if config.ProgressWriter.AuxFormatter != nil {
|
||||
if err = config.ProgressWriter.AuxFormatter.Emit("moby.image.id", types.BuildResult{ID: imageID}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !useBuildKit {
|
||||
stdout := config.ProgressWriter.StdoutFormatter
|
||||
fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
|
||||
}
|
||||
if imageID != "" && !useBuildKit {
|
||||
err = tagImages(ctx, b.imageComponent, config.ProgressWriter.StdoutFormatter, image.ID(imageID), tags)
|
||||
}
|
||||
return imageID, err
|
||||
}
|
||||
|
||||
// PruneCache removes all cached build sources
|
||||
func (b *Backend) PruneCache(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
|
||||
buildCacheSize, cacheIDs, err := b.buildkit.Prune(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to prune build cache")
|
||||
}
|
||||
b.eventsService.Log(events.ActionPrune, events.BuilderEventType, events.Actor{
|
||||
Attributes: map[string]string{
|
||||
"reclaimed": strconv.FormatInt(buildCacheSize, 10),
|
||||
},
|
||||
})
|
||||
return &types.BuildCachePruneReport{SpaceReclaimed: uint64(buildCacheSize), CachesDeleted: cacheIDs}, nil
|
||||
}
|
||||
|
||||
// Cancel cancels the build by ID
|
||||
func (b *Backend) Cancel(ctx context.Context, id string) error {
|
||||
return b.buildkit.Cancel(ctx, id)
|
||||
}
|
||||
|
||||
func squashBuild(build *builder.Result, imageComponent ImageComponent) (string, error) {
|
||||
var fromID string
|
||||
if build.FromImage != nil {
|
||||
fromID = build.FromImage.ImageID()
|
||||
}
|
||||
imageID, err := imageComponent.SquashImage(build.ImageID, fromID)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error squashing image")
|
||||
}
|
||||
return imageID, nil
|
||||
}
|
||||
51
api/server/backend/build/tag.go
Normal file
51
api/server/backend/build/tag.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package build // import "github.com/docker/docker/api/server/backend/build"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// tagImages creates image tags for the imageID.
|
||||
func tagImages(ctx context.Context, ic ImageComponent, stdout io.Writer, imageID image.ID, repoAndTags []reference.Named) error {
|
||||
for _, rt := range repoAndTags {
|
||||
if err := ic.TagImage(ctx, imageID, rt); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(stdout, "Successfully tagged", reference.FamiliarString(rt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sanitizeRepoAndTags parses the raw "t" parameter received from the client
|
||||
// to a slice of repoAndTag. It removes duplicates, and validates each name
|
||||
// to not contain a digest.
|
||||
func sanitizeRepoAndTags(names []string) (repoAndTags []reference.Named, err error) {
|
||||
uniqNames := map[string]struct{}{}
|
||||
for _, repo := range names {
|
||||
if repo == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := ref.(reference.Digested); ok {
|
||||
return nil, errors.New("build tag cannot contain a digest")
|
||||
}
|
||||
|
||||
ref = reference.TagNameOnly(ref)
|
||||
nameWithTag := ref.String()
|
||||
if _, exists := uniqNames[nameWithTag]; !exists {
|
||||
uniqNames[nameWithTag] = struct{}{}
|
||||
repoAndTags = append(repoAndTags, ref)
|
||||
}
|
||||
}
|
||||
return repoAndTags, nil
|
||||
}
|
||||
152
api/server/httpstatus/status.go
Normal file
152
api/server/httpstatus/status.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package httpstatus // import "github.com/docker/docker/api/server/httpstatus"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// FromError retrieves status code from error message.
|
||||
func FromError(err error) int {
|
||||
if err == nil {
|
||||
log.G(context.TODO()).WithError(err).Error("unexpected HTTP error handling")
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
var statusCode int
|
||||
|
||||
// Stop right there
|
||||
// Are you sure you should be adding a new error class here? Do one of the existing ones work?
|
||||
|
||||
// Note that the below functions are already checking the error causal chain for matches.
|
||||
switch {
|
||||
case errdefs.IsNotFound(err):
|
||||
statusCode = http.StatusNotFound
|
||||
case errdefs.IsInvalidParameter(err):
|
||||
statusCode = http.StatusBadRequest
|
||||
case errdefs.IsConflict(err):
|
||||
statusCode = http.StatusConflict
|
||||
case errdefs.IsUnauthorized(err):
|
||||
statusCode = http.StatusUnauthorized
|
||||
case errdefs.IsUnavailable(err):
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
case errdefs.IsForbidden(err):
|
||||
statusCode = http.StatusForbidden
|
||||
case errdefs.IsNotModified(err):
|
||||
statusCode = http.StatusNotModified
|
||||
case errdefs.IsNotImplemented(err):
|
||||
statusCode = http.StatusNotImplemented
|
||||
case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
|
||||
statusCode = http.StatusInternalServerError
|
||||
default:
|
||||
statusCode = statusCodeFromGRPCError(err)
|
||||
if statusCode != http.StatusInternalServerError {
|
||||
return statusCode
|
||||
}
|
||||
statusCode = statusCodeFromContainerdError(err)
|
||||
if statusCode != http.StatusInternalServerError {
|
||||
return statusCode
|
||||
}
|
||||
statusCode = statusCodeFromDistributionError(err)
|
||||
if statusCode != http.StatusInternalServerError {
|
||||
return statusCode
|
||||
}
|
||||
if e, ok := err.(causer); ok {
|
||||
return FromError(e.Cause())
|
||||
}
|
||||
|
||||
log.G(context.TODO()).WithFields(log.Fields{
|
||||
"module": "api",
|
||||
"error": err,
|
||||
"error_type": fmt.Sprintf("%T", err),
|
||||
}).Debug("FIXME: Got an API for which error does not match any expected type!!!")
|
||||
}
|
||||
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
return statusCode
|
||||
}
|
||||
|
||||
// statusCodeFromGRPCError returns status code according to gRPC error
|
||||
func statusCodeFromGRPCError(err error) int {
|
||||
switch status.Code(err) {
|
||||
case codes.InvalidArgument: // code 3
|
||||
return http.StatusBadRequest
|
||||
case codes.NotFound: // code 5
|
||||
return http.StatusNotFound
|
||||
case codes.AlreadyExists: // code 6
|
||||
return http.StatusConflict
|
||||
case codes.PermissionDenied: // code 7
|
||||
return http.StatusForbidden
|
||||
case codes.FailedPrecondition: // code 9
|
||||
return http.StatusBadRequest
|
||||
case codes.Unauthenticated: // code 16
|
||||
return http.StatusUnauthorized
|
||||
case codes.OutOfRange: // code 11
|
||||
return http.StatusBadRequest
|
||||
case codes.Unimplemented: // code 12
|
||||
return http.StatusNotImplemented
|
||||
case codes.Unavailable: // code 14
|
||||
return http.StatusServiceUnavailable
|
||||
default:
|
||||
// codes.Canceled(1)
|
||||
// codes.Unknown(2)
|
||||
// codes.DeadlineExceeded(4)
|
||||
// codes.ResourceExhausted(8)
|
||||
// codes.Aborted(10)
|
||||
// codes.Internal(13)
|
||||
// codes.DataLoss(15)
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// statusCodeFromDistributionError returns status code according to registry errcode
|
||||
// code is loosely based on errcode.ServeJSON() in docker/distribution
|
||||
func statusCodeFromDistributionError(err error) int {
|
||||
switch errs := err.(type) {
|
||||
case errcode.Errors:
|
||||
if len(errs) < 1 {
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
if _, ok := errs[0].(errcode.ErrorCoder); ok {
|
||||
return statusCodeFromDistributionError(errs[0])
|
||||
}
|
||||
case errcode.ErrorCoder:
|
||||
return errs.ErrorCode().Descriptor().HTTPStatusCode
|
||||
}
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
// statusCodeFromContainerdError returns status code for containerd errors when
|
||||
// consumed directly (not through gRPC)
|
||||
func statusCodeFromContainerdError(err error) int {
|
||||
switch {
|
||||
case cerrdefs.IsInvalidArgument(err):
|
||||
return http.StatusBadRequest
|
||||
case cerrdefs.IsNotFound(err):
|
||||
return http.StatusNotFound
|
||||
case cerrdefs.IsAlreadyExists(err):
|
||||
return http.StatusConflict
|
||||
case cerrdefs.IsFailedPrecondition(err):
|
||||
return http.StatusPreconditionFailed
|
||||
case cerrdefs.IsUnavailable(err):
|
||||
return http.StatusServiceUnavailable
|
||||
case cerrdefs.IsNotImplemented(err):
|
||||
return http.StatusNotImplemented
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
15
api/server/httputils/decoder.go
Normal file
15
api/server/httputils/decoder.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
// ContainerDecoder specifies how
|
||||
// to translate an io.Reader into
|
||||
// container configuration.
|
||||
type ContainerDecoder interface {
|
||||
DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error)
|
||||
}
|
||||
111
api/server/httputils/form.go
Normal file
111
api/server/httputils/form.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
)
|
||||
|
||||
// BoolValue transforms a form value in different formats into a boolean type.
|
||||
func BoolValue(r *http.Request, k string) bool {
|
||||
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
|
||||
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
||||
}
|
||||
|
||||
// BoolValueOrDefault returns the default bool passed if the query param is
|
||||
// missing, otherwise it's just a proxy to boolValue above.
|
||||
func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
|
||||
if _, ok := r.Form[k]; !ok {
|
||||
return d
|
||||
}
|
||||
return BoolValue(r, k)
|
||||
}
|
||||
|
||||
// Int64ValueOrZero parses a form value into an int64 type.
|
||||
// It returns 0 if the parsing fails.
|
||||
func Int64ValueOrZero(r *http.Request, k string) int64 {
|
||||
val, err := Int64ValueOrDefault(r, k, 0)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Int64ValueOrDefault parses a form value into an int64 type. If there is an
|
||||
// error, returns the error. If there is no value returns the default value.
|
||||
func Int64ValueOrDefault(r *http.Request, field string, def int64) (int64, error) {
|
||||
if r.Form.Get(field) != "" {
|
||||
value, err := strconv.ParseInt(r.Form.Get(field), 10, 64)
|
||||
return value, err
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
|
||||
// RepoTagReference parses form values "repo" and "tag" and returns a valid
|
||||
// reference with repository and tag.
|
||||
// If repo is empty, then a nil reference is returned.
|
||||
// If no tag is given, then the default "latest" tag is set.
|
||||
func RepoTagReference(repo, tag string) (reference.NamedTagged, error) {
|
||||
if repo == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, isDigested := ref.(reference.Digested); isDigested {
|
||||
return nil, fmt.Errorf("cannot import digest reference")
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
return reference.WithTag(ref, tag)
|
||||
}
|
||||
|
||||
withDefaultTag := reference.TagNameOnly(ref)
|
||||
|
||||
namedTagged, ok := withDefaultTag.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected reference: %q", ref.String())
|
||||
}
|
||||
|
||||
return namedTagged, nil
|
||||
}
|
||||
|
||||
// ArchiveOptions stores archive information for different operations.
|
||||
type ArchiveOptions struct {
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
|
||||
type badParameterError struct {
|
||||
param string
|
||||
}
|
||||
|
||||
func (e badParameterError) Error() string {
|
||||
return "bad parameter: " + e.param + "cannot be empty"
|
||||
}
|
||||
|
||||
func (e badParameterError) InvalidParameter() {}
|
||||
|
||||
// ArchiveFormValues parses form values and turns them into ArchiveOptions.
|
||||
// It fails if the archive name and path are not in the request.
|
||||
func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
|
||||
if err := ParseForm(r); err != nil {
|
||||
return ArchiveOptions{}, err
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
if name == "" {
|
||||
return ArchiveOptions{}, badParameterError{"name"}
|
||||
}
|
||||
path := r.Form.Get("path")
|
||||
if path == "" {
|
||||
return ArchiveOptions{}, badParameterError{"path"}
|
||||
}
|
||||
return ArchiveOptions{name, path}, nil
|
||||
}
|
||||
105
api/server/httputils/form_test.go
Normal file
105
api/server/httputils/form_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBoolValue(t *testing.T) {
|
||||
cases := map[string]bool{
|
||||
"": false,
|
||||
"0": false,
|
||||
"no": false,
|
||||
"false": false,
|
||||
"none": false,
|
||||
"1": true,
|
||||
"yes": true,
|
||||
"true": true,
|
||||
"one": true,
|
||||
"100": true,
|
||||
}
|
||||
|
||||
for c, e := range cases {
|
||||
v := url.Values{}
|
||||
v.Set("test", c)
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r.Form = v
|
||||
|
||||
a := BoolValue(r, "test")
|
||||
if a != e {
|
||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolValueOrDefault(t *testing.T) {
|
||||
r, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
if !BoolValueOrDefault(r, "queryparam", true) {
|
||||
t.Fatal("Expected to get true default value, got false")
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("param", "")
|
||||
r, _ = http.NewRequest(http.MethodGet, "", nil)
|
||||
r.Form = v
|
||||
if BoolValueOrDefault(r, "param", true) {
|
||||
t.Fatal("Expected not to get true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64ValueOrZero(t *testing.T) {
|
||||
cases := map[string]int64{
|
||||
"": 0,
|
||||
"asdf": 0,
|
||||
"0": 0,
|
||||
"1": 1,
|
||||
}
|
||||
|
||||
for c, e := range cases {
|
||||
v := url.Values{}
|
||||
v.Set("test", c)
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r.Form = v
|
||||
|
||||
a := Int64ValueOrZero(r, "test")
|
||||
if a != e {
|
||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64ValueOrDefault(t *testing.T) {
|
||||
cases := map[string]int64{
|
||||
"": -1,
|
||||
"-1": -1,
|
||||
"42": 42,
|
||||
}
|
||||
|
||||
for c, e := range cases {
|
||||
v := url.Values{}
|
||||
v.Set("test", c)
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r.Form = v
|
||||
|
||||
a, err := Int64ValueOrDefault(r, "test", -1)
|
||||
if a != e {
|
||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Error should be nil, but received: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64ValueOrDefaultWithError(t *testing.T) {
|
||||
v := url.Values{}
|
||||
v.Set("test", "invalid")
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r.Form = v
|
||||
|
||||
_, err := Int64ValueOrDefault(r, "test", -1)
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error.")
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package httputils
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
}
|
||||
|
||||
// CloseStreams ensures that a list for http streams are properly closed.
|
||||
func CloseStreams(streams ...any) {
|
||||
func CloseStreams(streams ...interface{}) {
|
||||
for _, stream := range streams {
|
||||
if tcpc, ok := stream.(interface {
|
||||
CloseWrite() error
|
||||
@@ -59,7 +59,7 @@ func CheckForJSON(r *http.Request) error {
|
||||
|
||||
// ReadJSON validates the request to have the correct content-type, and decodes
|
||||
// the request's Body into out.
|
||||
func ReadJSON(r *http.Request, out any) error {
|
||||
func ReadJSON(r *http.Request, out interface{}) error {
|
||||
err := CheckForJSON(r)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -74,7 +74,7 @@ func ReadJSON(r *http.Request, out any) error {
|
||||
err = dec.Decode(out)
|
||||
defer r.Body.Close()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
if err == io.EOF {
|
||||
return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body"))
|
||||
}
|
||||
return errdefs.InvalidParameter(errors.Wrap(err, "invalid JSON"))
|
||||
@@ -87,7 +87,7 @@ func ReadJSON(r *http.Request, out any) error {
|
||||
}
|
||||
|
||||
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
|
||||
func WriteJSON(w http.ResponseWriter, code int, v any) error {
|
||||
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
enc := json.NewEncoder(w)
|
||||
@@ -1,4 +1,4 @@
|
||||
package httputils
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -33,7 +33,7 @@ func TestJsonContentType(t *testing.T) {
|
||||
|
||||
func TestReadJSON(t *testing.T) {
|
||||
t.Run("nil body", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPost, "https://example.com/some/path", http.NoBody)
|
||||
req, err := http.NewRequest(http.MethodPost, "https://example.com/some/path", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package httputils
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -8,16 +8,13 @@ import (
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/moby/moby/api/pkg/stdcopy"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/pkg/ioutils"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
// rfc3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
|
||||
// ensure the formatted time isalways the same number of characters.
|
||||
const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
|
||||
|
||||
// WriteLogStream writes an encoded byte stream of log messages from the
|
||||
// messages channel, multiplexing them with a stdcopy.Writer if mux is true
|
||||
func WriteLogStream(_ context.Context, w http.ResponseWriter, msgs <-chan *backend.LogMessage, config *container.LogsOptions, mux bool) {
|
||||
@@ -56,7 +53,7 @@ func WriteLogStream(_ context.Context, w http.ResponseWriter, msgs <-chan *backe
|
||||
logLine = append(logLine, msg.Line...)
|
||||
}
|
||||
if config.Timestamps {
|
||||
logLine = append([]byte(msg.Timestamp.Format(rfc3339NanoFixed)+" "), logLine...)
|
||||
logLine = append([]byte(msg.Timestamp.Format(jsonmessage.RFC3339NanoFixed)+" "), logLine...)
|
||||
}
|
||||
if msg.Source == "stdout" && config.ShowStdout {
|
||||
_, _ = outStream.Write(logLine)
|
||||
24
api/server/middleware.go
Normal file
24
api/server/middleware.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package server // import "github.com/docker/docker/api/server"
|
||||
|
||||
import (
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/middleware"
|
||||
)
|
||||
|
||||
// handlerWithGlobalMiddlewares wraps the handler function for a request with
|
||||
// the server's global middlewares. The order of the middlewares is backwards,
|
||||
// meaning that the first in the list will be evaluated last.
|
||||
func (s *Server) handlerWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
|
||||
next := handler
|
||||
|
||||
for _, m := range s.middlewares {
|
||||
next = m.WrapHandler(next)
|
||||
}
|
||||
|
||||
if log.GetLevel() == log.DebugLevel {
|
||||
next = middleware.DebugRequestMiddleware(next)
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
38
api/server/middleware/cors.go
Normal file
38
api/server/middleware/cors.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// CORSMiddleware injects CORS headers to each request
|
||||
// when it's configured.
|
||||
type CORSMiddleware struct {
|
||||
defaultHeaders string
|
||||
}
|
||||
|
||||
// NewCORSMiddleware creates a new CORSMiddleware with default headers.
|
||||
func NewCORSMiddleware(d string) CORSMiddleware {
|
||||
return CORSMiddleware{defaultHeaders: d}
|
||||
}
|
||||
|
||||
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
|
||||
func (c CORSMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
|
||||
// otherwise, all head values will be passed to HTTP handler
|
||||
corsHeaders := c.defaultHeaders
|
||||
if corsHeaders == "" {
|
||||
corsHeaders = "*"
|
||||
}
|
||||
|
||||
log.G(ctx).Debugf("CORS header is enabled and set to: %s", corsHeaders)
|
||||
w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
|
||||
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, "+registry.AuthHeader)
|
||||
w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
90
api/server/middleware/debug.go
Normal file
90
api/server/middleware/debug.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
// DebugRequestMiddleware dumps the request to logger
|
||||
func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
log.G(ctx).Debugf("Calling %s %s", r.Method, r.RequestURI)
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
if err := httputils.CheckForJSON(r); err != nil {
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
maxBodySize := 4096 // 4KB
|
||||
if r.ContentLength > int64(maxBodySize) {
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
|
||||
body := r.Body
|
||||
bufReader := bufio.NewReaderSize(body, maxBodySize)
|
||||
r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
|
||||
|
||||
b, err := bufReader.Peek(maxBodySize)
|
||||
if err != io.EOF {
|
||||
// either there was an error reading, or the buffer is full (in which case the request is too large)
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
|
||||
var postForm map[string]interface{}
|
||||
if err := json.Unmarshal(b, &postForm); err == nil {
|
||||
maskSecretKeys(postForm)
|
||||
formStr, errMarshal := json.Marshal(postForm)
|
||||
if errMarshal == nil {
|
||||
log.G(ctx).Debugf("form data: %s", string(formStr))
|
||||
} else {
|
||||
log.G(ctx).Debugf("form data: %q", postForm)
|
||||
}
|
||||
}
|
||||
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
|
||||
func maskSecretKeys(inp interface{}) {
|
||||
if arr, ok := inp.([]interface{}); ok {
|
||||
for _, f := range arr {
|
||||
maskSecretKeys(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if form, ok := inp.(map[string]interface{}); ok {
|
||||
scrub := []string{
|
||||
// Note: The Data field contains the base64-encoded secret in 'secret'
|
||||
// and 'config' create and update requests. Currently, no other POST
|
||||
// API endpoints use a data field, so we scrub this field unconditionally.
|
||||
// Change this handling to be conditional if a new endpoint is added
|
||||
// in future where this field should not be scrubbed.
|
||||
"data",
|
||||
"jointoken",
|
||||
"password",
|
||||
"secret",
|
||||
"signingcakey",
|
||||
"unlockkey",
|
||||
}
|
||||
loop0:
|
||||
for k, v := range form {
|
||||
for _, m := range scrub {
|
||||
if strings.EqualFold(m, k) {
|
||||
form[k] = "*****"
|
||||
continue loop0
|
||||
}
|
||||
}
|
||||
maskSecretKeys(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
75
api/server/middleware/debug_test.go
Normal file
75
api/server/middleware/debug_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestMaskSecretKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input map[string]interface{}
|
||||
expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
doc: "secret/config create and update requests",
|
||||
input: map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
|
||||
expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
|
||||
},
|
||||
{
|
||||
doc: "masking other fields (recursively)",
|
||||
input: map[string]interface{}{
|
||||
"password": "pass",
|
||||
"secret": "secret",
|
||||
"jointoken": "jointoken",
|
||||
"unlockkey": "unlockkey",
|
||||
"signingcakey": "signingcakey",
|
||||
"other": map[string]interface{}{
|
||||
"password": "pass",
|
||||
"secret": "secret",
|
||||
"jointoken": "jointoken",
|
||||
"unlockkey": "unlockkey",
|
||||
"signingcakey": "signingcakey",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"password": "*****",
|
||||
"secret": "*****",
|
||||
"jointoken": "*****",
|
||||
"unlockkey": "*****",
|
||||
"signingcakey": "*****",
|
||||
"other": map[string]interface{}{
|
||||
"password": "*****",
|
||||
"secret": "*****",
|
||||
"jointoken": "*****",
|
||||
"unlockkey": "*****",
|
||||
"signingcakey": "*****",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "case insensitive field matching",
|
||||
input: map[string]interface{}{
|
||||
"PASSWORD": "pass",
|
||||
"other": map[string]interface{}{
|
||||
"PASSWORD": "pass",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"PASSWORD": "*****",
|
||||
"other": map[string]interface{}{
|
||||
"PASSWORD": "*****",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range tests {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
maskSecretKeys(testcase.input)
|
||||
assert.Check(t, is.DeepEqual(testcase.expected, testcase.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
28
api/server/middleware/experimental.go
Normal file
28
api/server/middleware/experimental.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ExperimentalMiddleware is a the middleware in charge of adding the
|
||||
// 'Docker-Experimental' header to every outgoing request
|
||||
type ExperimentalMiddleware struct {
|
||||
experimental string
|
||||
}
|
||||
|
||||
// NewExperimentalMiddleware creates a new ExperimentalMiddleware
|
||||
func NewExperimentalMiddleware(experimentalEnabled bool) ExperimentalMiddleware {
|
||||
if experimentalEnabled {
|
||||
return ExperimentalMiddleware{"true"}
|
||||
}
|
||||
return ExperimentalMiddleware{"false"}
|
||||
}
|
||||
|
||||
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
|
||||
func (e ExperimentalMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.Header().Set("Docker-Experimental", e.experimental)
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
12
api/server/middleware/middleware.go
Normal file
12
api/server/middleware/middleware.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Middleware is an interface to allow the use of ordinary functions as Docker API filters.
|
||||
// Any struct that has the appropriate signature can be registered as a middleware.
|
||||
type Middleware interface {
|
||||
WrapHandler(func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
||||
}
|
||||
86
api/server/middleware/version.go
Normal file
86
api/server/middleware/version.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
)
|
||||
|
||||
// VersionMiddleware is a middleware that
|
||||
// validates the client and server versions.
|
||||
type VersionMiddleware struct {
|
||||
serverVersion string
|
||||
|
||||
// defaultAPIVersion is the default API version provided by the API server,
|
||||
// specified as "major.minor". It is usually configured to the latest API
|
||||
// version [github.com/docker/docker/api.DefaultVersion].
|
||||
//
|
||||
// API requests for API versions greater than this version are rejected by
|
||||
// the server and produce a [versionUnsupportedError].
|
||||
defaultAPIVersion string
|
||||
|
||||
// minAPIVersion is the minimum API version provided by the API server,
|
||||
// specified as "major.minor".
|
||||
//
|
||||
// API requests for API versions lower than this version are rejected by
|
||||
// the server and produce a [versionUnsupportedError].
|
||||
minAPIVersion string
|
||||
}
|
||||
|
||||
// NewVersionMiddleware creates a VersionMiddleware with the given versions.
|
||||
func NewVersionMiddleware(serverVersion, defaultAPIVersion, minAPIVersion string) (*VersionMiddleware, error) {
|
||||
if versions.LessThan(defaultAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(defaultAPIVersion, api.DefaultVersion) {
|
||||
return nil, fmt.Errorf("invalid default API version (%s): must be between %s and %s", defaultAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
|
||||
}
|
||||
if versions.LessThan(minAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(minAPIVersion, api.DefaultVersion) {
|
||||
return nil, fmt.Errorf("invalid minimum API version (%s): must be between %s and %s", minAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
|
||||
}
|
||||
if versions.GreaterThan(minAPIVersion, defaultAPIVersion) {
|
||||
return nil, fmt.Errorf("invalid API version: the minimum API version (%s) is higher than the default version (%s)", minAPIVersion, defaultAPIVersion)
|
||||
}
|
||||
return &VersionMiddleware{
|
||||
serverVersion: serverVersion,
|
||||
defaultAPIVersion: defaultAPIVersion,
|
||||
minAPIVersion: minAPIVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type versionUnsupportedError struct {
|
||||
version, minVersion, maxVersion string
|
||||
}
|
||||
|
||||
func (e versionUnsupportedError) Error() string {
|
||||
if e.minVersion != "" {
|
||||
return fmt.Sprintf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", e.version, e.minVersion)
|
||||
}
|
||||
return fmt.Sprintf("client version %s is too new. Maximum supported API version is %s", e.version, e.maxVersion)
|
||||
}
|
||||
|
||||
func (e versionUnsupportedError) InvalidParameter() {}
|
||||
|
||||
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
|
||||
func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.Header().Set("Server", fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS))
|
||||
w.Header().Set("API-Version", v.defaultAPIVersion)
|
||||
w.Header().Set("OSType", runtime.GOOS)
|
||||
|
||||
apiVersion := vars["version"]
|
||||
if apiVersion == "" {
|
||||
apiVersion = v.defaultAPIVersion
|
||||
}
|
||||
if versions.LessThan(apiVersion, v.minAPIVersion) {
|
||||
return versionUnsupportedError{version: apiVersion, minVersion: v.minAPIVersion}
|
||||
}
|
||||
if versions.GreaterThan(apiVersion, v.defaultAPIVersion) {
|
||||
return versionUnsupportedError{version: apiVersion, maxVersion: v.defaultAPIVersion}
|
||||
}
|
||||
ctx = context.WithValue(ctx, httputils.APIVersionKey{}, apiVersion)
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
146
api/server/middleware/version_test.go
Normal file
146
api/server/middleware/version_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestNewVersionMiddlewareValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc, defaultVersion, minVersion, expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "defaults",
|
||||
defaultVersion: api.DefaultVersion,
|
||||
minVersion: api.MinSupportedAPIVersion,
|
||||
},
|
||||
{
|
||||
doc: "invalid default lower than min",
|
||||
defaultVersion: api.MinSupportedAPIVersion,
|
||||
minVersion: api.DefaultVersion,
|
||||
expectedErr: fmt.Sprintf("invalid API version: the minimum API version (%s) is higher than the default version (%s)", api.DefaultVersion, api.MinSupportedAPIVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid default too low",
|
||||
defaultVersion: "0.1",
|
||||
minVersion: api.MinSupportedAPIVersion,
|
||||
expectedErr: fmt.Sprintf("invalid default API version (0.1): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid default too high",
|
||||
defaultVersion: "9999.9999",
|
||||
minVersion: api.DefaultVersion,
|
||||
expectedErr: fmt.Sprintf("invalid default API version (9999.9999): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid minimum too low",
|
||||
defaultVersion: api.MinSupportedAPIVersion,
|
||||
minVersion: "0.1",
|
||||
expectedErr: fmt.Sprintf("invalid minimum API version (0.1): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid minimum too high",
|
||||
defaultVersion: api.DefaultVersion,
|
||||
minVersion: "9999.9999",
|
||||
expectedErr: fmt.Sprintf("invalid minimum API version (9999.9999): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
_, err := NewVersionMiddleware("1.2.3", tc.defaultVersion, tc.minVersion)
|
||||
if tc.expectedErr == "" {
|
||||
assert.Check(t, err)
|
||||
} else {
|
||||
assert.Check(t, is.Error(err, tc.expectedErr))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionMiddlewareVersion(t *testing.T) {
|
||||
expectedVersion := "<not set>"
|
||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v := httputils.VersionFromContext(ctx)
|
||||
assert.Check(t, is.Equal(expectedVersion, v))
|
||||
return nil
|
||||
}
|
||||
|
||||
m, err := NewVersionMiddleware("1.2.3", api.DefaultVersion, api.MinSupportedAPIVersion)
|
||||
assert.NilError(t, err)
|
||||
h := m.WrapHandler(handler)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
ctx := context.Background()
|
||||
|
||||
tests := []struct {
|
||||
reqVersion string
|
||||
expectedVersion string
|
||||
errString string
|
||||
}{
|
||||
{
|
||||
expectedVersion: api.DefaultVersion,
|
||||
},
|
||||
{
|
||||
reqVersion: api.MinSupportedAPIVersion,
|
||||
expectedVersion: api.MinSupportedAPIVersion,
|
||||
},
|
||||
{
|
||||
reqVersion: "0.1",
|
||||
errString: fmt.Sprintf("client version 0.1 is too old. Minimum supported API version is %s, please upgrade your client to a newer version", api.MinSupportedAPIVersion),
|
||||
},
|
||||
{
|
||||
reqVersion: "9999.9999",
|
||||
errString: fmt.Sprintf("client version 9999.9999 is too new. Maximum supported API version is %s", api.DefaultVersion),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
expectedVersion = test.expectedVersion
|
||||
|
||||
err := h(ctx, resp, req, map[string]string{"version": test.reqVersion})
|
||||
|
||||
if test.errString != "" {
|
||||
assert.Check(t, is.Error(err, test.errString))
|
||||
} else {
|
||||
assert.Check(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionMiddlewareWithErrorsReturnsHeaders(t *testing.T) {
|
||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v := httputils.VersionFromContext(ctx)
|
||||
assert.Check(t, len(v) != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
m, err := NewVersionMiddleware("1.2.3", api.DefaultVersion, api.MinSupportedAPIVersion)
|
||||
assert.NilError(t, err)
|
||||
h := m.WrapHandler(handler)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
ctx := context.Background()
|
||||
|
||||
vars := map[string]string{"version": "0.1"}
|
||||
err = h(ctx, resp, req, vars)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
hdr := resp.Result().Header
|
||||
assert.Check(t, is.Contains(hdr.Get("Server"), "Docker/1.2.3"))
|
||||
assert.Check(t, is.Contains(hdr.Get("Server"), runtime.GOOS))
|
||||
assert.Check(t, is.Equal(hdr.Get("API-Version"), api.DefaultVersion))
|
||||
assert.Check(t, is.Equal(hdr.Get("OSType"), runtime.GOOS))
|
||||
}
|
||||
23
api/server/router/build/backend.go
Normal file
23
api/server/router/build/backend.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package build // import "github.com/docker/docker/api/server/router/build"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
)
|
||||
|
||||
// Backend abstracts an image builder whose only purpose is to build an image referenced by an imageID.
|
||||
type Backend interface {
|
||||
// Build a Docker image returning the id of the image
|
||||
// TODO: make this return a reference instead of string
|
||||
Build(context.Context, backend.BuildConfig) (string, error)
|
||||
|
||||
// Prune build cache
|
||||
PruneCache(context.Context, types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error)
|
||||
Cancel(context.Context, string) error
|
||||
}
|
||||
|
||||
type experimentalProvider interface {
|
||||
HasExperimental() bool
|
||||
}
|
||||
60
api/server/router/build/build.go
Normal file
60
api/server/router/build/build.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package build // import "github.com/docker/docker/api/server/router/build"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/api/server/router"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
// buildRouter is a router to talk with the build controller
|
||||
type buildRouter struct {
|
||||
backend Backend
|
||||
daemon experimentalProvider
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new build router
|
||||
func NewRouter(b Backend, d experimentalProvider) router.Router {
|
||||
r := &buildRouter{
|
||||
backend: b,
|
||||
daemon: d,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routers to the build controller
|
||||
func (r *buildRouter) Routes() []router.Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
func (r *buildRouter) initRoutes() {
|
||||
r.routes = []router.Route{
|
||||
router.NewPostRoute("/build", r.postBuild),
|
||||
router.NewPostRoute("/build/prune", r.postPrune),
|
||||
router.NewPostRoute("/build/cancel", r.postCancel),
|
||||
}
|
||||
}
|
||||
|
||||
// BuilderVersion derives the default docker builder version from the config.
|
||||
//
|
||||
// The default on Linux is version "2" (BuildKit), but the daemon can be
|
||||
// configured to recommend version "1" (classic Builder). Windows does not
|
||||
// yet support BuildKit for native Windows images, and uses "1" (classic builder)
|
||||
// as a default.
|
||||
//
|
||||
// This value is only a recommendation as advertised by the daemon, and it is
|
||||
// up to the client to choose which builder to use.
|
||||
func BuilderVersion(features map[string]bool) types.BuilderVersion {
|
||||
// TODO(thaJeztah) move the default to daemon/config
|
||||
if runtime.GOOS == "windows" {
|
||||
return types.BuilderV1
|
||||
}
|
||||
|
||||
bv := types.BuilderBuildKit
|
||||
if v, ok := features["buildkit"]; ok && !v {
|
||||
bv = types.BuilderV1
|
||||
}
|
||||
return bv
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package build
|
||||
package build // import "github.com/docker/docker/api/server/router/build"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -13,19 +13,19 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/pkg/progress"
|
||||
"github.com/moby/moby/api/pkg/streamformatter"
|
||||
"github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/pkg/ioutils"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -35,9 +35,9 @@ type invalidParam struct {
|
||||
|
||||
func (e invalidParam) InvalidParameter() {}
|
||||
|
||||
func newImageBuildOptions(ctx context.Context, r *http.Request) (*build.ImageBuildOptions, error) {
|
||||
options := &build.ImageBuildOptions{
|
||||
Version: build.BuilderV1, // Builder V1 is the default, but can be overridden
|
||||
func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) {
|
||||
options := &types.ImageBuildOptions{
|
||||
Version: types.BuilderV1, // Builder V1 is the default, but can be overridden
|
||||
Dockerfile: r.FormValue("dockerfile"),
|
||||
SuppressOutput: httputils.BoolValue(r, "q"),
|
||||
NoCache: httputils.BoolValue(r, "nocache"),
|
||||
@@ -81,7 +81,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*build.ImageBui
|
||||
if versions.GreaterThanOrEqualTo(version, "1.40") {
|
||||
outputsJSON := r.FormValue("outputs")
|
||||
if outputsJSON != "" {
|
||||
var outputs []build.ImageBuildOutput
|
||||
var outputs []types.ImageBuildOutput
|
||||
if err := json.Unmarshal([]byte(outputsJSON), &outputs); err != nil {
|
||||
return nil, invalidParam{errors.Wrap(err, "invalid outputs specified")}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*build.ImageBui
|
||||
}
|
||||
|
||||
if ulimitsJSON := r.FormValue("ulimits"); ulimitsJSON != "" {
|
||||
buildUlimits := []*container.Ulimit{}
|
||||
buildUlimits := []*units.Ulimit{}
|
||||
if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil {
|
||||
return nil, invalidParam{errors.Wrap(err, "error reading ulimit settings")}
|
||||
}
|
||||
@@ -159,12 +159,12 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*build.ImageBui
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func parseVersion(s string) (build.BuilderVersion, error) {
|
||||
switch build.BuilderVersion(s) {
|
||||
case build.BuilderV1:
|
||||
return build.BuilderV1, nil
|
||||
case build.BuilderBuildKit:
|
||||
return build.BuilderBuildKit, nil
|
||||
func parseVersion(s string) (types.BuilderVersion, error) {
|
||||
switch types.BuilderVersion(s) {
|
||||
case types.BuilderV1:
|
||||
return types.BuilderV1, nil
|
||||
case types.BuilderBuildKit:
|
||||
return types.BuilderBuildKit, nil
|
||||
default:
|
||||
return "", invalidParam{errors.Errorf("invalid version %q", s)}
|
||||
}
|
||||
@@ -178,56 +178,19 @@ func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := build.CachePruneOptions{
|
||||
All: httputils.BoolValue(r, "all"),
|
||||
Filters: fltrs,
|
||||
ksfv := r.FormValue("keep-storage")
|
||||
if ksfv == "" {
|
||||
ksfv = "0"
|
||||
}
|
||||
ks, err := strconv.Atoi(ksfv)
|
||||
if err != nil {
|
||||
return invalidParam{errors.Wrapf(err, "keep-storage is in bytes and expects an integer, got %v", ksfv)}
|
||||
}
|
||||
|
||||
parseBytesFromFormValue := func(name string) (int64, error) {
|
||||
if fv := r.FormValue(name); fv != "" {
|
||||
bs, err := strconv.Atoi(fv)
|
||||
if err != nil {
|
||||
return 0, invalidParam{errors.Wrapf(err, "%s is in bytes and expects an integer, got %v", name, fv)}
|
||||
}
|
||||
return int64(bs), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.48") {
|
||||
if bs, err := parseBytesFromFormValue("reserved-space"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if bs == 0 {
|
||||
// Deprecated parameter. Only checked if reserved-space is not used.
|
||||
bs, err = parseBytesFromFormValue("keep-storage")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
opts.ReservedSpace = bs
|
||||
}
|
||||
|
||||
if bs, err := parseBytesFromFormValue("max-used-space"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
opts.MaxUsedSpace = bs
|
||||
}
|
||||
|
||||
if bs, err := parseBytesFromFormValue("min-free-space"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
opts.MinFreeSpace = bs
|
||||
}
|
||||
} else {
|
||||
// Only keep-storage was valid in pre-1.48 versions.
|
||||
if bs, err := parseBytesFromFormValue("keep-storage"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
opts.ReservedSpace = bs
|
||||
}
|
||||
opts := types.BuildCachePruneOptions{
|
||||
All: httputils.BoolValue(r, "all"),
|
||||
Filters: fltrs,
|
||||
KeepStorage: int64(ks),
|
||||
}
|
||||
|
||||
report, err := br.backend.PruneCache(ctx, opts)
|
||||
@@ -282,9 +245,8 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
|
||||
return err
|
||||
}
|
||||
_, err = output.Write(streamformatter.FormatError(err))
|
||||
// don't log broken pipe errors as this is the normal case when a client aborts.
|
||||
if err != nil && !errors.Is(err, syscall.EPIPE) {
|
||||
log.G(ctx).WithError(err).Warn("could not write error response")
|
||||
if err != nil {
|
||||
log.G(ctx).Warnf("could not write error response: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -319,9 +281,6 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
|
||||
ProgressWriter: buildProgressWriter(out, wantAux, createProgressReader),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
log.G(ctx).Debug("build canceled")
|
||||
}
|
||||
return errf(err)
|
||||
}
|
||||
|
||||
@@ -353,14 +312,14 @@ type syncWriter struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *syncWriter) Write(b []byte) (int, error) {
|
||||
func (s *syncWriter) Write(b []byte) (count int, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.w.Write(b)
|
||||
count, err = s.w.Write(b)
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(io.ReadCloser) io.ReadCloser) backend.ProgressWriter {
|
||||
// see https://github.com/moby/moby/pull/21406
|
||||
out = &syncWriter{w: out}
|
||||
|
||||
var aux *streamformatter.AuxFormatter
|
||||
@@ -381,12 +340,8 @@ type flusher interface {
|
||||
Flush()
|
||||
}
|
||||
|
||||
type nopFlusher struct{}
|
||||
|
||||
func (f *nopFlusher) Flush() {}
|
||||
|
||||
func wrapOutputBufferedUntilRequestRead(rc io.ReadCloser, out io.Writer) (io.ReadCloser, io.Writer) {
|
||||
var fl flusher = &nopFlusher{}
|
||||
var fl flusher = &ioutils.NopFlusher{}
|
||||
if f, ok := out.(flusher); ok {
|
||||
fl = f
|
||||
}
|
||||
10
api/server/router/checkpoint/backend.go
Normal file
10
api/server/router/checkpoint/backend.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package checkpoint // import "github.com/docker/docker/api/server/router/checkpoint"
|
||||
|
||||
import "github.com/docker/docker/api/types/checkpoint"
|
||||
|
||||
// Backend for Checkpoint
|
||||
type Backend interface {
|
||||
CheckpointCreate(container string, config checkpoint.CreateOptions) error
|
||||
CheckpointDelete(container string, config checkpoint.DeleteOptions) error
|
||||
CheckpointList(container string, config checkpoint.ListOptions) ([]checkpoint.Summary, error)
|
||||
}
|
||||
36
api/server/router/checkpoint/checkpoint.go
Normal file
36
api/server/router/checkpoint/checkpoint.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package checkpoint // import "github.com/docker/docker/api/server/router/checkpoint"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// checkpointRouter is a router to talk with the checkpoint controller
|
||||
type checkpointRouter struct {
|
||||
backend Backend
|
||||
decoder httputils.ContainerDecoder
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new checkpoint router
|
||||
func NewRouter(b Backend, decoder httputils.ContainerDecoder) router.Router {
|
||||
r := &checkpointRouter{
|
||||
backend: b,
|
||||
decoder: decoder,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routers to the checkpoint controller
|
||||
func (r *checkpointRouter) Routes() []router.Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
func (r *checkpointRouter) initRoutes() {
|
||||
r.routes = []router.Route{
|
||||
router.NewGetRoute("/containers/{name:.*}/checkpoints", r.getContainerCheckpoints, router.Experimental),
|
||||
router.NewPostRoute("/containers/{name:.*}/checkpoints", r.postContainerCheckpoint, router.Experimental),
|
||||
router.NewDeleteRoute("/containers/{name}/checkpoints/{checkpoint}", r.deleteContainerCheckpoint, router.Experimental),
|
||||
}
|
||||
}
|
||||
60
api/server/router/checkpoint/checkpoint_routes.go
Normal file
60
api/server/router/checkpoint/checkpoint_routes.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package checkpoint // import "github.com/docker/docker/api/server/router/checkpoint"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
)
|
||||
|
||||
func (s *checkpointRouter) postContainerCheckpoint(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var options checkpoint.CreateOptions
|
||||
if err := httputils.ReadJSON(r, &options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.backend.CheckpointCreate(vars["name"], options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *checkpointRouter) getContainerCheckpoints(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkpoints, err := s.backend.CheckpointList(vars["name"], checkpoint.ListOptions{
|
||||
CheckpointDir: r.Form.Get("dir"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, checkpoints)
|
||||
}
|
||||
|
||||
func (s *checkpointRouter) deleteContainerCheckpoint(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.backend.CheckpointDelete(vars["name"], checkpoint.DeleteOptions{
|
||||
CheckpointDir: r.Form.Get("dir"),
|
||||
CheckpointID: vars["checkpoint"],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
81
api/server/router/container/backend.go
Normal file
81
api/server/router/container/backend.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package container // import "github.com/docker/docker/api/server/router/container"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
containerpkg "github.com/docker/docker/container"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
// execBackend includes functions to implement to provide exec functionality.
|
||||
type execBackend interface {
|
||||
ContainerExecCreate(name string, config *types.ExecConfig) (string, error)
|
||||
ContainerExecInspect(id string) (*backend.ExecInspect, error)
|
||||
ContainerExecResize(name string, height, width int) error
|
||||
ContainerExecStart(ctx context.Context, name string, options container.ExecStartOptions) error
|
||||
ExecExists(name string) (bool, error)
|
||||
}
|
||||
|
||||
// copyBackend includes functions to implement to provide container copy functionality.
|
||||
type copyBackend interface {
|
||||
ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error)
|
||||
ContainerExport(ctx context.Context, name string, out io.Writer) error
|
||||
ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error
|
||||
ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error)
|
||||
}
|
||||
|
||||
// stateBackend includes functions to implement to provide container state lifecycle functionality.
|
||||
type stateBackend interface {
|
||||
ContainerCreate(ctx context.Context, config backend.ContainerCreateConfig) (container.CreateResponse, error)
|
||||
ContainerKill(name string, signal string) error
|
||||
ContainerPause(name string) error
|
||||
ContainerRename(oldName, newName string) error
|
||||
ContainerResize(name string, height, width int) error
|
||||
ContainerRestart(ctx context.Context, name string, options container.StopOptions) error
|
||||
ContainerRm(name string, config *backend.ContainerRmConfig) error
|
||||
ContainerStart(ctx context.Context, name string, checkpoint string, checkpointDir string) error
|
||||
ContainerStop(ctx context.Context, name string, options container.StopOptions) error
|
||||
ContainerUnpause(name string) error
|
||||
ContainerUpdate(name string, hostConfig *container.HostConfig) (container.ContainerUpdateOKBody, error)
|
||||
ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error)
|
||||
}
|
||||
|
||||
// monitorBackend includes functions to implement to provide containers monitoring functionality.
|
||||
type monitorBackend interface {
|
||||
ContainerChanges(ctx context.Context, name string) ([]archive.Change, error)
|
||||
ContainerInspect(ctx context.Context, name string, size bool, version string) (interface{}, error)
|
||||
ContainerLogs(ctx context.Context, name string, config *container.LogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error)
|
||||
ContainerStats(ctx context.Context, name string, config *backend.ContainerStatsConfig) error
|
||||
ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error)
|
||||
Containers(ctx context.Context, config *container.ListOptions) ([]*types.Container, error)
|
||||
}
|
||||
|
||||
// attachBackend includes function to implement to provide container attaching functionality.
|
||||
type attachBackend interface {
|
||||
ContainerAttach(name string, c *backend.ContainerAttachConfig) error
|
||||
}
|
||||
|
||||
// systemBackend includes functions to implement to provide system wide containers functionality
|
||||
type systemBackend interface {
|
||||
ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error)
|
||||
}
|
||||
|
||||
type commitBackend interface {
|
||||
CreateImageFromContainer(ctx context.Context, name string, config *backend.CreateImageConfig) (imageID string, err error)
|
||||
}
|
||||
|
||||
// Backend is all the methods that need to be implemented to provide container specific functionality.
|
||||
type Backend interface {
|
||||
commitBackend
|
||||
execBackend
|
||||
copyBackend
|
||||
stateBackend
|
||||
monitorBackend
|
||||
attachBackend
|
||||
systemBackend
|
||||
}
|
||||
71
api/server/router/container/container.go
Normal file
71
api/server/router/container/container.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package container // import "github.com/docker/docker/api/server/router/container"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// containerRouter is a router to talk with the container controller
|
||||
type containerRouter struct {
|
||||
backend Backend
|
||||
decoder httputils.ContainerDecoder
|
||||
routes []router.Route
|
||||
cgroup2 bool
|
||||
}
|
||||
|
||||
// NewRouter initializes a new container router
|
||||
func NewRouter(b Backend, decoder httputils.ContainerDecoder, cgroup2 bool) router.Router {
|
||||
r := &containerRouter{
|
||||
backend: b,
|
||||
decoder: decoder,
|
||||
cgroup2: cgroup2,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routes to the container controller
|
||||
func (r *containerRouter) Routes() []router.Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
// initRoutes initializes the routes in container router
|
||||
func (r *containerRouter) initRoutes() {
|
||||
r.routes = []router.Route{
|
||||
// HEAD
|
||||
router.NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive),
|
||||
// GET
|
||||
router.NewGetRoute("/containers/json", r.getContainersJSON),
|
||||
router.NewGetRoute("/containers/{name:.*}/export", r.getContainersExport),
|
||||
router.NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges),
|
||||
router.NewGetRoute("/containers/{name:.*}/json", r.getContainersByName),
|
||||
router.NewGetRoute("/containers/{name:.*}/top", r.getContainersTop),
|
||||
router.NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs),
|
||||
router.NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats),
|
||||
router.NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach),
|
||||
router.NewGetRoute("/exec/{id:.*}/json", r.getExecByID),
|
||||
router.NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive),
|
||||
// POST
|
||||
router.NewPostRoute("/containers/create", r.postContainersCreate),
|
||||
router.NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),
|
||||
router.NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),
|
||||
router.NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause),
|
||||
router.NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
|
||||
router.NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
|
||||
router.NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
|
||||
router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
|
||||
router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
|
||||
router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
|
||||
router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
|
||||
router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
|
||||
router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
|
||||
router.NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
|
||||
router.NewPostRoute("/containers/{name:.*}/update", r.postContainerUpdate),
|
||||
router.NewPostRoute("/containers/prune", r.postContainersPrune),
|
||||
router.NewPostRoute("/commit", r.postCommit),
|
||||
// PUT
|
||||
router.NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
|
||||
// DELETE
|
||||
router.NewDeleteRoute("/containers/{name:.*}", r.deleteContainers),
|
||||
}
|
||||
}
|
||||
925
api/server/router/container/container_routes.go
Normal file
925
api/server/router/container/container_routes.go
Normal file
@@ -0,0 +1,925 @@
|
||||
package container // import "github.com/docker/docker/api/server/router/container"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/server/httpstatus"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
containerpkg "github.com/docker/docker/container"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/runconfig"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := httputils.CheckForJSON(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, _, _, err := s.decoder.DecodeConfig(r.Body)
|
||||
if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty.
|
||||
return err
|
||||
}
|
||||
|
||||
ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag"))
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
imgID, err := s.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{
|
||||
Pause: httputils.BoolValueOrDefault(r, "pause", true), // TODO(dnephin): remove pause arg, and always pause in backend
|
||||
Tag: ref,
|
||||
Author: r.Form.Get("author"),
|
||||
Comment: r.Form.Get("comment"),
|
||||
Config: config,
|
||||
Changes: r.Form["changes"],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID})
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
filter, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := &container.ListOptions{
|
||||
All: httputils.BoolValue(r, "all"),
|
||||
Size: httputils.BoolValue(r, "size"),
|
||||
Since: r.Form.Get("since"),
|
||||
Before: r.Form.Get("before"),
|
||||
Filters: filter,
|
||||
}
|
||||
|
||||
if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
|
||||
limit, err := strconv.Atoi(tmpLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Limit = limit
|
||||
}
|
||||
|
||||
containers, err := s.backend.Containers(ctx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, containers)
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stream := httputils.BoolValueOrDefault(r, "stream", true)
|
||||
if !stream {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
var oneShot bool
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.41") {
|
||||
oneShot = httputils.BoolValueOrDefault(r, "one-shot", false)
|
||||
}
|
||||
|
||||
return s.backend.ContainerStats(ctx, vars["name"], &backend.ContainerStatsConfig{
|
||||
Stream: stream,
|
||||
OneShot: oneShot,
|
||||
OutStream: func() io.Writer {
|
||||
// Assume that when this is called the request is OK.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if !stream {
|
||||
return w
|
||||
}
|
||||
wf := ioutils.NewWriteFlusher(w)
|
||||
wf.Flush()
|
||||
return wf
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Args are validated before the stream starts because when it starts we're
|
||||
// sending HTTP 200 by writing an empty chunk of data to tell the client that
|
||||
// daemon is going to stream. By sending this initial HTTP 200 we can't report
|
||||
// any error after the stream starts (i.e. container not found, wrong parameters)
|
||||
// with the appropriate status code.
|
||||
stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
|
||||
if !(stdout || stderr) {
|
||||
return errdefs.InvalidParameter(errors.New("Bad parameters: you must choose at least one stream"))
|
||||
}
|
||||
|
||||
containerName := vars["name"]
|
||||
logsConfig := &container.LogsOptions{
|
||||
Follow: httputils.BoolValue(r, "follow"),
|
||||
Timestamps: httputils.BoolValue(r, "timestamps"),
|
||||
Since: r.Form.Get("since"),
|
||||
Until: r.Form.Get("until"),
|
||||
Tail: r.Form.Get("tail"),
|
||||
ShowStdout: stdout,
|
||||
ShowStderr: stderr,
|
||||
Details: httputils.BoolValue(r, "details"),
|
||||
}
|
||||
|
||||
msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentType := types.MediaTypeRawStream
|
||||
if !tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
|
||||
contentType = types.MediaTypeMultiplexedStream
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
// if has a tty, we're not muxing streams. if it doesn't, we are. simple.
|
||||
// this is the point of no return for writing a response. once we call
|
||||
// WriteLogStream, the response has been started and errors will be
|
||||
// returned in band by WriteLogStream
|
||||
httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return s.backend.ContainerExport(ctx, vars["name"], w)
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
// If contentLength is -1, we can assumed chunked encoding
|
||||
// or more technically that the length is unknown
|
||||
// https://golang.org/src/pkg/net/http/request.go#L139
|
||||
// net/http otherwise seems to swallow any headers related to chunked encoding
|
||||
// including r.TransferEncoding
|
||||
// allow a nil body for backwards compatibility
|
||||
//
|
||||
// A non-nil json object is at least 7 characters.
|
||||
if r.ContentLength > 7 || r.ContentLength == -1 {
|
||||
return errdefs.InvalidParameter(errors.New("starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"))
|
||||
}
|
||||
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.backend.ContainerStart(ctx, vars["name"], r.Form.Get("checkpoint"), r.Form.Get("checkpoint-dir")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
options container.StopOptions
|
||||
version = httputils.VersionFromContext(ctx)
|
||||
)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
options.Signal = r.Form.Get("signal")
|
||||
}
|
||||
if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
|
||||
valSeconds, err := strconv.Atoi(tmpSeconds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.Timeout = &valSeconds
|
||||
}
|
||||
|
||||
if err := s.backend.ContainerStop(ctx, vars["name"], options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersKill(_ context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
if err := s.backend.ContainerKill(name, r.Form.Get("signal")); err != nil {
|
||||
return errors.Wrapf(err, "cannot kill container: %s", name)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
options container.StopOptions
|
||||
version = httputils.VersionFromContext(ctx)
|
||||
)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
options.Signal = r.Form.Get("signal")
|
||||
}
|
||||
if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
|
||||
valSeconds, err := strconv.Atoi(tmpSeconds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.Timeout = &valSeconds
|
||||
}
|
||||
|
||||
if err := s.backend.ContainerRestart(ctx, vars["name"], options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.backend.ContainerPause(vars["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.backend.ContainerUnpause(vars["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
// Behavior changed in version 1.30 to handle wait condition and to
|
||||
// return headers immediately.
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
legacyBehaviorPre130 := versions.LessThan(version, "1.30")
|
||||
legacyRemovalWaitPre134 := false
|
||||
|
||||
// The wait condition defaults to "not-running".
|
||||
waitCondition := containerpkg.WaitConditionNotRunning
|
||||
if !legacyBehaviorPre130 {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if v := r.Form.Get("condition"); v != "" {
|
||||
switch container.WaitCondition(v) {
|
||||
case container.WaitConditionNotRunning:
|
||||
waitCondition = containerpkg.WaitConditionNotRunning
|
||||
case container.WaitConditionNextExit:
|
||||
waitCondition = containerpkg.WaitConditionNextExit
|
||||
case container.WaitConditionRemoved:
|
||||
waitCondition = containerpkg.WaitConditionRemoved
|
||||
legacyRemovalWaitPre134 = versions.LessThan(version, "1.34")
|
||||
default:
|
||||
return errdefs.InvalidParameter(errors.Errorf("invalid condition: %q", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if !legacyBehaviorPre130 {
|
||||
// Write response header immediately.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if flusher, ok := w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Block on the result of the wait operation.
|
||||
status := <-waitC
|
||||
|
||||
// With API < 1.34, wait on WaitConditionRemoved did not return
|
||||
// in case container removal failed. The only way to report an
|
||||
// error back to the client is to not write anything (i.e. send
|
||||
// an empty response which will be treated as an error).
|
||||
if legacyRemovalWaitPre134 && status.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var waitError *container.WaitExitError
|
||||
if status.Err() != nil {
|
||||
waitError = &container.WaitExitError{Message: status.Err().Error()}
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(&container.WaitResponse{
|
||||
StatusCode: int64(status.ExitCode()),
|
||||
Error: waitError,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
changes, err := s.backend.ContainerChanges(ctx, vars["name"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, changes)
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, procList)
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
newName := r.Form.Get("name")
|
||||
if err := s.backend.ContainerRename(name, newName); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var updateConfig container.UpdateConfig
|
||||
if err := httputils.ReadJSON(r, &updateConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") {
|
||||
updateConfig.PidsLimit = nil
|
||||
}
|
||||
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
|
||||
// Ignore KernelMemory removed in API 1.42.
|
||||
updateConfig.KernelMemory = 0
|
||||
}
|
||||
|
||||
if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 {
|
||||
// Both `0` and `-1` are accepted to set "unlimited" when updating.
|
||||
// Historically, any negative value was accepted, so treat them as
|
||||
// "unlimited" as well.
|
||||
var unlimited int64
|
||||
updateConfig.PidsLimit = &unlimited
|
||||
}
|
||||
|
||||
hostConfig := &container.HostConfig{
|
||||
Resources: updateConfig.Resources,
|
||||
RestartPolicy: updateConfig.RestartPolicy,
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
resp, err := s.backend.ContainerUpdate(name, hostConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := httputils.CheckForJSON(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := r.Form.Get("name")
|
||||
|
||||
config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body"))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
return errdefs.InvalidParameter(runconfig.ErrEmptyConfig)
|
||||
}
|
||||
if hostConfig == nil {
|
||||
hostConfig = &container.HostConfig{}
|
||||
}
|
||||
if networkingConfig == nil {
|
||||
networkingConfig = &network.NetworkingConfig{}
|
||||
}
|
||||
if networkingConfig.EndpointsConfig == nil {
|
||||
networkingConfig.EndpointsConfig = make(map[string]*network.EndpointSettings)
|
||||
}
|
||||
// The NetworkMode "default" is used as a way to express a container should
|
||||
// be attached to the OS-dependant default network, in an OS-independent
|
||||
// way. Doing this conversion as soon as possible ensures we have less
|
||||
// NetworkMode to handle down the path (including in the
|
||||
// backward-compatibility layer we have just below).
|
||||
//
|
||||
// Note that this is not the only place where this conversion has to be
|
||||
// done (as there are various other places where containers get created).
|
||||
if hostConfig.NetworkMode == "" || hostConfig.NetworkMode.IsDefault() {
|
||||
hostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode()
|
||||
if nw, ok := networkingConfig.EndpointsConfig[network.NetworkDefault]; ok {
|
||||
networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()] = nw
|
||||
delete(networkingConfig.EndpointsConfig, network.NetworkDefault)
|
||||
}
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
|
||||
// When using API 1.24 and under, the client is responsible for removing the container
|
||||
if versions.LessThan(version, "1.25") {
|
||||
hostConfig.AutoRemove = false
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.40") {
|
||||
// Ignore BindOptions.NonRecursive because it was added in API 1.40.
|
||||
for _, m := range hostConfig.Mounts {
|
||||
if bo := m.BindOptions; bo != nil {
|
||||
bo.NonRecursive = false
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore KernelMemoryTCP because it was added in API 1.40.
|
||||
hostConfig.KernelMemoryTCP = 0
|
||||
|
||||
// Older clients (API < 1.40) expects the default to be shareable, make them happy
|
||||
if hostConfig.IpcMode.IsEmpty() {
|
||||
hostConfig.IpcMode = container.IPCModeShareable
|
||||
}
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.41") {
|
||||
// Older clients expect the default to be "host" on cgroup v1 hosts
|
||||
if !s.cgroup2 && hostConfig.CgroupnsMode.IsEmpty() {
|
||||
hostConfig.CgroupnsMode = container.CgroupnsModeHost
|
||||
}
|
||||
}
|
||||
|
||||
var platform *ocispec.Platform
|
||||
if versions.GreaterThanOrEqualTo(version, "1.41") {
|
||||
if v := r.Form.Get("platform"); v != "" {
|
||||
p, err := platforms.Parse(v)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
platform = &p
|
||||
}
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.42") {
|
||||
for _, m := range hostConfig.Mounts {
|
||||
// Ignore BindOptions.CreateMountpoint because it was added in API 1.42.
|
||||
if bo := m.BindOptions; bo != nil {
|
||||
bo.CreateMountpoint = false
|
||||
}
|
||||
|
||||
// These combinations are invalid, but weren't validated in API < 1.42.
|
||||
// We reset them here, so that validation doesn't produce an error.
|
||||
if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume {
|
||||
m.VolumeOptions = nil
|
||||
}
|
||||
if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs {
|
||||
m.TmpfsOptions = nil
|
||||
}
|
||||
if bo := m.BindOptions; bo != nil {
|
||||
// Ignore BindOptions.CreateMountpoint because it was added in API 1.42.
|
||||
bo.CreateMountpoint = false
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
// ConsoleSize is not respected by Linux daemon before API 1.42
|
||||
hostConfig.ConsoleSize = [2]uint{0, 0}
|
||||
}
|
||||
}
|
||||
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
// Ignore KernelMemory removed in API 1.42.
|
||||
hostConfig.KernelMemory = 0
|
||||
for _, m := range hostConfig.Mounts {
|
||||
if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume {
|
||||
return errdefs.InvalidParameter(fmt.Errorf("VolumeOptions must not be specified on mount type %q", m.Type))
|
||||
}
|
||||
if o := m.BindOptions; o != nil && m.Type != mount.TypeBind {
|
||||
return errdefs.InvalidParameter(fmt.Errorf("BindOptions must not be specified on mount type %q", m.Type))
|
||||
}
|
||||
if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs {
|
||||
return errdefs.InvalidParameter(fmt.Errorf("TmpfsOptions must not be specified on mount type %q", m.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.43") {
|
||||
// Ignore Annotations because it was added in API v1.43.
|
||||
hostConfig.Annotations = nil
|
||||
}
|
||||
|
||||
defaultReadOnlyNonRecursive := false
|
||||
if versions.LessThan(version, "1.44") {
|
||||
if config.Healthcheck != nil {
|
||||
// StartInterval was added in API 1.44
|
||||
config.Healthcheck.StartInterval = 0
|
||||
}
|
||||
|
||||
// Set ReadOnlyNonRecursive to true because it was added in API 1.44
|
||||
// Before that all read-only mounts were non-recursive.
|
||||
// Keep that behavior for clients on older APIs.
|
||||
defaultReadOnlyNonRecursive = true
|
||||
|
||||
for _, m := range hostConfig.Mounts {
|
||||
if m.Type == mount.TypeBind {
|
||||
if m.BindOptions != nil && m.BindOptions.ReadOnlyForceRecursive {
|
||||
// NOTE: that technically this is a breaking change for older
|
||||
// API versions, and we should ignore the new field.
|
||||
// However, this option may be incorrectly set by a client with
|
||||
// the expectation that the failing to apply recursive read-only
|
||||
// is enforced, so we decided to produce an error instead,
|
||||
// instead of silently ignoring.
|
||||
return errdefs.InvalidParameter(errors.New("BindOptions.ReadOnlyForceRecursive needs API v1.44 or newer"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a container connected to several networks is not supported until v1.44.
|
||||
if len(networkingConfig.EndpointsConfig) > 1 {
|
||||
l := make([]string, 0, len(networkingConfig.EndpointsConfig))
|
||||
for k := range networkingConfig.EndpointsConfig {
|
||||
l = append(l, k)
|
||||
}
|
||||
return errdefs.InvalidParameter(errors.Errorf("Container cannot be created with multiple network endpoints: %s", strings.Join(l, ", ")))
|
||||
}
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.45") {
|
||||
for _, m := range hostConfig.Mounts {
|
||||
if m.VolumeOptions != nil && m.VolumeOptions.Subpath != "" {
|
||||
return errdefs.InvalidParameter(errors.New("VolumeOptions.Subpath needs API v1.45 or newer"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var warnings []string
|
||||
if warn, err := handleMACAddressBC(config, hostConfig, networkingConfig, version); err != nil {
|
||||
return err
|
||||
} else if warn != "" {
|
||||
warnings = append(warnings, warn)
|
||||
}
|
||||
|
||||
if hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
|
||||
// Don't set a limit if either no limit was specified, or "unlimited" was
|
||||
// explicitly set.
|
||||
// Both `0` and `-1` are accepted as "unlimited", and historically any
|
||||
// negative value was accepted, so treat those as "unlimited" as well.
|
||||
hostConfig.PidsLimit = nil
|
||||
}
|
||||
|
||||
ccr, err := s.backend.ContainerCreate(ctx, backend.ContainerCreateConfig{
|
||||
Name: name,
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
NetworkingConfig: networkingConfig,
|
||||
Platform: platform,
|
||||
DefaultReadOnlyNonRecursive: defaultReadOnlyNonRecursive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ccr.Warnings = append(ccr.Warnings, warnings...)
|
||||
return httputils.WriteJSON(w, http.StatusCreated, ccr)
|
||||
}
|
||||
|
||||
// handleMACAddressBC takes care of backward-compatibility for the container-wide MAC address by mutating the
|
||||
// networkingConfig to set the endpoint-specific MACAddress field introduced in API v1.44. It returns a warning message
|
||||
// or an error if the container-wide field was specified for API >= v1.44.
|
||||
func handleMACAddressBC(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, version string) (string, error) {
|
||||
deprecatedMacAddress := config.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
|
||||
// For older versions of the API, migrate the container-wide MAC address to EndpointsConfig.
|
||||
if versions.LessThan(version, "1.44") {
|
||||
if deprecatedMacAddress == "" {
|
||||
// If a MAC address is supplied in EndpointsConfig, discard it because the old API
|
||||
// would have ignored it.
|
||||
for _, ep := range networkingConfig.EndpointsConfig {
|
||||
ep.MacAddress = ""
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
if !hostConfig.NetworkMode.IsBridge() && !hostConfig.NetworkMode.IsUserDefined() {
|
||||
return "", runconfig.ErrConflictContainerNetworkAndMac
|
||||
}
|
||||
|
||||
// There cannot be more than one entry in EndpointsConfig with API < 1.44.
|
||||
|
||||
// If there's no EndpointsConfig, create a place to store the configured address. It is
|
||||
// safe to use NetworkMode as the network name, whether it's a name or id/short-id, as
|
||||
// it will be normalised later and there is no other EndpointSettings object that might
|
||||
// refer to this network/endpoint.
|
||||
if len(networkingConfig.EndpointsConfig) == 0 {
|
||||
nwName := hostConfig.NetworkMode.NetworkName()
|
||||
networkingConfig.EndpointsConfig[nwName] = &network.EndpointSettings{}
|
||||
}
|
||||
// There's exactly one network in EndpointsConfig, either from the API or just-created.
|
||||
// Migrate the container-wide setting to it.
|
||||
// No need to check for a match between NetworkMode and the names/ids in EndpointsConfig,
|
||||
// the old version of the API would have applied the address to this network anyway.
|
||||
for _, ep := range networkingConfig.EndpointsConfig {
|
||||
ep.MacAddress = deprecatedMacAddress
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// The container-wide MacAddress parameter is deprecated and should now be specified in EndpointsConfig.
|
||||
if deprecatedMacAddress == "" {
|
||||
return "", nil
|
||||
}
|
||||
var warning string
|
||||
if hostConfig.NetworkMode.IsBridge() || hostConfig.NetworkMode.IsUserDefined() {
|
||||
nwName := hostConfig.NetworkMode.NetworkName()
|
||||
// If there's no endpoint config, create a place to store the configured address.
|
||||
if len(networkingConfig.EndpointsConfig) == 0 {
|
||||
networkingConfig.EndpointsConfig[nwName] = &network.EndpointSettings{
|
||||
MacAddress: deprecatedMacAddress,
|
||||
}
|
||||
} else {
|
||||
// There is existing endpoint config - if it's not indexed by NetworkMode.Name(), we
|
||||
// can't tell which network the container-wide settings was intended for. NetworkMode,
|
||||
// the keys in EndpointsConfig and the NetworkID in EndpointsConfig may mix network
|
||||
// name/id/short-id. It's not safe to create EndpointsConfig under the NetworkMode
|
||||
// name to store the container-wide MAC address, because that may result in two sets
|
||||
// of EndpointsConfig for the same network and one set will be discarded later. So,
|
||||
// reject the request ...
|
||||
ep, ok := networkingConfig.EndpointsConfig[nwName]
|
||||
if !ok {
|
||||
return "", errdefs.InvalidParameter(errors.New("if a container-wide MAC address is supplied, HostConfig.NetworkMode must match the identity of a network in NetworkSettings.Networks"))
|
||||
}
|
||||
// ep is the endpoint that needs the container-wide MAC address; migrate the address
|
||||
// to it, or bail out if there's a mismatch.
|
||||
if ep.MacAddress == "" {
|
||||
ep.MacAddress = deprecatedMacAddress
|
||||
} else if ep.MacAddress != deprecatedMacAddress {
|
||||
return "", errdefs.InvalidParameter(errors.New("the container-wide MAC address must match the endpoint-specific MAC address for the main network, or be left empty"))
|
||||
}
|
||||
}
|
||||
}
|
||||
warning = "The container-wide MacAddress field is now deprecated. It should be specified in EndpointsConfig instead."
|
||||
config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
|
||||
return warning, nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
config := &backend.ContainerRmConfig{
|
||||
ForceRemove: httputils.BoolValue(r, "force"),
|
||||
RemoveVolume: httputils.BoolValue(r, "v"),
|
||||
RemoveLink: httputils.BoolValue(r, "link"),
|
||||
}
|
||||
|
||||
if err := s.backend.ContainerRm(name, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
return s.backend.ContainerResize(vars["name"], height, width)
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
err := httputils.ParseForm(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containerName := vars["name"]
|
||||
|
||||
_, upgrade := r.Header["Upgrade"]
|
||||
detachKeys := r.FormValue("detachKeys")
|
||||
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName))
|
||||
}
|
||||
|
||||
contentType := types.MediaTypeRawStream
|
||||
setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) {
|
||||
conn, _, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// set raw mode
|
||||
conn.Write([]byte{})
|
||||
|
||||
if upgrade {
|
||||
if multiplexed && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
|
||||
contentType = types.MediaTypeMultiplexedStream
|
||||
}
|
||||
// FIXME(thaJeztah): we should not ignore errors here; see https://github.com/moby/moby/pull/48359#discussion_r1725562802
|
||||
fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: %v\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n", contentType)
|
||||
} else {
|
||||
// FIXME(thaJeztah): we should not ignore errors here; see https://github.com/moby/moby/pull/48359#discussion_r1725562802
|
||||
fmt.Fprint(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
}
|
||||
|
||||
closer := func() error {
|
||||
httputils.CloseStreams(conn)
|
||||
return nil
|
||||
}
|
||||
return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
|
||||
}
|
||||
|
||||
attachConfig := &backend.ContainerAttachConfig{
|
||||
GetStreams: setupStreams,
|
||||
UseStdin: httputils.BoolValue(r, "stdin"),
|
||||
UseStdout: httputils.BoolValue(r, "stdout"),
|
||||
UseStderr: httputils.BoolValue(r, "stderr"),
|
||||
Logs: httputils.BoolValue(r, "logs"),
|
||||
Stream: httputils.BoolValue(r, "stream"),
|
||||
DetachKeys: detachKeys,
|
||||
MuxStreams: true,
|
||||
}
|
||||
|
||||
if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
|
||||
log.G(ctx).WithError(err).Errorf("Handler for %s %s returned error", r.Method, r.URL.Path)
|
||||
// Remember to close stream if error happens
|
||||
conn, _, errHijack := hijacker.Hijack()
|
||||
if errHijack != nil {
|
||||
log.G(ctx).WithError(err).Errorf("Handler for %s %s: unable to close stream; error when hijacking connection", r.Method, r.URL.Path)
|
||||
} else {
|
||||
statusCode := httpstatus.FromError(err)
|
||||
statusText := http.StatusText(statusCode)
|
||||
fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n\r\n%s\r\n", statusCode, statusText, contentType, err.Error())
|
||||
httputils.CloseStreams(conn)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
containerName := vars["name"]
|
||||
|
||||
var err error
|
||||
detachKeys := r.FormValue("detachKeys")
|
||||
|
||||
done := make(chan struct{})
|
||||
started := make(chan struct{})
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
|
||||
setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) {
|
||||
wsChan := make(chan *websocket.Conn)
|
||||
h := func(conn *websocket.Conn) {
|
||||
wsChan <- conn
|
||||
<-done
|
||||
}
|
||||
|
||||
srv := websocket.Server{Handler: h, Handshake: nil}
|
||||
go func() {
|
||||
close(started)
|
||||
srv.ServeHTTP(w, r)
|
||||
}()
|
||||
|
||||
conn := <-wsChan
|
||||
// In case version 1.28 and above, a binary frame will be sent.
|
||||
// See 28176 for details.
|
||||
if versions.GreaterThanOrEqualTo(version, "1.28") {
|
||||
conn.PayloadType = websocket.BinaryFrame
|
||||
}
|
||||
return conn, conn, conn, nil
|
||||
}
|
||||
|
||||
useStdin, useStdout, useStderr := true, true, true
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
useStdin = httputils.BoolValue(r, "stdin")
|
||||
useStdout = httputils.BoolValue(r, "stdout")
|
||||
useStderr = httputils.BoolValue(r, "stderr")
|
||||
}
|
||||
|
||||
attachConfig := &backend.ContainerAttachConfig{
|
||||
GetStreams: setupStreams,
|
||||
UseStdin: useStdin,
|
||||
UseStdout: useStdout,
|
||||
UseStderr: useStderr,
|
||||
Logs: httputils.BoolValue(r, "logs"),
|
||||
Stream: httputils.BoolValue(r, "stream"),
|
||||
DetachKeys: detachKeys,
|
||||
MuxStreams: false, // never multiplex, as we rely on websocket to manage distinct streams
|
||||
}
|
||||
|
||||
err = s.backend.ContainerAttach(containerName, attachConfig)
|
||||
close(done)
|
||||
select {
|
||||
case <-started:
|
||||
if err != nil {
|
||||
log.G(ctx).Errorf("Error attaching websocket: %s", err)
|
||||
} else {
|
||||
log.G(ctx).Debug("websocket connection was closed by client")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, pruneReport)
|
||||
}
|
||||
160
api/server/router/container/container_routes_test.go
Normal file
160
api/server/router/container/container_routes_test.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestHandleMACAddressBC(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
apiVersion string
|
||||
ctrWideMAC string
|
||||
networkMode container.NetworkMode
|
||||
epConfig map[string]*network.EndpointSettings
|
||||
expEpWithCtrWideMAC string
|
||||
expEpWithNoMAC string
|
||||
expCtrWideMAC string
|
||||
expWarning string
|
||||
expError string
|
||||
}{
|
||||
{
|
||||
name: "old api ctr-wide mac mix id and name",
|
||||
apiVersion: "1.43",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "old api clear ep mac",
|
||||
apiVersion: "1.43",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: "11:22:33:44:55:66"}},
|
||||
expEpWithNoMAC: "aNetName",
|
||||
},
|
||||
{
|
||||
name: "old api no-network ctr-wide mac",
|
||||
apiVersion: "1.43",
|
||||
networkMode: "none",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
expError: "conflicting options: mac-address and the network mode",
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "old api create ep",
|
||||
apiVersion: "1.43",
|
||||
networkMode: "aNetId",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
epConfig: map[string]*network.EndpointSettings{},
|
||||
expEpWithCtrWideMAC: "aNetId",
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "old api migrate ctr-wide mac",
|
||||
apiVersion: "1.43",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "new api no macs",
|
||||
apiVersion: "1.44",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
},
|
||||
{
|
||||
name: "new api ep specific mac",
|
||||
apiVersion: "1.44",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: "11:22:33:44:55:66"}},
|
||||
},
|
||||
{
|
||||
name: "new api migrate ctr-wide mac to new ep",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expWarning: "The container-wide MacAddress field is now deprecated",
|
||||
expCtrWideMAC: "",
|
||||
},
|
||||
{
|
||||
name: "new api migrate ctr-wide mac to existing ep",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expWarning: "The container-wide MacAddress field is now deprecated",
|
||||
expCtrWideMAC: "",
|
||||
},
|
||||
{
|
||||
name: "new api mode vs name mismatch",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expError: "if a container-wide MAC address is supplied, HostConfig.NetworkMode must match the identity of a network in NetworkSettings.Networks",
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "new api mac mismatch",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: "00:11:22:33:44:55"}},
|
||||
expError: "the container-wide MAC address must match the endpoint-specific MAC address",
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
MacAddress: tc.ctrWideMAC, //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
NetworkMode: tc.networkMode,
|
||||
}
|
||||
epConfig := make(map[string]*network.EndpointSettings, len(tc.epConfig))
|
||||
for k, v := range tc.epConfig {
|
||||
v := v
|
||||
epConfig[k] = v
|
||||
}
|
||||
netCfg := &network.NetworkingConfig{
|
||||
EndpointsConfig: epConfig,
|
||||
}
|
||||
|
||||
warning, err := handleMACAddressBC(cfg, hostCfg, netCfg, tc.apiVersion)
|
||||
|
||||
if tc.expError == "" {
|
||||
assert.Check(t, err)
|
||||
} else {
|
||||
assert.Check(t, is.ErrorContains(err, tc.expError))
|
||||
}
|
||||
if tc.expWarning == "" {
|
||||
assert.Check(t, is.Equal(warning, ""))
|
||||
} else {
|
||||
assert.Check(t, is.Contains(warning, tc.expWarning))
|
||||
}
|
||||
if tc.expEpWithCtrWideMAC != "" {
|
||||
got := netCfg.EndpointsConfig[tc.expEpWithCtrWideMAC].MacAddress
|
||||
assert.Check(t, is.Equal(got, tc.ctrWideMAC))
|
||||
}
|
||||
if tc.expEpWithNoMAC != "" {
|
||||
got := netCfg.EndpointsConfig[tc.expEpWithNoMAC].MacAddress
|
||||
assert.Check(t, is.Equal(got, ""))
|
||||
}
|
||||
gotCtrWideMAC := cfg.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
assert.Check(t, is.Equal(gotCtrWideMAC, tc.expCtrWideMAC))
|
||||
})
|
||||
}
|
||||
}
|
||||
99
api/server/router/container/copy.go
Normal file
99
api/server/router/container/copy.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package container // import "github.com/docker/docker/api/server/router/container"
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
gddohttputil "github.com/golang/gddo/httputil"
|
||||
)
|
||||
|
||||
// setContainerPathStatHeader encodes the stat to JSON, base64 encode, and place in a header.
|
||||
func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
|
||||
statJSON, err := json.Marshal(stat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Set(
|
||||
"X-Docker-Container-Path-Stat",
|
||||
base64.StdEncoding.EncodeToString(statJSON),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v, err := httputils.ArchiveFormValues(r, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, err := s.backend.ContainerStatPath(v.Name, v.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setContainerPathStatHeader(stat, w.Header())
|
||||
}
|
||||
|
||||
func writeCompressedResponse(w http.ResponseWriter, r *http.Request, body io.Reader) error {
|
||||
var cw io.Writer
|
||||
switch gddohttputil.NegotiateContentEncoding(r, []string{"gzip", "deflate"}) {
|
||||
case "gzip":
|
||||
gw := gzip.NewWriter(w)
|
||||
defer gw.Close()
|
||||
cw = gw
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
case "deflate":
|
||||
fw, err := flate.NewWriter(w, flate.DefaultCompression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fw.Close()
|
||||
cw = fw
|
||||
w.Header().Set("Content-Encoding", "deflate")
|
||||
default:
|
||||
cw = w
|
||||
}
|
||||
_, err := io.Copy(cw, body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v, err := httputils.ArchiveFormValues(r, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tarArchive, stat, err := s.backend.ContainerArchivePath(v.Name, v.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tarArchive.Close()
|
||||
|
||||
if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
return writeCompressedResponse(w, r, tarArchive)
|
||||
}
|
||||
|
||||
func (s *containerRouter) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v, err := httputils.ArchiveFormValues(r, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
noOverwriteDirNonDir := httputils.BoolValue(r, "noOverwriteDirNonDir")
|
||||
copyUIDGID := httputils.BoolValue(r, "copyUIDGID")
|
||||
|
||||
return s.backend.ContainerExtractToDir(v.Name, v.Path, copyUIDGID, noOverwriteDirNonDir, r.Body)
|
||||
}
|
||||
169
api/server/router/container/exec.go
Normal file
169
api/server/router/container/exec.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package container // import "github.com/docker/docker/api/server/router/container"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
func (s *containerRouter) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
eConfig, err := s.backend.ContainerExecInspect(vars["id"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, eConfig)
|
||||
}
|
||||
|
||||
type execCommandError struct{}
|
||||
|
||||
func (execCommandError) Error() string {
|
||||
return "No exec command specified"
|
||||
}
|
||||
|
||||
func (execCommandError) InvalidParameter() {}
|
||||
|
||||
func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
execConfig := &types.ExecConfig{}
|
||||
if err := httputils.ReadJSON(r, execConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(execConfig.Cmd) == 0 {
|
||||
return execCommandError{}
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.LessThan(version, "1.42") {
|
||||
// Not supported by API versions before 1.42
|
||||
execConfig.ConsoleSize = nil
|
||||
}
|
||||
|
||||
// Register an instance of Exec in container.
|
||||
id, err := s.backend.ContainerExecCreate(vars["name"], execConfig)
|
||||
if err != nil {
|
||||
log.G(ctx).Errorf("Error setting up exec command in container %s: %v", vars["name"], err)
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{
|
||||
ID: id,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
|
||||
func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
execName = vars["name"]
|
||||
stdin, inStream io.ReadCloser
|
||||
stdout, stderr, outStream io.Writer
|
||||
)
|
||||
|
||||
execStartCheck := &types.ExecStartCheck{}
|
||||
if err := httputils.ReadJSON(r, execStartCheck); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists, err := s.backend.ExecExists(execName); !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
if execStartCheck.ConsoleSize != nil {
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
|
||||
// Not supported before 1.42
|
||||
if versions.LessThan(version, "1.42") {
|
||||
execStartCheck.ConsoleSize = nil
|
||||
}
|
||||
|
||||
// No console without tty
|
||||
if !execStartCheck.Tty {
|
||||
execStartCheck.ConsoleSize = nil
|
||||
}
|
||||
}
|
||||
|
||||
if !execStartCheck.Detach {
|
||||
var err error
|
||||
// Setting up the streaming http interface.
|
||||
inStream, outStream, err = httputils.HijackConnection(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer httputils.CloseStreams(inStream, outStream)
|
||||
|
||||
if _, ok := r.Header["Upgrade"]; ok {
|
||||
contentType := types.MediaTypeRawStream
|
||||
if !execStartCheck.Tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
|
||||
contentType = types.MediaTypeMultiplexedStream
|
||||
}
|
||||
fmt.Fprint(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n")
|
||||
} else {
|
||||
fmt.Fprint(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n")
|
||||
}
|
||||
|
||||
// copy headers that were removed as part of hijack
|
||||
if err := w.Header().WriteSubset(outStream, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(outStream, "\r\n")
|
||||
|
||||
stdin = inStream
|
||||
stdout = outStream
|
||||
if !execStartCheck.Tty {
|
||||
stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
options := container.ExecStartOptions{
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
ConsoleSize: execStartCheck.ConsoleSize,
|
||||
}
|
||||
|
||||
// Now run the user process in container.
|
||||
// Maybe we should we pass ctx here if we're not detaching?
|
||||
if err := s.backend.ContainerExecStart(context.Background(), execName, options); err != nil {
|
||||
if execStartCheck.Detach {
|
||||
return err
|
||||
}
|
||||
stdout.Write([]byte(err.Error() + "\r\n"))
|
||||
log.G(ctx).Errorf("Error running exec %s in container: %v", execName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *containerRouter) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
return s.backend.ContainerExecResize(vars["name"], height, width)
|
||||
}
|
||||
21
api/server/router/container/inspect.go
Normal file
21
api/server/router/container/inspect.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package container // import "github.com/docker/docker/api/server/router/container"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
)
|
||||
|
||||
// getContainersByName inspects container's configuration and serializes it as json.
|
||||
func (s *containerRouter) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
displaySize := httputils.BoolValue(r, "size")
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
json, err := s.backend.ContainerInspect(ctx, vars["name"], displaySize, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, json)
|
||||
}
|
||||
53
api/server/router/debug/debug.go
Normal file
53
api/server/router/debug/debug.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package debug // import "github.com/docker/docker/api/server/router/debug"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"expvar"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// NewRouter creates a new debug router
|
||||
// The debug router holds endpoints for debug the daemon, such as those for pprof.
|
||||
func NewRouter() router.Router {
|
||||
r := &debugRouter{}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
type debugRouter struct {
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
func (r *debugRouter) initRoutes() {
|
||||
r.routes = []router.Route{
|
||||
router.NewGetRoute("/vars", frameworkAdaptHandler(expvar.Handler())),
|
||||
router.NewGetRoute("/pprof/", frameworkAdaptHandlerFunc(pprof.Index)),
|
||||
router.NewGetRoute("/pprof/cmdline", frameworkAdaptHandlerFunc(pprof.Cmdline)),
|
||||
router.NewGetRoute("/pprof/profile", frameworkAdaptHandlerFunc(pprof.Profile)),
|
||||
router.NewGetRoute("/pprof/symbol", frameworkAdaptHandlerFunc(pprof.Symbol)),
|
||||
router.NewGetRoute("/pprof/trace", frameworkAdaptHandlerFunc(pprof.Trace)),
|
||||
router.NewGetRoute("/pprof/{name}", handlePprof),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *debugRouter) Routes() []router.Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
func frameworkAdaptHandler(handler http.Handler) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
handler.ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func frameworkAdaptHandlerFunc(handler http.HandlerFunc) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
handler(w, r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
12
api/server/router/debug/debug_routes.go
Normal file
12
api/server/router/debug/debug_routes.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package debug // import "github.com/docker/docker/api/server/router/debug"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
func handlePprof(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
pprof.Handler(vars["name"]).ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
15
api/server/router/distribution/backend.go
Normal file
15
api/server/router/distribution/backend.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package distribution // import "github.com/docker/docker/api/server/router/distribution"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// Backend is all the methods that need to be implemented
|
||||
// to provide image specific functionality.
|
||||
type Backend interface {
|
||||
GetRepositories(context.Context, reference.Named, *registry.AuthConfig) ([]distribution.Repository, error)
|
||||
}
|
||||
31
api/server/router/distribution/distribution.go
Normal file
31
api/server/router/distribution/distribution.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package distribution // import "github.com/docker/docker/api/server/router/distribution"
|
||||
|
||||
import "github.com/docker/docker/api/server/router"
|
||||
|
||||
// distributionRouter is a router to talk with the registry
|
||||
type distributionRouter struct {
|
||||
backend Backend
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new distribution router
|
||||
func NewRouter(backend Backend) router.Router {
|
||||
r := &distributionRouter{
|
||||
backend: backend,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routes
|
||||
func (r *distributionRouter) Routes() []router.Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
// initRoutes initializes the routes in the distribution router
|
||||
func (r *distributionRouter) initRoutes() {
|
||||
r.routes = []router.Route{
|
||||
// GET
|
||||
router.NewGetRoute("/distribution/{name:.*}/json", r.getDistributionInfo),
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
package distribution
|
||||
package distribution // import "github.com/docker/docker/api/server/router/distribution"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
distributionpkg "github.com/moby/moby/v2/daemon/internal/distribution"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
distributionpkg "github.com/docker/docker/distribution"
|
||||
"github.com/docker/docker/errdefs"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (dr *distributionRouter) getDistributionInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -43,7 +45,7 @@ func (dr *distributionRouter) getDistributionInfo(ctx context.Context, w http.Re
|
||||
// For a search it is not an error if no auth was given. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
|
||||
repos, err := dr.backend.GetRepositories(ctx, namedRef, authConfig)
|
||||
repos, err := s.backend.GetRepositories(ctx, namedRef, authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,7 +66,7 @@ func (dr *distributionRouter) getDistributionInfo(ctx context.Context, w http.Re
|
||||
// - https://github.com/moby/moby/blob/12c7411b6b7314bef130cd59f1c7384a7db06d0b/distribution/pull.go#L76-L152
|
||||
var lastErr error
|
||||
for _, repo := range repos {
|
||||
distributionInspect, err := fetchManifest(ctx, repo, namedRef)
|
||||
distributionInspect, err := s.fetchManifest(ctx, repo, namedRef)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
@@ -74,7 +76,7 @@ func (dr *distributionRouter) getDistributionInfo(ctx context.Context, w http.Re
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func fetchManifest(ctx context.Context, distrepo distribution.Repository, namedRef reference.Named) (registry.DistributionInspect, error) {
|
||||
func (s *distributionRouter) fetchManifest(ctx context.Context, distrepo distribution.Repository, namedRef reference.Named) (registry.DistributionInspect, error) {
|
||||
var distributionInspect registry.DistributionInspect
|
||||
if canonicalRef, ok := namedRef.(reference.Canonical); !ok {
|
||||
namedRef = reference.TagNameOnly(namedRef)
|
||||
@@ -107,14 +109,14 @@ func fetchManifest(ctx context.Context, distrepo distribution.Repository, namedR
|
||||
}
|
||||
mnfst, err := mnfstsrvc.Get(ctx, distributionInspect.Descriptor.Digest)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, reference.ErrReferenceInvalidFormat),
|
||||
errors.Is(err, reference.ErrTagInvalidFormat),
|
||||
errors.Is(err, reference.ErrDigestInvalidFormat),
|
||||
errors.Is(err, reference.ErrNameContainsUppercase),
|
||||
errors.Is(err, reference.ErrNameEmpty),
|
||||
errors.Is(err, reference.ErrNameTooLong),
|
||||
errors.Is(err, reference.ErrNameNotCanonical):
|
||||
switch err {
|
||||
case reference.ErrReferenceInvalidFormat,
|
||||
reference.ErrTagInvalidFormat,
|
||||
reference.ErrDigestInvalidFormat,
|
||||
reference.ErrNameContainsUppercase,
|
||||
reference.ErrNameEmpty,
|
||||
reference.ErrNameTooLong,
|
||||
reference.ErrNameNotCanonical:
|
||||
return registry.DistributionInspect{}, errdefs.InvalidParameter(err)
|
||||
}
|
||||
return registry.DistributionInspect{}, err
|
||||
@@ -124,11 +126,6 @@ func fetchManifest(ctx context.Context, distrepo distribution.Repository, namedR
|
||||
if err != nil {
|
||||
return registry.DistributionInspect{}, err
|
||||
}
|
||||
switch mediaType {
|
||||
case distributionpkg.MediaTypeDockerSchema1Manifest, distributionpkg.MediaTypeDockerSchema1SignedManifest:
|
||||
return registry.DistributionInspect{}, distributionpkg.DeprecatedSchema1ImageError(namedRef)
|
||||
}
|
||||
|
||||
// update MediaType because registry might return something incorrect
|
||||
distributionInspect.Descriptor.MediaType = mediaType
|
||||
if distributionInspect.Descriptor.Size == 0 {
|
||||
@@ -157,6 +154,15 @@ func fetchManifest(ctx context.Context, distrepo distribution.Repository, namedR
|
||||
distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
|
||||
}
|
||||
}
|
||||
case *schema1.SignedManifest:
|
||||
if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" {
|
||||
return registry.DistributionInspect{}, distributionpkg.DeprecatedSchema1ImageError(namedRef)
|
||||
}
|
||||
platform := ocispec.Platform{
|
||||
Architecture: mnfstObj.Architecture,
|
||||
OS: "linux",
|
||||
}
|
||||
distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
|
||||
}
|
||||
return distributionInspect, nil
|
||||
}
|
||||
68
api/server/router/experimental.go
Normal file
68
api/server/router/experimental.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package router // import "github.com/docker/docker/api/server/router"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
)
|
||||
|
||||
// ExperimentalRoute defines an experimental API route that can be enabled or disabled.
|
||||
type ExperimentalRoute interface {
|
||||
Route
|
||||
|
||||
Enable()
|
||||
Disable()
|
||||
}
|
||||
|
||||
// experimentalRoute defines an experimental API route that can be enabled or disabled.
|
||||
// It implements ExperimentalRoute
|
||||
type experimentalRoute struct {
|
||||
local Route
|
||||
handler httputils.APIFunc
|
||||
}
|
||||
|
||||
// Enable enables this experimental route
|
||||
func (r *experimentalRoute) Enable() {
|
||||
r.handler = r.local.Handler()
|
||||
}
|
||||
|
||||
// Disable disables the experimental route
|
||||
func (r *experimentalRoute) Disable() {
|
||||
r.handler = experimentalHandler
|
||||
}
|
||||
|
||||
type notImplementedError struct{}
|
||||
|
||||
func (notImplementedError) Error() string {
|
||||
return "This experimental feature is disabled by default. Start the Docker daemon in experimental mode in order to enable it."
|
||||
}
|
||||
|
||||
func (notImplementedError) NotImplemented() {}
|
||||
|
||||
func experimentalHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return notImplementedError{}
|
||||
}
|
||||
|
||||
// Handler returns the APIFunc to let the server wrap it in middlewares.
|
||||
func (r *experimentalRoute) Handler() httputils.APIFunc {
|
||||
return r.handler
|
||||
}
|
||||
|
||||
// Method returns the http method that the route responds to.
|
||||
func (r *experimentalRoute) Method() string {
|
||||
return r.local.Method()
|
||||
}
|
||||
|
||||
// Path returns the subpath where the route responds to.
|
||||
func (r *experimentalRoute) Path() string {
|
||||
return r.local.Path()
|
||||
}
|
||||
|
||||
// Experimental will mark a route as experimental.
|
||||
func Experimental(r Route) Route {
|
||||
return &experimentalRoute{
|
||||
local: r,
|
||||
handler: experimentalHandler,
|
||||
}
|
||||
}
|
||||
8
api/server/router/grpc/backend.go
Normal file
8
api/server/router/grpc/backend.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package grpc // import "github.com/docker/docker/api/server/router/grpc"
|
||||
|
||||
import "google.golang.org/grpc"
|
||||
|
||||
// Backend abstracts a registerable GRPC service.
|
||||
type Backend interface {
|
||||
RegisterGRPC(*grpc.Server)
|
||||
}
|
||||
60
api/server/router/grpc/grpc.go
Normal file
60
api/server/router/grpc/grpc.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package grpc // import "github.com/docker/docker/api/server/router/grpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/server/router"
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/moby/buildkit/util/grpcerrors"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type grpcRouter struct {
|
||||
routes []router.Route
|
||||
grpcServer *grpc.Server
|
||||
h2Server *http2.Server
|
||||
}
|
||||
|
||||
// NewRouter initializes a new grpc http router
|
||||
func NewRouter(backends ...Backend) router.Router {
|
||||
unary := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unaryInterceptor(), grpcerrors.UnaryServerInterceptor))
|
||||
stream := grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(otelgrpc.StreamServerInterceptor(), grpcerrors.StreamServerInterceptor)) //nolint:staticcheck // TODO(thaJeztah): ignore SA1019 for deprecated options: see https://github.com/moby/moby/issues/47437
|
||||
|
||||
r := &grpcRouter{
|
||||
h2Server: &http2.Server{},
|
||||
grpcServer: grpc.NewServer(unary, stream),
|
||||
}
|
||||
for _, b := range backends {
|
||||
b.RegisterGRPC(r.grpcServer)
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routers to the session controller
|
||||
func (gr *grpcRouter) Routes() []router.Route {
|
||||
return gr.routes
|
||||
}
|
||||
|
||||
func (gr *grpcRouter) initRoutes() {
|
||||
gr.routes = []router.Route{
|
||||
router.NewPostRoute("/grpc", gr.serveGRPC),
|
||||
}
|
||||
}
|
||||
|
||||
func unaryInterceptor() grpc.UnaryServerInterceptor {
|
||||
withTrace := otelgrpc.UnaryServerInterceptor() //nolint:staticcheck // TODO(thaJeztah): ignore SA1019 for deprecated options: see https://github.com/moby/moby/issues/47437
|
||||
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
// This method is used by the clients to send their traces to buildkit so they can be included
|
||||
// in the daemon trace and stored in the build history record. This method can not be traced because
|
||||
// it would cause an infinite loop.
|
||||
if strings.HasSuffix(info.FullMethod, "opentelemetry.proto.collector.trace.v1.TraceService/Export") {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
return withTrace(ctx, req, info, handler)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package grpc
|
||||
package grpc // import "github.com/docker/docker/api/server/router/grpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
47
api/server/router/image/backend.go
Normal file
47
api/server/router/image/backend.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package image // import "github.com/docker/docker/api/server/router/image"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
dockerimage "github.com/docker/docker/image"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// Backend is all the methods that need to be implemented
|
||||
// to provide image specific functionality.
|
||||
type Backend interface {
|
||||
imageBackend
|
||||
importExportBackend
|
||||
registryBackend
|
||||
}
|
||||
|
||||
type imageBackend interface {
|
||||
ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]image.DeleteResponse, error)
|
||||
ImageHistory(ctx context.Context, imageName string) ([]*image.HistoryResponseItem, error)
|
||||
Images(ctx context.Context, opts image.ListOptions) ([]*image.Summary, error)
|
||||
GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (*dockerimage.Image, error)
|
||||
TagImage(ctx context.Context, id dockerimage.ID, newRef reference.Named) error
|
||||
ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error)
|
||||
}
|
||||
|
||||
type importExportBackend interface {
|
||||
LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error
|
||||
ImportImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, msg string, layerReader io.Reader, changes []string) (dockerimage.ID, error)
|
||||
ExportImage(ctx context.Context, names []string, outStream io.Writer) error
|
||||
}
|
||||
|
||||
type registryBackend interface {
|
||||
PullImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
}
|
||||
|
||||
type Searcher interface {
|
||||
Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error)
|
||||
}
|
||||
57
api/server/router/image/image.go
Normal file
57
api/server/router/image/image.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package image // import "github.com/docker/docker/api/server/router/image"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/router"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/reference"
|
||||
)
|
||||
|
||||
// imageRouter is a router to talk with the image controller
|
||||
type imageRouter struct {
|
||||
backend Backend
|
||||
searcher Searcher
|
||||
referenceBackend reference.Store
|
||||
imageStore image.Store
|
||||
layerStore layer.Store
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new image router
|
||||
func NewRouter(backend Backend, searcher Searcher, referenceBackend reference.Store, imageStore image.Store, layerStore layer.Store) router.Router {
|
||||
ir := &imageRouter{
|
||||
backend: backend,
|
||||
searcher: searcher,
|
||||
referenceBackend: referenceBackend,
|
||||
imageStore: imageStore,
|
||||
layerStore: layerStore,
|
||||
}
|
||||
ir.initRoutes()
|
||||
return ir
|
||||
}
|
||||
|
||||
// Routes returns the available routes to the image controller
|
||||
func (ir *imageRouter) Routes() []router.Route {
|
||||
return ir.routes
|
||||
}
|
||||
|
||||
// initRoutes initializes the routes in the image router
|
||||
func (ir *imageRouter) initRoutes() {
|
||||
ir.routes = []router.Route{
|
||||
// GET
|
||||
router.NewGetRoute("/images/json", ir.getImagesJSON),
|
||||
router.NewGetRoute("/images/search", ir.getImagesSearch),
|
||||
router.NewGetRoute("/images/get", ir.getImagesGet),
|
||||
router.NewGetRoute("/images/{name:.*}/get", ir.getImagesGet),
|
||||
router.NewGetRoute("/images/{name:.*}/history", ir.getImagesHistory),
|
||||
router.NewGetRoute("/images/{name:.*}/json", ir.getImagesByName),
|
||||
// POST
|
||||
router.NewPostRoute("/images/load", ir.postImagesLoad),
|
||||
router.NewPostRoute("/images/create", ir.postImagesCreate),
|
||||
router.NewPostRoute("/images/{name:.*}/push", ir.postImagesPush),
|
||||
router.NewPostRoute("/images/{name:.*}/tag", ir.postImagesTag),
|
||||
router.NewPostRoute("/images/prune", ir.postImagesPrune),
|
||||
// DELETE
|
||||
router.NewDeleteRoute("/images/{name:.*}", ir.deleteImages),
|
||||
}
|
||||
}
|
||||
539
api/server/router/image/image_routes.go
Normal file
539
api/server/router/image/image_routes.go
Normal file
@@ -0,0 +1,539 @@
|
||||
package image // import "github.com/docker/docker/api/server/router/image"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/builder/remotecontext"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Creates an image from Pull or from Import
|
||||
func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
img = r.Form.Get("fromImage")
|
||||
repo = r.Form.Get("repo")
|
||||
tag = r.Form.Get("tag")
|
||||
comment = r.Form.Get("message")
|
||||
progressErr error
|
||||
output = ioutils.NewWriteFlusher(w)
|
||||
platform *ocispec.Platform
|
||||
)
|
||||
defer output.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.32") {
|
||||
if p := r.FormValue("platform"); p != "" {
|
||||
sp, err := platforms.Parse(p)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
platform = &sp
|
||||
}
|
||||
}
|
||||
|
||||
if img != "" { // pull
|
||||
metaHeaders := map[string][]string{}
|
||||
for k, v := range r.Header {
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
metaHeaders[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: "pull -a" may send an image name with a
|
||||
// trailing :. This is ugly, but let's not break API
|
||||
// compatibility.
|
||||
imgName := strings.TrimSuffix(img, ":")
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(imgName)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
// TODO(thaJeztah) this could use a WithTagOrDigest() utility
|
||||
if tag != "" {
|
||||
// The "tag" could actually be a digest.
|
||||
var dgst digest.Digest
|
||||
dgst, err = digest.Parse(tag)
|
||||
if err == nil {
|
||||
ref, err = reference.WithDigest(reference.TrimNamed(ref), dgst)
|
||||
} else {
|
||||
ref, err = reference.WithTag(ref, tag)
|
||||
}
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateRepoName(ref); err != nil {
|
||||
return errdefs.Forbidden(err)
|
||||
}
|
||||
|
||||
// For a pull it is not an error if no auth was given. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
|
||||
progressErr = ir.backend.PullImage(ctx, ref, platform, metaHeaders, authConfig, output)
|
||||
} else { // import
|
||||
src := r.Form.Get("fromSrc")
|
||||
|
||||
tagRef, err := httputils.RepoTagReference(repo, tag)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
if len(comment) == 0 {
|
||||
comment = "Imported from " + src
|
||||
}
|
||||
|
||||
var layerReader io.ReadCloser
|
||||
defer r.Body.Close()
|
||||
if src == "-" {
|
||||
layerReader = r.Body
|
||||
} else {
|
||||
if len(strings.Split(src, "://")) == 1 {
|
||||
src = "http://" + src
|
||||
}
|
||||
u, err := url.Parse(src)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
resp, err := remotecontext.GetWithStatusError(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output.Write(streamformatter.FormatStatus("", "Downloading from %s", u))
|
||||
progressOutput := streamformatter.NewJSONProgressOutput(output, true)
|
||||
layerReader = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing")
|
||||
defer layerReader.Close()
|
||||
}
|
||||
|
||||
var id image.ID
|
||||
id, progressErr = ir.backend.ImportImage(ctx, tagRef, platform, comment, layerReader, r.Form["changes"])
|
||||
|
||||
if progressErr == nil {
|
||||
_, _ = output.Write(streamformatter.FormatStatus("", "%v", id.String()))
|
||||
}
|
||||
}
|
||||
if progressErr != nil {
|
||||
if !output.Flushed() {
|
||||
return progressErr
|
||||
}
|
||||
_, _ = output.Write(streamformatter.FormatError(progressErr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
metaHeaders := map[string][]string{}
|
||||
for k, v := range r.Header {
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
metaHeaders[k] = v
|
||||
}
|
||||
}
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var authConfig *registry.AuthConfig
|
||||
if authEncoded := r.Header.Get(registry.AuthHeader); authEncoded != "" {
|
||||
// the new format is to handle the authConfig as a header. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
authConfig, _ = registry.DecodeAuthConfig(authEncoded)
|
||||
} else {
|
||||
// the old format is supported for compatibility if there was no authConfig header
|
||||
var err error
|
||||
authConfig, err = registry.DecodeAuthConfigBody(r.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "bad parameters and missing X-Registry-Auth")
|
||||
}
|
||||
}
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
img := vars["name"]
|
||||
tag := r.Form.Get("tag")
|
||||
|
||||
var ref reference.Named
|
||||
|
||||
// Tag is empty only in case PushOptions.All is true.
|
||||
if tag != "" {
|
||||
r, err := httputils.RepoTagReference(img, tag)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
ref = r
|
||||
} else {
|
||||
r, err := reference.ParseNormalizedNamed(img)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
ref = r
|
||||
}
|
||||
|
||||
if err := ir.backend.PushImage(ctx, ref, metaHeaders, authConfig, output); err != nil {
|
||||
if !output.Flushed() {
|
||||
return err
|
||||
}
|
||||
_, _ = output.Write(streamformatter.FormatError(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
var names []string
|
||||
if name, ok := vars["name"]; ok {
|
||||
names = []string{name}
|
||||
} else {
|
||||
names = r.Form["names"]
|
||||
}
|
||||
|
||||
if err := ir.backend.ExportImage(ctx, names, output); err != nil {
|
||||
if !output.Flushed() {
|
||||
return err
|
||||
}
|
||||
_, _ = output.Write(streamformatter.FormatError(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
quiet := httputils.BoolValueOrDefault(r, "quiet", true)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
if err := ir.backend.LoadImage(ctx, r.Body, output, quiet); err != nil {
|
||||
_, _ = output.Write(streamformatter.FormatError(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type missingImageError struct{}
|
||||
|
||||
func (missingImageError) Error() string {
|
||||
return "image name cannot be blank"
|
||||
}
|
||||
|
||||
func (missingImageError) InvalidParameter() {}
|
||||
|
||||
func (ir *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return missingImageError{}
|
||||
}
|
||||
|
||||
force := httputils.BoolValue(r, "force")
|
||||
prune := !httputils.BoolValue(r, "noprune")
|
||||
|
||||
list, err := ir.backend.ImageDelete(ctx, name, force, prune)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
img, err := ir.backend.GetImage(ctx, vars["name"], backend.GetImageOpts{Details: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageInspect, err := ir.toImageInspect(img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.LessThan(version, "1.44") {
|
||||
imageInspect.VirtualSize = imageInspect.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
|
||||
|
||||
if imageInspect.Created == "" {
|
||||
// backwards compatibility for Created not existing returning "0001-01-01T00:00:00Z"
|
||||
// https://github.com/moby/moby/issues/47368
|
||||
imageInspect.Created = time.Time{}.Format(time.RFC3339Nano)
|
||||
}
|
||||
}
|
||||
if versions.GreaterThanOrEqualTo(version, "1.45") {
|
||||
imageInspect.Container = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
imageInspect.ContainerConfig = nil //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) toImageInspect(img *image.Image) (*types.ImageInspect, error) {
|
||||
var repoTags, repoDigests []string
|
||||
for _, ref := range img.Details.References {
|
||||
switch ref.(type) {
|
||||
case reference.NamedTagged:
|
||||
repoTags = append(repoTags, reference.FamiliarString(ref))
|
||||
case reference.Canonical:
|
||||
repoDigests = append(repoDigests, reference.FamiliarString(ref))
|
||||
}
|
||||
}
|
||||
|
||||
comment := img.Comment
|
||||
if len(comment) == 0 && len(img.History) > 0 {
|
||||
comment = img.History[len(img.History)-1].Comment
|
||||
}
|
||||
|
||||
// Make sure we output empty arrays instead of nil.
|
||||
if repoTags == nil {
|
||||
repoTags = []string{}
|
||||
}
|
||||
if repoDigests == nil {
|
||||
repoDigests = []string{}
|
||||
}
|
||||
|
||||
var created string
|
||||
if img.Created != nil {
|
||||
created = img.Created.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
return &types.ImageInspect{
|
||||
ID: img.ID().String(),
|
||||
RepoTags: repoTags,
|
||||
RepoDigests: repoDigests,
|
||||
Parent: img.Parent.String(),
|
||||
Comment: comment,
|
||||
Created: created,
|
||||
Container: img.Container, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
ContainerConfig: &img.ContainerConfig, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
DockerVersion: img.DockerVersion,
|
||||
Author: img.Author,
|
||||
Config: img.Config,
|
||||
Architecture: img.Architecture,
|
||||
Variant: img.Variant,
|
||||
Os: img.OperatingSystem(),
|
||||
OsVersion: img.OSVersion,
|
||||
Size: img.Details.Size,
|
||||
GraphDriver: types.GraphDriverData{
|
||||
Name: img.Details.Driver,
|
||||
Data: img.Details.Metadata,
|
||||
},
|
||||
RootFS: rootFSToAPIType(img.RootFS),
|
||||
Metadata: imagetypes.Metadata{
|
||||
LastTagTime: img.Details.LastUpdated,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func rootFSToAPIType(rootfs *image.RootFS) types.RootFS {
|
||||
var layers []string
|
||||
for _, l := range rootfs.DiffIDs {
|
||||
layers = append(layers, l.String())
|
||||
}
|
||||
return types.RootFS{
|
||||
Type: rootfs.Type,
|
||||
Layers: layers,
|
||||
}
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.LessThan(version, "1.41") {
|
||||
// NOTE: filter is a shell glob string applied to repository names.
|
||||
filterParam := r.Form.Get("filter")
|
||||
if filterParam != "" {
|
||||
imageFilters.Add("reference", filterParam)
|
||||
}
|
||||
}
|
||||
|
||||
var sharedSize bool
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
// NOTE: Support for the "shared-size" parameter was added in API 1.42.
|
||||
sharedSize = httputils.BoolValue(r, "shared-size")
|
||||
}
|
||||
|
||||
images, err := ir.backend.Images(ctx, imagetypes.ListOptions{
|
||||
All: httputils.BoolValue(r, "all"),
|
||||
Filters: imageFilters,
|
||||
SharedSize: sharedSize,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
useNone := versions.LessThan(version, "1.43")
|
||||
withVirtualSize := versions.LessThan(version, "1.44")
|
||||
for _, img := range images {
|
||||
if useNone {
|
||||
if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 {
|
||||
img.RepoTags = append(img.RepoTags, "<none>:<none>")
|
||||
img.RepoDigests = append(img.RepoDigests, "<none>@<none>")
|
||||
}
|
||||
} else {
|
||||
if img.RepoTags == nil {
|
||||
img.RepoTags = []string{}
|
||||
}
|
||||
if img.RepoDigests == nil {
|
||||
img.RepoDigests = []string{}
|
||||
}
|
||||
}
|
||||
if withVirtualSize {
|
||||
img.VirtualSize = img.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
|
||||
}
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, images)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
history, err := ir.backend.ImageHistory(ctx, vars["name"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, history)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag"))
|
||||
if ref == nil || err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
refName := reference.FamiliarName(ref)
|
||||
if refName == string(digest.Canonical) {
|
||||
return errdefs.InvalidParameter(errors.New("refusing to create an ambiguous tag using digest algorithm as name"))
|
||||
}
|
||||
|
||||
img, err := ir.backend.GetImage(ctx, vars["name"], backend.GetImageOpts{})
|
||||
if err != nil {
|
||||
return errdefs.NotFound(err)
|
||||
}
|
||||
|
||||
if err := ir.backend.TagImage(ctx, img.ID(), ref); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var limit int
|
||||
if r.Form.Get("limit") != "" {
|
||||
var err error
|
||||
limit, err = strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil || limit < 0 {
|
||||
return errdefs.InvalidParameter(errors.Wrap(err, "invalid limit specified"))
|
||||
}
|
||||
}
|
||||
searchFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For a search it is not an error if no auth was given. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
|
||||
|
||||
headers := http.Header{}
|
||||
for k, v := range r.Header {
|
||||
k = http.CanonicalHeaderKey(k)
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
headers[k] = v
|
||||
}
|
||||
}
|
||||
headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx))
|
||||
res, err := ir.searcher.Search(ctx, searchFilters, r.Form.Get("term"), limit, authConfig, headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, res)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pruneReport, err := ir.backend.ImagesPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, pruneReport)
|
||||
}
|
||||
|
||||
// validateRepoName validates the name of a repository.
|
||||
func validateRepoName(name reference.Named) error {
|
||||
familiarName := reference.FamiliarName(name)
|
||||
if familiarName == api.NoBaseImageSpecifier {
|
||||
return fmt.Errorf("'%s' is a reserved name", familiarName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
73
api/server/router/local.go
Normal file
73
api/server/router/local.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package router // import "github.com/docker/docker/api/server/router"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
)
|
||||
|
||||
// RouteWrapper wraps a route with extra functionality.
|
||||
// It is passed in when creating a new route.
|
||||
type RouteWrapper func(r Route) Route
|
||||
|
||||
// localRoute defines an individual API route to connect
|
||||
// with the docker daemon. It implements Route.
|
||||
type localRoute struct {
|
||||
method string
|
||||
path string
|
||||
handler httputils.APIFunc
|
||||
}
|
||||
|
||||
// Handler returns the APIFunc to let the server wrap it in middlewares.
|
||||
func (l localRoute) Handler() httputils.APIFunc {
|
||||
return l.handler
|
||||
}
|
||||
|
||||
// Method returns the http method that the route responds to.
|
||||
func (l localRoute) Method() string {
|
||||
return l.method
|
||||
}
|
||||
|
||||
// Path returns the subpath where the route responds to.
|
||||
func (l localRoute) Path() string {
|
||||
return l.path
|
||||
}
|
||||
|
||||
// NewRoute initializes a new local route for the router.
|
||||
func NewRoute(method, path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
var r Route = localRoute{method, path, handler}
|
||||
for _, o := range opts {
|
||||
r = o(r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// NewGetRoute initializes a new route with the http method GET.
|
||||
func NewGetRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodGet, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewPostRoute initializes a new route with the http method POST.
|
||||
func NewPostRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodPost, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewPutRoute initializes a new route with the http method PUT.
|
||||
func NewPutRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodPut, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewDeleteRoute initializes a new route with the http method DELETE.
|
||||
func NewDeleteRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodDelete, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewOptionsRoute initializes a new route with the http method OPTIONS.
|
||||
func NewOptionsRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodOptions, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewHeadRoute initializes a new route with the http method HEAD.
|
||||
func NewHeadRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodHead, path, handler, opts...)
|
||||
}
|
||||
31
api/server/router/network/backend.go
Normal file
31
api/server/router/network/backend.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package network // import "github.com/docker/docker/api/server/router/network"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
// Backend is all the methods that need to be implemented
|
||||
// to provide network specific functionality.
|
||||
type Backend interface {
|
||||
GetNetworks(filters.Args, backend.NetworkListConfig) ([]types.NetworkResource, error)
|
||||
CreateNetwork(nc types.NetworkCreateRequest) (*types.NetworkCreateResponse, error)
|
||||
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
|
||||
DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
|
||||
DeleteNetwork(networkID string) error
|
||||
NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error)
|
||||
}
|
||||
|
||||
// ClusterBackend is all the methods that need to be implemented
|
||||
// to provide cluster network specific functionality.
|
||||
type ClusterBackend interface {
|
||||
GetNetworks(filters.Args) ([]types.NetworkResource, error)
|
||||
GetNetwork(name string) (types.NetworkResource, error)
|
||||
GetNetworksByName(name string) ([]types.NetworkResource, error)
|
||||
CreateNetwork(nc types.NetworkCreateRequest) (string, error)
|
||||
RemoveNetwork(name string) error
|
||||
}
|
||||
43
api/server/router/network/network.go
Normal file
43
api/server/router/network/network.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package network // import "github.com/docker/docker/api/server/router/network"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// networkRouter is a router to talk with the network controller
|
||||
type networkRouter struct {
|
||||
backend Backend
|
||||
cluster ClusterBackend
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new network router
|
||||
func NewRouter(b Backend, c ClusterBackend) router.Router {
|
||||
r := &networkRouter{
|
||||
backend: b,
|
||||
cluster: c,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routes to the network controller
|
||||
func (r *networkRouter) Routes() []router.Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
func (r *networkRouter) initRoutes() {
|
||||
r.routes = []router.Route{
|
||||
// GET
|
||||
router.NewGetRoute("/networks", r.getNetworksList),
|
||||
router.NewGetRoute("/networks/", r.getNetworksList),
|
||||
router.NewGetRoute("/networks/{id:.+}", r.getNetwork),
|
||||
// POST
|
||||
router.NewPostRoute("/networks/create", r.postNetworkCreate),
|
||||
router.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
|
||||
router.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
|
||||
router.NewPostRoute("/networks/prune", r.postNetworksPrune),
|
||||
// DELETE
|
||||
router.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package network
|
||||
package network // import "github.com/docker/docker/api/server/router/network"
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/scope"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/libnetwork"
|
||||
"github.com/docker/docker/libnetwork/scope"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -31,7 +32,7 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit
|
||||
return err
|
||||
}
|
||||
|
||||
var list []network.Summary
|
||||
var list []types.NetworkResource
|
||||
nr, err := n.cluster.GetNetworks(filter)
|
||||
if err == nil {
|
||||
list = nr
|
||||
@@ -59,7 +60,7 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
list = []network.Summary{}
|
||||
list = []types.NetworkResource{}
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, list)
|
||||
@@ -75,13 +76,13 @@ func (e invalidRequestError) Error() string {
|
||||
|
||||
func (e invalidRequestError) InvalidParameter() {}
|
||||
|
||||
type ambiguousResultsError string
|
||||
type ambigousResultsError string
|
||||
|
||||
func (e ambiguousResultsError) Error() string {
|
||||
func (e ambigousResultsError) Error() string {
|
||||
return "network " + string(e) + " is ambiguous"
|
||||
}
|
||||
|
||||
func (ambiguousResultsError) InvalidParameter() {}
|
||||
func (ambigousResultsError) InvalidParameter() {}
|
||||
|
||||
func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
@@ -108,8 +109,8 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
|
||||
// For full name and partial ID, save the result first, and process later
|
||||
// in case multiple records was found based on the same term
|
||||
listByFullName := map[string]network.Inspect{}
|
||||
listByPartialID := map[string]network.Inspect{}
|
||||
listByFullName := map[string]types.NetworkResource{}
|
||||
listByPartialID := map[string]types.NetworkResource{}
|
||||
|
||||
// TODO(@cpuguy83): All this logic for figuring out which network to return does not belong here
|
||||
// Instead there should be a backend function to just get one network.
|
||||
@@ -145,7 +146,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
// ex: overlay/partial_ID or name/swarm_scope
|
||||
if nwv, ok := listByPartialID[nwk.ID]; ok {
|
||||
nwk = nwv
|
||||
} else if nwv, ok = listByFullName[nwk.ID]; ok {
|
||||
} else if nwv, ok := listByFullName[nwk.ID]; ok {
|
||||
nwk = nwv
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, nwk)
|
||||
@@ -182,7 +183,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
}
|
||||
}
|
||||
if len(listByFullName) > 1 {
|
||||
return errors.Wrapf(ambiguousResultsError(term), "%d matches found based on name", len(listByFullName))
|
||||
return errors.Wrapf(ambigousResultsError(term), "%d matches found based on name", len(listByFullName))
|
||||
}
|
||||
|
||||
// Find based on partial ID, returns true only if no duplicates
|
||||
@@ -192,7 +193,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
}
|
||||
}
|
||||
if len(listByPartialID) > 1 {
|
||||
return errors.Wrapf(ambiguousResultsError(term), "%d matches found based on ID prefix", len(listByPartialID))
|
||||
return errors.Wrapf(ambigousResultsError(term), "%d matches found based on ID prefix", len(listByPartialID))
|
||||
}
|
||||
|
||||
return libnetwork.ErrNoSuchNetwork(term)
|
||||
@@ -203,7 +204,7 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr
|
||||
return err
|
||||
}
|
||||
|
||||
var create network.CreateRequest
|
||||
var create types.NetworkCreateRequest
|
||||
if err := httputils.ReadJSON(r, &create); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -212,18 +213,11 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr
|
||||
return libnetwork.NetworkNameError(create.Name)
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
|
||||
// EnableIPv4 was introduced in API 1.48.
|
||||
if versions.LessThan(version, "1.48") {
|
||||
create.EnableIPv4 = nil
|
||||
}
|
||||
|
||||
// For a Swarm-scoped network, this call to backend.CreateNetwork is used to
|
||||
// validate the configuration. The network will not be created but, if the
|
||||
// configuration is valid, ManagerRedirectError will be returned and handled
|
||||
// below.
|
||||
nw, err := n.backend.CreateNetwork(ctx, create)
|
||||
nw, err := n.backend.CreateNetwork(create)
|
||||
if err != nil {
|
||||
if _, ok := err.(libnetwork.ManagerRedirectError); !ok {
|
||||
return err
|
||||
@@ -232,7 +226,7 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nw = &network.CreateResponse{
|
||||
nw = &types.NetworkCreateResponse{
|
||||
ID: id,
|
||||
}
|
||||
}
|
||||
@@ -245,7 +239,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
|
||||
return err
|
||||
}
|
||||
|
||||
var connect network.ConnectOptions
|
||||
var connect types.NetworkConnect
|
||||
if err := httputils.ReadJSON(r, &connect); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -254,7 +248,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
|
||||
// The reason is that, In case of attachable network in swarm scope, the actual local network
|
||||
// may not be available at the time. At the same time, inside daemon `ConnectContainerToNetwork`
|
||||
// does the ambiguity check anyway. Therefore, passing the name to daemon would be enough.
|
||||
return n.backend.ConnectContainerToNetwork(ctx, connect.Container, vars["id"], connect.EndpointConfig)
|
||||
return n.backend.ConnectContainerToNetwork(connect.Container, vars["id"], connect.EndpointConfig)
|
||||
}
|
||||
|
||||
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
@@ -262,7 +256,7 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
|
||||
return err
|
||||
}
|
||||
|
||||
var disconnect network.DisconnectOptions
|
||||
var disconnect types.NetworkDisconnect
|
||||
if err := httputils.ReadJSON(r, &disconnect); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -317,9 +311,9 @@ func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWr
|
||||
// For full name and partial ID, save the result first, and process later
|
||||
// in case multiple records was found based on the same term
|
||||
// TODO (yongtang): should we wrap with version here for backward compatibility?
|
||||
func (n *networkRouter) findUniqueNetwork(term string) (network.Inspect, error) {
|
||||
listByFullName := map[string]network.Inspect{}
|
||||
listByPartialID := map[string]network.Inspect{}
|
||||
func (n *networkRouter) findUniqueNetwork(term string) (types.NetworkResource, error) {
|
||||
listByFullName := map[string]types.NetworkResource{}
|
||||
listByPartialID := map[string]types.NetworkResource{}
|
||||
|
||||
filter := filters.NewArgs(filters.Arg("idOrName", term))
|
||||
networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: true})
|
||||
@@ -369,7 +363,7 @@ func (n *networkRouter) findUniqueNetwork(term string) (network.Inspect, error)
|
||||
}
|
||||
}
|
||||
if len(listByFullName) > 1 {
|
||||
return network.Inspect{}, errdefs.InvalidParameter(errors.Errorf("network %s is ambiguous (%d matches found based on name)", term, len(listByFullName)))
|
||||
return types.NetworkResource{}, errdefs.InvalidParameter(errors.Errorf("network %s is ambiguous (%d matches found based on name)", term, len(listByFullName)))
|
||||
}
|
||||
|
||||
// Find based on partial ID, returns true only if no duplicates
|
||||
@@ -379,8 +373,8 @@ func (n *networkRouter) findUniqueNetwork(term string) (network.Inspect, error)
|
||||
}
|
||||
}
|
||||
if len(listByPartialID) > 1 {
|
||||
return network.Inspect{}, errdefs.InvalidParameter(errors.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID)))
|
||||
return types.NetworkResource{}, errdefs.InvalidParameter(errors.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID)))
|
||||
}
|
||||
|
||||
return network.Inspect{}, errdefs.NotFound(libnetwork.ErrNoSuchNetwork(term))
|
||||
return types.NetworkResource{}, errdefs.NotFound(libnetwork.ErrNoSuchNetwork(term))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user