Compare commits
29 Commits
v25.0.0-be
...
v24.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
| cb74dfcd85 | |||
| dc4707edb0 | |||
| 680212238b | |||
| 298e67926e | |||
| aa40216965 | |||
| 9175ffa9b2 | |||
| beb0330a72 | |||
| 405be90634 | |||
| 7a269817b5 | |||
| 41ef7c45cc | |||
| 199b872c98 | |||
| 661f70b52d | |||
| c184a61dab | |||
| e7a60449f7 | |||
| 77541afeab | |||
| f4b354f688 | |||
| e67a7acd06 | |||
| 98fdcd769b | |||
| fb6ae356c7 | |||
| 1d7dd91593 | |||
| de93c9b260 | |||
| 75f2669d56 | |||
| 46615e8724 | |||
| cafdcf283e | |||
| 3768143c2e | |||
| 59e9fbd497 | |||
| 52ac1a974c | |||
| f25ae85b8e | |||
| 58f37f630c |
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@ -50,15 +50,15 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
set: |
|
||||
@ -93,7 +93,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@ -115,13 +115,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
targets: plugins-cross
|
||||
set: |
|
||||
|
||||
@ -1,15 +1,6 @@
|
||||
name: codeql
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
@ -24,17 +15,11 @@ on:
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 360
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
-
|
||||
@ -42,11 +27,6 @@ jobs:
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
git checkout HEAD^2
|
||||
-
|
||||
name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
@ -58,5 +38,3 @@ jobs:
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:go"
|
||||
6
.github/workflows/e2e.yml
vendored
6
.github/workflows/e2e.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
- connhelper-ssh
|
||||
base:
|
||||
- alpine
|
||||
- debian
|
||||
- bullseye
|
||||
engine-version:
|
||||
# - 20.10-dind # FIXME: Fails on 20.10
|
||||
- stable-dind # TODO: Use 20.10-dind, stable-dind is deprecated
|
||||
@ -36,7 +36,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Update daemon.json
|
||||
run: |
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
docker info
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Run ${{ matrix.target }}
|
||||
run: |
|
||||
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@ -20,13 +20,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
targets: test-coverage
|
||||
-
|
||||
@ -56,14 +56,14 @@ jobs:
|
||||
git config --system core.eol lf
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.21.5
|
||||
go-version: 1.20.4
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
||||
8
.github/workflows/validate.yml
vendored
8
.github/workflows/validate.yml
vendored
@ -28,10 +28,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Run
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@ -41,7 +41,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Generate
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
@ -67,7 +67,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Run
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
|
||||
@ -3,41 +3,23 @@ linters:
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupword # Detects duplicate words.
|
||||
- durationcheck
|
||||
- errchkjson
|
||||
- exportloopref # Detects pointers to enclosing loop variables.
|
||||
- gocritic # Metalinter; detects bugs, performance, and styling issues.
|
||||
- gocyclo
|
||||
- gofumpt # Detects whether code was gofumpt-ed.
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosec # Detects security problems.
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- megacheck
|
||||
- misspell # Detects commonly misspelled English words in comments.
|
||||
- misspell
|
||||
- nakedret
|
||||
- nilerr # Detects code that returns nil even if it checks that the error is not nil.
|
||||
- nolintlint # Detects ill-formed or insufficient nolint directives.
|
||||
- perfsprint # Detects fmt.Sprintf uses that can be replaced with a faster alternative.
|
||||
- prealloc # Detects slice declarations that could potentially be pre-allocated.
|
||||
- predeclared # Detects code that shadows one of Go's predeclared identifiers
|
||||
- reassign
|
||||
- revive # Metalinter; drop-in replacement for golint.
|
||||
- revive
|
||||
- staticcheck
|
||||
- stylecheck # Replacement for golint
|
||||
- tenv # Detects using os.Setenv instead of t.Setenv.
|
||||
- thelper # Detects test helpers without t.Helper().
|
||||
- tparallel # Detects inappropriate usage of t.Parallel().
|
||||
- typecheck
|
||||
- unconvert # Detects unnecessary type conversions.
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- vet
|
||||
- wastedassign
|
||||
|
||||
disable:
|
||||
- errcheck
|
||||
@ -50,43 +32,22 @@ run:
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
|
||||
list-type: blacklist
|
||||
include-go-root: true
|
||||
packages:
|
||||
# The io/ioutil package has been deprecated.
|
||||
# https://go.dev/doc/go1.16#ioutil
|
||||
- io/ioutil
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
govet:
|
||||
check-shadowing: true
|
||||
settings:
|
||||
shadow:
|
||||
strict: true
|
||||
check-shadowing: false
|
||||
lll:
|
||||
line-length: 200
|
||||
nakedret:
|
||||
command: nakedret
|
||||
pattern: ^(?P<path>.*?\\.go):(?P<line>\\d+)\\s*(?P<message>.*)$
|
||||
|
||||
revive:
|
||||
rules:
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing
|
||||
- name: import-shadowing
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block
|
||||
- name: empty-block
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines
|
||||
- name: empty-lines
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#use-any
|
||||
- name: use-any
|
||||
severity: warning
|
||||
disabled: false
|
||||
|
||||
issues:
|
||||
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
|
||||
exclude-use-default: false
|
||||
@ -123,7 +84,7 @@ issues:
|
||||
- gosec
|
||||
# EXC0008
|
||||
# TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close" (gosec)
|
||||
- text: "G307"
|
||||
- text: "(G104|G307)"
|
||||
linters:
|
||||
- gosec
|
||||
# EXC0009
|
||||
@ -137,13 +98,10 @@ issues:
|
||||
|
||||
# G113 Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772)
|
||||
# only affects gp < 1.16.14. and go < 1.17.7
|
||||
- text: "G113"
|
||||
linters:
|
||||
- gosec
|
||||
# TODO: G104: Errors unhandled. (gosec)
|
||||
- text: "G104"
|
||||
- text: "(G113)"
|
||||
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"
|
||||
@ -159,24 +117,12 @@ issues:
|
||||
- text: "package-comments: should have a package comment"
|
||||
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
|
||||
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
- gosec
|
||||
- text: "ST1000: at least one file in a package should have a package comment"
|
||||
linters:
|
||||
- stylecheck
|
||||
|
||||
# Allow "err" and "ok" vars to shadow existing declarations, otherwise we get too many false positives.
|
||||
- text: '^shadow: declaration of "(err|ok)" shadows declaration'
|
||||
linters:
|
||||
- govet
|
||||
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-issues-per-linter: 0
|
||||
|
||||
16
.mailmap
16
.mailmap
@ -22,8 +22,6 @@ Akihiro Matsushima <amatsusbit@gmail.com> <amatsus@users.noreply.github.com>
|
||||
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>
|
||||
Albin Kerouanton <albinker@gmail.com>
|
||||
Albin Kerouanton <albinker@gmail.com> <albin@akerouanton.name>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
|
||||
Aleksa Sarai <asarai@suse.de> <cyphar@cyphar.com>
|
||||
@ -34,7 +32,6 @@ Alex Ellis <alexellis2@gmail.com>
|
||||
Alexander Larsson <alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com> <lk4d4@docker.com>
|
||||
Alexander Chneerov <achneerov@gmail.com>
|
||||
Alexandre Beslic <alexandre.beslic@gmail.com> <abronan@docker.com>
|
||||
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
|
||||
Alicia Lauerman <alicia@eta.im> <allydevour@me.com>
|
||||
@ -75,9 +72,6 @@ Bill Wang <ozbillwang@gmail.com> <SydOps@users.noreply.github.com>
|
||||
Bin Liu <liubin0329@gmail.com>
|
||||
Bin Liu <liubin0329@gmail.com> <liubin0329@users.noreply.github.com>
|
||||
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
|
||||
Bjorn Neergaard <bjorn.neergaard@docker.com>
|
||||
Bjorn Neergaard <bjorn.neergaard@docker.com> <bneergaard@mirantis.com>
|
||||
Bjorn Neergaard <bjorn.neergaard@docker.com> <bjorn@neersighted.com>
|
||||
Boaz Shuster <ripcurld.github@gmail.com>
|
||||
Brad Baker <brad@brad.fi>
|
||||
Brad Baker <brad@brad.fi> <88946291+brdbkr@users.noreply.github.com>
|
||||
@ -87,7 +81,6 @@ Brent Salisbury <brent.salisbury@docker.com> <brent@docker.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.local>
|
||||
Brian Tracy <brian.tracy33@gmail.com>
|
||||
Carlos de Paula <me@carlosedp.com>
|
||||
Chad Faragher <wyckster@hotmail.com>
|
||||
Chander Govindarajan <chandergovind@gmail.com>
|
||||
@ -108,7 +101,6 @@ Chun Chen <ramichen@tencent.com> <chenchun.feed@gmail.com>
|
||||
Comical Derskeal <27731088+derskeal@users.noreply.github.com>
|
||||
Corbin Coleman <corbin.coleman@docker.com>
|
||||
Cory Bennet <cbennett@netflix.com>
|
||||
Craig Osterhout <craig.osterhout@docker.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
|
||||
@ -122,7 +114,6 @@ Daniel Dao <dqminh@cloudflare.com>
|
||||
Daniel Dao <dqminh@cloudflare.com> <dqminh89@gmail.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Daniel Gasienica <daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
Danial Gharib <danial.mail.gh@gmail.com>
|
||||
Daniel Goosen <daniel.goosen@surveysampling.com> <djgoosen@users.noreply.github.com>
|
||||
Daniel Grunwell <mwgrunny@gmail.com>
|
||||
Daniel J Walsh <dwalsh@redhat.com>
|
||||
@ -140,8 +131,6 @@ Dave Henderson <dhenderson@gmail.com> <Dave.Henderson@ca.ibm.com>
|
||||
Dave Tucker <dt@docker.com> <dave@dtucker.co.uk>
|
||||
David Alvarez <david.alvarez@flyeralarm.com>
|
||||
David Alvarez <david.alvarez@flyeralarm.com> <busilezas@gmail.com>
|
||||
David Karlsson <david.karlsson@docker.com>
|
||||
David Karlsson <david.karlsson@docker.com> <35727626+dvdksn@users.noreply.github.com>
|
||||
David M. Karr <davidmichaelkarr@gmail.com>
|
||||
David Sheets <dsheets@docker.com> <sheets@alum.mit.edu>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
@ -300,8 +289,7 @@ Kelton Bassingthwaite <KeltonBassingthwaite@gmail.com> <github@bassingthwaite.or
|
||||
Ken Cochrane <kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
Ken Herner <kherner@progress.com> <chosenken@gmail.com>
|
||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||
Kevin Alvarez <github@crazymax.dev>
|
||||
Kevin Alvarez <github@crazymax.dev> <crazy-max@users.noreply.github.com>
|
||||
Kevin Alvarez <crazy-max@users.noreply.github.com>
|
||||
Kevin Feyrer <kevin.feyrer@btinternet.com> <kevinfeyrer@users.noreply.github.com>
|
||||
Kevin Kern <kaiwentan@harmonycloud.cn>
|
||||
Kevin Meredith <kevin.m.meredith@gmail.com>
|
||||
@ -342,7 +330,6 @@ Mansi Nahar <mmn4185@rit.edu> <mansi.nahar@macbookpro-mansinahar.local>
|
||||
Mansi Nahar <mmn4185@rit.edu> <mansinahar@users.noreply.github.com>
|
||||
Marc Abramowitz <marc@marc-abramowitz.com> <msabramo@gmail.com>
|
||||
Marcelo Horacio Fortino <info@fortinux.com> <fortinux@users.noreply.github.com>
|
||||
Marco Spiess <marco.spiess@hotmail.de>
|
||||
Marcus Linke <marcus.linke@gmx.de>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
Marius Ileana <marius.ileana@gmail.com>
|
||||
@ -442,7 +429,6 @@ Sandeep Bansal <sabansal@microsoft.com>
|
||||
Sandeep Bansal <sabansal@microsoft.com> <msabansal@microsoft.com>
|
||||
Sandro Jäckel <sandro.jaeckel@gmail.com>
|
||||
Sargun Dhillon <sargun@netflix.com> <sargun@sargun.me>
|
||||
Saurabh Kumar <saurabhkumar0184@gmail.com>
|
||||
Sean Lee <seanlee@tw.ibm.com> <scaleoutsean@users.noreply.github.com>
|
||||
Sebastiaan van Stijn <github@gone.nl> <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
||||
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||
|
||||
38
AUTHORS
38
AUTHORS
@ -2,7 +2,6 @@
|
||||
# This file lists all contributors to the repository.
|
||||
# See scripts/docs/generate-authors.sh to make modifications.
|
||||
|
||||
A. Lester Buck III <github-reg@nbolt.com>
|
||||
Aanand Prasad <aanand.prasad@gmail.com>
|
||||
Aaron L. Xu <liker.xu@foxmail.com>
|
||||
Aaron Lehmann <alehmann@netflix.com>
|
||||
@ -17,7 +16,6 @@ Adolfo Ochagavía <aochagavia92@gmail.com>
|
||||
Adrian Plata <adrian.plata@docker.com>
|
||||
Adrien Duermael <adrien@duermael.com>
|
||||
Adrien Folie <folie.adrien@gmail.com>
|
||||
Adyanth Hosavalike <ahosavalike@ucsd.edu>
|
||||
Ahmet Alp Balkan <ahmetb@microsoft.com>
|
||||
Aidan Feldman <aidan.feldman@gmail.com>
|
||||
Aidan Hobson Sayers <aidanhs@cantab.net>
|
||||
@ -28,7 +26,7 @@ Akim Demaille <akim.demaille@docker.com>
|
||||
Alan Thompson <cloojure@gmail.com>
|
||||
Albert Callarisa <shark234@gmail.com>
|
||||
Alberto Roura <mail@albertoroura.com>
|
||||
Albin Kerouanton <albinker@gmail.com>
|
||||
Albin Kerouanton <albin@akerouanton.name>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Aleksander Piotrowski <apiotrowski312@gmail.com>
|
||||
Alessandro Boch <aboch@tetrationanalytics.com>
|
||||
@ -36,7 +34,6 @@ Alex Couture-Beil <alex@earthly.dev>
|
||||
Alex Mavrogiannis <alex.mavrogiannis@docker.com>
|
||||
Alex Mayer <amayer5125@gmail.com>
|
||||
Alexander Boyd <alex@opengroove.org>
|
||||
Alexander Chneerov <achneerov@gmail.com>
|
||||
Alexander Larsson <alexl@redhat.com>
|
||||
Alexander Morozov <lk4d4math@gmail.com>
|
||||
Alexander Ryabov <i@sepa.spb.ru>
|
||||
@ -44,7 +41,6 @@ Alexandre González <agonzalezro@gmail.com>
|
||||
Alexey Igrychev <alexey.igrychev@flant.com>
|
||||
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
|
||||
Alfred Landrum <alfred.landrum@docker.com>
|
||||
Ali Rostami <rostami.ali@gmail.com>
|
||||
Alicia Lauerman <alicia@eta.im>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com>
|
||||
Alvin Deng <alvin.q.deng@utexas.edu>
|
||||
@ -83,9 +79,7 @@ Arko Dasgupta <arko@tetrate.io>
|
||||
Arnaud Porterie <icecrime@gmail.com>
|
||||
Arnaud Rebillout <elboulangero@gmail.com>
|
||||
Arthur Peka <arthur.peka@outlook.com>
|
||||
Ashly Mathew <ashly.mathew@sap.com>
|
||||
Ashwini Oruganti <ashwini.oruganti@gmail.com>
|
||||
Aslam Ahemad <aslamahemad@gmail.com>
|
||||
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
||||
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
||||
Barnaby Gray <barnaby@pickle.me.uk>
|
||||
@ -104,9 +98,7 @@ Bill Wang <ozbillwang@gmail.com>
|
||||
Bin Liu <liubin0329@gmail.com>
|
||||
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
|
||||
Bishal Das <bishalhnj127@gmail.com>
|
||||
Bjorn Neergaard <bjorn.neergaard@docker.com>
|
||||
Boaz Shuster <ripcurld.github@gmail.com>
|
||||
Boban Acimovic <boban.acimovic@gmail.com>
|
||||
Bogdan Anton <contact@bogdananton.ro>
|
||||
Boris Pruessmann <boris@pruessmann.org>
|
||||
Brad Baker <brad@brad.fi>
|
||||
@ -117,7 +109,6 @@ Brent Salisbury <brent.salisbury@docker.com>
|
||||
Bret Fisher <bret@bretfisher.com>
|
||||
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Brian Tracy <brian.tracy33@gmail.com>
|
||||
Brian Wieder <brian@4wieders.com>
|
||||
Bruno Sousa <bruno.sousa@docker.com>
|
||||
Bryan Bess <squarejaw@bsbess.com>
|
||||
@ -145,7 +136,6 @@ Chen Chuanliang <chen.chuanliang@zte.com.cn>
|
||||
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
|
||||
Chen Mingjie <chenmingjie0828@163.com>
|
||||
Chen Qiu <cheney-90@hotmail.com>
|
||||
Chris Chinchilla <chris.ward@docker.com>
|
||||
Chris Couzens <ccouzens@gmail.com>
|
||||
Chris Gavin <chris@chrisgavin.me>
|
||||
Chris Gibson <chris@chrisg.io>
|
||||
@ -173,8 +163,6 @@ Conner Crosby <conner@cavcrosby.tech>
|
||||
Corey Farrell <git@cfware.com>
|
||||
Corey Quon <corey.quon@docker.com>
|
||||
Cory Bennet <cbennett@netflix.com>
|
||||
Cory Snider <csnider@mirantis.com>
|
||||
Craig Osterhout <craig.osterhout@docker.com>
|
||||
Craig Wilhite <crwilhit@microsoft.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
@ -183,7 +171,6 @@ Daisuke Ito <itodaisuke00@gmail.com>
|
||||
dalanlan <dalanlan925@gmail.com>
|
||||
Damien Nadé <github@livna.org>
|
||||
Dan Cotora <dan@bluevision.ro>
|
||||
Danial Gharib <danial.mail.gh@gmail.com>
|
||||
Daniel Artine <daniel.artine@ufrj.br>
|
||||
Daniel Cassidy <mail@danielcassidy.me.uk>
|
||||
Daniel Dao <dqminh@cloudflare.com>
|
||||
@ -223,7 +210,6 @@ Denis Defreyne <denis@soundcloud.com>
|
||||
Denis Gladkikh <denis@gladkikh.email>
|
||||
Denis Ollier <larchunix@users.noreply.github.com>
|
||||
Dennis Docter <dennis@d23.nl>
|
||||
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||
Derek McGowan <derek@mcg.dev>
|
||||
Des Preston <despreston@gmail.com>
|
||||
Deshi Xiao <dxiao@redhat.com>
|
||||
@ -246,13 +232,11 @@ DongGeon Lee <secmatth1996@gmail.com>
|
||||
Doug Davis <dug@us.ibm.com>
|
||||
Drew Erny <derny@mirantis.com>
|
||||
Ed Costello <epc@epcostello.com>
|
||||
Ed Morley <501702+edmorley@users.noreply.github.com>
|
||||
Elango Sivanandam <elango.siva@docker.com>
|
||||
Eli Uriegas <eli.uriegas@docker.com>
|
||||
Eli Uriegas <seemethere101@gmail.com>
|
||||
Elias Faxö <elias.faxo@tre.se>
|
||||
Elliot Luo <956941328@qq.com>
|
||||
Eric Bode <eric.bode@foundries.io>
|
||||
Eric Curtin <ericcurtin17@gmail.com>
|
||||
Eric Engestrom <eric@engestrom.ch>
|
||||
Eric G. Noriega <enoriega@vizuri.com>
|
||||
@ -270,7 +254,6 @@ Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||
Evan Allrich <evan@unguku.com>
|
||||
Evan Hazlett <ejhazlett@gmail.com>
|
||||
Evan Krall <krall@yelp.com>
|
||||
Evan Lezar <elezar@nvidia.com>
|
||||
Evelyn Xu <evelynhsu21@gmail.com>
|
||||
Everett Toews <everett.toews@rackspace.com>
|
||||
Fabio Falci <fabiofalci@gmail.com>
|
||||
@ -292,7 +275,6 @@ Frederik Nordahl Jul Sabroe <frederikns@gmail.com>
|
||||
Frieder Bluemle <frieder.bluemle@gmail.com>
|
||||
Gabriel Gore <gabgore@cisco.com>
|
||||
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
|
||||
Gabriela Georgieva <gabriela.georgieva@docker.com>
|
||||
Gaetan de Villele <gdevillele@gmail.com>
|
||||
Gang Qiao <qiaohai8866@gmail.com>
|
||||
Gary Schaetz <gary@schaetzkc.com>
|
||||
@ -347,12 +329,9 @@ Ivan Grund <ivan.grund@gmail.com>
|
||||
Ivan Markin <sw@nogoegst.net>
|
||||
Jacob Atzen <jacob@jacobatzen.dk>
|
||||
Jacob Tomlinson <jacob@tom.linson.uk>
|
||||
Jacopo Rigoli <rigoli.jacopo@gmail.com>
|
||||
Jaivish Kothari <janonymous.codevulture@gmail.com>
|
||||
Jake Lambert <jake.lambert@volusion.com>
|
||||
Jake Sanders <jsand@google.com>
|
||||
Jake Stokes <contactjake@developerjake.com>
|
||||
Jakub Panek <me@panekj.dev>
|
||||
James Nesbitt <james.nesbitt@wunderkraut.com>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
Jamie Hannaford <jamie@limetree.org>
|
||||
@ -429,12 +408,10 @@ Josh Chorlton <jchorlton@gmail.com>
|
||||
Josh Hawn <josh.hawn@docker.com>
|
||||
Josh Horwitz <horwitz@addthis.com>
|
||||
Josh Soref <jsoref@gmail.com>
|
||||
Julian <gitea+julian@ic.thejulian.uk>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Julien Kassar <github@kassisol.com>
|
||||
Julien Maitrehenry <julien.maitrehenry@me.com>
|
||||
Justas Brazauskas <brazauskasjustas@gmail.com>
|
||||
Justin Chadwell <me@jedevc.com>
|
||||
Justin Cormack <justin.cormack@docker.com>
|
||||
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||
Justyn Temme <justyntemme@gmail.com>
|
||||
@ -457,7 +434,7 @@ Kelton Bassingthwaite <KeltonBassingthwaite@gmail.com>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||
Kevin Alvarez <github@crazymax.dev>
|
||||
Kevin Alvarez <crazy-max@users.noreply.github.com>
|
||||
Kevin Burke <kev@inburke.com>
|
||||
Kevin Feyrer <kevin.feyrer@btinternet.com>
|
||||
Kevin Kern <kaiwentan@harmonycloud.cn>
|
||||
@ -477,7 +454,6 @@ Kyle Mitofsky <Kylemit@gmail.com>
|
||||
Lachlan Cooper <lachlancooper@gmail.com>
|
||||
Lai Jiangshan <jiangshanlai@gmail.com>
|
||||
Lars Kellogg-Stedman <lars@redhat.com>
|
||||
Laura Brehm <laurabrehm@hey.com>
|
||||
Laura Frank <ljfrank@gmail.com>
|
||||
Laurent Erignoux <lerignoux@gmail.com>
|
||||
Lee Gaines <eightlimbed@gmail.com>
|
||||
@ -504,7 +480,6 @@ Louis Opter <kalessin@kalessin.fr>
|
||||
Luca Favatella <luca.favatella@erlang-solutions.com>
|
||||
Luca Marturana <lucamarturana@gmail.com>
|
||||
Lucas Chan <lucas-github@lucaschan.com>
|
||||
Luis Henrique Mulinari <luis.mulinari@gmail.com>
|
||||
Luka Hartwig <mail@lukahartwig.de>
|
||||
Lukas Heeren <lukas-heeren@hotmail.com>
|
||||
Lukasz Zajaczkowski <Lukasz.Zajaczkowski@ts.fujitsu.com>
|
||||
@ -523,7 +498,6 @@ mapk0y <mapk0y@gmail.com>
|
||||
Marc Bihlmaier <marc.bihlmaier@reddoxx.com>
|
||||
Marc Cornellà <hello@mcornella.com>
|
||||
Marco Mariani <marco.mariani@alterway.fr>
|
||||
Marco Spiess <marco.spiess@hotmail.de>
|
||||
Marco Vedovati <mvedovati@suse.com>
|
||||
Marcus Martins <marcus@docker.com>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
@ -548,7 +522,6 @@ Max Shytikov <mshytikov@gmail.com>
|
||||
Maxime Petazzoni <max@signalfuse.com>
|
||||
Maximillian Fan Xavier <maximillianfx@gmail.com>
|
||||
Mei ChunTao <mei.chuntao@zte.com.cn>
|
||||
Melroy van den Berg <melroy@melroy.org>
|
||||
Metal <2466052+tedhexaflow@users.noreply.github.com>
|
||||
Micah Zoltu <micah@newrelic.com>
|
||||
Michael A. Smith <michael@smith-li.com>
|
||||
@ -620,7 +593,6 @@ Nishant Totla <nishanttotla@gmail.com>
|
||||
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
|
||||
Noah Treuhaft <noah.treuhaft@docker.com>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
Oded Arbel <oded@geek.co.il>
|
||||
Odin Ugedal <odin@ugedal.com>
|
||||
ohmystack <jun.jiang02@ele.me>
|
||||
OKA Naoya <git@okanaoya.com>
|
||||
@ -632,14 +604,12 @@ Otto Kekäläinen <otto@seravo.fi>
|
||||
Ovidio Mallo <ovidio.mallo@gmail.com>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Patrick Böänziger <patrick.baenziger@bsi-software.com>
|
||||
Patrick Daigle <114765035+pdaig@users.noreply.github.com>
|
||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||
Patrick Lang <plang@microsoft.com>
|
||||
Paul <paul9869@gmail.com>
|
||||
Paul Kehrer <paul.l.kehrer@gmail.com>
|
||||
Paul Lietar <paul@lietar.net>
|
||||
Paul Mulders <justinkb@gmail.com>
|
||||
Paul Seyfert <pseyfert.mathphys@gmail.com>
|
||||
Paul Weaver <pauweave@cisco.com>
|
||||
Pavel Pospisil <pospispa@gmail.com>
|
||||
Paweł Gronowski <pawel.gronowski@docker.com>
|
||||
@ -719,7 +689,6 @@ Sandro Jäckel <sandro.jaeckel@gmail.com>
|
||||
Santhosh Manohar <santhosh@docker.com>
|
||||
Sargun Dhillon <sargun@netflix.com>
|
||||
Saswat Bhattacharya <sas.saswat@gmail.com>
|
||||
Saurabh Kumar <saurabhkumar0184@gmail.com>
|
||||
Scott Brenner <scott@scottbrenner.me>
|
||||
Scott Collier <emailscottcollier@gmail.com>
|
||||
Sean Christopherson <sean.j.christopherson@intel.com>
|
||||
@ -819,7 +788,6 @@ uhayate <uhayate.gong@daocloud.io>
|
||||
Ulrich Bareth <ulrich.bareth@gmail.com>
|
||||
Ulysses Souza <ulysses.souza@docker.com>
|
||||
Umesh Yadav <umesh4257@gmail.com>
|
||||
Vaclav Struhar <struharv@gmail.com>
|
||||
Valentin Lorentz <progval+git@progval.net>
|
||||
Vardan Pogosian <vardan.pogosyan@gmail.com>
|
||||
Venkateswara Reddy Bukkasamudram <bukkasamudram@outlook.com>
|
||||
@ -827,7 +795,6 @@ Veres Lajos <vlajos@gmail.com>
|
||||
Victor Vieux <victor.vieux@docker.com>
|
||||
Victoria Bialas <victoria.bialas@docker.com>
|
||||
Viktor Stanchev <me@viktorstanchev.com>
|
||||
Ville Skyttä <ville.skytta@iki.fi>
|
||||
Vimal Raghubir <vraghubir0418@gmail.com>
|
||||
Vincent Batts <vbatts@redhat.com>
|
||||
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
||||
@ -864,7 +831,6 @@ Yong Tang <yong.tang.github@outlook.com>
|
||||
Yosef Fertel <yfertel@gmail.com>
|
||||
Yu Peng <yu.peng36@zte.com.cn>
|
||||
Yuan Sun <sunyuan3@huawei.com>
|
||||
Yucheng Wu <wyc123wyc@gmail.com>
|
||||
Yue Zhang <zy675793960@yeah.net>
|
||||
Yunxiang Huang <hyxqshk@vip.qq.com>
|
||||
Zachary Romero <zacromero3@gmail.com>
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome! We have a contributor's guide that explains
|
||||
[setting up a Docker development environment and the contribution
|
||||
process](https://docs.docker.com/opensource/project/who-written-for/).
|
||||
|
||||
This page contains information about reporting issues as well as some tips and
|
||||
guidelines useful to experienced open source contributors. Finally, make sure
|
||||
you read our [community guidelines](#docker-community-guidelines) before you
|
||||
@ -188,7 +192,7 @@ For more details, see the [MAINTAINERS](MAINTAINERS) page.
|
||||
The sign-off is a simple line at the end of the explanation for the patch. Your
|
||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
||||
it on as an open-source patch. The rules are pretty simple: if you can certify
|
||||
the below (from [developercertificate.org](https://developercertificate.org):
|
||||
the below (from [developercertificate.org](http://developercertificate.org/)):
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
@ -332,8 +336,9 @@ The rules:
|
||||
1. All code should be formatted with `gofumpt` (preferred) or `gofmt -s`.
|
||||
2. All code should pass the default levels of
|
||||
[`golint`](https://github.com/golang/lint).
|
||||
3. All code should follow the guidelines covered in [Effective Go](https://go.dev/doc/effective_go)
|
||||
and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
3. All code should follow the guidelines covered in [Effective
|
||||
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
4. Comment the code. Tell us the why, the history and the context.
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
@ -355,6 +360,6 @@ The rules:
|
||||
guidelines. Since you've read all the rules, you now know that.
|
||||
|
||||
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
||||
reading through [Effective Go](https://go.dev/doc/effective_go). The
|
||||
[Go Blog](https://go.dev/blog/) is also a great resource. Drinking the
|
||||
reading through [Effective Go](https://golang.org/doc/effective_go.html). The
|
||||
[Go Blog](https://blog.golang.org) is also a great resource. Drinking the
|
||||
kool-aid is a lot easier than going thirsty.
|
||||
|
||||
67
Dockerfile
67
Dockerfile
@ -1,21 +1,17 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.18
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.21.5
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GO_VERSION=1.20.4
|
||||
ARG ALPINE_VERSION=3.16
|
||||
ARG XX_VERSION=1.1.1
|
||||
ARG GOVERSIONINFO_VERSION=v1.3.0
|
||||
ARG GOTESTSUM_VERSION=v1.10.0
|
||||
ARG BUILDX_VERSION=0.12.0
|
||||
ARG COMPOSE_VERSION=v2.22.0
|
||||
ARG GOTESTSUM_VERSION=v1.8.2
|
||||
ARG BUILDX_VERSION=0.10.4
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-base-alpine
|
||||
ENV GOTOOLCHAIN=local
|
||||
COPY --link --from=xx / /
|
||||
COPY --from=xx / /
|
||||
RUN apk add --no-cache bash clang lld llvm file git
|
||||
WORKDIR /go/src/github.com/docker/cli
|
||||
|
||||
@ -24,27 +20,33 @@ ARG TARGETPLATFORM
|
||||
# gcc is installed for libgcc only
|
||||
RUN xx-apk add --no-cache musl-dev gcc
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO} AS build-base-debian
|
||||
ENV GOTOOLCHAIN=local
|
||||
COPY --link --from=xx / /
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bullseye AS build-base-bullseye
|
||||
COPY --from=xx / /
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y bash clang lld llvm file
|
||||
WORKDIR /go/src/github.com/docker/cli
|
||||
|
||||
FROM build-base-debian AS build-debian
|
||||
FROM build-base-bullseye AS build-bullseye
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-apt-get install --no-install-recommends -y libc6-dev libgcc-12-dev pkgconf
|
||||
RUN xx-apt-get install --no-install-recommends -y libc6-dev libgcc-10-dev
|
||||
# workaround for issue with llvm 11 for darwin/amd64 platform:
|
||||
# # github.com/docker/cli/cmd/docker
|
||||
# /usr/local/go/pkg/tool/linux_amd64/link: /usr/local/go/pkg/tool/linux_amd64/link: running strip failed: exit status 1
|
||||
# llvm-strip: error: unsupported load command (cmd=0x5)
|
||||
# more info: https://github.com/docker/cli/pull/3717
|
||||
# FIXME: remove once llvm 12 available on debian
|
||||
RUN [ "$TARGETPLATFORM" != "darwin/amd64" ] || ln -sfnT /bin/true /usr/bin/llvm-strip
|
||||
|
||||
FROM build-base-${BASE_VARIANT} AS goversioninfo
|
||||
ARG GOVERSIONINFO_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/out GO111MODULE=on CGO_ENABLED=0 go install "github.com/josephspurrier/goversioninfo/cmd/goversioninfo@${GOVERSIONINFO_VERSION}"
|
||||
GOBIN=/out GO111MODULE=on go install "github.com/josephspurrier/goversioninfo/cmd/goversioninfo@${GOVERSIONINFO_VERSION}"
|
||||
|
||||
FROM build-base-${BASE_VARIANT} AS gotestsum
|
||||
ARG GOTESTSUM_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/out GO111MODULE=on CGO_ENABLED=0 go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
|
||||
GOBIN=/out GO111MODULE=on go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
|
||||
&& /out/gotestsum --version
|
||||
|
||||
FROM build-${BASE_VARIANT} AS build
|
||||
@ -60,7 +62,9 @@ ARG CGO_ENABLED
|
||||
ARG VERSION
|
||||
# PACKAGER_NAME sets the company that produced the windows binary
|
||||
ARG PACKAGER_NAME
|
||||
COPY --link --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo
|
||||
COPY --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo
|
||||
# in bullseye arm64 target does not link with lld so configure it to use ld instead
|
||||
RUN [ ! -f /etc/alpine-release ] && xx-info is-cross && [ "$(xx-info arch)" = "arm64" ] && XX_CC_PREFER_LINKER=ld xx-clang --setup-target-triple || true
|
||||
RUN --mount=type=bind,target=.,ro \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=from=dockercore/golang-cross:xx-sdk-extras,target=/xx-sdk,src=/xx-sdk \
|
||||
@ -72,7 +76,7 @@ RUN --mount=type=bind,target=.,ro \
|
||||
xx-verify $([ "$GO_LINKMODE" = "static" ] && echo "--static") /out/docker
|
||||
|
||||
FROM build-${BASE_VARIANT} AS test
|
||||
COPY --link --from=gotestsum /out/gotestsum /usr/bin/gotestsum
|
||||
COPY --from=gotestsum /out/gotestsum /usr/bin/gotestsum
|
||||
ENV GO111MODULE=auto
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
@ -94,31 +98,32 @@ RUN --mount=ro --mount=type=cache,target=/root/.cache \
|
||||
TARGET=/out ./scripts/build/plugins e2e/cli-plugins/plugins/*
|
||||
|
||||
FROM build-base-alpine AS e2e-base-alpine
|
||||
RUN apk add --no-cache build-base curl openssl openssh-client
|
||||
RUN apk add --no-cache build-base curl docker-compose openssl openssh-client
|
||||
|
||||
FROM build-base-debian AS e2e-base-debian
|
||||
FROM build-base-bullseye AS e2e-base-bullseye
|
||||
RUN apt-get update && apt-get install -y build-essential curl openssl openssh-client
|
||||
ARG COMPOSE_VERSION=1.29.2
|
||||
RUN curl -fsSL https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose && \
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
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 e2e-base-${BASE_VARIANT} AS e2e
|
||||
ARG NOTARY_VERSION=v0.6.1
|
||||
ADD --chmod=0755 https://github.com/theupdateframework/notary/releases/download/${NOTARY_VERSION}/notary-Linux-amd64 /usr/local/bin/notary
|
||||
COPY --link e2e/testdata/notary/root-ca.cert /usr/share/ca-certificates/notary.cert
|
||||
COPY e2e/testdata/notary/root-ca.cert /usr/share/ca-certificates/notary.cert
|
||||
RUN echo 'notary.cert' >> /etc/ca-certificates.conf && update-ca-certificates
|
||||
COPY --link --from=gotestsum /out/gotestsum /usr/bin/gotestsum
|
||||
COPY --link --from=build /out ./build/
|
||||
COPY --link --from=build-plugins /out ./build/
|
||||
COPY --link --from=buildx /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||
COPY --link --from=compose /docker-compose /usr/libexec/docker/cli-plugins/docker-compose
|
||||
COPY --link . .
|
||||
COPY --from=gotestsum /out/gotestsum /usr/bin/gotestsum
|
||||
COPY --from=build /out ./build/
|
||||
COPY --from=build-plugins /out ./build/
|
||||
COPY --from=buildx /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||
COPY . .
|
||||
ENV DOCKER_BUILDKIT=1
|
||||
ENV PATH=/go/src/github.com/docker/cli/build:$PATH
|
||||
CMD ./scripts/test/e2e/entry
|
||||
|
||||
FROM build-base-${BASE_VARIANT} AS dev
|
||||
COPY --link . .
|
||||
COPY . .
|
||||
|
||||
FROM scratch AS plugins
|
||||
COPY --from=build-plugins /out .
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
people = [
|
||||
"albers",
|
||||
"cpuguy83",
|
||||
"ndeloof",
|
||||
"rumpl",
|
||||
"silvin-lubecki",
|
||||
"stevvooe",
|
||||
@ -97,6 +98,11 @@
|
||||
Email = "dnephin@gmail.com"
|
||||
GitHub = "dnephin"
|
||||
|
||||
[people.ndeloof]
|
||||
Name = "Nicolas De Loof"
|
||||
Email = "nicolas.deloof@gmail.com"
|
||||
GitHub = "ndeloof"
|
||||
|
||||
[people.neersighted]
|
||||
Name = "Bjorn Neergaard"
|
||||
Email = "bneergaard@mirantis.com"
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
|
||||
## About
|
||||
|
||||
This repository is the home of the Docker CLI.
|
||||
This repository is the home of the cli used in the Docker CE and
|
||||
Docker EE products.
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package manager
|
||||
|
||||
import "os/exec"
|
||||
import (
|
||||
exec "golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
// Candidate represents a possible plugin candidate, for mocking purposes
|
||||
type Candidate interface {
|
||||
|
||||
@ -75,14 +75,13 @@ func TestValidateCandidate(t *testing.T) {
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p, err := newPlugin(tc.c, fakeroot.Commands())
|
||||
switch {
|
||||
case tc.err != "":
|
||||
if tc.err != "" {
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
case tc.invalid != "":
|
||||
} else if tc.invalid != "" {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, cmp.ErrorType(p.Err, reflect.TypeOf(&pluginError{})))
|
||||
assert.ErrorContains(t, p.Err, tc.invalid)
|
||||
default:
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, NamePrefix+p.Name, goodPluginName)
|
||||
assert.Equal(t, p.SchemaVersion, "0.1.0")
|
||||
|
||||
@ -43,6 +43,6 @@ func wrapAsPluginError(err error, msg string) error {
|
||||
|
||||
// NewPluginError creates a new pluginError, analogous to
|
||||
// errors.Errorf.
|
||||
func NewPluginError(msg string, args ...any) error {
|
||||
func NewPluginError(msg string, args ...interface{}) error {
|
||||
return &pluginError{cause: errors.Errorf(msg, args...)}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package manager
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -14,6 +13,7 @@ import (
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
exec "golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
// ReexecEnvvar is the name of an ennvar which is set to the command
|
||||
|
||||
@ -46,7 +46,7 @@ func TestListPluginCandidates(t *testing.T) {
|
||||
)
|
||||
defer dir.Remove()
|
||||
|
||||
dirs := make([]string, 0, 6)
|
||||
var dirs []string
|
||||
for _, d := range []string{"plugins1", "nonexistent", "plugins2", "plugins3", "plugins4", "plugins5"} {
|
||||
dirs = append(dirs, dir.Join(d))
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package manager
|
||||
|
||||
|
||||
@ -22,4 +22,8 @@ type Metadata struct {
|
||||
ShortDescription string `json:",omitempty"`
|
||||
// URL is a pointer to the plugin's homepage.
|
||||
URL string `json:",omitempty"`
|
||||
// Experimental specifies whether the plugin is experimental.
|
||||
//
|
||||
// Deprecated: experimental features are now always enabled in the CLI
|
||||
Experimental bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package manager
|
||||
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
@ -18,11 +14,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CLIPluginSocketEnvKey is used to pass the plugin being
|
||||
// executed the abstract socket name it should listen on to know
|
||||
// when the CLI has exited.
|
||||
const CLIPluginSocketEnvKey = "DOCKER_CLI_PLUGIN_SOCKET"
|
||||
|
||||
// PersistentPreRunE must be called by any plugin command (or
|
||||
// subcommand) which uses the cobra `PersistentPreRun*` hook. Plugins
|
||||
// which do not make use of `PersistentPreRun*` do not need to call
|
||||
@ -33,56 +24,14 @@ const CLIPluginSocketEnvKey = "DOCKER_CLI_PLUGIN_SOCKET"
|
||||
// called.
|
||||
var PersistentPreRunE func(*cobra.Command, []string) error
|
||||
|
||||
// closeOnCLISocketClose connects to the socket specified
|
||||
// by the DOCKER_CLI_PLUGIN_SOCKET env var, if present, and attempts
|
||||
// to read from it until it receives an EOF, which signals that
|
||||
// the CLI is going to exit and the plugin should also exit.
|
||||
func closeOnCLISocketClose(cancel func()) {
|
||||
socketAddr, ok := os.LookupEnv(CLIPluginSocketEnvKey)
|
||||
if !ok {
|
||||
// if a plugin compiled against a more recent version of docker/cli
|
||||
// is executed by an older CLI binary, ignore missing environment
|
||||
// variable and behave as usual
|
||||
return
|
||||
}
|
||||
addr, err := net.ResolveUnixAddr("unix", socketAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cliCloseConn, err := net.DialUnix("unix", nil, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
b := make([]byte, 1)
|
||||
for {
|
||||
_, err := cliCloseConn.Read(b)
|
||||
if errors.Is(err, io.EOF) {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// RunPlugin executes the specified plugin command
|
||||
func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) error {
|
||||
tcmd := newPluginCommand(dockerCli, plugin, meta)
|
||||
|
||||
var persistentPreRunOnce sync.Once
|
||||
PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
|
||||
PersistentPreRunE = func(_ *cobra.Command, _ []string) error {
|
||||
var err error
|
||||
persistentPreRunOnce.Do(func() {
|
||||
cmdContext := cmd.Context()
|
||||
// TODO: revisit and make sure this check makes sense
|
||||
// see: https://github.com/docker/cli/pull/4599#discussion_r1422487271
|
||||
if cmdContext == nil {
|
||||
cmdContext = context.TODO()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(cmdContext)
|
||||
cmd.SetContext(ctx)
|
||||
closeOnCLISocketClose(cancel)
|
||||
|
||||
var opts []command.InitializeOpt
|
||||
if os.Getenv("DOCKER_CLI_PLUGIN_USE_DIAL_STDIO") != "" {
|
||||
opts = append(opts, withPluginClientConn(plugin.Name()))
|
||||
@ -182,7 +131,7 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
|
||||
DisableDescriptions: true,
|
||||
},
|
||||
}
|
||||
opts, _ := cli.SetupPluginRootCommand(cmd)
|
||||
opts, flags := cli.SetupPluginRootCommand(cmd)
|
||||
|
||||
cmd.SetIn(dockerCli.In())
|
||||
cmd.SetOut(dockerCli.Out())
|
||||
@ -195,7 +144,7 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
|
||||
|
||||
cli.DisableFlagsInUseLine(cmd)
|
||||
|
||||
return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags())
|
||||
return cli.NewTopLevelCommand(cmd, dockerCli, opts, flags)
|
||||
}
|
||||
|
||||
func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.Command {
|
||||
|
||||
16
cli/cobra.go
16
cli/cobra.go
@ -9,6 +9,7 @@ import (
|
||||
|
||||
pluginmanager "github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/docker/docker/registry"
|
||||
@ -22,9 +23,12 @@ import (
|
||||
|
||||
// setupCommonRootCommand contains the setup common to
|
||||
// SetupRootCommand and SetupPluginRootCommand.
|
||||
func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *cobra.Command) {
|
||||
func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet, *cobra.Command) {
|
||||
opts := cliflags.NewClientOptions()
|
||||
opts.InstallFlags(rootCmd.Flags())
|
||||
flags := rootCmd.Flags()
|
||||
|
||||
flags.StringVar(&opts.ConfigDir, "config", config.Dir(), "Location of client config files")
|
||||
opts.InstallFlags(flags)
|
||||
|
||||
cobra.AddTemplateFunc("add", func(a, b int) int { return a + b })
|
||||
cobra.AddTemplateFunc("hasAliases", hasAliases)
|
||||
@ -69,20 +73,20 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *c
|
||||
}
|
||||
}
|
||||
|
||||
return opts, helpCommand
|
||||
return opts, flags, helpCommand
|
||||
}
|
||||
|
||||
// SetupRootCommand sets default usage, help, and error handling for the
|
||||
// root command.
|
||||
func SetupRootCommand(rootCmd *cobra.Command) (opts *cliflags.ClientOptions, helpCmd *cobra.Command) {
|
||||
func SetupRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet, *cobra.Command) {
|
||||
rootCmd.SetVersionTemplate("Docker version {{.Version}}\n")
|
||||
return setupCommonRootCommand(rootCmd)
|
||||
}
|
||||
|
||||
// SetupPluginRootCommand sets default usage, help and error handling for a plugin root command.
|
||||
func SetupPluginRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet) {
|
||||
opts, _ := setupCommonRootCommand(rootCmd)
|
||||
return opts, rootCmd.Flags()
|
||||
opts, flags, _ := setupCommonRootCommand(rootCmd)
|
||||
return opts, flags
|
||||
}
|
||||
|
||||
// FlagErrorFunc prints an error message which matches the format of the
|
||||
|
||||
@ -3,34 +3,34 @@ package checkpoint
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
checkpointCreateFunc func(container string, options checkpoint.CreateOptions) error
|
||||
checkpointDeleteFunc func(container string, options checkpoint.DeleteOptions) error
|
||||
checkpointListFunc func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error)
|
||||
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
|
||||
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
|
||||
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) CheckpointCreate(_ context.Context, container string, options checkpoint.CreateOptions) error {
|
||||
func (cli *fakeClient) CheckpointCreate(_ context.Context, container string, options types.CheckpointCreateOptions) error {
|
||||
if cli.checkpointCreateFunc != nil {
|
||||
return cli.checkpointCreateFunc(container, options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) CheckpointDelete(_ context.Context, container string, options checkpoint.DeleteOptions) error {
|
||||
func (cli *fakeClient) CheckpointDelete(_ context.Context, container string, options types.CheckpointDeleteOptions) error {
|
||||
if cli.checkpointDeleteFunc != nil {
|
||||
return cli.checkpointDeleteFunc(container, options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) CheckpointList(_ context.Context, container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
|
||||
func (cli *fakeClient) CheckpointList(_ context.Context, container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
|
||||
if cli.checkpointListFunc != nil {
|
||||
return cli.checkpointListFunc(container, options)
|
||||
}
|
||||
return []checkpoint.Summary{}, nil
|
||||
return []types.Checkpoint{}, nil
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -41,11 +41,15 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runCreate(dockerCli command.Cli, opts createOptions) error {
|
||||
err := dockerCli.Client().CheckpointCreate(context.Background(), opts.container, checkpoint.CreateOptions{
|
||||
client := dockerCli.Client()
|
||||
|
||||
checkpointOpts := types.CheckpointCreateOptions{
|
||||
CheckpointID: opts.checkpoint,
|
||||
CheckpointDir: opts.checkpointDir,
|
||||
Exit: !opts.leaveRunning,
|
||||
})
|
||||
}
|
||||
|
||||
err := client.CheckpointCreate(context.Background(), opts.container, checkpointOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -15,7 +15,7 @@ import (
|
||||
func TestCheckpointCreateErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
checkpointCreateFunc func(container string, options checkpoint.CreateOptions) error
|
||||
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -28,7 +28,7 @@ func TestCheckpointCreateErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{"foo", "bar"},
|
||||
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
|
||||
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
|
||||
return errors.Errorf("error creating checkpoint for container foo")
|
||||
},
|
||||
expectedError: "error creating checkpoint for container foo",
|
||||
@ -50,7 +50,7 @@ func TestCheckpointCreateWithOptions(t *testing.T) {
|
||||
var containerID, checkpointID, checkpointDir string
|
||||
var exit bool
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
|
||||
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
|
||||
containerID = container
|
||||
checkpointID = options.CheckpointID
|
||||
checkpointDir = options.CheckpointDir
|
||||
@ -59,14 +59,14 @@ func TestCheckpointCreateWithOptions(t *testing.T) {
|
||||
},
|
||||
})
|
||||
cmd := newCreateCommand(cli)
|
||||
cp := "checkpoint-bar"
|
||||
cmd.SetArgs([]string{"container-foo", cp})
|
||||
checkpoint := "checkpoint-bar"
|
||||
cmd.SetArgs([]string{"container-foo", checkpoint})
|
||||
cmd.Flags().Set("leave-running", "true")
|
||||
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Check(t, is.Equal("container-foo", containerID))
|
||||
assert.Check(t, is.Equal(cp, checkpointID))
|
||||
assert.Check(t, is.Equal(checkpoint, checkpointID))
|
||||
assert.Check(t, is.Equal("/dir/foo", checkpointDir))
|
||||
assert.Check(t, is.Equal(false, exit))
|
||||
assert.Check(t, is.Equal(cp, strings.TrimSpace(cli.OutBuffer().String())))
|
||||
assert.Check(t, is.Equal(checkpoint, strings.TrimSpace(cli.OutBuffer().String())))
|
||||
}
|
||||
|
||||
@ -2,27 +2,29 @@ package checkpoint
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCheckpointFormat = "table {{.Name}}"
|
||||
checkpointNameHeader = "CHECKPOINT NAME"
|
||||
|
||||
checkpointNameHeader = "CHECKPOINT NAME"
|
||||
)
|
||||
|
||||
// NewFormat returns a format for use with a checkpoint Context
|
||||
func NewFormat(source string) formatter.Format {
|
||||
if source == formatter.TableFormatKey {
|
||||
switch source {
|
||||
case formatter.TableFormatKey:
|
||||
return defaultCheckpointFormat
|
||||
}
|
||||
return formatter.Format(source)
|
||||
}
|
||||
|
||||
// FormatWrite writes formatted checkpoints using the Context
|
||||
func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error {
|
||||
func FormatWrite(ctx formatter.Context, checkpoints []types.Checkpoint) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, cp := range checkpoints {
|
||||
if err := format(&checkpointContext{c: cp}); err != nil {
|
||||
for _, checkpoint := range checkpoints {
|
||||
if err := format(&checkpointContext{c: checkpoint}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -33,7 +35,7 @@ func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error
|
||||
|
||||
type checkpointContext struct {
|
||||
formatter.HeaderContext
|
||||
c checkpoint.Summary
|
||||
c types.Checkpoint
|
||||
}
|
||||
|
||||
func newCheckpointContext() *checkpointContext {
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@ -38,14 +38,15 @@ checkpoint-3:
|
||||
},
|
||||
}
|
||||
|
||||
checkpoints := []types.Checkpoint{
|
||||
{Name: "checkpoint-1"},
|
||||
{Name: "checkpoint-2"},
|
||||
{Name: "checkpoint-3"},
|
||||
}
|
||||
for _, testcase := range cases {
|
||||
out := bytes.NewBufferString("")
|
||||
testcase.context.Output = out
|
||||
err := FormatWrite(testcase.context, []checkpoint.Summary{
|
||||
{Name: "checkpoint-1"},
|
||||
{Name: "checkpoint-2"},
|
||||
{Name: "checkpoint-3"},
|
||||
})
|
||||
err := FormatWrite(testcase.context, checkpoints)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, out.String(), testcase.expected)
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -36,9 +36,13 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runList(dockerCli command.Cli, container string, opts listOptions) error {
|
||||
checkpoints, err := dockerCli.Client().CheckpointList(context.Background(), container, checkpoint.ListOptions{
|
||||
client := dockerCli.Client()
|
||||
|
||||
listOpts := types.CheckpointListOptions{
|
||||
CheckpointDir: opts.checkpointDir,
|
||||
})
|
||||
}
|
||||
|
||||
checkpoints, err := client.CheckpointList(context.Background(), container, listOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -15,7 +15,7 @@ import (
|
||||
func TestCheckpointListErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
checkpointListFunc func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error)
|
||||
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -28,8 +28,8 @@ func TestCheckpointListErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{"foo"},
|
||||
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
|
||||
return []checkpoint.Summary{}, errors.Errorf("error getting checkpoints for container foo")
|
||||
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
|
||||
return []types.Checkpoint{}, errors.Errorf("error getting checkpoints for container foo")
|
||||
},
|
||||
expectedError: "error getting checkpoints for container foo",
|
||||
},
|
||||
@ -49,10 +49,10 @@ func TestCheckpointListErrors(t *testing.T) {
|
||||
func TestCheckpointListWithOptions(t *testing.T) {
|
||||
var containerID, checkpointDir string
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
|
||||
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
|
||||
containerID = container
|
||||
checkpointDir = options.CheckpointDir
|
||||
return []checkpoint.Summary{
|
||||
return []types.Checkpoint{
|
||||
{Name: "checkpoint-foo"},
|
||||
}, nil
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -32,9 +32,13 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(dockerCli command.Cli, container string, checkpointID string, opts removeOptions) error {
|
||||
return dockerCli.Client().CheckpointDelete(context.Background(), container, checkpoint.DeleteOptions{
|
||||
CheckpointID: checkpointID,
|
||||
func runRemove(dockerCli command.Cli, container string, checkpoint string, opts removeOptions) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
removeOpts := types.CheckpointDeleteOptions{
|
||||
CheckpointID: checkpoint,
|
||||
CheckpointDir: opts.checkpointDir,
|
||||
})
|
||||
}
|
||||
|
||||
return client.CheckpointDelete(context.Background(), container, removeOpts)
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
func TestCheckpointRemoveErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
checkpointDeleteFunc func(container string, options checkpoint.DeleteOptions) error
|
||||
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -27,7 +27,7 @@ func TestCheckpointRemoveErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{"foo", "bar"},
|
||||
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
|
||||
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
|
||||
return errors.Errorf("error deleting checkpoint")
|
||||
},
|
||||
expectedError: "error deleting checkpoint",
|
||||
@ -48,7 +48,7 @@ func TestCheckpointRemoveErrors(t *testing.T) {
|
||||
func TestCheckpointRemoveWithOptions(t *testing.T) {
|
||||
var containerID, checkpointID, checkpointDir string
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
|
||||
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
|
||||
containerID = container
|
||||
checkpointID = options.CheckpointID
|
||||
checkpointDir = options.CheckpointDir
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -189,7 +190,7 @@ func (cli *DockerCli) ManifestStore() manifeststore.Store {
|
||||
// registry
|
||||
func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient {
|
||||
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
|
||||
return ResolveAuthConfig(cli.ConfigFile(), index)
|
||||
return ResolveAuthConfig(ctx, cli, index)
|
||||
}
|
||||
return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure)
|
||||
}
|
||||
@ -260,15 +261,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
|
||||
}
|
||||
|
||||
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||
opts, err := ep.ClientOpts()
|
||||
clientOpts, err := ep.ClientOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(configFile.HTTPHeaders) > 0 {
|
||||
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
|
||||
customHeaders := make(map[string]string, len(configFile.HTTPHeaders))
|
||||
for k, v := range configFile.HTTPHeaders {
|
||||
customHeaders[k] = v
|
||||
}
|
||||
opts = append(opts, client.WithUserAgent(UserAgent()))
|
||||
return client.NewClientWithOpts(opts...)
|
||||
customHeaders["User-Agent"] = UserAgent()
|
||||
clientOpts = append(clientOpts, client.WithHTTPHeaders(customHeaders))
|
||||
return client.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
|
||||
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
|
||||
@ -324,8 +327,13 @@ func (cli *DockerCli) getInitTimeout() time.Duration {
|
||||
|
||||
func (cli *DockerCli) initializeFromClient() {
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(ctx, cli.getInitTimeout())
|
||||
defer cancel()
|
||||
if !strings.HasPrefix(cli.dockerEndpoint.Host, "ssh://") {
|
||||
// @FIXME context.WithTimeout doesn't work with connhelper / ssh connections
|
||||
// time="2020-04-10T10:16:26Z" level=warning msg="commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, cli.getInitTimeout())
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
ping, err := cli.client.Ping(ctx)
|
||||
if err != nil {
|
||||
@ -362,7 +370,7 @@ func (cli *DockerCli) ContextStore() store.Store {
|
||||
// order of preference:
|
||||
//
|
||||
// 1. The "--context" command-line option.
|
||||
// 2. The "DOCKER_CONTEXT" environment variable ([EnvOverrideContext]).
|
||||
// 2. The "DOCKER_CONTEXT" environment variable.
|
||||
// 3. The current context as configured through the in "currentContext"
|
||||
// field in the CLI configuration file ("~/.docker/config.json").
|
||||
// 4. If no context is configured, use the "default" context.
|
||||
@ -373,7 +381,7 @@ func (cli *DockerCli) ContextStore() store.Store {
|
||||
// the "default" context is used if:
|
||||
//
|
||||
// - The "--host" option is set
|
||||
// - The "DOCKER_HOST" ([client.EnvOverrideHost]) environment variable is set
|
||||
// - The "DOCKER_HOST" ([DefaultContextName]) environment variable is set
|
||||
// to a non-empty value.
|
||||
//
|
||||
// In these cases, the default context is used, which uses the host as
|
||||
@ -394,7 +402,7 @@ func (cli *DockerCli) CurrentContext() string {
|
||||
// occur when trying to use it.
|
||||
//
|
||||
// Refer to [DockerCli.CurrentContext] above for further details.
|
||||
func resolveContextName(opts *cliflags.ClientOptions, cfg *configfile.ConfigFile) string {
|
||||
func resolveContextName(opts *cliflags.ClientOptions, config *configfile.ConfigFile) string {
|
||||
if opts != nil && opts.Context != "" {
|
||||
return opts.Context
|
||||
}
|
||||
@ -404,12 +412,12 @@ func resolveContextName(opts *cliflags.ClientOptions, cfg *configfile.ConfigFile
|
||||
if os.Getenv(client.EnvOverrideHost) != "" {
|
||||
return DefaultContextName
|
||||
}
|
||||
if ctxName := os.Getenv(EnvOverrideContext); ctxName != "" {
|
||||
if ctxName := os.Getenv("DOCKER_CONTEXT"); ctxName != "" {
|
||||
return ctxName
|
||||
}
|
||||
if cfg != nil && cfg.CurrentContext != "" {
|
||||
if config != nil && config.CurrentContext != "" {
|
||||
// We don't validate if this context exists: errors may occur when trying to use it.
|
||||
return cfg.CurrentContext
|
||||
return config.CurrentContext
|
||||
}
|
||||
return DefaultContextName
|
||||
}
|
||||
@ -514,7 +522,7 @@ func UserAgent() string {
|
||||
}
|
||||
|
||||
var defaultStoreEndpoints = []store.NamedTypeGetter{
|
||||
store.EndpointTypeGetter(docker.DockerEndpoint, func() any { return &docker.EndpointMeta{} }),
|
||||
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
|
||||
}
|
||||
|
||||
// RegisterDefaultStoreEndpoints registers a new named endpoint
|
||||
@ -528,7 +536,7 @@ func RegisterDefaultStoreEndpoints(ep ...store.NamedTypeGetter) {
|
||||
// DefaultContextStoreConfig returns a new store.Config with the default set of endpoints configured.
|
||||
func DefaultContextStoreConfig() store.Config {
|
||||
return store.NewConfig(
|
||||
func() any { return &DockerContext{} },
|
||||
func() interface{} { return &DockerContext{} },
|
||||
defaultStoreEndpoints...,
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func contentTrustEnabled(t *testing.T) bool {
|
||||
t.Helper()
|
||||
var cli DockerCli
|
||||
assert.NilError(t, WithContentTrustFromEnv()(&cli))
|
||||
return cli.contentTrust
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -34,7 +33,7 @@ func ImageNames(dockerCli command.Cli) ValidArgsFn {
|
||||
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
|
||||
func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Container) bool) ValidArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := dockerCli.Client().ContainerList(cmd.Context(), container.ListOptions{
|
||||
list, err := dockerCli.Client().ContainerList(cmd.Context(), types.ContainerListOptions{
|
||||
All: all,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@ -101,9 +101,9 @@ func (c *configContext) Labels() string {
|
||||
if mapLabels == nil {
|
||||
return ""
|
||||
}
|
||||
joinLabels := make([]string, 0, len(mapLabels))
|
||||
var joinLabels []string
|
||||
for k, v := range mapLabels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ func RunConfigInspect(dockerCli command.Cli, opts InspectOptions) error {
|
||||
opts.Format = "pretty"
|
||||
}
|
||||
|
||||
getRef := func(id string) (any, []byte, error) {
|
||||
getRef := func(id string) (interface{}, []byte, error) {
|
||||
return client.ConfigInspectWithRaw(ctx, id)
|
||||
}
|
||||
f := opts.Format
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -43,7 +43,7 @@ func TestConfigInspectErrors(t *testing.T) {
|
||||
args: []string{"foo", "bar"},
|
||||
configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) {
|
||||
if configID == "foo" {
|
||||
return *builders.Config(builders.ConfigName("foo")), nil, nil
|
||||
return *Config(ConfigName("foo")), nil, nil
|
||||
}
|
||||
return swarm.Config{}, nil, errors.Errorf("error while inspecting the config")
|
||||
},
|
||||
@ -58,7 +58,7 @@ func TestConfigInspectErrors(t *testing.T) {
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
assert.Check(t, cmd.Flags().Set(key, value))
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOut(io.Discard)
|
||||
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
@ -78,14 +78,14 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
|
||||
if name != "foo" {
|
||||
return swarm.Config{}, nil, errors.Errorf("Invalid name, expected %s, got %s", "foo", name)
|
||||
}
|
||||
return *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), nil, nil
|
||||
return *Config(ConfigID("ID-foo"), ConfigName("foo")), nil, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple-configs-with-labels",
|
||||
args: []string{"foo", "bar"},
|
||||
configInspectFunc: func(_ context.Context, name string) (swarm.Config, []byte, error) {
|
||||
return *builders.Config(builders.ConfigID("ID-"+name), builders.ConfigName(name), builders.ConfigLabels(map[string]string{
|
||||
return *Config(ConfigID("ID-"+name), ConfigName(name), ConfigLabels(map[string]string{
|
||||
"label1": "label-foo",
|
||||
})), nil, nil
|
||||
},
|
||||
@ -102,7 +102,7 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
|
||||
|
||||
func TestConfigInspectWithFormat(t *testing.T) {
|
||||
configInspectFunc := func(_ context.Context, name string) (swarm.Config, []byte, error) {
|
||||
return *builders.Config(builders.ConfigName("foo"), builders.ConfigLabels(map[string]string{
|
||||
return *Config(ConfigName("foo"), ConfigLabels(map[string]string{
|
||||
"label1": "label-foo",
|
||||
})), nil, nil
|
||||
}
|
||||
@ -131,7 +131,7 @@ func TestConfigInspectWithFormat(t *testing.T) {
|
||||
})
|
||||
cmd := newConfigInspectCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.Check(t, cmd.Flags().Set("format", tc.format))
|
||||
cmd.Flags().Set("format", tc.format)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-with-format.%s.golden", tc.name))
|
||||
}
|
||||
@ -145,15 +145,15 @@ func TestConfigInspectPretty(t *testing.T) {
|
||||
{
|
||||
name: "simple",
|
||||
configInspectFunc: func(_ context.Context, id string) (swarm.Config, []byte, error) {
|
||||
return *builders.Config(
|
||||
builders.ConfigLabels(map[string]string{
|
||||
return *Config(
|
||||
ConfigLabels(map[string]string{
|
||||
"lbl1": "value1",
|
||||
}),
|
||||
builders.ConfigID("configID"),
|
||||
builders.ConfigName("configName"),
|
||||
builders.ConfigCreatedAt(time.Time{}),
|
||||
builders.ConfigUpdatedAt(time.Time{}),
|
||||
builders.ConfigData([]byte("payload here")),
|
||||
ConfigID("configID"),
|
||||
ConfigName("configName"),
|
||||
ConfigCreatedAt(time.Time{}),
|
||||
ConfigUpdatedAt(time.Time{}),
|
||||
ConfigData([]byte("payload here")),
|
||||
), []byte{}, nil
|
||||
},
|
||||
},
|
||||
@ -165,7 +165,7 @@ func TestConfigInspectPretty(t *testing.T) {
|
||||
cmd := newConfigInspectCommand(cli)
|
||||
|
||||
cmd.SetArgs([]string{"configID"})
|
||||
assert.Check(t, cmd.Flags().Set("pretty", "true"))
|
||||
cmd.Flags().Set("pretty", "true")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-pretty.%s.golden", tc.name))
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
@ -50,23 +50,23 @@ func TestConfigList(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||
return []swarm.Config{
|
||||
*builders.Config(builders.ConfigID("ID-1-foo"),
|
||||
builders.ConfigName("1-foo"),
|
||||
builders.ConfigVersion(swarm.Version{Index: 10}),
|
||||
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
*Config(ConfigID("ID-1-foo"),
|
||||
ConfigName("1-foo"),
|
||||
ConfigVersion(swarm.Version{Index: 10}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
),
|
||||
*builders.Config(builders.ConfigID("ID-10-foo"),
|
||||
builders.ConfigName("10-foo"),
|
||||
builders.ConfigVersion(swarm.Version{Index: 11}),
|
||||
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
*Config(ConfigID("ID-10-foo"),
|
||||
ConfigName("10-foo"),
|
||||
ConfigVersion(swarm.Version{Index: 11}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
),
|
||||
*builders.Config(builders.ConfigID("ID-2-foo"),
|
||||
builders.ConfigName("2-foo"),
|
||||
builders.ConfigVersion(swarm.Version{Index: 11}),
|
||||
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
*Config(ConfigID("ID-2-foo"),
|
||||
ConfigName("2-foo"),
|
||||
ConfigVersion(swarm.Version{Index: 11}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
@ -80,15 +80,15 @@ func TestConfigListWithQuietOption(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||
return []swarm.Config{
|
||||
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
||||
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
||||
*Config(ConfigID("ID-foo"), ConfigName("foo")),
|
||||
*Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{
|
||||
"label": "label-bar",
|
||||
})),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
cmd := newConfigListCommand(cli)
|
||||
assert.Check(t, cmd.Flags().Set("quiet", "true"))
|
||||
cmd.Flags().Set("quiet", "true")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "config-list-with-quiet-option.golden")
|
||||
}
|
||||
@ -97,8 +97,8 @@ func TestConfigListWithConfigFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||
return []swarm.Config{
|
||||
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
||||
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
||||
*Config(ConfigID("ID-foo"), ConfigName("foo")),
|
||||
*Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{
|
||||
"label": "label-bar",
|
||||
})),
|
||||
}, nil
|
||||
@ -116,15 +116,15 @@ func TestConfigListWithFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||
return []swarm.Config{
|
||||
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
||||
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
||||
*Config(ConfigID("ID-foo"), ConfigName("foo")),
|
||||
*Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{
|
||||
"label": "label-bar",
|
||||
})),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
cmd := newConfigListCommand(cli)
|
||||
assert.Check(t, cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}"))
|
||||
cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "config-list-with-format.golden")
|
||||
}
|
||||
@ -135,24 +135,24 @@ func TestConfigListWithFilter(t *testing.T) {
|
||||
assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0]))
|
||||
assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0]))
|
||||
return []swarm.Config{
|
||||
*builders.Config(builders.ConfigID("ID-foo"),
|
||||
builders.ConfigName("foo"),
|
||||
builders.ConfigVersion(swarm.Version{Index: 10}),
|
||||
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
*Config(ConfigID("ID-foo"),
|
||||
ConfigName("foo"),
|
||||
ConfigVersion(swarm.Version{Index: 10}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
),
|
||||
*builders.Config(builders.ConfigID("ID-bar"),
|
||||
builders.ConfigName("bar"),
|
||||
builders.ConfigVersion(swarm.Version{Index: 11}),
|
||||
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
*Config(ConfigID("ID-bar"),
|
||||
ConfigName("bar"),
|
||||
ConfigVersion(swarm.Version{Index: 11}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
cmd := newConfigListCommand(cli)
|
||||
assert.Check(t, cmd.Flags().Set("filter", "name=foo"))
|
||||
assert.Check(t, cmd.Flags().Set("filter", "label=lbl1=Label-bar"))
|
||||
cmd.Flags().Set("filter", "name=foo")
|
||||
cmd.Flags().Set("filter", "label=lbl1=Label-bar")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "config-list-with-filter.golden")
|
||||
}
|
||||
|
||||
@ -17,15 +17,16 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// AttachOptions group options for `attach` command
|
||||
type AttachOptions struct {
|
||||
NoStdin bool
|
||||
Proxy bool
|
||||
DetachKeys string
|
||||
type attachOptions struct {
|
||||
noStdin bool
|
||||
proxy bool
|
||||
detachKeys string
|
||||
|
||||
container string
|
||||
}
|
||||
|
||||
func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClient, args string) (*types.ContainerJSON, error) {
|
||||
c, err := apiClient.ContainerInspect(ctx, args)
|
||||
func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) {
|
||||
c, err := cli.ContainerInspect(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -44,73 +45,71 @@ func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClie
|
||||
|
||||
// NewAttachCommand creates a new cobra.Command for `docker attach`
|
||||
func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts AttachOptions
|
||||
var ctr string
|
||||
var opts attachOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "attach [OPTIONS] CONTAINER",
|
||||
Short: "Attach local standard input, output, and error streams to a running container",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctr = args[0]
|
||||
return RunAttach(context.Background(), dockerCli, ctr, &opts)
|
||||
opts.container = args[0]
|
||||
return runAttach(dockerCli, &opts)
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"aliases": "docker container attach, docker attach",
|
||||
},
|
||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr types.Container) bool {
|
||||
return ctr.State != "paused"
|
||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool {
|
||||
return container.State != "paused"
|
||||
}),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.NoStdin, "no-stdin", false, "Do not attach STDIN")
|
||||
flags.BoolVar(&opts.Proxy, "sig-proxy", true, "Proxy all received signals to the process")
|
||||
flags.StringVar(&opts.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
||||
flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
|
||||
flags.BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
|
||||
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunAttach executes an `attach` command
|
||||
func RunAttach(ctx context.Context, dockerCLI command.Cli, target string, opts *AttachOptions) error {
|
||||
apiClient := dockerCLI.Client()
|
||||
func runAttach(dockerCli command.Cli, opts *attachOptions) error {
|
||||
ctx := context.Background()
|
||||
client := dockerCli.Client()
|
||||
|
||||
// request channel to wait for client
|
||||
resultC, errC := apiClient.ContainerWait(ctx, target, "")
|
||||
resultC, errC := client.ContainerWait(ctx, opts.container, "")
|
||||
|
||||
c, err := inspectContainerAndCheckState(ctx, apiClient, target)
|
||||
c, err := inspectContainerAndCheckState(ctx, client, opts.container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dockerCLI.In().CheckTty(!opts.NoStdin, c.Config.Tty); err != nil {
|
||||
if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
detachKeys := dockerCLI.ConfigFile().DetachKeys
|
||||
if opts.DetachKeys != "" {
|
||||
detachKeys = opts.DetachKeys
|
||||
if opts.detachKeys != "" {
|
||||
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
|
||||
}
|
||||
|
||||
options := container.AttachOptions{
|
||||
options := types.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: !opts.NoStdin && c.Config.OpenStdin,
|
||||
Stdin: !opts.noStdin && c.Config.OpenStdin,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
DetachKeys: detachKeys,
|
||||
DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
if options.Stdin {
|
||||
in = dockerCLI.In()
|
||||
in = dockerCli.In()
|
||||
}
|
||||
|
||||
if opts.Proxy && !c.Config.Tty {
|
||||
if opts.proxy && !c.Config.Tty {
|
||||
sigc := notifyAllSignals()
|
||||
go ForwardAllSignals(ctx, apiClient, target, sigc)
|
||||
go ForwardAllSignals(ctx, dockerCli, opts.container, sigc)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
resp, errAttach := apiClient.ContainerAttach(ctx, target, options)
|
||||
resp, errAttach := client.ContainerAttach(ctx, opts.container, options)
|
||||
if errAttach != nil {
|
||||
return errAttach
|
||||
}
|
||||
@ -124,20 +123,20 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, target string, opts *
|
||||
// the container and not exit.
|
||||
//
|
||||
// Recheck the container's state to avoid attach block.
|
||||
_, err = inspectContainerAndCheckState(ctx, apiClient, target)
|
||||
_, err = inspectContainerAndCheckState(ctx, client, opts.container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Config.Tty && dockerCLI.Out().IsTerminal() {
|
||||
resizeTTY(ctx, dockerCLI, target)
|
||||
if c.Config.Tty && dockerCli.Out().IsTerminal() {
|
||||
resizeTTY(ctx, dockerCli, opts.container)
|
||||
}
|
||||
|
||||
streamer := hijackedIOStreamer{
|
||||
streams: dockerCLI,
|
||||
streams: dockerCli,
|
||||
inputStream: in,
|
||||
outputStream: dockerCLI.Out(),
|
||||
errorStream: dockerCLI.Err(),
|
||||
outputStream: dockerCli.Out(),
|
||||
errorStream: dockerCli.Err(),
|
||||
resp: resp,
|
||||
tty: c.Config.Tty,
|
||||
detachKeys: options.DetachKeys,
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/docker/docker/client"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
@ -16,28 +15,28 @@ type fakeClient struct {
|
||||
client.Client
|
||||
inspectFunc func(string) (types.ContainerJSON, error)
|
||||
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
||||
execCreateFunc func(containerID string, config types.ExecConfig) (types.IDResponse, error)
|
||||
execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error)
|
||||
createContainerFunc func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig,
|
||||
platform *specs.Platform,
|
||||
containerName string) (container.CreateResponse, error)
|
||||
containerStartFunc func(containerID string, options container.StartOptions) error
|
||||
containerStartFunc func(container string, options types.ContainerStartOptions) error
|
||||
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (system.Info, error)
|
||||
containerStatPathFunc func(containerID, path string) (types.ContainerPathStat, error)
|
||||
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
||||
logFunc func(string, container.LogsOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (types.Info, error)
|
||||
containerStatPathFunc func(container, path string) (types.ContainerPathStat, error)
|
||||
containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
||||
logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||
waitFunc func(string) (<-chan container.WaitResponse, <-chan error)
|
||||
containerListFunc func(container.ListOptions) ([]types.Container, error)
|
||||
containerListFunc func(types.ContainerListOptions) ([]types.Container, error)
|
||||
containerExportFunc func(string) (io.ReadCloser, error)
|
||||
containerExecResizeFunc func(id string, options container.ResizeOptions) error
|
||||
containerRemoveFunc func(ctx context.Context, containerID string, options container.RemoveOptions) error
|
||||
containerKillFunc func(ctx context.Context, containerID, signal string) error
|
||||
containerExecResizeFunc func(id string, options types.ResizeOptions) error
|
||||
containerRemoveFunc func(ctx context.Context, container string, options types.ContainerRemoveOptions) error
|
||||
containerKillFunc func(ctx context.Context, container, signal string) error
|
||||
Version string
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) {
|
||||
func (f *fakeClient) ContainerList(_ context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||
if f.containerListFunc != nil {
|
||||
return f.containerListFunc(options)
|
||||
}
|
||||
@ -51,9 +50,9 @@ func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (ty
|
||||
return types.ContainerJSON{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerExecCreate(_ context.Context, containerID string, config types.ExecConfig) (types.IDResponse, error) {
|
||||
func (f *fakeClient) ContainerExecCreate(_ context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
|
||||
if f.execCreateFunc != nil {
|
||||
return f.execCreateFunc(containerID, config)
|
||||
return f.execCreateFunc(container, config)
|
||||
}
|
||||
return types.IDResponse{}, nil
|
||||
}
|
||||
@ -83,9 +82,9 @@ func (f *fakeClient) ContainerCreate(
|
||||
return container.CreateResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
|
||||
func (f *fakeClient) ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error {
|
||||
if f.containerRemoveFunc != nil {
|
||||
return f.containerRemoveFunc(ctx, containerID, options)
|
||||
return f.containerRemoveFunc(ctx, container, options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -97,30 +96,30 @@ func (f *fakeClient) ImageCreate(_ context.Context, parentReference string, opti
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) Info(_ context.Context) (system.Info, error) {
|
||||
func (f *fakeClient) Info(_ context.Context) (types.Info, error) {
|
||||
if f.infoFunc != nil {
|
||||
return f.infoFunc()
|
||||
}
|
||||
return system.Info{}, nil
|
||||
return types.Info{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID, path string) (types.ContainerPathStat, error) {
|
||||
func (f *fakeClient) ContainerStatPath(_ context.Context, container, path string) (types.ContainerPathStat, error) {
|
||||
if f.containerStatPathFunc != nil {
|
||||
return f.containerStatPathFunc(containerID, path)
|
||||
return f.containerStatPathFunc(container, path)
|
||||
}
|
||||
return types.ContainerPathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
func (f *fakeClient) CopyFromContainer(_ context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
if f.containerCopyFromFunc != nil {
|
||||
return f.containerCopyFromFunc(containerID, srcPath)
|
||||
return f.containerCopyFromFunc(container, srcPath)
|
||||
}
|
||||
return nil, types.ContainerPathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
||||
func (f *fakeClient) ContainerLogs(_ context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
if f.logFunc != nil {
|
||||
return f.logFunc(containerID, options)
|
||||
return f.logFunc(container, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@ -129,37 +128,37 @@ func (f *fakeClient) ClientVersion() string {
|
||||
return f.Version
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
func (f *fakeClient) ContainerWait(_ context.Context, container string, _ container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
if f.waitFunc != nil {
|
||||
return f.waitFunc(containerID)
|
||||
return f.waitFunc(container)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerStart(_ context.Context, containerID string, options container.StartOptions) error {
|
||||
func (f *fakeClient) ContainerStart(_ context.Context, container string, options types.ContainerStartOptions) error {
|
||||
if f.containerStartFunc != nil {
|
||||
return f.containerStartFunc(containerID, options)
|
||||
return f.containerStartFunc(container, options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerExport(_ context.Context, containerID string) (io.ReadCloser, error) {
|
||||
func (f *fakeClient) ContainerExport(_ context.Context, container string) (io.ReadCloser, error) {
|
||||
if f.containerExportFunc != nil {
|
||||
return f.containerExportFunc(containerID)
|
||||
return f.containerExportFunc(container)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options container.ResizeOptions) error {
|
||||
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options types.ResizeOptions) error {
|
||||
if f.containerExecResizeFunc != nil {
|
||||
return f.containerExecResizeFunc(id, options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerKill(ctx context.Context, containerID, signal string) error {
|
||||
func (f *fakeClient) ContainerKill(ctx context.Context, container, signal string) error {
|
||||
if f.containerKillFunc != nil {
|
||||
return f.containerKillFunc(ctx, containerID, signal)
|
||||
return f.containerKillFunc(ctx, container, signal)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -59,13 +59,18 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func runCommit(dockerCli command.Cli, options *commitOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
response, err := dockerCli.Client().ContainerCommit(ctx, options.container, container.CommitOptions{
|
||||
Reference: options.reference,
|
||||
name := options.container
|
||||
reference := options.reference
|
||||
|
||||
commitOptions := types.ContainerCommitOptions{
|
||||
Reference: reference,
|
||||
Comment: options.comment,
|
||||
Author: options.author,
|
||||
Changes: options.changes.GetAll(),
|
||||
Pause: options.pause,
|
||||
})
|
||||
}
|
||||
|
||||
response, err := dockerCli.Client().ContainerCommit(ctx, name, commitOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -246,6 +246,7 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
|
||||
linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
|
||||
srcPath = linkTarget
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
||||
|
||||
@ -8,17 +8,17 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/distribution/reference"
|
||||
"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"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
@ -91,7 +91,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, options *createOptio
|
||||
if v == nil {
|
||||
newEnv = append(newEnv, k)
|
||||
} else {
|
||||
newEnv = append(newEnv, k+"="+*v)
|
||||
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
|
||||
}
|
||||
}
|
||||
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
||||
@ -104,24 +104,24 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, options *createOptio
|
||||
reportError(dockerCli.Err(), "create", err.Error(), true)
|
||||
return cli.StatusError{StatusCode: 125}
|
||||
}
|
||||
id, err := createContainer(context.Background(), dockerCli, containerCfg, options)
|
||||
response, err := createContainer(context.Background(), dockerCli, containerCfg, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), id)
|
||||
fmt.Fprintln(dockerCli.Out(), response.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME(thaJeztah): this is the only code-path that uses APIClient.ImageCreate. Rewrite this to use the regular "pull" code (or vice-versa).
|
||||
func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *createOptions) error {
|
||||
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), img)
|
||||
func pullImage(ctx context.Context, dockerCli command.Cli, image string, opts *createOptions) error {
|
||||
encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
responseBody, err := dockerCli.Client().ImageCreate(ctx, img, types.ImageCreateOptions{
|
||||
responseBody, err := dockerCli.Client().ImageCreate(ctx, image, types.ImageCreateOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
Platform: options.platform,
|
||||
Platform: opts.platform,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -129,7 +129,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
|
||||
defer responseBody.Close()
|
||||
|
||||
out := dockerCli.Err()
|
||||
if options.quiet {
|
||||
if opts.quiet {
|
||||
out = io.Discard
|
||||
}
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(out), nil)
|
||||
@ -185,7 +185,7 @@ func newCIDFile(path string) (*cidFile, error) {
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *containerConfig, options *createOptions) (containerID string, err error) {
|
||||
func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *containerConfig, opts *createOptions) (*container.CreateResponse, error) {
|
||||
config := containerCfg.Config
|
||||
hostConfig := containerCfg.HostConfig
|
||||
networkingConfig := containerCfg.NetworkingConfig
|
||||
@ -200,29 +200,29 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
|
||||
containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
defer containerIDFile.Close()
|
||||
|
||||
ref, err := reference.ParseAnyReference(config.Image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if named, ok := ref.(reference.Named); ok {
|
||||
namedRef = reference.TagNameOnly(named)
|
||||
|
||||
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !options.untrusted {
|
||||
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !opts.untrusted {
|
||||
var err error
|
||||
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
config.Image = reference.FamiliarString(trustedRef)
|
||||
}
|
||||
}
|
||||
|
||||
pullAndTagImage := func() error {
|
||||
if err := pullImage(ctx, dockerCli, config.Image, options); err != nil {
|
||||
if err := pullImage(ctx, dockerCli, config.Image, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
|
||||
@ -236,50 +236,50 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
// create. It will produce an error if you try to set a platform on older API
|
||||
// versions, so check the API version here to maintain backwards
|
||||
// compatibility for CLI users.
|
||||
if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
|
||||
p, err := platforms.Parse(options.platform)
|
||||
if opts.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
|
||||
p, err := platforms.Parse(opts.platform)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing specified platform")
|
||||
return nil, errors.Wrap(err, "error parsing specified platform")
|
||||
}
|
||||
platform = &p
|
||||
}
|
||||
|
||||
if options.pull == PullImageAlways {
|
||||
if opts.pull == PullImageAlways {
|
||||
if err := pullAndTagImage(); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
|
||||
|
||||
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
|
||||
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name)
|
||||
if err != nil {
|
||||
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
|
||||
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
|
||||
if !options.quiet {
|
||||
if apiclient.IsErrNotFound(err) && namedRef != nil && opts.pull == PullImageMissing {
|
||||
if !opts.quiet {
|
||||
// we don't want to write to stdout anything apart from container.ID
|
||||
fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
||||
}
|
||||
|
||||
if err := pullAndTagImage(); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var retryErr error
|
||||
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
|
||||
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name)
|
||||
if retryErr != nil {
|
||||
return "", retryErr
|
||||
return nil, retryErr
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, w := range response.Warnings {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "WARNING: %s\n", w)
|
||||
fmt.Fprintf(dockerCli.Err(), "WARNING: %s\n", w)
|
||||
}
|
||||
err = containerIDFile.Write(response.ID)
|
||||
return response.ID, err
|
||||
return &response, err
|
||||
}
|
||||
|
||||
func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) {
|
||||
|
||||
@ -18,7 +18,6 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/spf13/pflag"
|
||||
@ -80,10 +79,8 @@ func TestCIDFileCloseWithWrite(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateContainerImagePullPolicy(t *testing.T) {
|
||||
const (
|
||||
imageName = "does-not-exist-locally"
|
||||
containerID = "abcdef"
|
||||
)
|
||||
imageName := "does-not-exist-locally"
|
||||
containerID := "abcdef"
|
||||
config := &containerConfig{
|
||||
Config: &container.Config{
|
||||
Image: imageName,
|
||||
@ -94,18 +91,18 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
|
||||
cases := []struct {
|
||||
PullPolicy string
|
||||
ExpectedPulls int
|
||||
ExpectedID string
|
||||
ExpectedBody container.CreateResponse
|
||||
ExpectedErrMsg string
|
||||
ResponseCounter int
|
||||
}{
|
||||
{
|
||||
PullPolicy: PullImageMissing,
|
||||
ExpectedPulls: 1,
|
||||
ExpectedID: containerID,
|
||||
ExpectedBody: container.CreateResponse{ID: containerID},
|
||||
}, {
|
||||
PullPolicy: PullImageAlways,
|
||||
ExpectedPulls: 1,
|
||||
ExpectedID: containerID,
|
||||
ExpectedBody: container.CreateResponse{ID: containerID},
|
||||
ResponseCounter: 1, // This lets us return a container on the first pull
|
||||
}, {
|
||||
PullPolicy: PullImageNever,
|
||||
@ -113,52 +110,50 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
|
||||
ExpectedErrMsg: "error fake not found",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||
pullCounter := 0
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
pullCounter := 0
|
||||
|
||||
client := &fakeClient{
|
||||
createContainerFunc: func(
|
||||
config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig,
|
||||
platform *specs.Platform,
|
||||
containerName string,
|
||||
) (container.CreateResponse, error) {
|
||||
defer func() { tc.ResponseCounter++ }()
|
||||
switch tc.ResponseCounter {
|
||||
case 0:
|
||||
return container.CreateResponse{}, fakeNotFound{}
|
||||
default:
|
||||
return container.CreateResponse{ID: containerID}, nil
|
||||
}
|
||||
},
|
||||
imageCreateFunc: func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
|
||||
defer func() { pullCounter++ }()
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
infoFunc: func() (system.Info, error) {
|
||||
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
|
||||
},
|
||||
}
|
||||
fakeCLI := test.NewFakeCli(client)
|
||||
id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
|
||||
name: "name",
|
||||
platform: runtime.GOOS,
|
||||
untrusted: true,
|
||||
pull: tc.PullPolicy,
|
||||
})
|
||||
|
||||
if tc.ExpectedErrMsg != "" {
|
||||
assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg))
|
||||
} else {
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(tc.ExpectedID, id))
|
||||
}
|
||||
|
||||
assert.Check(t, is.Equal(tc.ExpectedPulls, pullCounter))
|
||||
client := &fakeClient{
|
||||
createContainerFunc: func(
|
||||
config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig,
|
||||
platform *specs.Platform,
|
||||
containerName string,
|
||||
) (container.CreateResponse, error) {
|
||||
defer func() { c.ResponseCounter++ }()
|
||||
switch c.ResponseCounter {
|
||||
case 0:
|
||||
return container.CreateResponse{}, fakeNotFound{}
|
||||
default:
|
||||
return container.CreateResponse{ID: containerID}, nil
|
||||
}
|
||||
},
|
||||
imageCreateFunc: func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
|
||||
defer func() { pullCounter++ }()
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
|
||||
},
|
||||
}
|
||||
cli := test.NewFakeCli(client)
|
||||
body, err := createContainer(context.Background(), cli, config, &createOptions{
|
||||
name: "name",
|
||||
platform: runtime.GOOS,
|
||||
untrusted: true,
|
||||
pull: c.PullPolicy,
|
||||
})
|
||||
|
||||
if c.ExpectedErrMsg != "" {
|
||||
assert.ErrorContains(t, err, c.ExpectedErrMsg)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(c.ExpectedBody, *body))
|
||||
}
|
||||
|
||||
assert.Check(t, is.Equal(c.ExpectedPulls, pullCounter))
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +218,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig,
|
||||
@ -233,8 +228,8 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||
return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := NewCreateCommand(fakeCLI)
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := NewCreateCommand(cli)
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
err := cmd.Execute()
|
||||
@ -323,7 +318,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
|
||||
}
|
||||
sort.Strings(expected)
|
||||
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig,
|
||||
@ -335,7 +330,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
|
||||
return container.CreateResponse{}, nil
|
||||
},
|
||||
})
|
||||
fakeCLI.SetConfigFile(&configfile.ConfigFile{
|
||||
cli.SetConfigFile(&configfile.ConfigFile{
|
||||
Proxies: map[string]configfile.ProxyConfig{
|
||||
"default": {
|
||||
HTTPProxy: "httpProxy",
|
||||
@ -346,7 +341,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
cmd := NewCreateCommand(fakeCLI)
|
||||
cmd := NewCreateCommand(cli)
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetArgs([]string{"image:tag"})
|
||||
err := cmd.Execute()
|
||||
@ -355,5 +350,5 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
|
||||
|
||||
type fakeNotFound struct{}
|
||||
|
||||
func (f fakeNotFound) NotFound() {}
|
||||
func (f fakeNotFound) Error() string { return "error fake not found" }
|
||||
func (f fakeNotFound) NotFound() bool { return true }
|
||||
func (f fakeNotFound) Error() string { return "error fake not found" }
|
||||
|
||||
@ -28,6 +28,7 @@ type ExecOptions struct {
|
||||
Privileged bool
|
||||
Env opts.ListOpts
|
||||
Workdir string
|
||||
Container string
|
||||
Command []string
|
||||
EnvFile opts.ListOpts
|
||||
}
|
||||
@ -43,16 +44,15 @@ func NewExecOptions() ExecOptions {
|
||||
// NewExecCommand creates a new cobra.Command for `docker exec`
|
||||
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := NewExecOptions()
|
||||
var container string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
|
||||
Short: "Execute a command in a running container",
|
||||
Args: cli.RequiresMinArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
container = args[0]
|
||||
options.Container = args[0]
|
||||
options.Command = args[1:]
|
||||
return RunExec(context.Background(), dockerCli, container, options)
|
||||
return RunExec(dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool {
|
||||
return container.State != "paused"
|
||||
@ -96,19 +96,20 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
// RunExec executes an `exec` command
|
||||
func RunExec(ctx context.Context, dockerCli command.Cli, container string, options ExecOptions) error {
|
||||
func RunExec(dockerCli command.Cli, options ExecOptions) error {
|
||||
execConfig, err := parseExec(options, dockerCli.ConfigFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
client := dockerCli.Client()
|
||||
|
||||
// We need to check the tty _before_ we do the ContainerExecCreate, because
|
||||
// otherwise if we error out we will leak execIDs on the server (and
|
||||
// there's no easy way to clean those up). But also in order to make "not
|
||||
// exist" errors take precedence we do a dummy inspect first.
|
||||
if _, err := client.ContainerInspect(ctx, container); err != nil {
|
||||
if _, err := client.ContainerInspect(ctx, options.Container); err != nil {
|
||||
return err
|
||||
}
|
||||
if !execConfig.Detach {
|
||||
@ -119,7 +120,7 @@ func RunExec(ctx context.Context, dockerCli command.Cli, container string, optio
|
||||
|
||||
fillConsoleSize(execConfig, dockerCli)
|
||||
|
||||
response, err := client.ContainerExecCreate(ctx, container, *execConfig)
|
||||
response, err := client.ContainerExecCreate(ctx, options.Container, *execConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -169,7 +169,8 @@ func TestRunExec(t *testing.T) {
|
||||
{
|
||||
doc: "successful detach",
|
||||
options: withDefaultOpts(ExecOptions{
|
||||
Detach: true,
|
||||
Container: "thecontainer",
|
||||
Detach: true,
|
||||
}),
|
||||
client: fakeClient{execCreateFunc: execCreateWithID},
|
||||
},
|
||||
@ -194,11 +195,13 @@ func TestRunExec(t *testing.T) {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&testcase.client)
|
||||
|
||||
err := RunExec(context.Background(), cli, "thecontainer", testcase.options)
|
||||
err := RunExec(cli, testcase.options)
|
||||
if testcase.expectedError != "" {
|
||||
assert.ErrorContains(t, err, testcase.expectedError)
|
||||
} else if !assert.Check(t, err) {
|
||||
return
|
||||
} else {
|
||||
if !assert.Check(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
assert.Check(t, is.Equal(testcase.expectedOut, cli.OutBuffer().String()))
|
||||
assert.Check(t, is.Equal(testcase.expectedErr, cli.ErrBuffer().String()))
|
||||
@ -262,8 +265,8 @@ func TestNewExecCommandErrors(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
|
||||
cmd := NewExecCommand(fakeCLI)
|
||||
cli := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
|
||||
cmd := NewExecCommand(cli)
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
|
||||
@ -14,7 +14,8 @@ const (
|
||||
|
||||
// NewDiffFormat returns a format for use with a diff Context
|
||||
func NewDiffFormat(source string) formatter.Format {
|
||||
if source == formatter.TableFormatKey {
|
||||
switch source {
|
||||
case formatter.TableFormatKey:
|
||||
return defaultDiffTableFormat
|
||||
}
|
||||
return formatter.Format(source)
|
||||
|
||||
@ -24,7 +24,7 @@ const (
|
||||
pidsHeader = "PIDS" // Used only on Linux
|
||||
)
|
||||
|
||||
// StatsEntry represents the statistics data collected from a container
|
||||
// StatsEntry represents represents the statistics data collected from a container
|
||||
type StatsEntry struct {
|
||||
Container string
|
||||
Name string
|
||||
@ -116,9 +116,9 @@ func NewStats(container string) *Stats {
|
||||
}
|
||||
|
||||
// statsFormatWrite renders the context for a list of containers statistics
|
||||
func statsFormatWrite(ctx formatter.Context, stats []StatsEntry, osType string, trunc bool) error {
|
||||
func statsFormatWrite(ctx formatter.Context, Stats []StatsEntry, osType string, trunc bool) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, cstats := range stats {
|
||||
for _, cstats := range Stats {
|
||||
statsCtx := &statsContext{
|
||||
s: cstats,
|
||||
os: osType,
|
||||
|
||||
@ -87,7 +87,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
|
||||
var restoreOnce sync.Once
|
||||
restore = func() {
|
||||
restoreOnce.Do(func() {
|
||||
_ = restoreTerminal(h.streams, h.inputStream)
|
||||
restoreTerminal(h.streams, h.inputStream)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
getRefFunc := func(ref string) (any, []byte, error) {
|
||||
getRefFunc := func(ref string) (interface{}, []byte, error) {
|
||||
return client.ContainerInspectWithRaw(ctx, ref, opts.size)
|
||||
}
|
||||
return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRefFunc)
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/cli/templates"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -29,7 +29,7 @@ type psOptions struct {
|
||||
}
|
||||
|
||||
// NewPsCommand creates a new cobra.Command for `docker ps`
|
||||
func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
func NewPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := psOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -38,7 +38,7 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.sizeChanged = cmd.Flags().Changed("size")
|
||||
return runPs(dockerCLI, &options)
|
||||
return runPs(dockerCli, &options)
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"category-top": "3",
|
||||
@ -61,28 +61,28 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
cmd := *NewPsCommand(dockerCLI)
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := *NewPsCommand(dockerCli)
|
||||
cmd.Aliases = []string{"ps", "list"}
|
||||
cmd.Use = "ls [OPTIONS]"
|
||||
return &cmd
|
||||
}
|
||||
|
||||
func buildContainerListOptions(options *psOptions) (*container.ListOptions, error) {
|
||||
listOptions := &container.ListOptions{
|
||||
All: options.all,
|
||||
Limit: options.last,
|
||||
Size: options.size,
|
||||
Filters: options.filter.Value(),
|
||||
func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, error) {
|
||||
options := &types.ContainerListOptions{
|
||||
All: opts.all,
|
||||
Limit: opts.last,
|
||||
Size: opts.size,
|
||||
Filters: opts.filter.Value(),
|
||||
}
|
||||
|
||||
if options.nLatest && options.last == -1 {
|
||||
listOptions.Limit = 1
|
||||
if opts.nLatest && opts.last == -1 {
|
||||
options.Limit = 1
|
||||
}
|
||||
|
||||
// always validate template when `--format` is used, for consistency
|
||||
if len(options.format) > 0 {
|
||||
tmpl, err := templates.NewParse("", options.format)
|
||||
if len(opts.format) > 0 {
|
||||
tmpl, err := templates.NewParse("", opts.format)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse template")
|
||||
}
|
||||
@ -97,7 +97,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
|
||||
|
||||
// if `size` was not explicitly set to false (with `--size=false`)
|
||||
// and `--quiet` is not set, request size if the template requires it
|
||||
if !options.quiet && !listOptions.Size && !options.sizeChanged {
|
||||
if !opts.quiet && !options.Size && !opts.sizeChanged {
|
||||
// The --size option isn't set, but .Size may be used in the template.
|
||||
// Parse and execute the given template to detect if the .Size field is
|
||||
// used. If it is, then automatically enable the --size option. See #24696
|
||||
@ -106,22 +106,22 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
|
||||
// because calculating the size is a costly operation.
|
||||
|
||||
if _, ok := optionsProcessor.FieldsUsed["Size"]; ok {
|
||||
listOptions.Size = true
|
||||
options.Size = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return listOptions, nil
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func runPs(dockerCLI command.Cli, options *psOptions) error {
|
||||
func runPs(dockerCli command.Cli, options *psOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
if len(options.format) == 0 {
|
||||
// load custom psFormat from CLI config (if any)
|
||||
options.format = dockerCLI.ConfigFile().PsFormat
|
||||
options.format = dockerCli.ConfigFile().PsFormat
|
||||
} else if options.quiet {
|
||||
_, _ = dockerCLI.Err().Write([]byte("WARNING: Ignoring custom format, because both --format and --quiet are set.\n"))
|
||||
_, _ = dockerCli.Err().Write([]byte("WARNING: Ignoring custom format, because both --format and --quiet are set.\n"))
|
||||
}
|
||||
|
||||
listOptions, err := buildContainerListOptions(options)
|
||||
@ -129,13 +129,13 @@ func runPs(dockerCLI command.Cli, options *psOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
containers, err := dockerCLI.Client().ContainerList(ctx, *listOptions)
|
||||
containers, err := dockerCli.Client().ContainerList(ctx, *listOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerCtx := formatter.Context{
|
||||
Output: dockerCLI.Out(),
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewContainerFormat(options.format, options.quiet, listOptions.Size),
|
||||
Trunc: !options.noTrunc,
|
||||
}
|
||||
|
||||
@ -7,10 +7,9 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -130,7 +129,7 @@ func TestContainerListErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
containerListFunc func(container.ListOptions) ([]types.Container, error)
|
||||
containerListFunc func(types.ContainerListOptions) ([]types.Container, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -146,7 +145,7 @@ func TestContainerListErrors(t *testing.T) {
|
||||
expectedError: `wrong number of args for join`,
|
||||
},
|
||||
{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
|
||||
return nil, fmt.Errorf("error listing containers")
|
||||
},
|
||||
expectedError: "error listing containers",
|
||||
@ -160,7 +159,7 @@ func TestContainerListErrors(t *testing.T) {
|
||||
)
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
assert.Check(t, cmd.Flags().Set(key, value))
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOut(io.Discard)
|
||||
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
@ -169,13 +168,13 @@ func TestContainerListErrors(t *testing.T) {
|
||||
|
||||
func TestContainerListWithoutFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
|
||||
return []types.Container{
|
||||
*builders.Container("c1"),
|
||||
*builders.Container("c2", builders.WithName("foo")),
|
||||
*builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)),
|
||||
*builders.Container("c4", builders.WithPort(81, 81, builders.UDP)),
|
||||
*builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)),
|
||||
*Container("c1"),
|
||||
*Container("c2", WithName("foo")),
|
||||
*Container("c3", WithPort(80, 80, TCP), WithPort(81, 81, TCP), WithPort(82, 82, TCP)),
|
||||
*Container("c4", WithPort(81, 81, UDP)),
|
||||
*Container("c5", WithPort(82, 82, IP("8.8.8.8"), TCP)),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
@ -186,15 +185,15 @@ func TestContainerListWithoutFormat(t *testing.T) {
|
||||
|
||||
func TestContainerListNoTrunc(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
|
||||
return []types.Container{
|
||||
*builders.Container("c1"),
|
||||
*builders.Container("c2", builders.WithName("foo/bar")),
|
||||
*Container("c1"),
|
||||
*Container("c2", WithName("foo/bar")),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
cmd := newListCommand(cli)
|
||||
assert.Check(t, cmd.Flags().Set("no-trunc", "true"))
|
||||
cmd.Flags().Set("no-trunc", "true")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format-no-trunc.golden")
|
||||
}
|
||||
@ -202,15 +201,15 @@ func TestContainerListNoTrunc(t *testing.T) {
|
||||
// Test for GitHub issue docker/docker#21772
|
||||
func TestContainerListNamesMultipleTime(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
|
||||
return []types.Container{
|
||||
*builders.Container("c1"),
|
||||
*builders.Container("c2", builders.WithName("foo/bar")),
|
||||
*Container("c1"),
|
||||
*Container("c2", WithName("foo/bar")),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
cmd := newListCommand(cli)
|
||||
assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}"))
|
||||
cmd.Flags().Set("format", "{{.Names}} {{.Names}}")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-name-name.golden")
|
||||
}
|
||||
@ -218,15 +217,15 @@ func TestContainerListNamesMultipleTime(t *testing.T) {
|
||||
// Test for GitHub issue docker/docker#30291
|
||||
func TestContainerListFormatTemplateWithArg(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
|
||||
return []types.Container{
|
||||
*builders.Container("c1", builders.WithLabel("some.label", "value")),
|
||||
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
|
||||
*Container("c1", WithLabel("some.label", "value")),
|
||||
*Container("c2", WithName("foo/bar"), WithLabel("foo", "bar")),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
cmd := newListCommand(cli)
|
||||
assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`))
|
||||
cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-with-arg.golden")
|
||||
}
|
||||
@ -269,15 +268,15 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(options container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(options types.ContainerListOptions) ([]types.Container, error) {
|
||||
assert.Check(t, is.Equal(options.Size, tc.sizeExpected))
|
||||
return []types.Container{}, nil
|
||||
},
|
||||
})
|
||||
cmd := newListCommand(cli)
|
||||
assert.Check(t, cmd.Flags().Set("format", tc.format))
|
||||
cmd.Flags().Set("format", tc.format)
|
||||
if tc.sizeFlag != "" {
|
||||
assert.Check(t, cmd.Flags().Set("size", tc.sizeFlag))
|
||||
cmd.Flags().Set("size", tc.sizeFlag)
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
})
|
||||
@ -286,10 +285,10 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
|
||||
|
||||
func TestContainerListWithConfigFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
|
||||
return []types.Container{
|
||||
*builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)),
|
||||
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)),
|
||||
*Container("c1", WithLabel("some.label", "value"), WithSize(10700000)),
|
||||
*Container("c2", WithName("foo/bar"), WithLabel("foo", "bar"), WithSize(3200000)),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
@ -303,10 +302,10 @@ func TestContainerListWithConfigFormat(t *testing.T) {
|
||||
|
||||
func TestContainerListWithFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
|
||||
return []types.Container{
|
||||
*builders.Container("c1", builders.WithLabel("some.label", "value")),
|
||||
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
|
||||
*Container("c1", WithLabel("some.label", "value")),
|
||||
*Container("c2", WithName("foo/bar"), WithLabel("foo", "bar")),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -60,7 +60,7 @@ func runLogs(dockerCli command.Cli, opts *logsOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
responseBody, err := dockerCli.Client().ContainerLogs(ctx, c.ID, container.LogsOptions{
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Since: opts.since,
|
||||
@ -69,7 +69,8 @@ func runLogs(dockerCli command.Cli, opts *logsOptions) error {
|
||||
Follow: opts.follow,
|
||||
Tail: opts.tail,
|
||||
Details: opts.details,
|
||||
})
|
||||
}
|
||||
responseBody, err := dockerCli.Client().ContainerLogs(ctx, c.ID, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ import (
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
var logFn = func(expectedOut string) func(string, container.LogsOptions) (io.ReadCloser, error) {
|
||||
return func(container string, opts container.LogsOptions) (io.ReadCloser, error) {
|
||||
var logFn = func(expectedOut string) func(string, types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
return func(container string, opts types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader(expectedOut)), nil
|
||||
}
|
||||
}
|
||||
@ -49,8 +49,10 @@ func TestRunLogs(t *testing.T) {
|
||||
err := runLogs(cli, testcase.options)
|
||||
if testcase.expectedError != "" {
|
||||
assert.ErrorContains(t, err, testcase.expectedError)
|
||||
} else if !assert.Check(t, err) {
|
||||
return
|
||||
} else {
|
||||
if !assert.Check(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
assert.Check(t, is.Equal(testcase.expectedOut, cli.OutBuffer().String()))
|
||||
assert.Check(t, is.Equal(testcase.expectedErr, cli.ErrBuffer().String()))
|
||||
|
||||
@ -13,119 +13,117 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
networktypes "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
||||
)
|
||||
|
||||
var deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
|
||||
|
||||
// containerOptions is a data object with all the options for creating a container
|
||||
type containerOptions struct {
|
||||
attach opts.ListOpts
|
||||
volumes opts.ListOpts
|
||||
tmpfs opts.ListOpts
|
||||
mounts opts.MountOpt
|
||||
blkioWeightDevice opts.WeightdeviceOpt
|
||||
deviceReadBps opts.ThrottledeviceOpt
|
||||
deviceWriteBps opts.ThrottledeviceOpt
|
||||
links opts.ListOpts
|
||||
aliases opts.ListOpts
|
||||
linkLocalIPs opts.ListOpts
|
||||
deviceReadIOps opts.ThrottledeviceOpt
|
||||
deviceWriteIOps opts.ThrottledeviceOpt
|
||||
env opts.ListOpts
|
||||
labels opts.ListOpts
|
||||
deviceCgroupRules opts.ListOpts
|
||||
devices opts.ListOpts
|
||||
gpus opts.GpuOpts
|
||||
ulimits *opts.UlimitOpt
|
||||
sysctls *opts.MapOpts
|
||||
publish opts.ListOpts
|
||||
expose opts.ListOpts
|
||||
dns opts.ListOpts
|
||||
dnsSearch opts.ListOpts
|
||||
dnsOptions opts.ListOpts
|
||||
extraHosts opts.ListOpts
|
||||
volumesFrom opts.ListOpts
|
||||
envFile opts.ListOpts
|
||||
capAdd opts.ListOpts
|
||||
capDrop opts.ListOpts
|
||||
groupAdd opts.ListOpts
|
||||
securityOpt opts.ListOpts
|
||||
storageOpt opts.ListOpts
|
||||
labelsFile opts.ListOpts
|
||||
loggingOpts opts.ListOpts
|
||||
privileged bool
|
||||
pidMode string
|
||||
utsMode string
|
||||
usernsMode string
|
||||
cgroupnsMode string
|
||||
publishAll bool
|
||||
stdin bool
|
||||
tty bool
|
||||
oomKillDisable bool
|
||||
oomScoreAdj int
|
||||
containerIDFile string
|
||||
entrypoint string
|
||||
hostname string
|
||||
domainname string
|
||||
memory opts.MemBytes
|
||||
memoryReservation opts.MemBytes
|
||||
memorySwap opts.MemSwapBytes
|
||||
kernelMemory opts.MemBytes
|
||||
user string
|
||||
workingDir string
|
||||
cpuCount int64
|
||||
cpuShares int64
|
||||
cpuPercent int64
|
||||
cpuPeriod int64
|
||||
cpuRealtimePeriod int64
|
||||
cpuRealtimeRuntime int64
|
||||
cpuQuota int64
|
||||
cpus opts.NanoCPUs
|
||||
cpusetCpus string
|
||||
cpusetMems string
|
||||
blkioWeight uint16
|
||||
ioMaxBandwidth opts.MemBytes
|
||||
ioMaxIOps uint64
|
||||
swappiness int64
|
||||
netMode opts.NetworkOpt
|
||||
macAddress string
|
||||
ipv4Address string
|
||||
ipv6Address string
|
||||
ipcMode string
|
||||
pidsLimit int64
|
||||
restartPolicy string
|
||||
readonlyRootfs bool
|
||||
loggingDriver string
|
||||
cgroupParent string
|
||||
volumeDriver string
|
||||
stopSignal string
|
||||
stopTimeout int
|
||||
isolation string
|
||||
shmSize opts.MemBytes
|
||||
noHealthcheck bool
|
||||
healthCmd string
|
||||
healthInterval time.Duration
|
||||
healthTimeout time.Duration
|
||||
healthStartPeriod time.Duration
|
||||
healthStartInterval time.Duration
|
||||
healthRetries int
|
||||
runtime string
|
||||
autoRemove bool
|
||||
init bool
|
||||
annotations *opts.MapOpts
|
||||
attach opts.ListOpts
|
||||
volumes opts.ListOpts
|
||||
tmpfs opts.ListOpts
|
||||
mounts opts.MountOpt
|
||||
blkioWeightDevice opts.WeightdeviceOpt
|
||||
deviceReadBps opts.ThrottledeviceOpt
|
||||
deviceWriteBps opts.ThrottledeviceOpt
|
||||
links opts.ListOpts
|
||||
aliases opts.ListOpts
|
||||
linkLocalIPs opts.ListOpts
|
||||
deviceReadIOps opts.ThrottledeviceOpt
|
||||
deviceWriteIOps opts.ThrottledeviceOpt
|
||||
env opts.ListOpts
|
||||
labels opts.ListOpts
|
||||
deviceCgroupRules opts.ListOpts
|
||||
devices opts.ListOpts
|
||||
gpus opts.GpuOpts
|
||||
ulimits *opts.UlimitOpt
|
||||
sysctls *opts.MapOpts
|
||||
publish opts.ListOpts
|
||||
expose opts.ListOpts
|
||||
dns opts.ListOpts
|
||||
dnsSearch opts.ListOpts
|
||||
dnsOptions opts.ListOpts
|
||||
extraHosts opts.ListOpts
|
||||
volumesFrom opts.ListOpts
|
||||
envFile opts.ListOpts
|
||||
capAdd opts.ListOpts
|
||||
capDrop opts.ListOpts
|
||||
groupAdd opts.ListOpts
|
||||
securityOpt opts.ListOpts
|
||||
storageOpt opts.ListOpts
|
||||
labelsFile opts.ListOpts
|
||||
loggingOpts opts.ListOpts
|
||||
privileged bool
|
||||
pidMode string
|
||||
utsMode string
|
||||
usernsMode string
|
||||
cgroupnsMode string
|
||||
publishAll bool
|
||||
stdin bool
|
||||
tty bool
|
||||
oomKillDisable bool
|
||||
oomScoreAdj int
|
||||
containerIDFile string
|
||||
entrypoint string
|
||||
hostname string
|
||||
domainname string
|
||||
memory opts.MemBytes
|
||||
memoryReservation opts.MemBytes
|
||||
memorySwap opts.MemSwapBytes
|
||||
kernelMemory opts.MemBytes
|
||||
user string
|
||||
workingDir string
|
||||
cpuCount int64
|
||||
cpuShares int64
|
||||
cpuPercent int64
|
||||
cpuPeriod int64
|
||||
cpuRealtimePeriod int64
|
||||
cpuRealtimeRuntime int64
|
||||
cpuQuota int64
|
||||
cpus opts.NanoCPUs
|
||||
cpusetCpus string
|
||||
cpusetMems string
|
||||
blkioWeight uint16
|
||||
ioMaxBandwidth opts.MemBytes
|
||||
ioMaxIOps uint64
|
||||
swappiness int64
|
||||
netMode opts.NetworkOpt
|
||||
macAddress string
|
||||
ipv4Address string
|
||||
ipv6Address string
|
||||
ipcMode string
|
||||
pidsLimit int64
|
||||
restartPolicy string
|
||||
readonlyRootfs bool
|
||||
loggingDriver string
|
||||
cgroupParent string
|
||||
volumeDriver string
|
||||
stopSignal string
|
||||
stopTimeout int
|
||||
isolation string
|
||||
shmSize opts.MemBytes
|
||||
noHealthcheck bool
|
||||
healthCmd string
|
||||
healthInterval time.Duration
|
||||
healthTimeout time.Duration
|
||||
healthStartPeriod time.Duration
|
||||
healthRetries int
|
||||
runtime string
|
||||
autoRemove bool
|
||||
init bool
|
||||
annotations *opts.MapOpts
|
||||
|
||||
Image string
|
||||
Args []string
|
||||
@ -185,7 +183,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||
flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
|
||||
flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
|
||||
flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
|
||||
flags.StringVar(&copts.restartPolicy, "restart", string(container.RestartPolicyDisabled), "Restart policy to apply when a container exits")
|
||||
flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
|
||||
flags.StringVar(&copts.stopSignal, "stop-signal", "", "Signal to stop the container")
|
||||
flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
|
||||
flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
|
||||
@ -252,8 +250,6 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||
flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)")
|
||||
flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)")
|
||||
flags.SetAnnotation("health-start-period", "version", []string{"1.29"})
|
||||
flags.DurationVar(&copts.healthStartInterval, "health-start-interval", 0, "Time between running the check during the start period (ms|s|m|h) (default 0s)")
|
||||
flags.SetAnnotation("health-start-interval", "version", []string{"1.44"})
|
||||
flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
|
||||
|
||||
// Resource management
|
||||
@ -358,10 +354,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
volumes := copts.volumes.GetMap()
|
||||
// add any bind targets to the list of container volumes
|
||||
for bind := range copts.volumes.GetMap() {
|
||||
parsed, err := loader.ParseVolume(bind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed, _ := loader.ParseVolume(bind)
|
||||
|
||||
if parsed.Source != "" {
|
||||
toBind := bind
|
||||
@ -456,17 +449,12 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
// parsing flags, we haven't yet sent a _ping to the daemon to determine
|
||||
// what operating system it is.
|
||||
deviceMappings := []container.DeviceMapping{}
|
||||
var cdiDeviceNames []string
|
||||
for _, device := range copts.devices.GetAll() {
|
||||
var (
|
||||
validated string
|
||||
deviceMapping container.DeviceMapping
|
||||
err error
|
||||
)
|
||||
if cdi.IsQualifiedName(device) {
|
||||
cdiDeviceNames = append(cdiDeviceNames, device)
|
||||
continue
|
||||
}
|
||||
validated, err = validateDevice(device, serverOS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -538,8 +526,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
copts.healthInterval != 0 ||
|
||||
copts.healthTimeout != 0 ||
|
||||
copts.healthStartPeriod != 0 ||
|
||||
copts.healthRetries != 0 ||
|
||||
copts.healthStartInterval != 0
|
||||
copts.healthRetries != 0
|
||||
if copts.noHealthcheck {
|
||||
if haveHealthSettings {
|
||||
return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options")
|
||||
@ -562,29 +549,16 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
if copts.healthStartPeriod < 0 {
|
||||
return nil, fmt.Errorf("--health-start-period cannot be negative")
|
||||
}
|
||||
if copts.healthStartInterval < 0 {
|
||||
return nil, fmt.Errorf("--health-start-interval cannot be negative")
|
||||
}
|
||||
|
||||
healthConfig = &container.HealthConfig{
|
||||
Test: probe,
|
||||
Interval: copts.healthInterval,
|
||||
Timeout: copts.healthTimeout,
|
||||
StartPeriod: copts.healthStartPeriod,
|
||||
StartInterval: copts.healthStartInterval,
|
||||
Retries: copts.healthRetries,
|
||||
Test: probe,
|
||||
Interval: copts.healthInterval,
|
||||
Timeout: copts.healthTimeout,
|
||||
StartPeriod: copts.healthStartPeriod,
|
||||
Retries: copts.healthRetries,
|
||||
}
|
||||
}
|
||||
|
||||
deviceRequests := copts.gpus.Value()
|
||||
if len(cdiDeviceNames) > 0 {
|
||||
cdiDeviceRequest := container.DeviceRequest{
|
||||
Driver: "cdi",
|
||||
DeviceIDs: cdiDeviceNames,
|
||||
}
|
||||
deviceRequests = append(deviceRequests, cdiDeviceRequest)
|
||||
}
|
||||
|
||||
resources := container.Resources{
|
||||
CgroupParent: copts.cgroupParent,
|
||||
Memory: copts.memory.Value(),
|
||||
@ -615,7 +589,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
Ulimits: copts.ulimits.GetList(),
|
||||
DeviceCgroupRules: copts.deviceCgroupRules.GetAll(),
|
||||
Devices: deviceMappings,
|
||||
DeviceRequests: deviceRequests,
|
||||
DeviceRequests: copts.gpus.Value(),
|
||||
}
|
||||
|
||||
config := &container.Config{
|
||||
@ -712,12 +686,6 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Put the endpoint-specific MacAddress of the "main" network attachment into the container Config for backward
|
||||
// compatibility with older daemons.
|
||||
if nw, ok := networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()]; ok {
|
||||
config.MacAddress = nw.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
}
|
||||
|
||||
return &containerConfig{
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
@ -738,20 +706,6 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
|
||||
hasUserDefined, hasNonUserDefined bool
|
||||
)
|
||||
|
||||
if len(copts.netMode.Value()) == 0 {
|
||||
n := opts.NetworkAttachmentOpts{
|
||||
Target: "default",
|
||||
}
|
||||
if err := applyContainerOptions(&n, copts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ep, err := parseNetworkAttachmentOpt(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoints["default"] = ep
|
||||
}
|
||||
|
||||
for i, n := range copts.netMode.Value() {
|
||||
n := n
|
||||
if container.NetworkMode(n.Target).IsUserDefined() {
|
||||
@ -793,7 +747,8 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { //nolint:gocyclo
|
||||
func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error {
|
||||
// TODO should copts.MacAddress actually be set on the first network? (currently it's not)
|
||||
// TODO should we error if _any_ advanced option is used? (i.e. forbid to combine advanced notation with the "old" flags (`--network-alias`, `--link`, `--ip`, `--ip6`)?
|
||||
if len(n.Aliases) > 0 && copts.aliases.Len() > 0 {
|
||||
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias"))
|
||||
@ -807,17 +762,11 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
|
||||
if n.IPv6Address != "" && copts.ipv6Address != "" {
|
||||
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address"))
|
||||
}
|
||||
if n.MacAddress != "" && copts.macAddress != "" {
|
||||
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --mac-address and per-network MAC address"))
|
||||
}
|
||||
if len(n.LinkLocalIPs) > 0 && copts.linkLocalIPs.Len() > 0 {
|
||||
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses"))
|
||||
}
|
||||
if copts.aliases.Len() > 0 {
|
||||
n.Aliases = make([]string, copts.aliases.Len())
|
||||
copy(n.Aliases, copts.aliases.GetAll())
|
||||
}
|
||||
if n.Target != "default" && copts.links.Len() > 0 {
|
||||
if copts.links.Len() > 0 {
|
||||
n.Links = make([]string, copts.links.Len())
|
||||
copy(n.Links, copts.links.GetAll())
|
||||
}
|
||||
@ -827,9 +776,8 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
|
||||
if copts.ipv6Address != "" {
|
||||
n.IPv6Address = copts.ipv6Address
|
||||
}
|
||||
if copts.macAddress != "" {
|
||||
n.MacAddress = copts.macAddress
|
||||
}
|
||||
|
||||
// TODO should linkLocalIPs be added to the _first_ network only, or to _all_ networks? (should this be a per-network option as well?)
|
||||
if copts.linkLocalIPs.Len() > 0 {
|
||||
n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
|
||||
copy(n.LinkLocalIPs, copts.linkLocalIPs.GetAll())
|
||||
@ -866,12 +814,6 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
|
||||
LinkLocalIPs: ep.LinkLocalIPs,
|
||||
}
|
||||
}
|
||||
if ep.MacAddress != "" {
|
||||
if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil {
|
||||
return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
|
||||
}
|
||||
epConfig.MacAddress = ep.MacAddress
|
||||
}
|
||||
return epConfig, nil
|
||||
}
|
||||
|
||||
@ -1119,8 +1061,8 @@ func validateAttach(val string) (string, error) {
|
||||
|
||||
func validateAPIVersion(c *containerConfig, serverAPIVersion string) error {
|
||||
for _, m := range c.HostConfig.Mounts {
|
||||
if err := command.ValidateMountWithAPIVersion(m, serverAPIVersion); err != nil {
|
||||
return err
|
||||
if m.BindOptions != nil && m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") {
|
||||
return errors.Errorf("bind-nonrecursive requires API v1.40 or later")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -64,21 +64,21 @@ func setupRunFlags() (*pflag.FlagSet, *containerOptions) {
|
||||
return flags, copts
|
||||
}
|
||||
|
||||
func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig) {
|
||||
func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
|
||||
t.Helper()
|
||||
config, hostConfig, nwConfig, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash"))
|
||||
config, hostConfig, _, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash"))
|
||||
assert.NilError(t, err)
|
||||
return config, hostConfig, nwConfig
|
||||
return config, hostConfig
|
||||
}
|
||||
|
||||
func TestParseRunLinks(t *testing.T) {
|
||||
if _, hostConfig, _ := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
|
||||
if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
|
||||
t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
|
||||
}
|
||||
if _, hostConfig, _ := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
|
||||
if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
|
||||
t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
|
||||
}
|
||||
if _, hostConfig, _ := mustParse(t, ""); len(hostConfig.Links) != 0 {
|
||||
if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
|
||||
t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
|
||||
}
|
||||
}
|
||||
@ -128,7 +128,7 @@ func TestParseRunAttach(t *testing.T) {
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
config, _, _ := mustParse(t, tc.input)
|
||||
config, _ := mustParse(t, tc.input)
|
||||
assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin)
|
||||
assert.Equal(t, config.AttachStdout, tc.expected.AttachStdout)
|
||||
assert.Equal(t, config.AttachStderr, tc.expected.AttachStderr)
|
||||
@ -186,7 +186,7 @@ func TestParseRunWithInvalidArgs(t *testing.T) {
|
||||
func TestParseWithVolumes(t *testing.T) {
|
||||
// A single volume
|
||||
arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
|
||||
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds != nil {
|
||||
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes[arr[0]]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
|
||||
@ -194,23 +194,23 @@ func TestParseWithVolumes(t *testing.T) {
|
||||
|
||||
// Two volumes
|
||||
arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
|
||||
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds != nil {
|
||||
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes[arr[0]]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
|
||||
} else if _, exists := config.Volumes[arr[1]]; !exists { //nolint:govet // ignore shadow-check
|
||||
} else if _, exists := config.Volumes[arr[1]]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
|
||||
}
|
||||
|
||||
// A single bind mount
|
||||
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
|
||||
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
|
||||
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
|
||||
t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
|
||||
}
|
||||
|
||||
// Two bind mounts.
|
||||
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
|
||||
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
||||
}
|
||||
|
||||
@ -219,26 +219,26 @@ func TestParseWithVolumes(t *testing.T) {
|
||||
arr, tryit = setupPlatformVolume(
|
||||
[]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`},
|
||||
[]string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
|
||||
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
||||
}
|
||||
|
||||
// Similar to previous test but with alternate modes which are only supported by Linux
|
||||
if runtime.GOOS != "windows" {
|
||||
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
|
||||
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
||||
}
|
||||
|
||||
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
|
||||
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
|
||||
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
|
||||
}
|
||||
}
|
||||
|
||||
// One bind mount and one volume
|
||||
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
|
||||
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
|
||||
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
|
||||
t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes[arr[1]]; !exists {
|
||||
t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
|
||||
@ -247,7 +247,7 @@ func TestParseWithVolumes(t *testing.T) {
|
||||
// Root to non-c: drive letter (Windows specific)
|
||||
if runtime.GOOS == "windows" {
|
||||
arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
|
||||
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
|
||||
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
|
||||
t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
|
||||
}
|
||||
}
|
||||
@ -290,14 +290,8 @@ func TestParseWithMacAddress(t *testing.T) {
|
||||
if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
|
||||
t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
|
||||
}
|
||||
config, hostConfig, nwConfig := mustParse(t, validMacAddress)
|
||||
if config.MacAddress != "92:d0:c6:0a:29:33" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as container-wide MacAddress, got '%v'",
|
||||
config.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
}
|
||||
defaultNw := hostConfig.NetworkMode.NetworkName()
|
||||
if nwConfig.EndpointsConfig[defaultNw].MacAddress != "92:d0:c6:0a:29:33" {
|
||||
t.Fatalf("Expected the default endpoint to have the MacAddress '92:d0:c6:0a:29:33' set, got '%v'", nwConfig.EndpointsConfig[defaultNw].MacAddress)
|
||||
if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
|
||||
t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,7 +301,7 @@ func TestRunFlagsParseWithMemory(t *testing.T) {
|
||||
err := flags.Parse(args)
|
||||
assert.ErrorContains(t, err, `invalid argument "invalid" for "-m, --memory" flag`)
|
||||
|
||||
_, hostconfig, _ := mustParse(t, "--memory=1G")
|
||||
_, hostconfig := mustParse(t, "--memory=1G")
|
||||
assert.Check(t, is.Equal(int64(1073741824), hostconfig.Memory))
|
||||
}
|
||||
|
||||
@ -317,10 +311,10 @@ func TestParseWithMemorySwap(t *testing.T) {
|
||||
err := flags.Parse(args)
|
||||
assert.ErrorContains(t, err, `invalid argument "invalid" for "--memory-swap" flag`)
|
||||
|
||||
_, hostconfig, _ := mustParse(t, "--memory-swap=1G")
|
||||
_, hostconfig := mustParse(t, "--memory-swap=1G")
|
||||
assert.Check(t, is.Equal(int64(1073741824), hostconfig.MemorySwap))
|
||||
|
||||
_, hostconfig, _ = mustParse(t, "--memory-swap=-1")
|
||||
_, hostconfig = mustParse(t, "--memory-swap=-1")
|
||||
assert.Check(t, is.Equal(int64(-1), hostconfig.MemorySwap))
|
||||
}
|
||||
|
||||
@ -335,14 +329,14 @@ func TestParseHostname(t *testing.T) {
|
||||
hostnameWithDomain := "--hostname=hostname.domainname"
|
||||
hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
|
||||
for hostname, expectedHostname := range validHostnames {
|
||||
if config, _, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
|
||||
if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
|
||||
t.Fatalf("Expected the config to have 'hostname' as %q, got %q", expectedHostname, config.Hostname)
|
||||
}
|
||||
}
|
||||
if config, _, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" || config.Domainname != "" {
|
||||
if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" || config.Domainname != "" {
|
||||
t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got %q", config.Hostname)
|
||||
}
|
||||
if config, _, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" || config.Domainname != "" {
|
||||
if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" || config.Domainname != "" {
|
||||
t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got %q", config.Hostname)
|
||||
}
|
||||
}
|
||||
@ -356,14 +350,14 @@ func TestParseHostnameDomainname(t *testing.T) {
|
||||
"domainname-63-bytes-long-should-be-valid-and-without-any-errors": "domainname-63-bytes-long-should-be-valid-and-without-any-errors",
|
||||
}
|
||||
for domainname, expectedDomainname := range validDomainnames {
|
||||
if config, _, _ := mustParse(t, "--domainname="+domainname); config.Domainname != expectedDomainname {
|
||||
if config, _ := mustParse(t, "--domainname="+domainname); config.Domainname != expectedDomainname {
|
||||
t.Fatalf("Expected the config to have 'domainname' as %q, got %q", expectedDomainname, config.Domainname)
|
||||
}
|
||||
}
|
||||
if config, _, _ := mustParse(t, "--hostname=some.prefix --domainname=domainname"); config.Hostname != "some.prefix" || config.Domainname != "domainname" {
|
||||
if config, _ := mustParse(t, "--hostname=some.prefix --domainname=domainname"); config.Hostname != "some.prefix" || config.Domainname != "domainname" {
|
||||
t.Fatalf("Expected the config to have 'hostname' as 'some.prefix' and 'domainname' as 'domainname', got %q and %q", config.Hostname, config.Domainname)
|
||||
}
|
||||
if config, _, _ := mustParse(t, "--hostname=another-prefix --domainname=domainname.tld"); config.Hostname != "another-prefix" || config.Domainname != "domainname.tld" {
|
||||
if config, _ := mustParse(t, "--hostname=another-prefix --domainname=domainname.tld"); config.Hostname != "another-prefix" || config.Domainname != "domainname.tld" {
|
||||
t.Fatalf("Expected the config to have 'hostname' as 'another-prefix' and 'domainname' as 'domainname.tld', got %q and %q", config.Hostname, config.Domainname)
|
||||
}
|
||||
}
|
||||
@ -372,8 +366,8 @@ func TestParseWithExpose(t *testing.T) {
|
||||
invalids := map[string]string{
|
||||
":": "invalid port format for --expose: :",
|
||||
"8080:9090": "invalid port format for --expose: 8080:9090",
|
||||
"/tcp": "invalid range format for --expose: /tcp, error: empty string specified for ports",
|
||||
"/udp": "invalid range format for --expose: /udp, error: empty string specified for ports",
|
||||
"/tcp": "invalid range format for --expose: /tcp, error: Empty string specified for ports.",
|
||||
"/udp": "invalid range format for --expose: /udp, error: Empty string specified for ports.",
|
||||
"NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
||||
"NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
||||
"8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
||||
@ -423,114 +417,61 @@ func TestParseWithExpose(t *testing.T) {
|
||||
|
||||
func TestParseDevice(t *testing.T) {
|
||||
skip.If(t, runtime.GOOS != "linux") // Windows and macOS validate server-side
|
||||
testCases := []struct {
|
||||
devices []string
|
||||
deviceMapping *container.DeviceMapping
|
||||
deviceRequests []container.DeviceRequest
|
||||
}{
|
||||
{
|
||||
devices: []string{"/dev/snd"},
|
||||
deviceMapping: &container.DeviceMapping{
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/dev/snd",
|
||||
CgroupPermissions: "rwm",
|
||||
},
|
||||
valids := map[string]container.DeviceMapping{
|
||||
"/dev/snd": {
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/dev/snd",
|
||||
CgroupPermissions: "rwm",
|
||||
},
|
||||
{
|
||||
devices: []string{"/dev/snd:rw"},
|
||||
deviceMapping: &container.DeviceMapping{
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/dev/snd",
|
||||
CgroupPermissions: "rw",
|
||||
},
|
||||
"/dev/snd:rw": {
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/dev/snd",
|
||||
CgroupPermissions: "rw",
|
||||
},
|
||||
{
|
||||
devices: []string{"/dev/snd:/something"},
|
||||
deviceMapping: &container.DeviceMapping{
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/something",
|
||||
CgroupPermissions: "rwm",
|
||||
},
|
||||
"/dev/snd:/something": {
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/something",
|
||||
CgroupPermissions: "rwm",
|
||||
},
|
||||
{
|
||||
devices: []string{"/dev/snd:/something:rw"},
|
||||
deviceMapping: &container.DeviceMapping{
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/something",
|
||||
CgroupPermissions: "rw",
|
||||
},
|
||||
},
|
||||
{
|
||||
devices: []string{"vendor.com/class=name"},
|
||||
deviceMapping: nil,
|
||||
deviceRequests: []container.DeviceRequest{
|
||||
{
|
||||
Driver: "cdi",
|
||||
DeviceIDs: []string{"vendor.com/class=name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
devices: []string{"vendor.com/class=name", "/dev/snd:/something:rw"},
|
||||
deviceMapping: &container.DeviceMapping{
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/something",
|
||||
CgroupPermissions: "rw",
|
||||
},
|
||||
deviceRequests: []container.DeviceRequest{
|
||||
{
|
||||
Driver: "cdi",
|
||||
DeviceIDs: []string{"vendor.com/class=name"},
|
||||
},
|
||||
},
|
||||
"/dev/snd:/something:rw": {
|
||||
PathOnHost: "/dev/snd",
|
||||
PathInContainer: "/something",
|
||||
CgroupPermissions: "rw",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%s", tc.devices), func(t *testing.T) {
|
||||
var args []string
|
||||
for _, d := range tc.devices {
|
||||
args = append(args, fmt.Sprintf("--device=%v", d))
|
||||
}
|
||||
args = append(args, "img", "cmd")
|
||||
|
||||
_, hostconfig, _, err := parseRun(args)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
if tc.deviceMapping != nil {
|
||||
if assert.Check(t, is.Len(hostconfig.Devices, 1)) {
|
||||
assert.Check(t, is.DeepEqual(*tc.deviceMapping, hostconfig.Devices[0]))
|
||||
}
|
||||
} else {
|
||||
assert.Check(t, is.Len(hostconfig.Devices, 0))
|
||||
}
|
||||
|
||||
assert.Check(t, is.DeepEqual(tc.deviceRequests, hostconfig.DeviceRequests))
|
||||
})
|
||||
for device, deviceMapping := range valids {
|
||||
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hostconfig.Devices) != 1 {
|
||||
t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
|
||||
}
|
||||
if hostconfig.Devices[0] != deviceMapping {
|
||||
t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
flags []string
|
||||
expected map[string]*networktypes.EndpointSettings
|
||||
expectedCfg container.Config
|
||||
expectedHostCfg container.HostConfig
|
||||
expectedErr string
|
||||
name string
|
||||
flags []string
|
||||
expected map[string]*networktypes.EndpointSettings
|
||||
expectedCfg container.HostConfig
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "single-network-legacy",
|
||||
flags: []string{"--network", "net1"},
|
||||
expected: map[string]*networktypes.EndpointSettings{},
|
||||
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
name: "single-network-legacy",
|
||||
flags: []string{"--network", "net1"},
|
||||
expected: map[string]*networktypes.EndpointSettings{},
|
||||
expectedCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
},
|
||||
{
|
||||
name: "single-network-advanced",
|
||||
flags: []string{"--network", "name=net1"},
|
||||
expected: map[string]*networktypes.EndpointSettings{},
|
||||
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
name: "single-network-advanced",
|
||||
flags: []string{"--network", "name=net1"},
|
||||
expected: map[string]*networktypes.EndpointSettings{},
|
||||
expectedCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
},
|
||||
{
|
||||
name: "single-network-legacy-with-options",
|
||||
@ -556,7 +497,7 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
Aliases: []string{"web1", "web2"},
|
||||
},
|
||||
},
|
||||
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
expectedCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
},
|
||||
{
|
||||
name: "multiple-network-advanced-mixed",
|
||||
@ -572,7 +513,6 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
"--network-alias", "web2",
|
||||
"--network", "net2",
|
||||
"--network", "name=net3,alias=web3,driver-opt=field3=value3,ip=172.20.88.22,ip6=2001:db8::8822",
|
||||
"--network", "name=net4,mac-address=02:32:1c:23:00:04,link-local-ip=169.254.169.254",
|
||||
},
|
||||
expected: map[string]*networktypes.EndpointSettings{
|
||||
"net1": {
|
||||
@ -594,18 +534,12 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
},
|
||||
Aliases: []string{"web3"},
|
||||
},
|
||||
"net4": {
|
||||
MacAddress: "02:32:1c:23:00:04",
|
||||
IPAMConfig: &networktypes.EndpointIPAMConfig{
|
||||
LinkLocalIPs: []string{"169.254.169.254"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
expectedCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
},
|
||||
{
|
||||
name: "single-network-advanced-with-options",
|
||||
flags: []string{"--network", "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2,ip=172.20.88.22,ip6=2001:db8::8822,mac-address=02:32:1c:23:00:04"},
|
||||
flags: []string{"--network", "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2,ip=172.20.88.22,ip6=2001:db8::8822"},
|
||||
expected: map[string]*networktypes.EndpointSettings{
|
||||
"net1": {
|
||||
DriverOpts: map[string]string{
|
||||
@ -616,30 +550,16 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
IPv4Address: "172.20.88.22",
|
||||
IPv6Address: "2001:db8::8822",
|
||||
},
|
||||
Aliases: []string{"web1", "web2"},
|
||||
MacAddress: "02:32:1c:23:00:04",
|
||||
Aliases: []string{"web1", "web2"},
|
||||
},
|
||||
},
|
||||
expectedCfg: container.Config{MacAddress: "02:32:1c:23:00:04"},
|
||||
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
expectedCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
},
|
||||
{
|
||||
name: "multiple-networks",
|
||||
flags: []string{"--network", "net1", "--network", "name=net2"},
|
||||
expected: map[string]*networktypes.EndpointSettings{"net1": {}, "net2": {}},
|
||||
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
},
|
||||
{
|
||||
name: "advanced-options-with-standalone-mac-address-flag",
|
||||
flags: []string{"--network=name=net1,alias=foobar", "--mac-address", "52:0f:f3:dc:50:10"},
|
||||
expected: map[string]*networktypes.EndpointSettings{
|
||||
"net1": {
|
||||
Aliases: []string{"foobar"},
|
||||
MacAddress: "52:0f:f3:dc:50:10",
|
||||
},
|
||||
},
|
||||
expectedCfg: container.Config{MacAddress: "52:0f:f3:dc:50:10"},
|
||||
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
name: "multiple-networks",
|
||||
flags: []string{"--network", "net1", "--network", "name=net2"},
|
||||
expected: map[string]*networktypes.EndpointSettings{"net1": {}, "net2": {}},
|
||||
expectedCfg: container.HostConfig{NetworkMode: "net1"},
|
||||
},
|
||||
{
|
||||
name: "conflict-network",
|
||||
@ -666,26 +586,11 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
flags: []string{"--network", "name=host", "--network", "net1"},
|
||||
expectedErr: `conflicting options: cannot attach both user-defined and non-user-defined network-modes`,
|
||||
},
|
||||
{
|
||||
name: "conflict-options-link-local-ip",
|
||||
flags: []string{"--network", "name=net1,link-local-ip=169.254.169.254", "--link-local-ip", "169.254.10.8"},
|
||||
expectedErr: `conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses`,
|
||||
},
|
||||
{
|
||||
name: "conflict-options-mac-address",
|
||||
flags: []string{"--network", "name=net1,mac-address=02:32:1c:23:00:04", "--mac-address", "02:32:1c:23:00:04"},
|
||||
expectedErr: `conflicting options: cannot specify both --mac-address and per-network MAC address`,
|
||||
},
|
||||
{
|
||||
name: "invalid-mac-address",
|
||||
flags: []string{"--network", "name=net1,mac-address=foobar"},
|
||||
expectedErr: "foobar is not a valid mac address",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
config, hConfig, nwConfig, err := parseRun(tc.flags)
|
||||
_, hConfig, nwConfig, err := parseRun(tc.flags)
|
||||
|
||||
if tc.expectedErr != "" {
|
||||
assert.Error(t, err, tc.expectedErr)
|
||||
@ -693,8 +598,7 @@ func TestParseNetworkConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, config.MacAddress, tc.expectedCfg.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedHostCfg.NetworkMode)
|
||||
assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedCfg.NetworkMode)
|
||||
assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected)
|
||||
})
|
||||
}
|
||||
@ -744,75 +648,34 @@ func TestRunFlagsParseShmSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseRestartPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected container.RestartPolicy
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
invalids := map[string]string{
|
||||
"always:2:3": "invalid restart policy format: maximum retry count must be an integer",
|
||||
"on-failure:invalid": "invalid restart policy format: maximum retry count must be an integer",
|
||||
}
|
||||
valids := map[string]container.RestartPolicy{
|
||||
"": {},
|
||||
"always": {
|
||||
Name: "always",
|
||||
MaximumRetryCount: 0,
|
||||
},
|
||||
{
|
||||
input: "no",
|
||||
expected: container.RestartPolicy{
|
||||
Name: container.RestartPolicyDisabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ":1",
|
||||
expectedErr: "invalid restart policy format: no policy provided before colon",
|
||||
},
|
||||
{
|
||||
input: "always",
|
||||
expected: container.RestartPolicy{
|
||||
Name: container.RestartPolicyAlways,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "always:1",
|
||||
expected: container.RestartPolicy{
|
||||
Name: container.RestartPolicyAlways,
|
||||
MaximumRetryCount: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "always:2:3",
|
||||
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
|
||||
},
|
||||
{
|
||||
input: "on-failure:1",
|
||||
expected: container.RestartPolicy{
|
||||
Name: container.RestartPolicyOnFailure,
|
||||
MaximumRetryCount: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on-failure:invalid",
|
||||
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
|
||||
},
|
||||
{
|
||||
input: "unless-stopped",
|
||||
expected: container.RestartPolicy{
|
||||
Name: container.RestartPolicyUnlessStopped,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "unless-stopped:invalid",
|
||||
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
|
||||
"on-failure:1": {
|
||||
Name: "on-failure",
|
||||
MaximumRetryCount: 1,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
_, hostConfig, _, err := parseRun([]string{"--restart=" + tc.input, "img", "cmd"})
|
||||
if tc.expectedErr != "" {
|
||||
assert.Check(t, is.Error(err, tc.expectedErr))
|
||||
assert.Check(t, is.Nil(hostConfig))
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(hostConfig.RestartPolicy, tc.expected))
|
||||
}
|
||||
})
|
||||
for restart, expectedError := range invalids {
|
||||
if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
|
||||
t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
|
||||
}
|
||||
}
|
||||
for restart, expected := range valids {
|
||||
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hostconfig.RestartPolicy != expected {
|
||||
t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -857,8 +720,8 @@ func TestParseHealth(t *testing.T) {
|
||||
checkError("--no-healthcheck conflicts with --health-* options",
|
||||
"--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd")
|
||||
|
||||
health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "--health-start-period=5s", "--health-start-interval=1s", "img", "cmd")
|
||||
if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond || health.StartPeriod != 5*time.Second || health.StartInterval != 1*time.Second {
|
||||
health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "--health-start-period=5s", "img", "cmd")
|
||||
if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond || health.StartPeriod != 5*time.Second {
|
||||
t.Fatalf("--health-*: got %#v", health)
|
||||
}
|
||||
}
|
||||
@ -1011,8 +874,10 @@ func TestValidateDevice(t *testing.T) {
|
||||
for path, expectedError := range invalid {
|
||||
if _, err := validateDevice(path, runtime.GOOS); err == nil {
|
||||
t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
|
||||
} else if err.Error() != expectedError {
|
||||
t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
|
||||
} else {
|
||||
if err.Error() != expectedError {
|
||||
t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -52,16 +52,18 @@ func runRm(dockerCli command.Cli, opts *rmOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, ctrID string) error {
|
||||
ctrID = strings.Trim(ctrID, "/")
|
||||
if ctrID == "" {
|
||||
options := types.ContainerRemoveOptions{
|
||||
RemoveVolumes: opts.rmVolumes,
|
||||
RemoveLinks: opts.rmLink,
|
||||
Force: opts.force,
|
||||
}
|
||||
|
||||
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
|
||||
container = strings.Trim(container, "/")
|
||||
if container == "" {
|
||||
return errors.New("Container name cannot be empty")
|
||||
}
|
||||
return dockerCli.Client().ContainerRemove(ctx, ctrID, container.RemoveOptions{
|
||||
RemoveVolumes: opts.rmVolumes,
|
||||
RemoveLinks: opts.rmLink,
|
||||
Force: opts.force,
|
||||
})
|
||||
return dockerCli.Client().ContainerRemove(ctx, container, options)
|
||||
})
|
||||
|
||||
for _, name := range opts.containers {
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
@ -29,7 +29,7 @@ func TestRemoveForce(t *testing.T) {
|
||||
mutex := new(sync.Mutex)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerRemoveFunc: func(ctx context.Context, container string, options container.RemoveOptions) error {
|
||||
containerRemoveFunc: func(ctx context.Context, container string, options types.ContainerRemoveOptions) error {
|
||||
// containerRemoveFunc is called in parallel for each container
|
||||
// by the remove command so append must be synchronized.
|
||||
mutex.Lock()
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/sys/signal"
|
||||
"github.com/moby/term"
|
||||
@ -100,7 +101,7 @@ func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copt
|
||||
if v == nil {
|
||||
newEnv = append(newEnv, k)
|
||||
} else {
|
||||
newEnv = append(newEnv, k+"="+*v)
|
||||
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
|
||||
}
|
||||
}
|
||||
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
||||
@ -118,14 +119,14 @@ func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copt
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func runContainer(dockerCli command.Cli, runOpts *runOptions, copts *containerOptions, containerCfg *containerConfig) error {
|
||||
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerCfg *containerConfig) error {
|
||||
config := containerCfg.Config
|
||||
stdout, stderr := dockerCli.Out(), dockerCli.Err()
|
||||
apiClient := dockerCli.Client()
|
||||
client := dockerCli.Client()
|
||||
|
||||
config.ArgsEscaped = false
|
||||
|
||||
if !runOpts.detach {
|
||||
if !opts.detach {
|
||||
if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -143,14 +144,14 @@ func runContainer(dockerCli command.Cli, runOpts *runOptions, copts *containerOp
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
defer cancelFun()
|
||||
|
||||
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
|
||||
createResponse, err := createContainer(ctx, dockerCli, containerCfg, &opts.createOptions)
|
||||
if err != nil {
|
||||
reportError(stderr, "run", err.Error(), true)
|
||||
return runStartContainerErr(err)
|
||||
}
|
||||
if runOpts.sigProxy {
|
||||
if opts.sigProxy {
|
||||
sigc := notifyAllSignals()
|
||||
go ForwardAllSignals(ctx, apiClient, containerID, sigc)
|
||||
go ForwardAllSignals(ctx, dockerCli, createResponse.ID, sigc)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
@ -163,33 +164,26 @@ func runContainer(dockerCli command.Cli, runOpts *runOptions, copts *containerOp
|
||||
waitDisplayID = make(chan struct{})
|
||||
go func() {
|
||||
defer close(waitDisplayID)
|
||||
_, _ = fmt.Fprintln(stdout, containerID)
|
||||
fmt.Fprintln(stdout, createResponse.ID)
|
||||
}()
|
||||
}
|
||||
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
|
||||
if attach {
|
||||
detachKeys := dockerCli.ConfigFile().DetachKeys
|
||||
if runOpts.detachKeys != "" {
|
||||
detachKeys = runOpts.detachKeys
|
||||
if opts.detachKeys != "" {
|
||||
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
|
||||
}
|
||||
|
||||
closeFn, err := attachContainer(ctx, dockerCli, containerID, &errCh, config, container.AttachOptions{
|
||||
Stream: true,
|
||||
Stdin: config.AttachStdin,
|
||||
Stdout: config.AttachStdout,
|
||||
Stderr: config.AttachStderr,
|
||||
DetachKeys: detachKeys,
|
||||
})
|
||||
closeFn, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeFn()
|
||||
}
|
||||
|
||||
statusChan := waitExitOrRemoved(ctx, apiClient, containerID, copts.autoRemove)
|
||||
statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)
|
||||
|
||||
// start the container
|
||||
if err := apiClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
|
||||
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
|
||||
// If we have hijackedIOStreamer, we should notify
|
||||
// hijackedIOStreamer we are going to exit and wait
|
||||
// to avoid the terminal are not restored.
|
||||
@ -207,8 +201,8 @@ func runContainer(dockerCli command.Cli, runOpts *runOptions, copts *containerOp
|
||||
}
|
||||
|
||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
|
||||
if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil {
|
||||
_, _ = fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
|
||||
if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
|
||||
fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +232,15 @@ func runContainer(dockerCli command.Cli, runOpts *runOptions, copts *containerOp
|
||||
return nil
|
||||
}
|
||||
|
||||
func attachContainer(ctx context.Context, dockerCli command.Cli, containerID string, errCh *chan error, config *container.Config, options container.AttachOptions) (func(), error) {
|
||||
func attachContainer(ctx context.Context, dockerCli command.Cli, errCh *chan error, config *container.Config, containerID string) (func(), error) {
|
||||
options := types.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: config.AttachStdin,
|
||||
Stdout: config.AttachStdout,
|
||||
Stderr: config.AttachStderr,
|
||||
DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
||||
}
|
||||
|
||||
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options)
|
||||
if errAttach != nil {
|
||||
return nil, errAttach
|
||||
@ -248,13 +250,13 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
|
||||
out, cerr io.Writer
|
||||
in io.ReadCloser
|
||||
)
|
||||
if options.Stdin {
|
||||
if config.AttachStdin {
|
||||
in = dockerCli.In()
|
||||
}
|
||||
if options.Stdout {
|
||||
if config.AttachStdout {
|
||||
out = dockerCli.Out()
|
||||
}
|
||||
if options.Stderr {
|
||||
if config.AttachStderr {
|
||||
if config.Tty {
|
||||
cerr = dockerCli.Out()
|
||||
} else {
|
||||
|
||||
@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func TestRunLabel(t *testing.T) {
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *specs.Platform, _ string) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{
|
||||
ID: "id",
|
||||
@ -26,7 +26,7 @@ func TestRunLabel(t *testing.T) {
|
||||
},
|
||||
Version: "1.36",
|
||||
})
|
||||
cmd := NewRunCommand(fakeCLI)
|
||||
cmd := NewRunCommand(cli)
|
||||
cmd.SetArgs([]string{"--detach=true", "--label", "foo", "busybox"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
||||
@ -58,7 +58,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig,
|
||||
@ -68,13 +68,13 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := NewRunCommand(fakeCLI)
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := NewRunCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOut(io.Discard)
|
||||
err := cmd.Execute()
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, is.Contains(fakeCLI.ErrBuffer().String(), tc.expectedError))
|
||||
assert.Assert(t, is.Contains(cli.ErrBuffer().String(), tc.expectedError))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/sys/signal"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -13,7 +13,7 @@ import (
|
||||
// ForwardAllSignals forwards signals to the container
|
||||
//
|
||||
// The channel you pass in must already be setup to receive any signals you want to forward.
|
||||
func ForwardAllSignals(ctx context.Context, apiClient client.ContainerAPIClient, cid string, sigc <-chan os.Signal) {
|
||||
func ForwardAllSignals(ctx context.Context, cli command.Cli, cid string, sigc <-chan os.Signal) {
|
||||
var (
|
||||
s os.Signal
|
||||
ok bool
|
||||
@ -48,7 +48,7 @@ func ForwardAllSignals(ctx context.Context, apiClient client.ContainerAPIClient,
|
||||
continue
|
||||
}
|
||||
|
||||
if err := apiClient.ContainerKill(ctx, cid, sig); err != nil {
|
||||
if err := cli.Client().ContainerKill(ctx, cid, sig); err != nil {
|
||||
logrus.Debugf("Error sending signal: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/sys/signal"
|
||||
)
|
||||
|
||||
@ -14,15 +15,16 @@ func TestForwardSignals(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
called := make(chan struct{})
|
||||
apiClient := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
|
||||
client := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
|
||||
close(called)
|
||||
return nil
|
||||
}}
|
||||
|
||||
cli := test.NewFakeCli(client)
|
||||
sigc := make(chan os.Signal)
|
||||
defer close(sigc)
|
||||
|
||||
go ForwardAllSignals(ctx, apiClient, t.Name(), sigc)
|
||||
go ForwardAllSignals(ctx, cli, t.Name(), sigc)
|
||||
|
||||
timer := time.NewTimer(30 * time.Second)
|
||||
defer timer.Stop()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package container
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package container
|
||||
|
||||
@ -9,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"golang.org/x/sys/unix"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
@ -22,17 +24,18 @@ func TestIgnoredSignals(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
var called bool
|
||||
apiClient := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
|
||||
client := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
|
||||
called = true
|
||||
return nil
|
||||
}}
|
||||
|
||||
cli := test.NewFakeCli(client)
|
||||
sigc := make(chan os.Signal)
|
||||
defer close(sigc)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
ForwardAllSignals(ctx, apiClient, t.Name(), sigc)
|
||||
ForwardAllSignals(ctx, cli, t.Name(), sigc)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/sys/signal"
|
||||
"github.com/moby/term"
|
||||
"github.com/pkg/errors"
|
||||
@ -28,9 +27,7 @@ type StartOptions struct {
|
||||
Containers []string
|
||||
}
|
||||
|
||||
// NewStartOptions creates a new StartOptions.
|
||||
//
|
||||
// Deprecated: create a new [StartOptions] directly.
|
||||
// NewStartOptions creates a new StartOptions
|
||||
func NewStartOptions() StartOptions {
|
||||
return StartOptions{}
|
||||
}
|
||||
@ -76,8 +73,7 @@ func RunStart(dockerCli command.Cli, opts *StartOptions) error {
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
defer cancelFun()
|
||||
|
||||
switch {
|
||||
case opts.Attach || opts.OpenStdin:
|
||||
if opts.Attach || opts.OpenStdin {
|
||||
// We're going to attach to a container.
|
||||
// 1. Ensure we only have one container.
|
||||
if len(opts.Containers) > 1 {
|
||||
@ -85,8 +81,8 @@ func RunStart(dockerCli command.Cli, opts *StartOptions) error {
|
||||
}
|
||||
|
||||
// 2. Attach to the container.
|
||||
ctr := opts.Containers[0]
|
||||
c, err := dockerCli.Client().ContainerInspect(ctx, ctr)
|
||||
container := opts.Containers[0]
|
||||
c, err := dockerCli.Client().ContainerInspect(ctx, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -94,21 +90,20 @@ func RunStart(dockerCli command.Cli, opts *StartOptions) error {
|
||||
// We always use c.ID instead of container to maintain consistency during `docker start`
|
||||
if !c.Config.Tty {
|
||||
sigc := notifyAllSignals()
|
||||
go ForwardAllSignals(ctx, dockerCli.Client(), c.ID, sigc)
|
||||
go ForwardAllSignals(ctx, dockerCli, c.ID, sigc)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
detachKeys := dockerCli.ConfigFile().DetachKeys
|
||||
if opts.DetachKeys != "" {
|
||||
detachKeys = opts.DetachKeys
|
||||
dockerCli.ConfigFile().DetachKeys = opts.DetachKeys
|
||||
}
|
||||
|
||||
options := container.AttachOptions{
|
||||
options := types.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: opts.OpenStdin && c.Config.OpenStdin,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
DetachKeys: detachKeys,
|
||||
DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
@ -147,14 +142,14 @@ func RunStart(dockerCli command.Cli, opts *StartOptions) error {
|
||||
|
||||
// 3. We should open a channel for receiving status code of the container
|
||||
// no matter it's detached, removed on daemon side(--rm) or exit normally.
|
||||
statusChan := waitExitOrRemoved(ctx, dockerCli.Client(), c.ID, c.HostConfig.AutoRemove)
|
||||
|
||||
// 4. Start the container.
|
||||
err = dockerCli.Client().ContainerStart(ctx, c.ID, container.StartOptions{
|
||||
statusChan := waitExitOrRemoved(ctx, dockerCli, c.ID, c.HostConfig.AutoRemove)
|
||||
startOptions := types.ContainerStartOptions{
|
||||
CheckpointID: opts.Checkpoint,
|
||||
CheckpointDir: opts.CheckpointDir,
|
||||
})
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
// 4. Start the container.
|
||||
if err := dockerCli.Client().ContainerStart(ctx, c.ID, startOptions); err != nil {
|
||||
cancelFun()
|
||||
<-cErr
|
||||
if c.HostConfig.AutoRemove {
|
||||
@ -181,32 +176,35 @@ func RunStart(dockerCli command.Cli, opts *StartOptions) error {
|
||||
if status := <-statusChan; status != 0 {
|
||||
return cli.StatusError{StatusCode: status}
|
||||
}
|
||||
return nil
|
||||
case opts.Checkpoint != "":
|
||||
} else if opts.Checkpoint != "" {
|
||||
if len(opts.Containers) > 1 {
|
||||
return errors.New("you cannot restore multiple containers at once")
|
||||
}
|
||||
ctr := opts.Containers[0]
|
||||
return dockerCli.Client().ContainerStart(ctx, ctr, container.StartOptions{
|
||||
container := opts.Containers[0]
|
||||
startOptions := types.ContainerStartOptions{
|
||||
CheckpointID: opts.Checkpoint,
|
||||
CheckpointDir: opts.CheckpointDir,
|
||||
})
|
||||
default:
|
||||
}
|
||||
return dockerCli.Client().ContainerStart(ctx, container, startOptions)
|
||||
|
||||
} else {
|
||||
// We're not going to attach to anything.
|
||||
// Start as many containers as we want.
|
||||
return startContainersWithoutAttachments(ctx, dockerCli, opts.Containers)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func startContainersWithoutAttachments(ctx context.Context, dockerCli command.Cli, containers []string) error {
|
||||
var failedContainers []string
|
||||
for _, ctr := range containers {
|
||||
if err := dockerCli.Client().ContainerStart(ctx, ctr, container.StartOptions{}); err != nil {
|
||||
for _, container := range containers {
|
||||
if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
|
||||
fmt.Fprintln(dockerCli.Err(), err)
|
||||
failedContainers = append(failedContainers, ctr)
|
||||
failedContainers = append(failedContainers, container)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), ctr)
|
||||
fmt.Fprintln(dockerCli.Out(), container)
|
||||
}
|
||||
|
||||
if len(failedContainers) > 0 {
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/pkg/errors"
|
||||
@ -111,15 +110,15 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error {
|
||||
// getContainerList simulates creation event for all previously existing
|
||||
// containers (only used when calling `docker stats` without arguments).
|
||||
getContainerList := func() {
|
||||
options := container.ListOptions{
|
||||
options := types.ContainerListOptions{
|
||||
All: opts.all,
|
||||
}
|
||||
cs, err := dockerCli.Client().ContainerList(ctx, options)
|
||||
if err != nil {
|
||||
closeChan <- err
|
||||
}
|
||||
for _, ctr := range cs {
|
||||
s := NewStats(ctr.ID[:12])
|
||||
for _, container := range cs {
|
||||
s := NewStats(container.ID[:12])
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
|
||||
@ -134,9 +133,9 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error {
|
||||
// would "miss" a creation.
|
||||
started := make(chan struct{})
|
||||
eh := command.InitEventHandler()
|
||||
eh.Handle(events.ActionCreate, func(e events.Message) {
|
||||
eh.Handle("create", func(e events.Message) {
|
||||
if opts.all {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
s := NewStats(e.ID[:12])
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
|
||||
@ -144,17 +143,17 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error {
|
||||
}
|
||||
})
|
||||
|
||||
eh.Handle(events.ActionStart, func(e events.Message) {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
eh.Handle("start", func(e events.Message) {
|
||||
s := NewStats(e.ID[:12])
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
|
||||
}
|
||||
})
|
||||
|
||||
eh.Handle(events.ActionDie, func(e events.Message) {
|
||||
eh.Handle("die", func(e events.Message) {
|
||||
if !opts.all {
|
||||
cStats.remove(e.Actor.ID[:12])
|
||||
cStats.remove(e.ID[:12])
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -206,9 +206,9 @@ func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
|
||||
}
|
||||
switch bioEntry.Op[0] {
|
||||
case 'r', 'R':
|
||||
blkRead += bioEntry.Value
|
||||
blkRead = blkRead + bioEntry.Value
|
||||
case 'w', 'W':
|
||||
blkWrite += bioEntry.Value
|
||||
blkWrite = blkWrite + bioEntry.Value
|
||||
}
|
||||
}
|
||||
return blkRead, blkWrite
|
||||
|
||||
@ -9,28 +9,28 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/moby/sys/signal"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// resizeTtyTo resizes tty to specific height and width
|
||||
func resizeTtyTo(ctx context.Context, apiClient client.ContainerAPIClient, id string, height, width uint, isExec bool) error {
|
||||
func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id string, height, width uint, isExec bool) error {
|
||||
if height == 0 && width == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
options := container.ResizeOptions{
|
||||
options := types.ResizeOptions{
|
||||
Height: height,
|
||||
Width: width,
|
||||
}
|
||||
|
||||
var err error
|
||||
if isExec {
|
||||
err = apiClient.ContainerExecResize(ctx, id, options)
|
||||
err = client.ContainerExecResize(ctx, id, options)
|
||||
} else {
|
||||
err = apiClient.ContainerResize(ctx, id, options)
|
||||
err = client.ContainerResize(ctx, id, options)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -15,7 +15,7 @@ import (
|
||||
|
||||
func TestInitTtySizeErrors(t *testing.T) {
|
||||
expectedError := "failed to resize tty, using default size\n"
|
||||
fakeContainerExecResizeFunc := func(id string, options container.ResizeOptions) error {
|
||||
fakeContainerExecResizeFunc := func(id string, options types.ResizeOptions) error {
|
||||
return errors.Errorf("Error response from daemon: no such exec")
|
||||
}
|
||||
fakeResizeTtyFunc := func(ctx context.Context, cli command.Cli, id string, isExec bool) error {
|
||||
|
||||
@ -4,16 +4,16 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containerID string, waitRemove bool) <-chan int {
|
||||
func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||
if len(containerID) == 0 {
|
||||
// containerID can never be empty
|
||||
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
|
||||
@ -22,8 +22,8 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
|
||||
// Older versions used the Events API, and even older versions did not
|
||||
// support server-side removal. This legacyWaitExitOrRemoved method
|
||||
// preserves that old behavior and any issues it may have.
|
||||
if versions.LessThan(apiClient.ClientVersion(), "1.30") {
|
||||
return legacyWaitExitOrRemoved(ctx, apiClient, containerID, waitRemove)
|
||||
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
|
||||
return legacyWaitExitOrRemoved(ctx, dockerCli, containerID, waitRemove)
|
||||
}
|
||||
|
||||
condition := container.WaitConditionNextExit
|
||||
@ -31,7 +31,7 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
|
||||
condition = container.WaitConditionRemoved
|
||||
}
|
||||
|
||||
resultC, errC := apiClient.ContainerWait(ctx, containerID, condition)
|
||||
resultC, errC := dockerCli.Client().ContainerWait(ctx, containerID, condition)
|
||||
|
||||
statusC := make(chan int)
|
||||
go func() {
|
||||
@ -52,7 +52,7 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
|
||||
return statusC
|
||||
}
|
||||
|
||||
func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containerID string, waitRemove bool) <-chan int {
|
||||
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||
var removeErr error
|
||||
statusChan := make(chan int)
|
||||
exitCode := 125
|
||||
@ -65,7 +65,7 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
|
||||
Filters: f,
|
||||
}
|
||||
eventCtx, cancel := context.WithCancel(ctx)
|
||||
eventq, errq := apiClient.Events(eventCtx, options)
|
||||
eventq, errq := dockerCli.Client().Events(eventCtx, options)
|
||||
|
||||
eventProcessor := func(e events.Message) bool {
|
||||
stopProcessing := false
|
||||
@ -81,16 +81,18 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
|
||||
}
|
||||
if !waitRemove {
|
||||
stopProcessing = true
|
||||
} else if versions.LessThan(apiClient.ClientVersion(), "1.25") {
|
||||
} else {
|
||||
// If we are talking to an older daemon, `AutoRemove` is not supported.
|
||||
// We need to fall back to the old behavior, which is client-side removal
|
||||
go func() {
|
||||
removeErr = apiClient.ContainerRemove(ctx, containerID, container.RemoveOptions{RemoveVolumes: true})
|
||||
if removeErr != nil {
|
||||
logrus.Errorf("error removing container: %v", removeErr)
|
||||
cancel() // cancel the event Q
|
||||
}
|
||||
}()
|
||||
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.25") {
|
||||
go func() {
|
||||
removeErr = dockerCli.Client().ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true})
|
||||
if removeErr != nil {
|
||||
logrus.Errorf("error removing container: %v", removeErr)
|
||||
cancel() // cancel the event Q
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
case "detach":
|
||||
exitCode = 0
|
||||
@ -127,7 +129,7 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
|
||||
return statusChan
|
||||
}
|
||||
|
||||
func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, containerID string) error) chan error {
|
||||
func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error {
|
||||
if len(containers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2,12 +2,13 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
@ -23,7 +24,7 @@ func waitFn(cid string) (<-chan container.WaitResponse, <-chan error) {
|
||||
res.StatusCode = 42
|
||||
resC <- res
|
||||
case strings.Contains(cid, "non-existent"):
|
||||
err := fmt.Errorf("no such container: %v", cid)
|
||||
err := errors.Errorf("no such container: %v", cid)
|
||||
errC <- err
|
||||
case strings.Contains(cid, "wait-error"):
|
||||
res.Error = &container.WaitExitError{Message: "removal failed"}
|
||||
@ -60,7 +61,7 @@ func TestWaitExitOrRemoved(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
client := &fakeClient{waitFunc: waitFn, Version: api.DefaultVersion}
|
||||
client := test.NewFakeCli(&fakeClient{waitFunc: waitFn, Version: api.DefaultVersion})
|
||||
for _, testcase := range testcases {
|
||||
statusC := waitExitOrRemoved(context.Background(), client, testcase.cid, true)
|
||||
exitCode := <-statusC
|
||||
|
||||
@ -10,12 +10,12 @@ import (
|
||||
// DockerContext is a typed representation of what we put in Context metadata
|
||||
type DockerContext struct {
|
||||
Description string
|
||||
AdditionalFields map[string]any
|
||||
AdditionalFields map[string]interface{}
|
||||
}
|
||||
|
||||
// MarshalJSON implements custom JSON marshalling
|
||||
func (dc DockerContext) MarshalJSON() ([]byte, error) {
|
||||
s := map[string]any{}
|
||||
s := map[string]interface{}{}
|
||||
if dc.Description != "" {
|
||||
s["Description"] = dc.Description
|
||||
}
|
||||
@ -29,7 +29,7 @@ func (dc DockerContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// UnmarshalJSON implements custom JSON marshalling
|
||||
func (dc *DockerContext) UnmarshalJSON(payload []byte) error {
|
||||
var data map[string]any
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(payload, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -39,7 +39,7 @@ func (dc *DockerContext) UnmarshalJSON(payload []byte) error {
|
||||
dc.Description = v.(string)
|
||||
default:
|
||||
if dc.AdditionalFields == nil {
|
||||
dc.AdditionalFields = make(map[string]any)
|
||||
dc.AdditionalFields = make(map[string]interface{})
|
||||
}
|
||||
dc.AdditionalFields[k] = v
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ func longCreateDescription() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := &CreateOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] CONTEXT",
|
||||
@ -44,50 +44,61 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Name = args[0]
|
||||
return RunCreate(dockerCLI, opts)
|
||||
return RunCreate(dockerCli, opts)
|
||||
},
|
||||
Long: longCreateDescription(),
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
||||
flags.String(
|
||||
"default-stack-orchestrator", "",
|
||||
`Default orchestrator for stack operations to use with this context ("swarm", "kubernetes", "all")`,
|
||||
)
|
||||
flags.SetAnnotation("default-stack-orchestrator", "deprecated", nil)
|
||||
flags.SetAnnotation("default-stack-orchestrator", "deprecated", nil)
|
||||
flags.MarkDeprecated("default-stack-orchestrator", "option will be ignored")
|
||||
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
|
||||
flags.StringToString("kubernetes", nil, "set the kubernetes endpoint")
|
||||
flags.SetAnnotation("kubernetes", "kubernetes", nil)
|
||||
flags.SetAnnotation("kubernetes", "deprecated", nil)
|
||||
flags.MarkDeprecated("kubernetes", "option will be ignored")
|
||||
flags.StringVar(&opts.From, "from", "", "create context from a named context")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunCreate creates a Docker context
|
||||
func RunCreate(dockerCLI command.Cli, o *CreateOptions) error {
|
||||
s := dockerCLI.ContextStore()
|
||||
func RunCreate(cli command.Cli, o *CreateOptions) error {
|
||||
s := cli.ContextStore()
|
||||
err := checkContextNameForCreation(s, o.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case o.From == "" && o.Docker == nil:
|
||||
err = createFromExistingContext(s, dockerCLI.CurrentContext(), o)
|
||||
err = createFromExistingContext(s, cli.CurrentContext(), o)
|
||||
case o.From != "":
|
||||
err = createFromExistingContext(s, o.From, o)
|
||||
default:
|
||||
err = createNewContext(s, o)
|
||||
err = createNewContext(o, cli, s)
|
||||
}
|
||||
if err == nil {
|
||||
fmt.Fprintln(dockerCLI.Out(), o.Name)
|
||||
fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", o.Name)
|
||||
fmt.Fprintln(cli.Out(), o.Name)
|
||||
fmt.Fprintf(cli.Err(), "Successfully created context %q\n", o.Name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
||||
func createNewContext(o *CreateOptions, cli command.Cli, s store.Writer) error {
|
||||
if o.Docker == nil {
|
||||
return errors.New("docker endpoint configuration is required")
|
||||
}
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, o.Docker)
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(cli, o.Docker)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to create docker endpoint config")
|
||||
}
|
||||
contextMetadata := store.Metadata{
|
||||
Endpoints: map[string]any{
|
||||
Endpoints: map[string]interface{}{
|
||||
docker.DockerEndpoint: dockerEP,
|
||||
},
|
||||
Metadata: command.DockerContext{
|
||||
@ -104,10 +115,10 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
||||
if err := validateEndpoints(contextMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := contextStore.CreateOrUpdate(contextMetadata); err != nil {
|
||||
if err := s.CreateOrUpdate(contextMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
return contextStore.ResetTLSMaterial(o.Name, &contextTLSData)
|
||||
return s.ResetTLSMaterial(o.Name, &contextTLSData)
|
||||
}
|
||||
|
||||
func checkContextNameForCreation(s store.Reader, name string) error {
|
||||
|
||||
@ -16,15 +16,15 @@ func makeFakeCli(t *testing.T, opts ...func(*test.FakeCli)) *test.FakeCli {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
storeConfig := store.NewConfig(
|
||||
func() any { return &command.DockerContext{} },
|
||||
store.EndpointTypeGetter(docker.DockerEndpoint, func() any { return &docker.EndpointMeta{} }),
|
||||
func() interface{} { return &command.DockerContext{} },
|
||||
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
|
||||
)
|
||||
contextStore := &command.ContextStoreWithDefault{
|
||||
store := &command.ContextStoreWithDefault{
|
||||
Store: store.New(dir, storeConfig),
|
||||
Resolver: func() (*command.DefaultContext, error) {
|
||||
return &command.DefaultContext{
|
||||
Meta: store.Metadata{
|
||||
Endpoints: map[string]any{
|
||||
Endpoints: map[string]interface{}{
|
||||
docker.DockerEndpoint: docker.EndpointMeta{
|
||||
Host: "unix:///var/run/docker.sock",
|
||||
},
|
||||
@ -42,7 +42,7 @@ func makeFakeCli(t *testing.T, opts ...func(*test.FakeCli)) *test.FakeCli {
|
||||
for _, o := range opts {
|
||||
o(result)
|
||||
}
|
||||
result.SetContextStore(contextStore)
|
||||
result.SetContextStore(store)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -104,7 +104,6 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func assertContextCreateLogging(t *testing.T, cli *test.FakeCli, n string) {
|
||||
t.Helper()
|
||||
assert.Equal(t, n+"\n", cli.OutBuffer().String())
|
||||
assert.Equal(t, fmt.Sprintf("Successfully created context %q\n", n), cli.ErrBuffer().String())
|
||||
}
|
||||
|
||||
@ -19,14 +19,13 @@ type ExportOptions struct {
|
||||
}
|
||||
|
||||
func newExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
opts := &ExportOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "export [OPTIONS] CONTEXT [FILE|-]",
|
||||
Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.",
|
||||
Args: cli.RequiresRangeArgs(1, 2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts := &ExportOptions{
|
||||
ContextName: args[0],
|
||||
}
|
||||
opts.ContextName = args[0]
|
||||
if len(args) == 2 {
|
||||
opts.Dest = args[1]
|
||||
} else {
|
||||
@ -35,6 +34,13 @@ func newExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return RunExport(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Bool("kubeconfig", false, "Export as a kubeconfig file")
|
||||
flags.MarkDeprecated("kubeconfig", "option will be ignored")
|
||||
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
|
||||
flags.SetAnnotation("kubeconfig", "deprecated", nil)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error {
|
||||
|
||||
@ -40,7 +40,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
getRefFunc := func(ref string) (any, []byte, error) {
|
||||
getRefFunc := func(ref string) (interface{}, []byte, error) {
|
||||
c, err := dockerCli.ContextStore().GetMetadata(ref)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@ -17,7 +17,7 @@ func TestInspect(t *testing.T) {
|
||||
}))
|
||||
expected := string(golden.Get(t, "inspect.golden"))
|
||||
si := cli.ContextStore().GetStorageInfo("current")
|
||||
expected = strings.Replace(expected, "<METADATA_PATH>", strings.ReplaceAll(si.MetadataPath, `\`, `\\`), 1)
|
||||
expected = strings.Replace(expected, "<TLS_PATH>", strings.ReplaceAll(si.TLSPath, `\`, `\\`), 1)
|
||||
expected = strings.Replace(expected, "<METADATA_PATH>", strings.Replace(si.MetadataPath, `\`, `\\`, -1), 1)
|
||||
expected = strings.Replace(expected, "<TLS_PATH>", strings.Replace(si.TLSPath, `\`, `\\`, -1), 1)
|
||||
assert.Equal(t, cli.OutBuffer().String(), expected)
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ func runList(dockerCli command.Cli, opts *listOptions) error {
|
||||
var (
|
||||
curContext = dockerCli.CurrentContext()
|
||||
curFound bool
|
||||
contexts = make([]*formatter.ClientContext, 0, len(contextMap))
|
||||
contexts []*formatter.ClientContext
|
||||
)
|
||||
for _, rawMeta := range contextMap {
|
||||
isCurrent := rawMeta.Name == curContext
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/context"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
@ -85,12 +86,12 @@ func validateConfig(config map[string]string, allowedKeys map[string]struct{}) e
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
func getDockerEndpoint(contextStore store.Reader, config map[string]string) (docker.Endpoint, error) {
|
||||
func getDockerEndpoint(dockerCli command.Cli, config map[string]string) (docker.Endpoint, error) {
|
||||
if err := validateConfig(config, allowedDockerConfigKeys); err != nil {
|
||||
return docker.Endpoint{}, err
|
||||
}
|
||||
if contextName, ok := config[keyFrom]; ok {
|
||||
metadata, err := contextStore.GetMetadata(contextName)
|
||||
metadata, err := dockerCli.ContextStore().GetMetadata(contextName)
|
||||
if err != nil {
|
||||
return docker.Endpoint{}, err
|
||||
}
|
||||
@ -125,8 +126,8 @@ func getDockerEndpoint(contextStore store.Reader, config map[string]string) (doc
|
||||
return ep, nil
|
||||
}
|
||||
|
||||
func getDockerEndpointMetadataAndTLS(contextStore store.Reader, config map[string]string) (docker.EndpointMeta, *store.EndpointTLSData, error) {
|
||||
ep, err := getDockerEndpoint(contextStore, config)
|
||||
func getDockerEndpointMetadataAndTLS(dockerCli command.Cli, config map[string]string) (docker.EndpointMeta, *store.EndpointTLSData, error) {
|
||||
ep, err := getDockerEndpoint(dockerCli, config)
|
||||
if err != nil {
|
||||
return docker.EndpointMeta{}, nil, err
|
||||
}
|
||||
|
||||
@ -47,16 +47,26 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
||||
flags.String(
|
||||
"default-stack-orchestrator", "",
|
||||
"Default orchestrator for stack operations to use with this context (swarm|kubernetes|all)",
|
||||
)
|
||||
flags.SetAnnotation("default-stack-orchestrator", "deprecated", nil)
|
||||
flags.MarkDeprecated("default-stack-orchestrator", "option will be ignored")
|
||||
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
|
||||
flags.StringToString("kubernetes", nil, "set the kubernetes endpoint")
|
||||
flags.SetAnnotation("kubernetes", "kubernetes", nil)
|
||||
flags.SetAnnotation("kubernetes", "deprecated", nil)
|
||||
flags.MarkDeprecated("kubernetes", "option will be ignored")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunUpdate updates a Docker context
|
||||
func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
|
||||
func RunUpdate(cli command.Cli, o *UpdateOptions) error {
|
||||
if err := store.ValidateContextName(o.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
s := dockerCLI.ContextStore()
|
||||
s := cli.ContextStore()
|
||||
c, err := s.GetMetadata(o.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -74,7 +84,7 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
|
||||
tlsDataToReset := make(map[string]*store.EndpointTLSData)
|
||||
|
||||
if o.Docker != nil {
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, o.Docker)
|
||||
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(cli, o.Docker)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to create docker endpoint config")
|
||||
}
|
||||
@ -93,8 +103,8 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(dockerCLI.Out(), o.Name)
|
||||
fmt.Fprintf(dockerCLI.Err(), "Successfully updated context %q\n", o.Name)
|
||||
fmt.Fprintln(cli.Out(), o.Name)
|
||||
fmt.Fprintf(cli.Err(), "Successfully updated context %q\n", o.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
@ -14,6 +13,7 @@ import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
@ -57,11 +57,7 @@ func TestUseDefaultWithoutConfigFile(t *testing.T) {
|
||||
// the _default_ configuration file. If we specify a custom configuration
|
||||
// file, the CLI produces an error if the file doesn't exist.
|
||||
tmpHomeDir := t.TempDir()
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Setenv("USERPROFILE", tmpHomeDir)
|
||||
} else {
|
||||
t.Setenv("HOME", tmpHomeDir)
|
||||
}
|
||||
t.Setenv(homedir.Key(), tmpHomeDir)
|
||||
configDir := filepath.Join(tmpHomeDir, ".docker")
|
||||
configFilePath := filepath.Join(configDir, "config.json")
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
func TestDockerContextMetadataKeepAdditionalFields(t *testing.T) {
|
||||
c := DockerContext{
|
||||
Description: "test",
|
||||
AdditionalFields: map[string]any{
|
||||
AdditionalFields: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
|
||||
@ -11,12 +11,6 @@ import (
|
||||
const (
|
||||
// DefaultContextName is the name reserved for the default context (config & env based)
|
||||
DefaultContextName = "default"
|
||||
|
||||
// EnvOverrideContext is the name of the environment variable that can be
|
||||
// used to override the context to use. If set, it overrides the context
|
||||
// that's set in the CLI's configuration file, but takes no effect if the
|
||||
// "DOCKER_HOST" env-var is set (which takes precedence.
|
||||
EnvOverrideContext = "DOCKER_CONTEXT"
|
||||
)
|
||||
|
||||
// DefaultContext contains the default context data for all endpoints
|
||||
@ -44,9 +38,7 @@ type EndpointDefaultResolver interface {
|
||||
// the lack of a default (e.g. because the config file which
|
||||
// would contain it is missing). If there is no default then
|
||||
// returns nil, nil, nil.
|
||||
//
|
||||
//nolint:dupword // ignore "Duplicate words (nil,) found"
|
||||
ResolveDefault() (any, *store.EndpointTLSData, error)
|
||||
ResolveDefault() (interface{}, *store.EndpointTLSData, error)
|
||||
}
|
||||
|
||||
// ResolveDefaultContext creates a Metadata for the current CLI invocation parameters
|
||||
@ -55,7 +47,7 @@ func ResolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*
|
||||
Endpoints: make(map[string]store.EndpointTLSData),
|
||||
}
|
||||
contextMetadata := store.Metadata{
|
||||
Endpoints: make(map[string]any),
|
||||
Endpoints: make(map[string]interface{}),
|
||||
Metadata: DockerContext{
|
||||
Description: "",
|
||||
},
|
||||
|
||||
@ -23,14 +23,14 @@ type testContext struct {
|
||||
Bar string `json:"another_very_recognizable_field_name"`
|
||||
}
|
||||
|
||||
var testCfg = store.NewConfig(func() any { return &testContext{} },
|
||||
store.EndpointTypeGetter("ep1", func() any { return &endpoint{} }),
|
||||
store.EndpointTypeGetter("ep2", func() any { return &endpoint{} }),
|
||||
var testCfg = store.NewConfig(func() interface{} { return &testContext{} },
|
||||
store.EndpointTypeGetter("ep1", func() interface{} { return &endpoint{} }),
|
||||
store.EndpointTypeGetter("ep2", func() interface{} { return &endpoint{} }),
|
||||
)
|
||||
|
||||
func testDefaultMetadata() store.Metadata {
|
||||
return store.Metadata{
|
||||
Endpoints: map[string]any{
|
||||
Endpoints: map[string]interface{}{
|
||||
"ep1": endpoint{Foo: "bar"},
|
||||
},
|
||||
Metadata: testContext{Bar: "baz"},
|
||||
@ -149,7 +149,7 @@ func TestErrCreateDefault(t *testing.T) {
|
||||
meta := testDefaultMetadata()
|
||||
s := testStore(t, meta, store.ContextTLSData{})
|
||||
err := s.CreateOrUpdate(store.Metadata{
|
||||
Endpoints: map[string]any{
|
||||
Endpoints: map[string]interface{}{
|
||||
"ep1": endpoint{Foo: "bar"},
|
||||
},
|
||||
Metadata: testContext{Bar: "baz"},
|
||||
|
||||
@ -3,28 +3,28 @@ package command
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/api/types/events"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EventHandler is abstract interface for user to customize
|
||||
// own handle functions of each type of events
|
||||
type EventHandler interface {
|
||||
Handle(action events.Action, h func(events.Message))
|
||||
Watch(c <-chan events.Message)
|
||||
Handle(action string, h func(eventtypes.Message))
|
||||
Watch(c <-chan eventtypes.Message)
|
||||
}
|
||||
|
||||
// InitEventHandler initializes and returns an EventHandler
|
||||
func InitEventHandler() EventHandler {
|
||||
return &eventHandler{handlers: make(map[events.Action]func(events.Message))}
|
||||
return &eventHandler{handlers: make(map[string]func(eventtypes.Message))}
|
||||
}
|
||||
|
||||
type eventHandler struct {
|
||||
handlers map[events.Action]func(events.Message)
|
||||
handlers map[string]func(eventtypes.Message)
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *eventHandler) Handle(action events.Action, h func(events.Message)) {
|
||||
func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
|
||||
w.mu.Lock()
|
||||
w.handlers[action] = h
|
||||
w.mu.Unlock()
|
||||
@ -33,7 +33,7 @@ func (w *eventHandler) Handle(action events.Action, h func(events.Message)) {
|
||||
// Watch ranges over the passed in event chan and processes the events based on the
|
||||
// handlers created for a given action.
|
||||
// To stop watching, close the event chan.
|
||||
func (w *eventHandler) Watch(c <-chan events.Message) {
|
||||
func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
|
||||
for e := range c {
|
||||
w.mu.Lock()
|
||||
h, exists := w.handlers[e.Action]
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -171,13 +171,13 @@ func (c *buildCacheContext) LastUsedSince() string {
|
||||
}
|
||||
|
||||
func (c *buildCacheContext) UsageCount() string {
|
||||
return strconv.Itoa(c.v.UsageCount)
|
||||
return fmt.Sprintf("%d", c.v.UsageCount)
|
||||
}
|
||||
|
||||
func (c *buildCacheContext) InUse() string {
|
||||
return strconv.FormatBool(c.v.InUse)
|
||||
return fmt.Sprintf("%t", c.v.InUse)
|
||||
}
|
||||
|
||||
func (c *buildCacheContext) Shared() string {
|
||||
return strconv.FormatBool(c.v.Shared)
|
||||
return fmt.Sprintf("%t", c.v.Shared)
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
@ -86,7 +86,7 @@ type ContainerContext struct {
|
||||
// used in the template. It's currently only used to detect use of the .Size
|
||||
// field which (if used) automatically sets the '--size' option when making
|
||||
// the API call.
|
||||
FieldsUsed map[string]any
|
||||
FieldsUsed map[string]interface{}
|
||||
}
|
||||
|
||||
// NewContainerContext creates a new context for rendering containers
|
||||
@ -226,7 +226,7 @@ func (c *ContainerContext) Status() string {
|
||||
// Size returns the container's size and virtual size (e.g. "2B (virtual 21.5MB)")
|
||||
func (c *ContainerContext) Size() string {
|
||||
if c.FieldsUsed == nil {
|
||||
c.FieldsUsed = map[string]any{}
|
||||
c.FieldsUsed = map[string]interface{}{}
|
||||
}
|
||||
c.FieldsUsed["Size"] = struct{}{}
|
||||
srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
|
||||
@ -245,9 +245,9 @@ func (c *ContainerContext) Labels() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
joinLabels := make([]string, 0, len(c.c.Labels))
|
||||
var joinLabels []string
|
||||
for k, v := range c.c.Labels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
@ -265,7 +265,7 @@ func (c *ContainerContext) Label(name string) string {
|
||||
// If the trunc option is set, names can be truncated (ellipsized).
|
||||
func (c *ContainerContext) Mounts() string {
|
||||
var name string
|
||||
mounts := make([]string, 0, len(c.c.Mounts))
|
||||
var mounts []string
|
||||
for _, m := range c.c.Mounts {
|
||||
if m.Name == "" {
|
||||
name = m.Source
|
||||
@ -289,7 +289,7 @@ func (c *ContainerContext) LocalVolumes() string {
|
||||
}
|
||||
}
|
||||
|
||||
return strconv.Itoa(count)
|
||||
return fmt.Sprintf("%d", count)
|
||||
}
|
||||
|
||||
// Networks returns a comma-separated string of networks that the container is
|
||||
@ -299,7 +299,7 @@ func (c *ContainerContext) Networks() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
networks := make([]string, 0, len(c.c.NetworkSettings.Networks))
|
||||
networks := []string{}
|
||||
for k := range c.c.NetworkSettings.Networks {
|
||||
networks = append(networks, k)
|
||||
}
|
||||
@ -316,7 +316,7 @@ func DisplayablePorts(ports []types.Port) string {
|
||||
last uint16
|
||||
}
|
||||
groupMap := make(map[string]*portGroup)
|
||||
var result []string //nolint:prealloc
|
||||
var result []string
|
||||
var hostMappings []string
|
||||
var groupMapKeys []string
|
||||
sort.Slice(ports, func(i, j int) bool {
|
||||
@ -331,7 +331,7 @@ func DisplayablePorts(ports []types.Port) string {
|
||||
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||
continue
|
||||
}
|
||||
portKey = port.IP + "/" + port.Type
|
||||
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
||||
}
|
||||
group := groupMap[portKey]
|
||||
|
||||
@ -372,7 +372,7 @@ func formGroup(key string, start, last uint16) string {
|
||||
if ip != "" {
|
||||
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
||||
}
|
||||
return group + "/" + groupType
|
||||
return fmt.Sprintf("%s/%s", group, groupType)
|
||||
}
|
||||
|
||||
func comparePorts(i, j types.Port) bool {
|
||||
|
||||
@ -265,6 +265,7 @@ size: 0B
|
||||
assert.Equal(t, out.String(), tc.expected)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,7 +340,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unix, State: "running"},
|
||||
}
|
||||
expectedCreated := time.Unix(unix, 0).String()
|
||||
expectedJSONs := []map[string]any{
|
||||
expectedJSONs := []map[string]interface{}{
|
||||
{
|
||||
"Command": "\"\"",
|
||||
"CreatedAt": expectedCreated,
|
||||
@ -380,7 +381,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
}
|
||||
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
||||
msg := fmt.Sprintf("Output: line %d: %s", i, line)
|
||||
var m map[string]any
|
||||
var m map[string]interface{}
|
||||
err := json.Unmarshal([]byte(line), &m)
|
||||
assert.NilError(t, err, msg)
|
||||
assert.Check(t, is.DeepEqual(expectedJSONs[i], m), msg)
|
||||
|
||||
@ -22,7 +22,7 @@ const (
|
||||
|
||||
// SubContext defines what Context implementation should provide
|
||||
type SubContext interface {
|
||||
FullHeader() any
|
||||
FullHeader() interface{}
|
||||
}
|
||||
|
||||
// SubHeaderContext is a map destined to formatter header (table format)
|
||||
@ -39,10 +39,10 @@ func (c SubHeaderContext) Label(name string) string {
|
||||
|
||||
// HeaderContext provides the subContext interface for managing headers
|
||||
type HeaderContext struct {
|
||||
Header any
|
||||
Header interface{}
|
||||
}
|
||||
|
||||
// FullHeader returns the header as an interface
|
||||
func (c *HeaderContext) FullHeader() any {
|
||||
func (c *HeaderContext) FullHeader() interface{} {
|
||||
return c.Header
|
||||
}
|
||||
|
||||
@ -7,9 +7,8 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
@ -35,7 +34,7 @@ type DiskUsageContext struct {
|
||||
Context
|
||||
Verbose bool
|
||||
LayersSize int64
|
||||
Images []*image.Summary
|
||||
Images []*types.ImageSummary
|
||||
Containers []*types.Container
|
||||
Volumes []*volume.Volume
|
||||
BuildCache []*types.BuildCache
|
||||
@ -262,7 +261,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
||||
type diskUsageImagesContext struct {
|
||||
HeaderContext
|
||||
totalSize int64
|
||||
images []*image.Summary
|
||||
images []*types.ImageSummary
|
||||
}
|
||||
|
||||
func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
@ -41,7 +41,7 @@ func Ellipsis(s string, maxDisplayWidth int) string {
|
||||
}
|
||||
|
||||
var (
|
||||
display = make([]int, 0, len(rs))
|
||||
display []int
|
||||
displayWidth int
|
||||
)
|
||||
for _, r := range rs {
|
||||
|
||||
@ -51,7 +51,7 @@ type Context struct {
|
||||
|
||||
// internal element
|
||||
finalFormat string
|
||||
header any
|
||||
header interface{}
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ type fakeSubContext struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (f fakeSubContext) FullHeader() any {
|
||||
func (f fakeSubContext) FullHeader() interface{} {
|
||||
return map[string]string{"Name": "NAME"}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
@ -26,11 +26,11 @@ type ImageContext struct {
|
||||
Digest bool
|
||||
}
|
||||
|
||||
func isDangling(img image.Summary) bool {
|
||||
if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 {
|
||||
func isDangling(image types.ImageSummary) bool {
|
||||
if len(image.RepoTags) == 0 && len(image.RepoDigests) == 0 {
|
||||
return true
|
||||
}
|
||||
return len(img.RepoTags) == 1 && img.RepoTags[0] == "<none>:<none>" && len(img.RepoDigests) == 1 && img.RepoDigests[0] == "<none>@<none>"
|
||||
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
|
||||
}
|
||||
|
||||
// NewImageFormat returns a format for rendering an ImageContext
|
||||
@ -75,7 +75,7 @@ virtual_size: {{.Size}}
|
||||
}
|
||||
|
||||
// ImageWrite writes the formatter images using the ImageContext
|
||||
func ImageWrite(ctx ImageContext, images []image.Summary) error {
|
||||
func ImageWrite(ctx ImageContext, images []types.ImageSummary) error {
|
||||
render := func(format func(subContext SubContext) error) error {
|
||||
return imageFormat(ctx, images, format)
|
||||
}
|
||||
@ -87,19 +87,19 @@ func needDigest(ctx ImageContext) bool {
|
||||
return ctx.Digest || ctx.Format.Contains("{{.Digest}}")
|
||||
}
|
||||
|
||||
func imageFormat(ctx ImageContext, images []image.Summary, format func(subContext SubContext) error) error {
|
||||
for _, img := range images {
|
||||
func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext SubContext) error) error {
|
||||
for _, image := range images {
|
||||
formatted := []*imageContext{}
|
||||
if isDangling(img) {
|
||||
if isDangling(image) {
|
||||
formatted = append(formatted, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: img,
|
||||
i: image,
|
||||
repo: "<none>",
|
||||
tag: "<none>",
|
||||
digest: "<none>",
|
||||
})
|
||||
} else {
|
||||
formatted = imageFormatTaggedAndDigest(ctx, img)
|
||||
formatted = imageFormatTaggedAndDigest(ctx, image)
|
||||
}
|
||||
for _, imageCtx := range formatted {
|
||||
if err := format(imageCtx); err != nil {
|
||||
@ -110,12 +110,12 @@ func imageFormat(ctx ImageContext, images []image.Summary, format func(subContex
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageFormatTaggedAndDigest(ctx ImageContext, img image.Summary) []*imageContext {
|
||||
func imageFormatTaggedAndDigest(ctx ImageContext, image types.ImageSummary) []*imageContext {
|
||||
repoTags := map[string][]string{}
|
||||
repoDigests := map[string][]string{}
|
||||
images := []*imageContext{}
|
||||
|
||||
for _, refString := range img.RepoTags {
|
||||
for _, refString := range image.RepoTags {
|
||||
ref, err := reference.ParseNormalizedNamed(refString)
|
||||
if err != nil {
|
||||
continue
|
||||
@ -125,7 +125,7 @@ func imageFormatTaggedAndDigest(ctx ImageContext, img image.Summary) []*imageCon
|
||||
repoTags[familiarRef] = append(repoTags[familiarRef], nt.Tag())
|
||||
}
|
||||
}
|
||||
for _, refString := range img.RepoDigests {
|
||||
for _, refString := range image.RepoDigests {
|
||||
ref, err := reference.ParseNormalizedNamed(refString)
|
||||
if err != nil {
|
||||
continue
|
||||
@ -137,13 +137,14 @@ func imageFormatTaggedAndDigest(ctx ImageContext, img image.Summary) []*imageCon
|
||||
}
|
||||
|
||||
addImage := func(repo, tag, digest string) {
|
||||
images = append(images, &imageContext{
|
||||
image := &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: img,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: digest,
|
||||
})
|
||||
}
|
||||
images = append(images, image)
|
||||
}
|
||||
|
||||
for repo, tags := range repoTags {
|
||||
@ -166,6 +167,7 @@ func imageFormatTaggedAndDigest(ctx ImageContext, img image.Summary) []*imageCon
|
||||
for _, dgst := range digests {
|
||||
addImage(repo, tag, dgst)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,7 +188,7 @@ func imageFormatTaggedAndDigest(ctx ImageContext, img image.Summary) []*imageCon
|
||||
type imageContext struct {
|
||||
HeaderContext
|
||||
trunc bool
|
||||
i image.Summary
|
||||
i types.ImageSummary
|
||||
repo string
|
||||
tag string
|
||||
digest string
|
||||
@ -255,7 +257,7 @@ func (c *imageContext) Containers() string {
|
||||
if c.i.Containers == -1 {
|
||||
return "N/A"
|
||||
}
|
||||
return strconv.FormatInt(c.i.Containers, 10)
|
||||
return fmt.Sprintf("%d", c.i.Containers)
|
||||
}
|
||||
|
||||
// VirtualSize shows the virtual size of the image and all of its parent
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -26,66 +26,66 @@ func TestImageContext(t *testing.T) {
|
||||
call func() string
|
||||
}{
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{ID: imageID}, trunc: true},
|
||||
imageCtx: imageContext{i: types.ImageSummary{ID: imageID}, trunc: true},
|
||||
expValue: stringid.TruncateID(imageID),
|
||||
call: ctx.ID,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{ID: imageID}, trunc: false},
|
||||
imageCtx: imageContext{i: types.ImageSummary{ID: imageID}, trunc: false},
|
||||
expValue: imageID,
|
||||
call: ctx.ID,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{Size: 10}, trunc: true},
|
||||
imageCtx: imageContext{i: types.ImageSummary{Size: 10}, trunc: true},
|
||||
expValue: "10B",
|
||||
call: ctx.Size,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{Created: unix}, trunc: true},
|
||||
imageCtx: imageContext{i: types.ImageSummary{Created: unix}, trunc: true},
|
||||
expValue: time.Unix(unix, 0).String(), call: ctx.CreatedAt,
|
||||
},
|
||||
// FIXME
|
||||
// {imageContext{
|
||||
// i: image.Summary{Created: unix},
|
||||
// i: types.ImageSummary{Created: unix},
|
||||
// trunc: true,
|
||||
// }, units.HumanDuration(time.Unix(unix, 0)), createdSinceHeader, ctx.CreatedSince},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{}, repo: "busybox"},
|
||||
imageCtx: imageContext{i: types.ImageSummary{}, repo: "busybox"},
|
||||
expValue: "busybox",
|
||||
call: ctx.Repository,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{}, tag: "latest"},
|
||||
imageCtx: imageContext{i: types.ImageSummary{}, tag: "latest"},
|
||||
expValue: "latest",
|
||||
call: ctx.Tag,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{}, digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a"},
|
||||
imageCtx: imageContext{i: types.ImageSummary{}, digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a"},
|
||||
expValue: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a",
|
||||
call: ctx.Digest,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{Containers: 10}},
|
||||
imageCtx: imageContext{i: types.ImageSummary{Containers: 10}},
|
||||
expValue: "10",
|
||||
call: ctx.Containers,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{Size: 10000}},
|
||||
imageCtx: imageContext{i: types.ImageSummary{Size: 10000}},
|
||||
expValue: "10kB",
|
||||
call: ctx.VirtualSize, //nolint:nolintlint,staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
|
||||
call: ctx.VirtualSize, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{SharedSize: 10000}},
|
||||
imageCtx: imageContext{i: types.ImageSummary{SharedSize: 10000}},
|
||||
expValue: "10kB",
|
||||
call: ctx.SharedSize,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{SharedSize: 5000, Size: 20000}},
|
||||
imageCtx: imageContext{i: types.ImageSummary{SharedSize: 5000, Size: 20000}},
|
||||
expValue: "15kB",
|
||||
call: ctx.UniqueSize,
|
||||
},
|
||||
{
|
||||
imageCtx: imageContext{i: image.Summary{Created: zeroTime}},
|
||||
imageCtx: imageContext{i: types.ImageSummary{Created: zeroTime}},
|
||||
expValue: "",
|
||||
call: ctx.CreatedSince,
|
||||
},
|
||||
@ -148,7 +148,7 @@ image tag2 imageID2 N/A 0B
|
||||
Format: NewImageFormat("table {{.Repository}}", false, false),
|
||||
},
|
||||
},
|
||||
"REPOSITORY\nimage\nimage\n<none>\n", //nolint:dupword // ignore "Duplicate words (image) found"
|
||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@ -169,7 +169,7 @@ image <none>
|
||||
Format: NewImageFormat("table {{.Repository}}", true, false),
|
||||
},
|
||||
},
|
||||
"REPOSITORY\nimage\nimage\n<none>\n", //nolint:dupword // ignore "Duplicate words (image) found"
|
||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@ -284,7 +284,7 @@ image_id: imageID3
|
||||
Format: NewImageFormat("{{.Repository}}", false, false),
|
||||
},
|
||||
},
|
||||
"image\nimage\n<none>\n", //nolint:dupword // ignore "Duplicate words (image) found"
|
||||
"image\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@ -293,11 +293,11 @@ image_id: imageID3
|
||||
},
|
||||
Digest: true,
|
||||
},
|
||||
"image\nimage\n<none>\n", //nolint:dupword // ignore "Duplicate words (image) found"
|
||||
"image\nimage\n<none>\n",
|
||||
},
|
||||
}
|
||||
|
||||
images := []image.Summary{
|
||||
images := []types.ImageSummary{
|
||||
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
|
||||
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: zeroTime},
|
||||
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
|
||||
@ -320,7 +320,7 @@ image_id: imageID3
|
||||
|
||||
func TestImageContextWriteWithNoImage(t *testing.T) {
|
||||
out := bytes.NewBufferString("")
|
||||
images := []image.Summary{}
|
||||
images := []types.ImageSummary{}
|
||||
|
||||
cases := []struct {
|
||||
context ImageContext
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
|
||||
// MarshalJSON marshals x into json
|
||||
// It differs a bit from encoding/json MarshalJSON function for formatter
|
||||
func MarshalJSON(x any) ([]byte, error) {
|
||||
func MarshalJSON(x interface{}) ([]byte, error) {
|
||||
m, err := marshalMap(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -18,8 +18,8 @@ func MarshalJSON(x any) ([]byte, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// marshalMap marshals x to map[string]any
|
||||
func marshalMap(x any) (map[string]any, error) {
|
||||
// marshalMap marshals x to map[string]interface{}
|
||||
func marshalMap(x interface{}) (map[string]interface{}, error) {
|
||||
val := reflect.ValueOf(x)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return nil, errors.Errorf("expected a pointer to a struct, got %v", val.Kind())
|
||||
@ -32,7 +32,7 @@ func marshalMap(x any) (map[string]any, error) {
|
||||
return nil, errors.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind())
|
||||
}
|
||||
typ := val.Type()
|
||||
m := make(map[string]any)
|
||||
m := make(map[string]interface{})
|
||||
for i := 0; i < val.NumMethod(); i++ {
|
||||
k, v, err := marshalForMethod(typ.Method(i), val.Method(i))
|
||||
if err != nil {
|
||||
@ -49,7 +49,7 @@ var unmarshallableNames = map[string]struct{}{"FullHeader": {}}
|
||||
|
||||
// marshalForMethod returns the map key and the map value for marshalling the method.
|
||||
// It returns ("", nil, nil) for valid but non-marshallable parameter. (e.g. "unexportedFunc()")
|
||||
func marshalForMethod(typ reflect.Method, val reflect.Value) (string, any, error) {
|
||||
func marshalForMethod(typ reflect.Method, val reflect.Value) (string, interface{}, error) {
|
||||
if val.Kind() != reflect.Func {
|
||||
return "", nil, errors.Errorf("expected func, got %v", val.Kind())
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ func (d *dummy) FullHeader() string {
|
||||
return "FullHeader(should not be marshalled)"
|
||||
}
|
||||
|
||||
var dummyExpected = map[string]any{
|
||||
var dummyExpected = map[string]interface{}{
|
||||
"Func1": "Func1",
|
||||
"Func4": 4,
|
||||
"Func5": dummyType("Func5"),
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
// based on https://github.com/golang/go/blob/master/src/text/tabwriter/tabwriter.go Last modified 690ac40 on 31 Jan
|
||||
|
||||
//nolint:gocyclo,nakedret,stylecheck,unused // ignore linting errors, so that we can stick close to upstream
|
||||
//nolint:gocyclo,nakedret,revive,stylecheck,unused // ignore linting errors, so that we can stick close to upstream
|
||||
package tabwriter
|
||||
|
||||
import (
|
||||
@ -202,7 +202,7 @@ const (
|
||||
//
|
||||
// minwidth minimal cell width including any padding
|
||||
// tabwidth width of tab characters (equivalent number of spaces)
|
||||
// padding the padding added to a cell before computing its width
|
||||
// padding padding added to a cell before computing its width
|
||||
// padchar ASCII char used for padding
|
||||
// if padchar == '\t', the Writer will assume that the
|
||||
// width of a '\t' in the formatted output is tabwidth,
|
||||
@ -576,16 +576,18 @@ func (b *Writer) Write(buf []byte) (n int, err error) {
|
||||
b.startEscape(ch)
|
||||
}
|
||||
}
|
||||
} else if ch == b.endChar {
|
||||
} else {
|
||||
// inside escape
|
||||
// end of tag/entity
|
||||
j := i + 1
|
||||
if ch == Escape && b.flags&StripEscape != 0 {
|
||||
j = i // strip Escape
|
||||
if ch == b.endChar {
|
||||
// end of tag/entity
|
||||
j := i + 1
|
||||
if ch == Escape && b.flags&StripEscape != 0 {
|
||||
j = i // strip Escape
|
||||
}
|
||||
b.append(buf[n:j])
|
||||
n = i + 1 // ch consumed
|
||||
b.endEscape()
|
||||
}
|
||||
b.append(buf[n:j])
|
||||
n = i + 1 // ch consumed
|
||||
b.endEscape()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -37,7 +36,6 @@ func (b *buffer) Write(buf []byte) (written int, err error) {
|
||||
func (b *buffer) String() string { return string(b.a) }
|
||||
|
||||
func write(t *testing.T, testname string, w *Writer, src string) {
|
||||
t.Helper()
|
||||
written, err := io.WriteString(w, src)
|
||||
if err != nil {
|
||||
t.Errorf("--- test: %s\n--- src:\n%q\n--- write error: %v\n", testname, src, err)
|
||||
@ -48,7 +46,6 @@ func write(t *testing.T, testname string, w *Writer, src string) {
|
||||
}
|
||||
|
||||
func verify(t *testing.T, testname string, w *Writer, b *buffer, src, expected string) {
|
||||
t.Helper()
|
||||
err := w.Flush()
|
||||
if err != nil {
|
||||
t.Errorf("--- test: %s\n--- src:\n%q\n--- flush error: %v\n", testname, src, err)
|
||||
@ -61,7 +58,6 @@ func verify(t *testing.T, testname string, w *Writer, b *buffer, src, expected s
|
||||
}
|
||||
|
||||
func check(t *testing.T, testname string, minwidth, tabwidth, padding int, padchar byte, flags uint, src, expected string) {
|
||||
t.Helper()
|
||||
var b buffer
|
||||
b.init(1000)
|
||||
|
||||
@ -626,7 +622,6 @@ func (panicWriter) Write([]byte) (int, error) {
|
||||
}
|
||||
|
||||
func wantPanicString(t *testing.T, want string) {
|
||||
t.Helper()
|
||||
if e := recover(); e != nil {
|
||||
got, ok := e.(string)
|
||||
switch {
|
||||
@ -696,7 +691,7 @@ func BenchmarkPyramid(b *testing.B) {
|
||||
for _, x := range [...]int{10, 100, 1000} {
|
||||
// Build a line with x cells.
|
||||
line := bytes.Repeat([]byte("a\t"), x)
|
||||
b.Run(strconv.Itoa(x), func(b *testing.B) {
|
||||
b.Run(fmt.Sprintf("%d", x), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
|
||||
@ -718,7 +713,7 @@ func BenchmarkRagged(b *testing.B) {
|
||||
lines[i] = bytes.Repeat([]byte("a\t"), w)
|
||||
}
|
||||
for _, h := range [...]int{10, 100, 1000} {
|
||||
b.Run(strconv.Itoa(h), func(b *testing.B) {
|
||||
b.Run(fmt.Sprintf("%d", h), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user