fix: support docker create arguments from container.options (#1022) (#1351)

* fix: support docker create arguments from container.options (#1022)

* fix processing of errors, add verbose logging, fix test

* disable linter for code copied from docker/cli

* fix all linter issues

* Add license info

* Add opts_test.go from docker/cli and required testdata

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Alex Savchuk
2022-10-07 01:09:43 +03:00
committed by GitHub
parent 679cac1677
commit 48188a6280
20 changed files with 2354 additions and 45 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ DISABLE_LINTERS:
- MARKDOWN_MARKDOWN_LINK_CHECK - MARKDOWN_MARKDOWN_LINK_CHECK
- REPOSITORY_CHECKOV - REPOSITORY_CHECKOV
- REPOSITORY_TRIVY - REPOSITORY_TRIVY
FILTER_REGEX_EXCLUDE: (.*testdata/*|install.sh) FILTER_REGEX_EXCLUDE: (.*testdata/*|install.sh|pkg/container/docker_cli.go|pkg/container/DOCKER_LICENSE)
MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.yml MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.yml
PARALLEL: false PARALLEL: false
PRINT_ALPACA: false PRINT_ALPACA: false
+4 -4
View File
@@ -17,8 +17,8 @@
**Note: `catthehacker/ubuntu` images are based on Ubuntu root filesystem** **Note: `catthehacker/ubuntu` images are based on Ubuntu root filesystem**
| Image | GitHub Repository | | Image | GitHub Repository |
| -------------------------------------------------------------------- | ------------------------------------------------------------- | | ------------------------------------------------------------ | ------------------------------------------------------------- |
| [`catthehacker/ubuntu:act-latest`][ghcr/catthehacker/ubuntu] | [`catthehacker/docker-images`][gh/catthehacker/docker_images] | | [`catthehacker/ubuntu:act-latest`][ghcr/catthehacker/ubuntu] | [`catthehacker/docker-images`][gh/catthehacker/docker_images] |
| [`catthehacker/ubuntu:act-22.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/docker-images`][gh/catthehacker/docker_images] | | [`catthehacker/ubuntu:act-22.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/docker-images`][gh/catthehacker/docker_images] |
| [`catthehacker/ubuntu:act-20.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/docker-images`][gh/catthehacker/docker_images] | | [`catthehacker/ubuntu:act-20.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/docker-images`][gh/catthehacker/docker_images] |
@@ -34,8 +34,8 @@
| [`nektos/act-environments-ubuntu:18.04-lite`][hub/nektos/act-environments-ubuntu] | ![`nektos:18.04-lite`][hub/nektos/act-environments-ubuntu/18.04-lite/size] | [`nektos/act-environments`][gh/nektos/act-environments] | | [`nektos/act-environments-ubuntu:18.04-lite`][hub/nektos/act-environments-ubuntu] | ![`nektos:18.04-lite`][hub/nektos/act-environments-ubuntu/18.04-lite/size] | [`nektos/act-environments`][gh/nektos/act-environments] |
| [`nektos/act-environments-ubuntu:18.04-full`][hub/nektos/act-environments-ubuntu] | ![`nektos:18.04-full`][hub/nektos/act-environments-ubuntu/18.04-full/size] | [`nektos/act-environments`][gh/nektos/act-environments] | | [`nektos/act-environments-ubuntu:18.04-full`][hub/nektos/act-environments-ubuntu] | ![`nektos:18.04-full`][hub/nektos/act-environments-ubuntu/18.04-full/size] | [`nektos/act-environments`][gh/nektos/act-environments] |
| Image | GitHub Repository | | Image | GitHub Repository |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [`catthehacker/ubuntu:full-latest`][ghcr/catthehacker/ubuntu] | [`catthehacker/virtual-environments-fork`][gh/catthehacker/virtual-environments-fork] | | [`catthehacker/ubuntu:full-latest`][ghcr/catthehacker/ubuntu] | [`catthehacker/virtual-environments-fork`][gh/catthehacker/virtual-environments-fork] |
| [`catthehacker/ubuntu:full-20.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/virtual-environments-fork`][gh/catthehacker/virtual-environments-fork] | | [`catthehacker/ubuntu:full-20.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/virtual-environments-fork`][gh/catthehacker/virtual-environments-fork] |
| [`catthehacker/ubuntu:full-18.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/virtual-environments-fork`][gh/catthehacker/virtual-environments-fork] | | [`catthehacker/ubuntu:full-18.04`][ghcr/catthehacker/ubuntu] | [`catthehacker/virtual-environments-fork`][gh/catthehacker/virtual-environments-fork] |
+4 -4
View File
@@ -219,7 +219,7 @@ act -s GITHUB_TOKEN=[insert token or leave blank and omit equals for secure inpu
## Services ## Services
Services are not currently supported but are being worked on. See: https://github.com/nektos/act/issues/173 Services are not currently supported but are being worked on. See: [#173](https://github.com/nektos/act/issues/173)
## `MODULE_NOT_FOUND` ## `MODULE_NOT_FOUND`
@@ -256,10 +256,10 @@ export DOCKER_HOST=$(docker context inspect --format '{{.Endpoints.docker.Host}}
GitHub Actions offers managed [virtual environments](https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners) for running workflows. In order for `act` to run your workflows locally, it must run a container for the runner defined in your workflow file. Here are the images that `act` uses for each runner type and size: GitHub Actions offers managed [virtual environments](https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners) for running workflows. In order for `act` to run your workflows locally, it must run a container for the runner defined in your workflow file. Here are the images that `act` uses for each runner type and size:
| GitHub Runner | Micro Docker Image | Medium Docker Image | Large Docker Image | | GitHub Runner | Micro Docker Image | Medium Docker Image | Large Docker Image |
| --------------- | -------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------- | | --------------- | -------------------------------- | ------------------------------------------------- | -------------------------------------------------- |
| `ubuntu-latest` | [`node:16-buster-slim`][micro] | [`catthehacker/ubuntu:act-latest`][docker_images] | [`catthehacker/ubuntu:full-latest`][docker_images] | | `ubuntu-latest` | [`node:16-buster-slim`][micro] | [`catthehacker/ubuntu:act-latest`][docker_images] | [`catthehacker/ubuntu:full-latest`][docker_images] |
| `ubuntu-22.04` | [`node:16-bullseye-slim`][micro] | [`catthehacker/ubuntu:act-22.04`][docker_images] | `unavailable` | | `ubuntu-22.04` | [`node:16-bullseye-slim`][micro] | [`catthehacker/ubuntu:act-22.04`][docker_images] | `unavailable` |
| `ubuntu-20.04` | [`node:16-buster-slim`][micro] | [`catthehacker/ubuntu:act-20.04`][docker_images] | [`catthehacker/ubuntu:full-20.04`][docker_images] | | `ubuntu-20.04` | [`node:16-buster-slim`][micro] | [`catthehacker/ubuntu:act-20.04`][docker_images] | [`catthehacker/ubuntu:full-20.04`][docker_images] |
| `ubuntu-18.04` | [`node:16-buster-slim`][micro] | [`catthehacker/ubuntu:act-18.04`][docker_images] | [`catthehacker/ubuntu:full-18.04`][docker_images] | | `ubuntu-18.04` | [`node:16-buster-slim`][micro] | [`catthehacker/ubuntu:act-18.04`][docker_images] | [`catthehacker/ubuntu:full-18.04`][docker_images] |
+11 -3
View File
@@ -9,9 +9,11 @@ require (
github.com/docker/cli v20.10.18+incompatible github.com/docker/cli v20.10.18+incompatible
github.com/docker/distribution v2.8.1+incompatible github.com/docker/distribution v2.8.1+incompatible
github.com/docker/docker v20.10.18+incompatible github.com/docker/docker v20.10.18+incompatible
github.com/docker/go-connections v0.4.0
github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2 github.com/go-git/go-git/v5 v5.4.2
github.com/go-ini/ini v1.67.0 github.com/go-ini/ini v1.67.0
github.com/imdario/mergo v0.3.12
github.com/joho/godotenv v1.4.0 github.com/joho/godotenv v1.4.0
github.com/julienschmidt/httprouter v1.3.0 github.com/julienschmidt/httprouter v1.3.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
@@ -20,6 +22,7 @@ require (
github.com/moby/buildkit v0.10.4 github.com/moby/buildkit v0.10.4
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
github.com/opencontainers/selinux v1.10.2 github.com/opencontainers/selinux v1.10.2
github.com/pkg/errors v0.9.1
github.com/rhysd/actionlint v1.6.20 github.com/rhysd/actionlint v1.6.20
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
@@ -28,6 +31,7 @@ require (
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.0.3
) )
require ( require (
@@ -39,31 +43,34 @@ require (
github.com/containerd/containerd v1.6.6 // indirect github.com/containerd/containerd v1.6.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/google/go-cmp v0.5.7 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/moby/sys/mount v0.3.1 // indirect github.com/moby/sys/mount v0.3.1 // indirect
github.com/moby/sys/mountinfo v0.6.0 // indirect github.com/moby/sys/mountinfo v0.6.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.1.2 // indirect github.com/opencontainers/runc v1.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.3.4 // indirect github.com/rivo/uniseg v0.3.4 // indirect
github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron v1.2.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect github.com/stretchr/objx v0.4.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f // indirect
go.opencensus.io v0.23.0 // indirect go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
@@ -71,6 +78,7 @@ require (
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )
replace github.com/go-git/go-git/v5 => github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220315170230-29ec1bc1e5db replace github.com/go-git/go-git/v5 => github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220315170230-29ec1bc1e5db
+7 -1
View File
@@ -401,6 +401,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -412,6 +413,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -530,6 +533,7 @@ github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/moby/buildkit v0.10.4 h1:FvC+buO8isGpUFZ1abdSLdGHZVqg9sqI4BbFL8tlzP4= github.com/moby/buildkit v0.10.4 h1:FvC+buO8isGpUFZ1abdSLdGHZVqg9sqI4BbFL8tlzP4=
@@ -739,8 +743,11 @@ github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f h1:mvXjJIHRZyhNuGassLTcXTwjiWq7NmjdavZsUnmFybQ=
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -1136,7 +1143,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
+2 -1
View File
@@ -318,7 +318,8 @@ func gitOptions(token string) (fetchOptions git.FetchOptions, pullOptions git.Pu
} }
// NewGitCloneExecutor creates an executor to clone git repos // NewGitCloneExecutor creates an executor to clone git repos
// nolint:gocyclo //
//nolint:gocyclo
func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor { func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
+191
View File
@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2013-2017 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+1083
View File
@@ -0,0 +1,1083 @@
// This file is exact copy of https://github.com/docker/cli/blob/9ac8584acfd501c3f4da0e845e3a40ed15c85041/cli/command/container/opts.go
// appended with license information.
//
// docker/cli is licensed under the Apache License, Version 2.0.
// See DOCKER_LICENSE for the full license text.
//
//nolint:unparam,errcheck,depguard,deadcode,unused
package container
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"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"
)
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
healthRetries int
runtime string
autoRemove bool
init bool
Image string
Args []string
}
// addFlags adds all command line flags that will be used by parse to the FlagSet
func addFlags(flags *pflag.FlagSet) *containerOptions {
copts := &containerOptions{
aliases: opts.NewListOpts(nil),
attach: opts.NewListOpts(validateAttach),
blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice),
capAdd: opts.NewListOpts(nil),
capDrop: opts.NewListOpts(nil),
dns: opts.NewListOpts(opts.ValidateIPAddress),
dnsOptions: opts.NewListOpts(nil),
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
deviceCgroupRules: opts.NewListOpts(validateDeviceCgroupRule),
deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
deviceWriteIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
devices: opts.NewListOpts(nil), // devices can only be validated after we know the server OS
env: opts.NewListOpts(opts.ValidateEnv),
envFile: opts.NewListOpts(nil),
expose: opts.NewListOpts(nil),
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
groupAdd: opts.NewListOpts(nil),
labels: opts.NewListOpts(opts.ValidateLabel),
labelsFile: opts.NewListOpts(nil),
linkLocalIPs: opts.NewListOpts(nil),
links: opts.NewListOpts(opts.ValidateLink),
loggingOpts: opts.NewListOpts(nil),
publish: opts.NewListOpts(nil),
securityOpt: opts.NewListOpts(nil),
storageOpt: opts.NewListOpts(nil),
sysctls: opts.NewMapOpts(nil, opts.ValidateSysctl),
tmpfs: opts.NewListOpts(nil),
ulimits: opts.NewUlimitOpt(nil),
volumes: opts.NewListOpts(nil),
volumesFrom: opts.NewListOpts(nil),
}
// General purpose flags
flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
flags.Var(&copts.deviceCgroupRules, "device-cgroup-rule", "Add a rule to the cgroup allowed devices list")
flags.Var(&copts.devices, "device", "Add a host device to the container")
flags.Var(&copts.gpus, "gpus", "GPU devices to add to the container ('all' to pass all GPUs)")
flags.SetAnnotation("gpus", "version", []string{"1.40"})
flags.VarP(&copts.env, "env", "e", "Set environment variables")
flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables")
flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join")
flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name")
flags.StringVar(&copts.domainname, "domainname", "", "Container NIS domain name")
flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached")
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", "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"})
flags.Var(copts.sysctls, "sysctl", "Sysctl options")
flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
flags.Var(copts.ulimits, "ulimit", "Ulimit options")
flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
// Security
flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities")
flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container")
flags.Var(&copts.securityOpt, "security-opt", "Security Options")
flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use")
flags.StringVar(&copts.cgroupnsMode, "cgroupns", "", `Cgroup namespace to use (host|private)
'host': Run the container in the Docker host's cgroup namespace
'private': Run the container in its own private cgroup namespace
'': Use the cgroup namespace as configured by the
default-cgroupns-mode option on the daemon (default)`)
flags.SetAnnotation("cgroupns", "version", []string{"1.41"})
// Network and port publishing flag
flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
flags.Var(&copts.dns, "dns", "Set custom DNS servers")
// We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way.
// This is to be consistent with service create/update
flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options")
flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options")
flags.MarkHidden("dns-opt")
flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
flags.Var(&copts.links, "link", "Add link to another container")
flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)")
flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host")
flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
// We allow for both "--net" and "--network", although the latter is the recommended way.
flags.Var(&copts.netMode, "net", "Connect a container to a network")
flags.Var(&copts.netMode, "network", "Connect a container to a network")
flags.MarkHidden("net")
// We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way.
flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container")
flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container")
flags.MarkHidden("net-alias")
// Logging and storage
flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container")
flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container")
flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
flags.Var(&copts.mounts, "mount", "Attach a filesystem mount to the container")
// Health-checking
flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)")
flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
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.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
// Resource management
flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
flags.SetAnnotation("cpu-count", "ostype", []string{"windows"})
flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
flags.SetAnnotation("cpu-percent", "ostype", []string{"windows"})
flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"})
flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"})
flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
flags.Var(&copts.cpus, "cpus", "Number of CPUs")
flags.SetAnnotation("cpus", "version", []string{"1.25"})
flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
flags.Var(&copts.ioMaxBandwidth, "io-maxbandwidth", "Maximum IO bandwidth limit for the system drive (Windows only)")
flags.SetAnnotation("io-maxbandwidth", "ostype", []string{"windows"})
flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
flags.SetAnnotation("io-maxiops", "ostype", []string{"windows"})
flags.Var(&copts.kernelMemory, "kernel-memory", "Kernel memory limit")
flags.VarP(&copts.memory, "memory", "m", "Memory limit")
flags.Var(&copts.memoryReservation, "memory-reservation", "Memory soft limit")
flags.Var(&copts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)")
flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)")
// Low-level execution (cgroups, namespaces, ...)
flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
flags.StringVar(&copts.ipcMode, "ipc", "", "IPC mode to use")
flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology")
flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use")
flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm")
flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
flags.SetAnnotation("init", "version", []string{"1.25"})
return copts
}
type containerConfig struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *networktypes.NetworkingConfig
}
// parse parses the args for the specified command and generates a Config,
// a HostConfig and returns them with the specified command.
// If the specified args are not valid, it will return an error.
//
//nolint:gocyclo
func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) {
var (
attachStdin = copts.attach.Get("stdin")
attachStdout = copts.attach.Get("stdout")
attachStderr = copts.attach.Get("stderr")
)
// Validate the input mac address
if copts.macAddress != "" {
if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress)
}
}
if copts.stdin {
attachStdin = true
}
// If -a is not set, attach to stdout and stderr
if copts.attach.Len() == 0 {
attachStdout = true
attachStderr = true
}
var err error
swappiness := copts.swappiness
if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
}
mounts := copts.mounts.Value()
if len(mounts) > 0 && copts.volumeDriver != "" {
logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.")
}
var binds []string
volumes := copts.volumes.GetMap()
// add any bind targets to the list of container volumes
for bind := range copts.volumes.GetMap() {
parsed, _ := loader.ParseVolume(bind)
if parsed.Source != "" {
toBind := bind
if parsed.Type == string(mounttypes.TypeBind) {
if arr := strings.SplitN(bind, ":", 2); len(arr) == 2 {
hostPart := arr[0]
if strings.HasPrefix(hostPart, "."+string(filepath.Separator)) || hostPart == "." {
if absHostPart, err := filepath.Abs(hostPart); err == nil {
hostPart = absHostPart
}
}
toBind = hostPart + ":" + arr[1]
}
}
// after creating the bind mount we want to delete it from the copts.volumes values because
// we do not want bind mounts being committed to image configs
binds = append(binds, toBind)
// We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if
// there are duplicates entries.
delete(volumes, bind)
}
}
// Can't evaluate options passed into --tmpfs until we actually mount
tmpfs := make(map[string]string)
for _, t := range copts.tmpfs.GetAll() {
if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
tmpfs[arr[0]] = arr[1]
} else {
tmpfs[arr[0]] = ""
}
}
var (
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
)
if len(copts.Args) > 0 {
runCmd = strslice.StrSlice(copts.Args)
}
if copts.entrypoint != "" {
entrypoint = strslice.StrSlice{copts.entrypoint}
} else if flags.Changed("entrypoint") {
// if `--entrypoint=` is parsed then Entrypoint is reset
entrypoint = []string{""}
}
publishOpts := copts.publish.GetAll()
var (
ports map[nat.Port]struct{}
portBindings map[nat.Port][]nat.PortBinding
convertedOpts []string
)
convertedOpts, err = convertToStandardNotation(publishOpts)
if err != nil {
return nil, err
}
ports, portBindings, err = nat.ParsePortSpecs(convertedOpts)
if err != nil {
return nil, err
}
// Merge in exposed ports to the map of published ports
for _, e := range copts.expose.GetAll() {
if strings.Contains(e, ":") {
return nil, errors.Errorf("invalid port format for --expose: %s", e)
}
// support two formats for expose, original format <portnum>/[<proto>]
// or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
// parse the start and end port and create a sequence of ports to expose
// if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return nil, err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
}
// validate and parse device mappings. Note we do late validation of the
// device path (as opposed to during flag parsing), as at the time we are
// parsing flags, we haven't yet sent a _ping to the daemon to determine
// what operating system it is.
deviceMappings := []container.DeviceMapping{}
for _, device := range copts.devices.GetAll() {
var (
validated string
deviceMapping container.DeviceMapping
err error
)
validated, err = validateDevice(device, serverOS)
if err != nil {
return nil, err
}
deviceMapping, err = parseDevice(validated, serverOS)
if err != nil {
return nil, err
}
deviceMappings = append(deviceMappings, deviceMapping)
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetAll(), copts.env.GetAll())
if err != nil {
return nil, err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
if err != nil {
return nil, err
}
pidMode := container.PidMode(copts.pidMode)
if !pidMode.Valid() {
return nil, errors.Errorf("--pid: invalid PID mode")
}
utsMode := container.UTSMode(copts.utsMode)
if !utsMode.Valid() {
return nil, errors.Errorf("--uts: invalid UTS mode")
}
usernsMode := container.UsernsMode(copts.usernsMode)
if !usernsMode.Valid() {
return nil, errors.Errorf("--userns: invalid USER mode")
}
cgroupnsMode := container.CgroupnsMode(copts.cgroupnsMode)
if !cgroupnsMode.Valid() {
return nil, errors.Errorf("--cgroupns: invalid CGROUP mode")
}
restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy)
if err != nil {
return nil, err
}
loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll())
if err != nil {
return nil, err
}
securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll())
if err != nil {
return nil, err
}
securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(securityOpts)
storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll())
if err != nil {
return nil, err
}
// Healthcheck
var healthConfig *container.HealthConfig
haveHealthSettings := copts.healthCmd != "" ||
copts.healthInterval != 0 ||
copts.healthTimeout != 0 ||
copts.healthStartPeriod != 0 ||
copts.healthRetries != 0
if copts.noHealthcheck {
if haveHealthSettings {
return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options")
}
test := strslice.StrSlice{"NONE"}
healthConfig = &container.HealthConfig{Test: test}
} else if haveHealthSettings {
var probe strslice.StrSlice
if copts.healthCmd != "" {
args := []string{"CMD-SHELL", copts.healthCmd}
probe = strslice.StrSlice(args)
}
if copts.healthInterval < 0 {
return nil, errors.Errorf("--health-interval cannot be negative")
}
if copts.healthTimeout < 0 {
return nil, errors.Errorf("--health-timeout cannot be negative")
}
if copts.healthRetries < 0 {
return nil, errors.Errorf("--health-retries cannot be negative")
}
if copts.healthStartPeriod < 0 {
return nil, fmt.Errorf("--health-start-period cannot be negative")
}
healthConfig = &container.HealthConfig{
Test: probe,
Interval: copts.healthInterval,
Timeout: copts.healthTimeout,
StartPeriod: copts.healthStartPeriod,
Retries: copts.healthRetries,
}
}
resources := container.Resources{
CgroupParent: copts.cgroupParent,
Memory: copts.memory.Value(),
MemoryReservation: copts.memoryReservation.Value(),
MemorySwap: copts.memorySwap.Value(),
MemorySwappiness: &copts.swappiness,
KernelMemory: copts.kernelMemory.Value(),
OomKillDisable: &copts.oomKillDisable,
NanoCPUs: copts.cpus.Value(),
CPUCount: copts.cpuCount,
CPUPercent: copts.cpuPercent,
CPUShares: copts.cpuShares,
CPUPeriod: copts.cpuPeriod,
CpusetCpus: copts.cpusetCpus,
CpusetMems: copts.cpusetMems,
CPUQuota: copts.cpuQuota,
CPURealtimePeriod: copts.cpuRealtimePeriod,
CPURealtimeRuntime: copts.cpuRealtimeRuntime,
PidsLimit: &copts.pidsLimit,
BlkioWeight: copts.blkioWeight,
BlkioWeightDevice: copts.blkioWeightDevice.GetList(),
BlkioDeviceReadBps: copts.deviceReadBps.GetList(),
BlkioDeviceWriteBps: copts.deviceWriteBps.GetList(),
BlkioDeviceReadIOps: copts.deviceReadIOps.GetList(),
BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(),
IOMaximumIOps: copts.ioMaxIOps,
IOMaximumBandwidth: uint64(copts.ioMaxBandwidth),
Ulimits: copts.ulimits.GetList(),
DeviceCgroupRules: copts.deviceCgroupRules.GetAll(),
Devices: deviceMappings,
DeviceRequests: copts.gpus.Value(),
}
config := &container.Config{
Hostname: copts.hostname,
Domainname: copts.domainname,
ExposedPorts: ports,
User: copts.user,
Tty: copts.tty,
// TODO: deprecated, it comes from -n, --networking
// it's still needed internally to set the network to disabled
// if e.g. bridge is none in daemon opts, and in inspect
NetworkDisabled: false,
OpenStdin: copts.stdin,
AttachStdin: attachStdin,
AttachStdout: attachStdout,
AttachStderr: attachStderr,
Env: envVariables,
Cmd: runCmd,
Image: copts.Image,
Volumes: volumes,
MacAddress: copts.macAddress,
Entrypoint: entrypoint,
WorkingDir: copts.workingDir,
Labels: opts.ConvertKVStringsToMap(labels),
StopSignal: copts.stopSignal,
Healthcheck: healthConfig,
}
if flags.Changed("stop-timeout") {
config.StopTimeout = &copts.stopTimeout
}
hostConfig := &container.HostConfig{
Binds: binds,
ContainerIDFile: copts.containerIDFile,
OomScoreAdj: copts.oomScoreAdj,
AutoRemove: copts.autoRemove,
Privileged: copts.privileged,
PortBindings: portBindings,
Links: copts.links.GetAll(),
PublishAllPorts: copts.publishAll,
// Make sure the dns fields are never nil.
// New containers don't ever have those fields nil,
// but pre created containers can still have those nil values.
// See https://github.com/docker/docker/pull/17779
// for a more detailed explanation on why we don't want that.
DNS: copts.dns.GetAllOrEmpty(),
DNSSearch: copts.dnsSearch.GetAllOrEmpty(),
DNSOptions: copts.dnsOptions.GetAllOrEmpty(),
ExtraHosts: copts.extraHosts.GetAll(),
VolumesFrom: copts.volumesFrom.GetAll(),
IpcMode: container.IpcMode(copts.ipcMode),
NetworkMode: container.NetworkMode(copts.netMode.NetworkMode()),
PidMode: pidMode,
UTSMode: utsMode,
UsernsMode: usernsMode,
CgroupnsMode: cgroupnsMode,
CapAdd: strslice.StrSlice(copts.capAdd.GetAll()),
CapDrop: strslice.StrSlice(copts.capDrop.GetAll()),
GroupAdd: copts.groupAdd.GetAll(),
RestartPolicy: restartPolicy,
SecurityOpt: securityOpts,
StorageOpt: storageOpts,
ReadonlyRootfs: copts.readonlyRootfs,
LogConfig: container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts},
VolumeDriver: copts.volumeDriver,
Isolation: container.Isolation(copts.isolation),
ShmSize: copts.shmSize.Value(),
Resources: resources,
Tmpfs: tmpfs,
Sysctls: copts.sysctls.GetAll(),
Runtime: copts.runtime,
Mounts: mounts,
MaskedPaths: maskedPaths,
ReadonlyPaths: readonlyPaths,
}
if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() {
return nil, errors.Errorf("Conflicting options: --restart and --rm")
}
// only set this value if the user provided the flag, else it should default to nil
if flags.Changed("init") {
hostConfig.Init = &copts.init
}
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
networkingConfig := &networktypes.NetworkingConfig{
EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
}
networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts)
if err != nil {
return nil, err
}
return &containerConfig{
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
}, nil
}
// parseNetworkOpts converts --network advanced options to endpoint-specs, and combines
// them with the old --network-alias and --links. If returns an error if conflicting options
// are found.
//
// this function may return _multiple_ endpoints, which is not currently supported
// by the daemon, but may be in future; it's up to the daemon to produce an error
// in case that is not supported.
func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.EndpointSettings, error) {
var (
endpoints = make(map[string]*networktypes.EndpointSettings, len(copts.netMode.Value()))
hasUserDefined, hasNonUserDefined bool
)
for i, n := range copts.netMode.Value() {
n := n
if container.NetworkMode(n.Target).IsUserDefined() {
hasUserDefined = true
} else {
hasNonUserDefined = true
}
if i == 0 {
// The first network corresponds with what was previously the "only"
// network, and what would be used when using the non-advanced syntax
// `--network-alias`, `--link`, `--ip`, `--ip6`, and `--link-local-ip`
// are set on this network, to preserve backward compatibility with
// the non-advanced notation
if err := applyContainerOptions(&n, copts); err != nil {
return nil, err
}
}
ep, err := parseNetworkAttachmentOpt(n)
if err != nil {
return nil, err
}
if _, ok := endpoints[n.Target]; ok {
return nil, errdefs.InvalidParameter(errors.Errorf("network %q is specified multiple times", n.Target))
}
// For backward compatibility: if no custom options are provided for the network,
// and only a single network is specified, omit the endpoint-configuration
// on the client (the daemon will still create it when creating the container)
if i == 0 && len(copts.netMode.Value()) == 1 {
if ep == nil || reflect.DeepEqual(*ep, networktypes.EndpointSettings{}) {
continue
}
}
endpoints[n.Target] = ep
}
if hasUserDefined && hasNonUserDefined {
return nil, errdefs.InvalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes"))
}
return endpoints, nil
}
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"))
}
if len(n.Links) > 0 && copts.links.Len() > 0 {
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links"))
}
if n.IPv4Address != "" && copts.ipv4Address != "" {
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address"))
}
if n.IPv6Address != "" && copts.ipv6Address != "" {
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address"))
}
if copts.aliases.Len() > 0 {
n.Aliases = make([]string, copts.aliases.Len())
copy(n.Aliases, copts.aliases.GetAll())
}
if copts.links.Len() > 0 {
n.Links = make([]string, copts.links.Len())
copy(n.Links, copts.links.GetAll())
}
if copts.ipv4Address != "" {
n.IPv4Address = copts.ipv4Address
}
if copts.ipv6Address != "" {
n.IPv6Address = copts.ipv6Address
}
// 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())
}
return nil
}
func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.EndpointSettings, error) {
if strings.TrimSpace(ep.Target) == "" {
return nil, errors.New("no name set for network")
}
if !container.NetworkMode(ep.Target).IsUserDefined() {
if len(ep.Aliases) > 0 {
return nil, errors.New("network-scoped aliases are only supported for user-defined networks")
}
if len(ep.Links) > 0 {
return nil, errors.New("links are only supported for user-defined networks")
}
}
epConfig := &networktypes.EndpointSettings{}
epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...)
if len(ep.DriverOpts) > 0 {
epConfig.DriverOpts = make(map[string]string)
epConfig.DriverOpts = ep.DriverOpts
}
if len(ep.Links) > 0 {
epConfig.Links = ep.Links
}
if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 {
epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
IPv4Address: ep.IPv4Address,
IPv6Address: ep.IPv6Address,
LinkLocalIPs: ep.LinkLocalIPs,
}
}
return epConfig, nil
}
func convertToStandardNotation(ports []string) ([]string, error) {
optsList := []string{}
for _, publish := range ports {
if strings.Contains(publish, "=") {
params := map[string]string{"protocol": "tcp"}
for _, param := range strings.Split(publish, ",") {
opt := strings.Split(param, "=")
if len(opt) < 2 {
return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
}
params[opt[0]] = opt[1]
}
optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["published"], params["target"], params["protocol"]))
} else {
optsList = append(optsList, publish)
}
}
return optsList, nil
}
func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts)
if loggingDriver == "none" && len(loggingOpts) > 0 {
return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver)
}
return loggingOptsMap, nil
}
// takes a local seccomp daemon, reads the file contents for sending to the daemon
func parseSecurityOpts(securityOpts []string) ([]string, error) {
for key, opt := range securityOpts {
con := strings.SplitN(opt, "=", 2)
if len(con) == 1 && con[0] != "no-new-privileges" {
if strings.Contains(opt, ":") {
con = strings.SplitN(opt, ":", 2)
} else {
return securityOpts, errors.Errorf("Invalid --security-opt: %q", opt)
}
}
if con[0] == "seccomp" && con[1] != "unconfined" {
f, err := os.ReadFile(con[1])
if err != nil {
return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
}
b := bytes.NewBuffer(nil)
if err := json.Compact(b, f); err != nil {
return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
}
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
}
}
return securityOpts, nil
}
// parseSystemPaths checks if `systempaths=unconfined` security option is set,
// and returns the `MaskedPaths` and `ReadonlyPaths` accordingly. An updated
// list of security options is returned with this option removed, because the
// `unconfined` option is handled client-side, and should not be sent to the
// daemon.
func parseSystemPaths(securityOpts []string) (filtered, maskedPaths, readonlyPaths []string) {
filtered = securityOpts[:0]
for _, opt := range securityOpts {
if opt == "systempaths=unconfined" {
maskedPaths = []string{}
readonlyPaths = []string{}
} else {
filtered = append(filtered, opt)
}
}
return filtered, maskedPaths, readonlyPaths
}
// parses storage options per container into a map
func parseStorageOpts(storageOpts []string) (map[string]string, error) {
m := make(map[string]string)
for _, option := range storageOpts {
if strings.Contains(option, "=") {
opt := strings.SplitN(option, "=", 2)
m[opt[0]] = opt[1]
} else {
return nil, errors.Errorf("invalid storage option")
}
}
return m, nil
}
// parseDevice parses a device mapping string to a container.DeviceMapping struct
func parseDevice(device, serverOS string) (container.DeviceMapping, error) {
switch serverOS {
case "linux":
return parseLinuxDevice(device)
case "windows":
return parseWindowsDevice(device)
}
return container.DeviceMapping{}, errors.Errorf("unknown server OS: %s", serverOS)
}
// parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct
// knowing that the target is a Linux daemon
func parseLinuxDevice(device string) (container.DeviceMapping, error) {
var src, dst string
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
permissions = arr[2]
fallthrough
case 2:
if validDeviceMode(arr[1]) {
permissions = arr[1]
} else {
dst = arr[1]
}
fallthrough
case 1:
src = arr[0]
default:
return container.DeviceMapping{}, errors.Errorf("invalid device specification: %s", device)
}
if dst == "" {
dst = src
}
deviceMapping := container.DeviceMapping{
PathOnHost: src,
PathInContainer: dst,
CgroupPermissions: permissions,
}
return deviceMapping, nil
}
// parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct
// knowing that the target is a Windows daemon
func parseWindowsDevice(device string) (container.DeviceMapping, error) {
return container.DeviceMapping{PathOnHost: device}, nil
}
// validateDeviceCgroupRule validates a device cgroup rule string format
// It will make sure 'val' is in the form:
//
// 'type major:minor mode'
func validateDeviceCgroupRule(val string) (string, error) {
if deviceCgroupRuleRegexp.MatchString(val) {
return val, nil
}
return val, errors.Errorf("invalid device cgroup format '%s'", val)
}
// validDeviceMode checks if the mode for device is valid or not.
// Valid mode is a composition of r (read), w (write), and m (mknod).
func validDeviceMode(mode string) bool {
var legalDeviceMode = map[rune]bool{
'r': true,
'w': true,
'm': true,
}
if mode == "" {
return false
}
for _, c := range mode {
if !legalDeviceMode[c] {
return false
}
legalDeviceMode[c] = false
}
return true
}
// validateDevice validates a path for devices
func validateDevice(val string, serverOS string) (string, error) {
switch serverOS {
case "linux":
return validateLinuxPath(val, validDeviceMode)
case "windows":
// Windows does validation entirely server-side
return val, nil
}
return "", errors.Errorf("unknown server OS: %s", serverOS)
}
// validateLinuxPath is the implementation of validateDevice knowing that the
// target server operating system is a Linux daemon.
// It will make sure 'val' is in the form:
//
// [host-dir:]container-path[:mode]
//
// It also validates the device mode.
func validateLinuxPath(val string, validator func(string) bool) (string, error) {
var containerPath string
var mode string
if strings.Count(val, ":") > 2 {
return val, errors.Errorf("bad format for path: %s", val)
}
split := strings.SplitN(val, ":", 3)
if split[0] == "" {
return val, errors.Errorf("bad format for path: %s", val)
}
switch len(split) {
case 1:
containerPath = split[0]
val = path.Clean(containerPath)
case 2:
if isValid := validator(split[1]); isValid {
containerPath = split[0]
mode = split[1]
val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
} else {
containerPath = split[1]
val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
}
case 3:
containerPath = split[1]
mode = split[2]
if isValid := validator(split[2]); !isValid {
return val, errors.Errorf("bad mode specified: %s", mode)
}
val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
}
if !path.IsAbs(containerPath) {
return val, errors.Errorf("%s is not an absolute path", containerPath)
}
return val, nil
}
// validateAttach validates that the specified string is a valid attach option.
func validateAttach(val string) (string, error) {
s := strings.ToLower(val)
for _, str := range []string{"stdin", "stdout", "stderr"} {
if s == str {
return s, nil
}
}
return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR")
}
func validateAPIVersion(c *containerConfig, serverAPIVersion string) error {
for _, m := range c.HostConfig.Mounts {
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
}
+982
View File
@@ -0,0 +1,982 @@
// This file is exact copy of https://github.com/docker/cli/blob/9ac8584acfd501c3f4da0e845e3a40ed15c85041/cli/command/container/opts_test.go with:
// * appended with license information
// * commented out case 'invalid-mixed-network-types' in test TestParseNetworkConfig
//
// docker/cli is licensed under the Apache License, Version 2.0.
// See DOCKER_LICENSE for the full license text.
//
//nolint:unparam,whitespace,depguard,dupl,gocritic
package container
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
func TestValidateAttach(t *testing.T) {
valid := []string{
"stdin",
"stdout",
"stderr",
"STDIN",
"STDOUT",
"STDERR",
}
if _, err := validateAttach("invalid"); err == nil {
t.Fatal("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
}
for _, attach := range valid {
value, err := validateAttach(attach)
if err != nil {
t.Fatal(err)
}
if value != strings.ToLower(attach) {
t.Fatalf("Expected [%v], got [%v]", attach, value)
}
}
}
func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
flags, copts := setupRunFlags()
if err := flags.Parse(args); err != nil {
return nil, nil, nil, err
}
// TODO: fix tests to accept ContainerConfig
containerConfig, err := parse(flags, copts, runtime.GOOS)
if err != nil {
return nil, nil, nil, err
}
return containerConfig.Config, containerConfig.HostConfig, containerConfig.NetworkingConfig, err
}
func setupRunFlags() (*pflag.FlagSet, *containerOptions) {
flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
flags.SetOutput(io.Discard)
flags.Usage = nil
copts := addFlags(flags)
return flags, copts
}
func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
t.Helper()
config, hostConfig, _, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash"))
assert.NilError(t, err)
return config, hostConfig
}
func TestParseRunLinks(t *testing.T) {
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" {
t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
}
if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
}
}
func TestParseRunAttach(t *testing.T) {
tests := []struct {
input string
expected container.Config
}{
{
input: "",
expected: container.Config{
AttachStdout: true,
AttachStderr: true,
},
},
{
input: "-i",
expected: container.Config{
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
},
},
{
input: "-a stdin",
expected: container.Config{
AttachStdin: true,
},
},
{
input: "-a stdin -a stdout",
expected: container.Config{
AttachStdin: true,
AttachStdout: true,
},
},
{
input: "-a stdin -a stdout -a stderr",
expected: container.Config{
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.input, func(t *testing.T) {
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)
})
}
}
func TestParseRunWithInvalidArgs(t *testing.T) {
tests := []struct {
args []string
error string
}{
{
args: []string{"-a", "ubuntu", "bash"},
error: `invalid argument "ubuntu" for "-a, --attach" flag: valid streams are STDIN, STDOUT and STDERR`,
},
{
args: []string{"-a", "invalid", "ubuntu", "bash"},
error: `invalid argument "invalid" for "-a, --attach" flag: valid streams are STDIN, STDOUT and STDERR`,
},
{
args: []string{"-a", "invalid", "-a", "stdout", "ubuntu", "bash"},
error: `invalid argument "invalid" for "-a, --attach" flag: valid streams are STDIN, STDOUT and STDERR`,
},
{
args: []string{"-a", "stdout", "-a", "stderr", "-z", "ubuntu", "bash"},
error: `unknown shorthand flag: 'z' in -z`,
},
{
args: []string{"-a", "stdin", "-z", "ubuntu", "bash"},
error: `unknown shorthand flag: 'z' in -z`,
},
{
args: []string{"-a", "stdout", "-z", "ubuntu", "bash"},
error: `unknown shorthand flag: 'z' in -z`,
},
{
args: []string{"-a", "stderr", "-z", "ubuntu", "bash"},
error: `unknown shorthand flag: 'z' in -z`,
},
{
args: []string{"-z", "--rm", "ubuntu", "bash"},
error: `unknown shorthand flag: 'z' in -z`,
},
}
flags, _ := setupRunFlags()
for _, tc := range tests {
t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
assert.Error(t, flags.Parse(tc.args), tc.error)
})
}
}
//nolint:gocyclo
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 {
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)
}
// Two volumes
arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
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 {
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] {
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 {
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
}
// Two bind mounts, first read-only, second read-write.
// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
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 {
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 {
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 {
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] {
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)
}
// 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 {
t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
}
}
}
// setupPlatformVolume takes two arrays of volume specs - a Unix style
// spec and a Windows style spec. Depending on the platform being unit tested,
// it returns one of them, along with a volume string that would be passed
// on the docker CLI (e.g. -v /bar -v /foo).
func setupPlatformVolume(u []string, w []string) ([]string, string) {
var a []string
if runtime.GOOS == "windows" {
a = w
} else {
a = u
}
s := ""
for _, v := range a {
s = s + "-v " + v + " "
}
return a, s
}
// check if (a == c && b == d) || (a == d && b == c)
// because maps are randomized
func compareRandomizedStrings(a, b, c, d string) error {
if a == c && b == d {
return nil
}
if a == d && b == c {
return nil
}
return errors.Errorf("strings don't match")
}
// Simple parse with MacAddress validation
func TestParseWithMacAddress(t *testing.T) {
invalidMacAddress := "--mac-address=invalidMacAddress"
validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
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)
}
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)
}
}
func TestRunFlagsParseWithMemory(t *testing.T) {
flags, _ := setupRunFlags()
args := []string{"--memory=invalid", "img", "cmd"}
err := flags.Parse(args)
assert.ErrorContains(t, err, `invalid argument "invalid" for "-m, --memory" flag`)
_, hostconfig := mustParse(t, "--memory=1G")
assert.Check(t, is.Equal(int64(1073741824), hostconfig.Memory))
}
func TestParseWithMemorySwap(t *testing.T) {
flags, _ := setupRunFlags()
args := []string{"--memory-swap=invalid", "img", "cmd"}
err := flags.Parse(args)
assert.ErrorContains(t, err, `invalid argument "invalid" for "--memory-swap" flag`)
_, hostconfig := mustParse(t, "--memory-swap=1G")
assert.Check(t, is.Equal(int64(1073741824), hostconfig.MemorySwap))
_, hostconfig = mustParse(t, "--memory-swap=-1")
assert.Check(t, is.Equal(int64(-1), hostconfig.MemorySwap))
}
func TestParseHostname(t *testing.T) {
validHostnames := map[string]string{
"hostname": "hostname",
"host-name": "host-name",
"hostname123": "hostname123",
"123hostname": "123hostname",
"hostname-of-63-bytes-long-should-be-valid-and-without-any-error": "hostname-of-63-bytes-long-should-be-valid-and-without-any-error",
}
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 {
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 != "" {
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 != "" {
t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got %q", config.Hostname)
}
}
func TestParseHostnameDomainname(t *testing.T) {
validDomainnames := map[string]string{
"domainname": "domainname",
"domain-name": "domain-name",
"domainname123": "domainname123",
"123domainname": "123domainname",
"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 {
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" {
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" {
t.Fatalf("Expected the config to have 'hostname' as 'another-prefix' and 'domainname' as 'domainname.tld', got %q and %q", config.Hostname, config.Domainname)
}
}
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.",
"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`,
"1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
}
valids := map[string][]nat.Port{
"8080/tcp": {"8080/tcp"},
"8080/udp": {"8080/udp"},
"8080/ncp": {"8080/ncp"},
"8080-8080/udp": {"8080/udp"},
"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
}
for expose, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
}
}
for expose, exposedPorts := range valids {
config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.ExposedPorts) != len(exposedPorts) {
t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
}
for _, port := range exposedPorts {
if _, ok := config.ExposedPorts[port]; !ok {
t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
}
}
}
// Merge with actual published port
config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.ExposedPorts) != 2 {
t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
}
ports := []nat.Port{"80/tcp", "81/tcp"}
for _, port := range ports {
if _, ok := config.ExposedPorts[port]; !ok {
t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
}
}
}
func TestParseDevice(t *testing.T) {
skip.If(t, runtime.GOOS != "linux") // Windows and macOS validate server-side
valids := map[string]container.DeviceMapping{
"/dev/snd": {
PathOnHost: "/dev/snd",
PathInContainer: "/dev/snd",
CgroupPermissions: "rwm",
},
"/dev/snd:rw": {
PathOnHost: "/dev/snd",
PathInContainer: "/dev/snd",
CgroupPermissions: "rw",
},
"/dev/snd:/something": {
PathOnHost: "/dev/snd",
PathInContainer: "/something",
CgroupPermissions: "rwm",
},
"/dev/snd:/something:rw": {
PathOnHost: "/dev/snd",
PathInContainer: "/something",
CgroupPermissions: "rw",
},
}
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.HostConfig
expectedErr string
}{
{
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{},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "single-network-legacy-with-options",
flags: []string{
"--ip", "172.20.88.22",
"--ip6", "2001:db8::8822",
"--link", "foo:bar",
"--link", "bar:baz",
"--link-local-ip", "169.254.2.2",
"--link-local-ip", "fe80::169:254:2:2",
"--network", "name=net1",
"--network-alias", "web1",
"--network-alias", "web2",
},
expected: map[string]*networktypes.EndpointSettings{
"net1": {
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"},
},
Links: []string{"foo:bar", "bar:baz"},
Aliases: []string{"web1", "web2"},
},
},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "multiple-network-advanced-mixed",
flags: []string{
"--ip", "172.20.88.22",
"--ip6", "2001:db8::8822",
"--link", "foo:bar",
"--link", "bar:baz",
"--link-local-ip", "169.254.2.2",
"--link-local-ip", "fe80::169:254:2:2",
"--network", "name=net1,driver-opt=field1=value1",
"--network-alias", "web1",
"--network-alias", "web2",
"--network", "net2",
"--network", "name=net3,alias=web3,driver-opt=field3=value3,ip=172.20.88.22,ip6=2001:db8::8822",
},
expected: map[string]*networktypes.EndpointSettings{
"net1": {
DriverOpts: map[string]string{"field1": "value1"},
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"},
},
Links: []string{"foo:bar", "bar:baz"},
Aliases: []string{"web1", "web2"},
},
"net2": {},
"net3": {
DriverOpts: map[string]string{"field3": "value3"},
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
},
Aliases: []string{"web3"},
},
},
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"},
expected: map[string]*networktypes.EndpointSettings{
"net1": {
DriverOpts: map[string]string{
"field1": "value1",
"field2": "value2",
},
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
},
Aliases: []string{"web1", "web2"},
},
},
expectedCfg: 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",
flags: []string{"--network", "duplicate", "--network", "name=duplicate"},
expectedErr: `network "duplicate" is specified multiple times`,
},
{
name: "conflict-options-alias",
flags: []string{"--network", "name=net1,alias=web1", "--network-alias", "web1"},
expectedErr: `conflicting options: cannot specify both --network-alias and per-network alias`,
},
{
name: "conflict-options-ip",
flags: []string{"--network", "name=net1,ip=172.20.88.22,ip6=2001:db8::8822", "--ip", "172.20.88.22"},
expectedErr: `conflicting options: cannot specify both --ip and per-network IPv4 address`,
},
{
name: "conflict-options-ip6",
flags: []string{"--network", "name=net1,ip=172.20.88.22,ip6=2001:db8::8822", "--ip6", "2001:db8::8822"},
expectedErr: `conflicting options: cannot specify both --ip6 and per-network IPv6 address`,
},
// case is skipped as it fails w/o any change
//
//{
// name: "invalid-mixed-network-types",
// flags: []string{"--network", "name=host", "--network", "net1"},
// expectedErr: `conflicting options: cannot attach both user-defined and non-user-defined network-modes`,
//},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, hConfig, nwConfig, err := parseRun(tc.flags)
if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
return
}
assert.NilError(t, err)
assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedCfg.NetworkMode)
assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected)
})
}
}
func TestParseModes(t *testing.T) {
// pid ko
flags, copts := setupRunFlags()
args := []string{"--pid=container:", "img", "cmd"}
assert.NilError(t, flags.Parse(args))
_, err := parse(flags, copts, runtime.GOOS)
assert.ErrorContains(t, err, "--pid: invalid PID mode")
// pid ok
_, hostconfig, _, err := parseRun([]string{"--pid=host", "img", "cmd"})
assert.NilError(t, err)
if !hostconfig.PidMode.Valid() {
t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
}
// uts ko
_, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"}) //nolint:dogsled
assert.ErrorContains(t, err, "--uts: invalid UTS mode")
// uts ok
_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
assert.NilError(t, err)
if !hostconfig.UTSMode.Valid() {
t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
}
}
func TestRunFlagsParseShmSize(t *testing.T) {
// shm-size ko
flags, _ := setupRunFlags()
args := []string{"--shm-size=a128m", "img", "cmd"}
expectedErr := `invalid argument "a128m" for "--shm-size" flag:`
err := flags.Parse(args)
assert.ErrorContains(t, err, expectedErr)
// shm-size ok
_, hostconfig, _, err := parseRun([]string{"--shm-size=128m", "img", "cmd"})
assert.NilError(t, err)
if hostconfig.ShmSize != 134217728 {
t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
}
}
func TestParseRestartPolicy(t *testing.T) {
invalids := map[string]string{
"always:2:3": "invalid restart policy format",
"on-failure:invalid": "maximum retry count must be an integer",
}
valids := map[string]container.RestartPolicy{
"": {},
"always": {
Name: "always",
MaximumRetryCount: 0,
},
"on-failure:1": {
Name: "on-failure",
MaximumRetryCount: 1,
},
}
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)
}
}
}
func TestParseRestartPolicyAutoRemove(t *testing.T) {
expected := "Conflicting options: --restart and --rm"
_, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"}) //nolint:dogsled
if err == nil || err.Error() != expected {
t.Fatalf("Expected error %v, but got none", expected)
}
}
func TestParseHealth(t *testing.T) {
checkOk := func(args ...string) *container.HealthConfig {
config, _, _, err := parseRun(args)
if err != nil {
t.Fatalf("%#v: %v", args, err)
}
return config.Healthcheck
}
checkError := func(expected string, args ...string) {
config, _, _, err := parseRun(args)
if err == nil {
t.Fatalf("Expected error, but got %#v", config)
}
if err.Error() != expected {
t.Fatalf("Expected %#v, got %#v", expected, err)
}
}
health := checkOk("--no-healthcheck", "img", "cmd")
if health == nil || len(health.Test) != 1 || health.Test[0] != "NONE" {
t.Fatalf("--no-healthcheck failed: %#v", health)
}
health = checkOk("--health-cmd=/check.sh -q", "img", "cmd")
if len(health.Test) != 2 || health.Test[0] != "CMD-SHELL" || health.Test[1] != "/check.sh -q" {
t.Fatalf("--health-cmd: got %#v", health.Test)
}
if health.Timeout != 0 {
t.Fatalf("--health-cmd: timeout = %s", health.Timeout)
}
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", "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)
}
}
func TestParseLoggingOpts(t *testing.T) {
// logging opts ko
if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
}
// logging opts ok
_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
}
}
func TestParseEnvfileVariables(t *testing.T) {
e := "open nonexistent: no such file or directory"
if runtime.GOOS == "windows" {
e = "open nonexistent: The system cannot find the file specified."
}
// env ko
if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
t.Fatalf("Expected an error with message '%s', got %v", e, err)
}
// env ok
config, _, _, err := parseRun([]string{"--env-file=testdata/valid.env", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
}
config, _, _, err = parseRun([]string{"--env-file=testdata/valid.env", "--env=ENV2=value2", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
t.Fatalf("Expected a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
}
}
func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
// UTF8 with BOM
config, _, _, err := parseRun([]string{"--env-file=testdata/utf8.env", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"}
if len(config.Env) != len(env) {
t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env)
}
for i, v := range env {
if config.Env[i] != v {
t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i]))
}
}
// UTF16 with BOM
e := "contains invalid utf8 bytes at line"
if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
t.Fatalf("Expected an error with message '%s', got %v", e, err)
}
// UTF16BE with BOM
if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
t.Fatalf("Expected an error with message '%s', got %v", e, err)
}
}
func TestParseLabelfileVariables(t *testing.T) {
e := "open nonexistent: no such file or directory"
if runtime.GOOS == "windows" {
e = "open nonexistent: The system cannot find the file specified."
}
// label ko
if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
t.Fatalf("Expected an error with message '%s', got %v", e, err)
}
// label ok
config, _, _, err := parseRun([]string{"--label-file=testdata/valid.label", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
}
config, _, _, err = parseRun([]string{"--label-file=testdata/valid.label", "--label=LABEL2=value2", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
t.Fatalf("Expected a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
}
}
func TestParseEntryPoint(t *testing.T) {
config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
if err != nil {
t.Fatal(err)
}
if len(config.Entrypoint) != 1 && config.Entrypoint[0] != "anything" {
t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
}
}
func TestValidateDevice(t *testing.T) {
skip.If(t, runtime.GOOS != "linux") // Windows and macOS validate server-side
valid := []string{
"/home",
"/home:/home",
"/home:/something/else",
"/with space",
"/home:/with space",
"relative:/absolute-path",
"hostPath:/containerPath:r",
"/hostPath:/containerPath:rw",
"/hostPath:/containerPath:mrw",
}
invalid := map[string]string{
"": "bad format for path: ",
"./": "./ is not an absolute path",
"../": "../ is not an absolute path",
"/:../": "../ is not an absolute path",
"/:path": "path is not an absolute path",
":": "bad format for path: :",
"/tmp:": " is not an absolute path",
":test": "bad format for path: :test",
":/test": "bad format for path: :/test",
"tmp:": " is not an absolute path",
":test:": "bad format for path: :test:",
"::": "bad format for path: ::",
":::": "bad format for path: :::",
"/tmp:::": "bad format for path: /tmp:::",
":/tmp::": "bad format for path: :/tmp::",
"path:ro": "ro is not an absolute path",
"path:rr": "rr is not an absolute path",
"a:/b:ro": "bad mode specified: ro",
"a:/b:rr": "bad mode specified: rr",
}
for _, path := range valid {
if _, err := validateDevice(path, runtime.GOOS); err != nil {
t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
}
}
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())
}
}
}
}
func TestParseSystemPaths(t *testing.T) {
tests := []struct {
doc string
in, out, masked, readonly []string
}{
{
doc: "not set",
in: []string{},
out: []string{},
},
{
doc: "not set, preserve other options",
in: []string{
"seccomp=unconfined",
"apparmor=unconfined",
"label=user:USER",
"foo=bar",
},
out: []string{
"seccomp=unconfined",
"apparmor=unconfined",
"label=user:USER",
"foo=bar",
},
},
{
doc: "unconfined",
in: []string{"systempaths=unconfined"},
out: []string{},
masked: []string{},
readonly: []string{},
},
{
doc: "unconfined and other options",
in: []string{"foo=bar", "bar=baz", "systempaths=unconfined"},
out: []string{"foo=bar", "bar=baz"},
masked: []string{},
readonly: []string{},
},
{
doc: "unknown option",
in: []string{"foo=bar", "systempaths=unknown", "bar=baz"},
out: []string{"foo=bar", "systempaths=unknown", "bar=baz"},
},
}
for _, tc := range tests {
securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(tc.in)
assert.DeepEqual(t, securityOpts, tc.out)
assert.DeepEqual(t, maskedPaths, tc.masked)
assert.DeepEqual(t, readonlyPaths, tc.readonly)
}
}
func TestConvertToStandardNotation(t *testing.T) {
valid := map[string][]string{
"20:10/tcp": {"target=10,published=20"},
"40:30": {"40:30"},
"20:20 80:4444": {"20:20", "80:4444"},
"1500:2500/tcp 1400:1300": {"target=2500,published=1500", "1400:1300"},
"1500:200/tcp 90:80/tcp": {"published=1500,target=200", "target=80,published=90"},
}
invalid := [][]string{
{"published=1500,target:444"},
{"published=1500,444"},
{"published=1500,target,444"},
}
for key, ports := range valid {
convertedPorts, err := convertToStandardNotation(ports)
if err != nil {
assert.NilError(t, err)
}
assert.DeepEqual(t, strings.Split(key, " "), convertedPorts)
}
for _, ports := range invalid {
if _, err := convertToStandardNotation(ports); err == nil {
t.Fatalf("ConvertToStandardNotation(`%q`) should have failed conversion", ports)
}
}
}
+48 -6
View File
@@ -21,6 +21,10 @@ import (
"github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/imdario/mergo"
"github.com/kballard/go-shellquote"
"github.com/spf13/pflag"
"github.com/docker/cli/cli/connhelper" "github.com/docker/cli/cli/connhelper"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
@@ -53,7 +57,7 @@ type NewContainerInput struct {
Privileged bool Privileged bool
UsernsMode string UsernsMode string
Platform string Platform string
Hostname string Options string
} }
// FileEntry is a file to copy to a container // FileEntry is a file to copy to a container
@@ -379,13 +383,39 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
isTerminal := term.IsTerminal(int(os.Stdout.Fd())) isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
input := cr.input input := cr.input
// parse configuration from CLI container.options
flags := pflag.NewFlagSet("container_flags", pflag.ContinueOnError)
copts := addFlags(flags)
optionsArgs, err := shellquote.Split(input.Options)
if err != nil {
return fmt.Errorf("Cannot split container options: '%s': '%w'", input.Options, err)
}
err = flags.Parse(optionsArgs)
if err != nil {
return fmt.Errorf("Cannot parse container options: '%s': '%w'", input.Options, err)
}
containerConfig, err := parse(flags, copts, "")
if err != nil {
return fmt.Errorf("Cannot process container options: '%s': '%w'", input.Options, err)
}
config := &container.Config{ config := &container.Config{
Image: input.Image, Image: input.Image,
WorkingDir: input.WorkingDir, WorkingDir: input.WorkingDir,
Env: input.Env, Env: input.Env,
Tty: isTerminal, Tty: isTerminal,
Hostname: input.Hostname,
} }
logger.Debugf("Common container.Config ==> %+v", config)
logger.Debugf("Custom container.Config from options ==> %+v", containerConfig.Config)
err = mergo.Merge(config, containerConfig.Config, mergo.WithOverride)
if err != nil {
return fmt.Errorf("Cannot merge container.Config options: '%s': '%w'", input.Options, err)
}
logger.Debugf("Merged container.Config ==> %+v", config)
if len(input.Cmd) != 0 { if len(input.Cmd) != 0 {
config.Cmd = input.Cmd config.Cmd = input.Cmd
@@ -417,7 +447,8 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
OS: desiredPlatform[0], OS: desiredPlatform[0],
} }
} }
resp, err := cr.cli.ContainerCreate(ctx, config, &container.HostConfig{
hostConfig := &container.HostConfig{
CapAdd: capAdd, CapAdd: capAdd,
CapDrop: capDrop, CapDrop: capDrop,
Binds: input.Binds, Binds: input.Binds,
@@ -425,10 +456,21 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
NetworkMode: container.NetworkMode(input.NetworkMode), NetworkMode: container.NetworkMode(input.NetworkMode),
Privileged: input.Privileged, Privileged: input.Privileged,
UsernsMode: container.UsernsMode(input.UsernsMode), UsernsMode: container.UsernsMode(input.UsernsMode),
}, nil, platSpecs, input.Name)
if err != nil {
return fmt.Errorf("failed to create container: %w", err)
} }
logger.Debugf("Common container.HostConfig ==> %+v", hostConfig)
logger.Debugf("Custom container.HostConfig from options ==> %+v", containerConfig.HostConfig)
err = mergo.Merge(hostConfig, containerConfig.HostConfig, mergo.WithOverride)
if err != nil {
return fmt.Errorf("Cannot merge container.HostConfig options: '%s': '%w'", input.Options, err)
}
logger.Debugf("Merged container.HostConfig ==> %+v", hostConfig)
resp, err := cr.cli.ContainerCreate(ctx, config, hostConfig, nil, platSpecs, input.Name)
if err != nil {
return fmt.Errorf("failed to create container: '%w'", err)
}
logger.Debugf("Created container name=%s id=%v from image %v (platform: %s)", input.Name, resp.ID, input.Image, input.Platform) logger.Debugf("Created container name=%s id=%v from image %v (platform: %s)", input.Name, resp.ID, input.Image, input.Platform)
logger.Debugf("ENV ==> %v", input.Env) logger.Debugf("ENV ==> %v", input.Env)
+1 -1
View File
@@ -101,7 +101,7 @@ func (*defaultFs) Readlink(path string) (string, error) {
return os.Readlink(path) return os.Readlink(path)
} }
// nolint: gocyclo //nolint:gocyclo
func (fc *fileCollector) collectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc { func (fc *fileCollector) collectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc {
i, _ := fc.Fs.OpenGitIndex(path.Join(fc.SrcPath, path.Join(submodulePath...))) i, _ := fc.Fs.OpenGitIndex(path.Join(fc.SrcPath, path.Join(submodulePath...)))
return func(file string, fi os.FileInfo, err error) error { return func(file string, fi os.FileInfo, err error) error {
Binary file not shown.
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
FOO=BAR
HELLO=您好
BAR=FOO
+1
View File
@@ -0,0 +1 @@
ENV1=value1
+1
View File
@@ -0,0 +1 @@
LABEL1=value1
+1 -1
View File
@@ -557,7 +557,7 @@ func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.Logi
return nil, fmt.Errorf("Unable to compare incompatibles types '%s' and '%s'", leftValue.Kind(), rightValue.Kind()) return nil, fmt.Errorf("Unable to compare incompatibles types '%s' and '%s'", leftValue.Kind(), rightValue.Kind())
} }
// nolint:gocyclo //nolint:gocyclo
func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallNode) (interface{}, error) { func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallNode) (interface{}, error) {
args := make([]reflect.Value, 0) args := make([]reflect.Value, 0)
+2 -1
View File
@@ -56,7 +56,8 @@ type WorkflowFiles struct {
} }
// NewWorkflowPlanner will load a specific workflow, all workflows from a directory or all workflows from a directory and its subdirectories // NewWorkflowPlanner will load a specific workflow, all workflows from a directory or all workflows from a directory and its subdirectories
// nolint: gocyclo //
//nolint:gocyclo
func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, error) { func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, error) {
path, err := filepath.Abs(path) path, err := filepath.Abs(path)
if err != nil { if err != nil {
+3 -19
View File
@@ -10,11 +10,9 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/kballard/go-shellquote"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/opencontainers/selinux/go-selinux" "github.com/opencontainers/selinux/go-selinux"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/common/git" "github.com/nektos/act/pkg/common/git"
@@ -123,7 +121,6 @@ func (rc *RunContext) startJobContainer() common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
image := rc.platformImage(ctx) image := rc.platformImage(ctx)
hostname := rc.hostname(ctx)
rawLogger := logger.WithField("raw_output", true) rawLogger := logger.WithField("raw_output", true)
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool { logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
if rc.Config.LogOutput { if rc.Config.LogOutput {
@@ -168,7 +165,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
Privileged: rc.Config.Privileged, Privileged: rc.Config.Privileged,
UsernsMode: rc.Config.UsernsMode, UsernsMode: rc.Config.UsernsMode,
Platform: rc.Config.ContainerArchitecture, Platform: rc.Config.ContainerArchitecture,
Hostname: hostname, Options: rc.options(ctx),
}) })
return common.NewPipelineExecutor( return common.NewPipelineExecutor(
@@ -311,27 +308,14 @@ func (rc *RunContext) platformImage(ctx context.Context) string {
return "" return ""
} }
func (rc *RunContext) hostname(ctx context.Context) string { func (rc *RunContext) options(ctx context.Context) string {
logger := common.Logger(ctx)
job := rc.Run.Job() job := rc.Run.Job()
c := job.Container() c := job.Container()
if c == nil { if c == nil {
return "" return ""
} }
optionsFlags := pflag.NewFlagSet("container_options", pflag.ContinueOnError) return c.Options
hostname := optionsFlags.StringP("hostname", "h", "", "")
optionsArgs, err := shellquote.Split(c.Options)
if err != nil {
logger.Warnf("Cannot parse container options: %s", c.Options)
return ""
}
err = optionsFlags.Parse(optionsArgs)
if err != nil {
logger.Warnf("Cannot parse container options: %s", c.Options)
return ""
}
return *hostname
} }
func (rc *RunContext) isEnabled(ctx context.Context) (bool, error) { func (rc *RunContext) isEnabled(ctx context.Context) (bool, error) {
+9 -3
View File
@@ -10,10 +10,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: node:16-buster-slim image: node:16-buster-slim
options: "--hostname my.host.local" options: "--hostname my.host.local --user 100:101"
steps: steps:
- run: | - run: |
[[ $(uname -n) == "my.host.local" ]] echo "UID: $(id -u)"
echo "GID: $(id -g)"
echo "HOST: $(uname -n)"
[[ "$(id -u)" == "100" ]] && [[ "$(id -g)" == "101" ]] && [[ "$(uname -n)" == "my.host.local" ]]
default-hostname: default-hostname:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -21,4 +24,7 @@ jobs:
image: node:16-buster-slim image: node:16-buster-slim
steps: steps:
- run: | - run: |
[[ $(uname -n) ]] && [[ $(uname -n) != "my.host.local" ]] echo "UID: $(id -u)"
echo "GID: $(id -g)"
echo "HOST: $(uname -n)"
[[ "$(id -u)" == "0" ]] && [[ "$(id -g)" == "0" ]] && [[ $(uname -n) ]] && [[ "$(uname -n)" != "my.host.local" ]]