2016-12-23 14:09:12 -05:00
package container
import (
"fmt"
2022-02-25 07:05:59 -05:00
"io"
2016-12-23 14:09:12 -05:00
"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"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2016-12-23 14:09:12 -05:00
"github.com/spf13/pflag"
2020-02-22 12:12:14 -05:00
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
2016-12-23 14:09:12 -05:00
)
func TestValidateAttach ( t * testing . T ) {
valid := [ ] string {
"stdin" ,
"stdout" ,
"stderr" ,
"STDIN" ,
"STDOUT" ,
"STDERR" ,
}
if _ , err := validateAttach ( "invalid" ) ; err == nil {
2017-02-21 03:53:29 -05:00
t . Fatal ( "Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing" )
2016-12-23 14:09:12 -05:00
}
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 ) {
2017-10-25 12:59:32 -04:00
flags , copts := setupRunFlags ( )
2016-12-23 14:09:12 -05:00
if err := flags . Parse ( args ) ; err != nil {
return nil , nil , nil , err
}
2017-03-07 17:19:54 -05:00
// TODO: fix tests to accept ContainerConfig
2023-01-18 11:39:35 -05:00
containerCfg , err := parse ( flags , copts , runtime . GOOS )
2017-03-07 17:19:54 -05:00
if err != nil {
return nil , nil , nil , err
}
2023-01-18 11:39:35 -05:00
return containerCfg . Config , containerCfg . HostConfig , containerCfg . NetworkingConfig , err
2016-12-23 14:09:12 -05:00
}
2017-10-25 12:59:32 -04:00
func setupRunFlags ( ) ( * pflag . FlagSet , * containerOptions ) {
flags := pflag . NewFlagSet ( "run" , pflag . ContinueOnError )
2022-02-25 07:05:59 -05:00
flags . SetOutput ( io . Discard )
2017-10-25 12:59:32 -04:00
flags . Usage = nil
copts := addFlags ( flags )
return flags , copts
}
2023-11-07 12:45:48 -05:00
func mustParse ( t * testing . T , args string ) ( * container . Config , * container . HostConfig , * networktypes . NetworkingConfig ) {
2020-07-01 08:41:20 -04:00
t . Helper ( )
2023-11-07 12:45:48 -05:00
config , hostConfig , nwConfig , err := parseRun ( append ( strings . Split ( args , " " ) , "ubuntu" , "bash" ) )
2018-03-06 14:44:13 -05:00
assert . NilError ( t , err )
2023-11-07 12:45:48 -05:00
return config , hostConfig , nwConfig
2016-12-23 14:09:12 -05:00
}
func TestParseRunLinks ( t * testing . T ) {
2023-11-07 12:45:48 -05:00
if _ , hostConfig , _ := mustParse ( t , "--link a:b" ) ; len ( hostConfig . Links ) == 0 || hostConfig . Links [ 0 ] != "a:b" {
2016-12-23 14:09:12 -05:00
t . Fatalf ( "Error parsing links. Expected []string{\"a:b\"}, received: %v" , hostConfig . Links )
}
2023-11-07 12:45:48 -05:00
if _ , hostConfig , _ := mustParse ( t , "--link a:b --link c:d" ) ; len ( hostConfig . Links ) < 2 || hostConfig . Links [ 0 ] != "a:b" || hostConfig . Links [ 1 ] != "c:d" {
2016-12-23 14:09:12 -05:00
t . Fatalf ( "Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v" , hostConfig . Links )
}
2023-11-07 12:45:48 -05:00
if _ , hostConfig , _ := mustParse ( t , "" ) ; len ( hostConfig . Links ) != 0 {
2016-12-23 14:09:12 -05:00
t . Fatalf ( "Error parsing links. No link expected, received: %v" , hostConfig . Links )
}
}
func TestParseRunAttach ( t * testing . T ) {
2020-07-01 08:41:20 -04:00
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 ,
} ,
} ,
2016-12-23 14:09:12 -05:00
}
2020-07-01 08:41:20 -04:00
for _ , tc := range tests {
tc := tc
t . Run ( tc . input , func ( t * testing . T ) {
2023-11-07 12:45:48 -05:00
config , _ , _ := mustParse ( t , tc . input )
2020-07-01 08:41:20 -04:00
assert . Equal ( t , config . AttachStdin , tc . expected . AttachStdin )
assert . Equal ( t , config . AttachStdout , tc . expected . AttachStdout )
assert . Equal ( t , config . AttachStderr , tc . expected . AttachStderr )
} )
2016-12-23 14:09:12 -05:00
}
2017-06-09 17:16:56 -04:00
}
2016-12-23 14:09:12 -05:00
2017-06-09 17:16:56 -04:00
func TestParseRunWithInvalidArgs ( t * testing . T ) {
2020-07-01 08:41:20 -04:00
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 )
} )
}
2016-12-23 14:09:12 -05:00
}
2022-07-13 06:29:49 -04:00
//nolint:gocyclo
2017-08-29 13:19:28 -04:00
func TestParseWithVolumes ( t * testing . T ) {
2016-12-23 14:09:12 -05:00
// A single volume
arr , tryit := setupPlatformVolume ( [ ] string { ` /tmp ` } , [ ] string { ` c:\tmp ` } )
2023-11-07 12:45:48 -05:00
if config , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds != nil {
2016-12-23 14:09:12 -05:00
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 ` } )
2023-11-07 12:45:48 -05:00
if config , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds != nil {
2016-12-23 14:09:12 -05:00
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 )
}
2017-08-19 10:13:29 -04:00
// A single bind mount
2016-12-23 14:09:12 -05:00
arr , tryit = setupPlatformVolume ( [ ] string { ` /hostTmp:/containerTmp ` } , [ ] string { os . Getenv ( "TEMP" ) + ` :c:\containerTmp ` } )
2023-11-07 12:45:48 -05:00
if config , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds == nil || hostConfig . Binds [ 0 ] != arr [ 0 ] {
2016-12-23 14:09:12 -05:00
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 )
}
2017-08-19 10:13:29 -04:00
// Two bind mounts.
2016-12-23 14:09:12 -05:00
arr , tryit = setupPlatformVolume ( [ ] string { ` /hostTmp:/containerTmp ` , ` /hostVar:/containerVar ` } , [ ] string { os . Getenv ( "ProgramData" ) + ` :c:\ContainerPD ` , os . Getenv ( "TEMP" ) + ` :c:\containerTmp ` } )
2023-11-07 12:45:48 -05:00
if _ , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds == nil || compareRandomizedStrings ( hostConfig . Binds [ 0 ] , hostConfig . Binds [ 1 ] , arr [ 0 ] , arr [ 1 ] ) != nil {
2016-12-23 14:09:12 -05:00
t . Fatalf ( "Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v" , arr [ 0 ] , arr [ 1 ] , hostConfig . Binds )
}
2017-08-19 10:13:29 -04:00
// Two bind mounts, first read-only, second read-write.
2016-12-23 14:09:12 -05:00
// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
2017-05-03 18:14:30 -04:00
arr , tryit = setupPlatformVolume (
[ ] string { ` /hostTmp:/containerTmp:ro ` , ` /hostVar:/containerVar:rw ` } ,
[ ] string { os . Getenv ( "TEMP" ) + ` :c:\containerTmp:rw ` , os . Getenv ( "ProgramData" ) + ` :c:\ContainerPD:rw ` } )
2023-11-07 12:45:48 -05:00
if _ , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds == nil || compareRandomizedStrings ( hostConfig . Binds [ 0 ] , hostConfig . Binds [ 1 ] , arr [ 0 ] , arr [ 1 ] ) != nil {
2016-12-23 14:09:12 -05:00
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 { } )
2023-11-07 12:45:48 -05:00
if _ , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds == nil || compareRandomizedStrings ( hostConfig . Binds [ 0 ] , hostConfig . Binds [ 1 ] , arr [ 0 ] , arr [ 1 ] ) != nil {
2016-12-23 14:09:12 -05:00
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 { } )
2023-11-07 12:45:48 -05:00
if _ , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds == nil || compareRandomizedStrings ( hostConfig . Binds [ 0 ] , hostConfig . Binds [ 1 ] , arr [ 0 ] , arr [ 1 ] ) != nil {
2016-12-23 14:09:12 -05:00
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 ` } )
2023-11-07 12:45:48 -05:00
if config , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds == nil || len ( hostConfig . Binds ) > 1 || hostConfig . Binds [ 0 ] != arr [ 0 ] {
2016-12-23 14:09:12 -05:00
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: ` } )
2023-11-07 12:45:48 -05:00
if config , hostConfig , _ := mustParse ( t , tryit ) ; hostConfig . Binds == nil || len ( hostConfig . Binds ) > 1 || hostConfig . Binds [ 0 ] != arr [ 0 ] || len ( config . Volumes ) != 0 {
2016-12-23 14:09:12 -05:00
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
}
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "strings don't match" )
2016-12-23 14:09:12 -05:00
}
// 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 )
}
2023-11-07 12:54:01 -05:00
config , hostConfig , nwConfig := mustParse ( t , validMacAddress )
if config . MacAddress != "92:d0:c6:0a:29:33" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
2023-11-07 10:51:39 -05:00
t . Fatalf ( "Expected the config to have '92:d0:c6:0a:29:33' as container-wide MacAddress, got '%v'" ,
config . MacAddress ) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
2016-12-23 14:09:12 -05:00
}
2023-11-07 12:54:01 -05:00
defaultNw := hostConfig . NetworkMode . NetworkName ( )
if nwConfig . EndpointsConfig [ defaultNw ] . MacAddress != "92:d0:c6:0a:29:33" {
t . Fatalf ( "Expected the default endpoint to have the MacAddress '92:d0:c6:0a:29:33' set, got '%v'" , nwConfig . EndpointsConfig [ defaultNw ] . MacAddress )
}
2016-12-23 14:09:12 -05:00
}
2017-10-25 12:59:32 -04:00
func TestRunFlagsParseWithMemory ( t * testing . T ) {
flags , _ := setupRunFlags ( )
args := [ ] string { "--memory=invalid" , "img" , "cmd" }
err := flags . Parse ( args )
2018-03-06 14:03:47 -05:00
assert . ErrorContains ( t , err , ` invalid argument "invalid" for "-m, --memory" flag ` )
2017-03-06 16:01:04 -05:00
2023-11-07 12:45:48 -05:00
_ , hostconfig , _ := mustParse ( t , "--memory=1G" )
2018-03-05 18:53:52 -05:00
assert . Check ( t , is . Equal ( int64 ( 1073741824 ) , hostconfig . Memory ) )
2016-12-23 14:09:12 -05:00
}
func TestParseWithMemorySwap ( t * testing . T ) {
2017-10-25 12:59:32 -04:00
flags , _ := setupRunFlags ( )
args := [ ] string { "--memory-swap=invalid" , "img" , "cmd" }
err := flags . Parse ( args )
2018-03-06 14:03:47 -05:00
assert . ErrorContains ( t , err , ` invalid argument "invalid" for "--memory-swap" flag ` )
2017-03-06 16:01:04 -05:00
2023-11-07 12:45:48 -05:00
_ , hostconfig , _ := mustParse ( t , "--memory-swap=1G" )
2018-03-05 18:53:52 -05:00
assert . Check ( t , is . Equal ( int64 ( 1073741824 ) , hostconfig . MemorySwap ) )
2017-03-06 16:01:04 -05:00
2023-11-07 12:45:48 -05:00
_ , hostconfig , _ = mustParse ( t , "--memory-swap=-1" )
2018-03-05 18:53:52 -05:00
assert . Check ( t , is . Equal ( int64 ( - 1 ) , hostconfig . MemorySwap ) )
2016-12-23 14:09:12 -05:00
}
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 {
2023-11-07 12:45:48 -05:00
if config , _ , _ := mustParse ( t , fmt . Sprintf ( "--hostname=%s" , hostname ) ) ; config . Hostname != expectedHostname {
2018-06-18 07:58:23 -04:00
t . Fatalf ( "Expected the config to have 'hostname' as %q, got %q" , expectedHostname , config . Hostname )
2016-12-23 14:09:12 -05:00
}
}
2023-11-07 12:45:48 -05:00
if config , _ , _ := mustParse ( t , hostnameWithDomain ) ; config . Hostname != "hostname.domainname" || config . Domainname != "" {
2018-06-18 07:58:23 -04:00
t . Fatalf ( "Expected the config to have 'hostname' as hostname.domainname, got %q" , config . Hostname )
2016-12-23 14:09:12 -05:00
}
2023-11-07 12:45:48 -05:00
if config , _ , _ := mustParse ( t , hostnameWithDomainTld ) ; config . Hostname != "hostname.domainname.tld" || config . Domainname != "" {
2018-06-18 07:58:23 -04:00
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 {
2023-11-07 12:45:48 -05:00
if config , _ , _ := mustParse ( t , "--domainname=" + domainname ) ; config . Domainname != expectedDomainname {
2018-06-18 07:58:23 -04:00
t . Fatalf ( "Expected the config to have 'domainname' as %q, got %q" , expectedDomainname , config . Domainname )
}
}
2023-11-07 12:45:48 -05:00
if config , _ , _ := mustParse ( t , "--hostname=some.prefix --domainname=domainname" ) ; config . Hostname != "some.prefix" || config . Domainname != "domainname" {
2018-06-18 07:58:23 -04:00
t . Fatalf ( "Expected the config to have 'hostname' as 'some.prefix' and 'domainname' as 'domainname', got %q and %q" , config . Hostname , config . Domainname )
}
2023-11-07 12:45:48 -05:00
if config , _ , _ := mustParse ( t , "--hostname=another-prefix --domainname=domainname.tld" ) ; config . Hostname != "another-prefix" || config . Domainname != "domainname.tld" {
2018-06-18 07:58:23 -04:00
t . Fatalf ( "Expected the config to have 'hostname' as 'another-prefix' and 'domainname' as 'domainname.tld', got %q and %q" , config . Hostname , config . Domainname )
2016-12-23 14:09:12 -05:00
}
}
func TestParseWithExpose ( t * testing . T ) {
invalids := map [ string ] string {
":" : "invalid port format for --expose: :" ,
"8080:9090" : "invalid port format for --expose: 8080:9090" ,
2023-11-10 16:32:30 -05:00
"/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" ,
2016-12-23 14:09:12 -05:00
"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 ) {
2020-06-26 09:36:49 -04:00
skip . If ( t , runtime . GOOS != "linux" ) // Windows and macOS validate server-side
2023-03-10 01:29:47 -05:00
testCases := [ ] struct {
devices [ ] string
deviceMapping * container . DeviceMapping
deviceRequests [ ] container . DeviceRequest
} {
{
devices : [ ] string { "/dev/snd" } ,
deviceMapping : & container . DeviceMapping {
PathOnHost : "/dev/snd" ,
PathInContainer : "/dev/snd" ,
CgroupPermissions : "rwm" ,
} ,
2016-12-23 14:09:12 -05:00
} ,
2023-03-10 01:29:47 -05:00
{
devices : [ ] string { "/dev/snd:rw" } ,
deviceMapping : & container . DeviceMapping {
PathOnHost : "/dev/snd" ,
PathInContainer : "/dev/snd" ,
CgroupPermissions : "rw" ,
} ,
2016-12-23 14:09:12 -05:00
} ,
2023-03-10 01:29:47 -05:00
{
devices : [ ] string { "/dev/snd:/something" } ,
deviceMapping : & container . DeviceMapping {
PathOnHost : "/dev/snd" ,
PathInContainer : "/something" ,
CgroupPermissions : "rwm" ,
} ,
2016-12-23 14:09:12 -05:00
} ,
2023-03-10 01:29:47 -05:00
{
devices : [ ] string { "/dev/snd:/something:rw" } ,
deviceMapping : & container . DeviceMapping {
PathOnHost : "/dev/snd" ,
PathInContainer : "/something" ,
CgroupPermissions : "rw" ,
} ,
} ,
{
devices : [ ] string { "vendor.com/class=name" } ,
deviceMapping : nil ,
deviceRequests : [ ] container . DeviceRequest {
{
Driver : "cdi" ,
DeviceIDs : [ ] string { "vendor.com/class=name" } ,
} ,
} ,
} ,
{
devices : [ ] string { "vendor.com/class=name" , "/dev/snd:/something:rw" } ,
deviceMapping : & container . DeviceMapping {
PathOnHost : "/dev/snd" ,
PathInContainer : "/something" ,
CgroupPermissions : "rw" ,
} ,
deviceRequests : [ ] container . DeviceRequest {
{
Driver : "cdi" ,
DeviceIDs : [ ] string { "vendor.com/class=name" } ,
} ,
} ,
2016-12-23 14:09:12 -05:00
} ,
}
2023-03-10 01:29:47 -05:00
for _ , tc := range testCases {
t . Run ( fmt . Sprintf ( "%s" , tc . devices ) , func ( t * testing . T ) {
var args [ ] string
for _ , d := range tc . devices {
args = append ( args , fmt . Sprintf ( "--device=%v" , d ) )
}
args = append ( args , "img" , "cmd" )
_ , hostconfig , _ , err := parseRun ( args )
assert . NilError ( t , err )
if tc . deviceMapping != nil {
if assert . Check ( t , is . Len ( hostconfig . Devices , 1 ) ) {
assert . Check ( t , is . DeepEqual ( * tc . deviceMapping , hostconfig . Devices [ 0 ] ) )
}
} else {
assert . Check ( t , is . Len ( hostconfig . Devices , 0 ) )
}
assert . Check ( t , is . DeepEqual ( tc . deviceRequests , hostconfig . DeviceRequests ) )
} )
2016-12-23 14:09:12 -05:00
}
}
2019-03-20 12:53:44 -04:00
func TestParseNetworkConfig ( t * testing . T ) {
tests := [ ] struct {
2023-09-10 12:52:46 -04:00
name string
flags [ ] string
expected map [ string ] * networktypes . EndpointSettings
2023-07-13 05:50:01 -04:00
expectedCfg container . Config
2023-09-10 12:52:46 -04:00
expectedHostCfg container . HostConfig
expectedErr string
2019-03-20 12:53:44 -04:00
} {
{
2023-09-10 12:52:46 -04:00
name : "single-network-legacy" ,
flags : [ ] string { "--network" , "net1" } ,
expected : map [ string ] * networktypes . EndpointSettings { } ,
expectedHostCfg : container . HostConfig { NetworkMode : "net1" } ,
2019-03-20 12:53:44 -04:00
} ,
{
2023-09-10 12:52:46 -04:00
name : "single-network-advanced" ,
flags : [ ] string { "--network" , "name=net1" } ,
expected : map [ string ] * networktypes . EndpointSettings { } ,
expectedHostCfg : container . HostConfig { NetworkMode : "net1" } ,
2019-03-20 12:53:44 -04:00
} ,
{
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" } ,
} ,
} ,
2023-09-10 12:52:46 -04:00
expectedHostCfg : container . HostConfig { NetworkMode : "net1" } ,
2019-03-20 12:53:44 -04:00
} ,
{
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" ,
2019-04-03 08:37:26 -04:00
"--network" , "name=net3,alias=web3,driver-opt=field3=value3,ip=172.20.88.22,ip6=2001:db8::8822" ,
2023-07-13 05:50:01 -04:00
"--network" , "name=net4,mac-address=02:32:1c:23:00:04,link-local-ip=169.254.169.254" ,
2019-03-20 12:53:44 -04:00
} ,
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" } ,
2019-04-03 08:37:26 -04:00
IPAMConfig : & networktypes . EndpointIPAMConfig {
IPv4Address : "172.20.88.22" ,
IPv6Address : "2001:db8::8822" ,
} ,
Aliases : [ ] string { "web3" } ,
2019-03-20 12:53:44 -04:00
} ,
2023-07-13 05:50:01 -04:00
"net4" : {
MacAddress : "02:32:1c:23:00:04" ,
IPAMConfig : & networktypes . EndpointIPAMConfig {
LinkLocalIPs : [ ] string { "169.254.169.254" } ,
} ,
} ,
2019-03-20 12:53:44 -04:00
} ,
2023-09-10 12:52:46 -04:00
expectedHostCfg : container . HostConfig { NetworkMode : "net1" } ,
2019-03-20 12:53:44 -04:00
} ,
{
name : "single-network-advanced-with-options" ,
2023-07-13 05:50:01 -04:00
flags : [ ] string { "--network" , "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2,ip=172.20.88.22,ip6=2001:db8::8822,mac-address=02:32:1c:23:00:04" } ,
2019-03-20 12:53:44 -04:00
expected : map [ string ] * networktypes . EndpointSettings {
"net1" : {
DriverOpts : map [ string ] string {
"field1" : "value1" ,
"field2" : "value2" ,
} ,
2019-04-03 08:37:26 -04:00
IPAMConfig : & networktypes . EndpointIPAMConfig {
IPv4Address : "172.20.88.22" ,
IPv6Address : "2001:db8::8822" ,
} ,
2023-07-13 05:50:01 -04:00
Aliases : [ ] string { "web1" , "web2" } ,
MacAddress : "02:32:1c:23:00:04" ,
2019-03-20 12:53:44 -04:00
} ,
} ,
2023-07-13 05:50:01 -04:00
expectedCfg : container . Config { MacAddress : "02:32:1c:23:00:04" } ,
2023-09-10 12:52:46 -04:00
expectedHostCfg : container . HostConfig { NetworkMode : "net1" } ,
2019-03-20 12:53:44 -04:00
} ,
{
2023-09-10 12:52:46 -04:00
name : "multiple-networks" ,
flags : [ ] string { "--network" , "net1" , "--network" , "name=net2" } ,
expected : map [ string ] * networktypes . EndpointSettings { "net1" : { } , "net2" : { } } ,
expectedHostCfg : container . HostConfig { NetworkMode : "net1" } ,
2019-03-20 12:53:44 -04:00
} ,
2023-07-13 05:50:01 -04:00
{
name : "advanced-options-with-standalone-mac-address-flag" ,
flags : [ ] string { "--network=name=net1,alias=foobar" , "--mac-address" , "52:0f:f3:dc:50:10" } ,
expected : map [ string ] * networktypes . EndpointSettings {
"net1" : {
Aliases : [ ] string { "foobar" } ,
MacAddress : "52:0f:f3:dc:50:10" ,
} ,
} ,
expectedCfg : container . Config { MacAddress : "52:0f:f3:dc:50:10" } ,
expectedHostCfg : container . HostConfig { NetworkMode : "net1" } ,
} ,
2019-03-20 12:53:44 -04:00
{
name : "conflict-network" ,
flags : [ ] string { "--network" , "duplicate" , "--network" , "name=duplicate" } ,
expectedErr : ` network "duplicate" is specified multiple times ` ,
} ,
{
2019-04-03 08:37:26 -04:00
name : "conflict-options-alias" ,
2019-03-20 12:53:44 -04:00
flags : [ ] string { "--network" , "name=net1,alias=web1" , "--network-alias" , "web1" } ,
expectedErr : ` conflicting options: cannot specify both --network-alias and per-network alias ` ,
} ,
2019-04-03 08:37:26 -04:00
{
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 ` ,
} ,
2019-03-20 12:53:44 -04:00
{
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 ` ,
} ,
2023-07-13 05:50:01 -04:00
{
name : "conflict-options-link-local-ip" ,
flags : [ ] string { "--network" , "name=net1,link-local-ip=169.254.169.254" , "--link-local-ip" , "169.254.10.8" } ,
expectedErr : ` conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses ` ,
} ,
{
name : "conflict-options-mac-address" ,
flags : [ ] string { "--network" , "name=net1,mac-address=02:32:1c:23:00:04" , "--mac-address" , "02:32:1c:23:00:04" } ,
expectedErr : ` conflicting options: cannot specify both --mac-address and per-network MAC address ` ,
} ,
{
name : "invalid-mac-address" ,
flags : [ ] string { "--network" , "name=net1,mac-address=foobar" } ,
expectedErr : "foobar is not a valid mac address" ,
} ,
2019-03-20 12:53:44 -04:00
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
2023-07-13 05:50:01 -04:00
config , hConfig , nwConfig , err := parseRun ( tc . flags )
2019-03-20 12:53:44 -04:00
if tc . expectedErr != "" {
assert . Error ( t , err , tc . expectedErr )
return
}
assert . NilError ( t , err )
2023-11-07 10:51:39 -05:00
assert . DeepEqual ( t , config . MacAddress , tc . expectedCfg . MacAddress ) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
2023-09-10 12:52:46 -04:00
assert . DeepEqual ( t , hConfig . NetworkMode , tc . expectedHostCfg . NetworkMode )
2019-03-20 12:53:44 -04:00
assert . DeepEqual ( t , nwConfig . EndpointsConfig , tc . expected )
} )
}
}
2016-12-23 14:09:12 -05:00
func TestParseModes ( t * testing . T ) {
// pid ko
2017-10-25 12:59:32 -04:00
flags , copts := setupRunFlags ( )
args := [ ] string { "--pid=container:" , "img" , "cmd" }
2018-03-05 18:53:52 -05:00
assert . NilError ( t , flags . Parse ( args ) )
2019-01-07 18:34:33 -05:00
_ , err := parse ( flags , copts , runtime . GOOS )
2018-03-06 14:03:47 -05:00
assert . ErrorContains ( t , err , "--pid: invalid PID mode" )
2017-05-09 19:21:17 -04:00
2016-12-23 14:09:12 -05:00
// pid ok
2017-07-20 17:42:51 -04:00
_ , hostconfig , _ , err := parseRun ( [ ] string { "--pid=host" , "img" , "cmd" } )
2018-03-05 18:53:52 -05:00
assert . NilError ( t , err )
2016-12-23 14:09:12 -05:00
if ! hostconfig . PidMode . Valid ( ) {
t . Fatalf ( "Expected a valid PidMode, got %v" , hostconfig . PidMode )
}
2017-05-09 19:21:17 -04:00
2016-12-23 14:09:12 -05:00
// uts ko
cli/command/container: suppress dogsled warnings
```
cli/command/container/opts_test.go:68:2: declaration has 3 blank identifiers (dogsled)
_, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
^
cli/command/container/opts_test.go:542:2: declaration has 3 blank identifiers (dogsled)
_, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"})
^
cli/command/container/opts_test.go:603:2: declaration has 3 blank identifiers (dogsled)
_, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"})
^
```
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-29 09:04:33 -04:00
_ , _ , _ , err = parseRun ( [ ] string { "--uts=container:" , "img" , "cmd" } ) //nolint:dogsled
2018-03-06 14:03:47 -05:00
assert . ErrorContains ( t , err , "--uts: invalid UTS mode" )
2017-05-09 19:21:17 -04:00
2016-12-23 14:09:12 -05:00
// uts ok
_ , hostconfig , _ , err = parseRun ( [ ] string { "--uts=host" , "img" , "cmd" } )
2018-03-05 18:53:52 -05:00
assert . NilError ( t , err )
2016-12-23 14:09:12 -05:00
if ! hostconfig . UTSMode . Valid ( ) {
t . Fatalf ( "Expected a valid UTSMode, got %v" , hostconfig . UTSMode )
}
2017-10-25 12:59:32 -04:00
}
2017-05-09 19:21:17 -04:00
2017-10-25 12:59:32 -04:00
func TestRunFlagsParseShmSize ( t * testing . T ) {
2016-12-23 14:09:12 -05:00
// shm-size ko
2017-10-25 12:59:32 -04:00
flags , _ := setupRunFlags ( )
args := [ ] string { "--shm-size=a128m" , "img" , "cmd" }
2022-08-31 13:10:26 -04:00
expectedErr := ` invalid argument "a128m" for "--shm-size" flag: `
2017-10-25 12:59:32 -04:00
err := flags . Parse ( args )
2018-03-06 14:03:47 -05:00
assert . ErrorContains ( t , err , expectedErr )
2017-05-09 19:21:17 -04:00
2016-12-23 14:09:12 -05:00
// shm-size ok
2017-10-25 12:59:32 -04:00
_ , hostconfig , _ , err := parseRun ( [ ] string { "--shm-size=128m" , "img" , "cmd" } )
2018-03-05 18:53:52 -05:00
assert . NilError ( t , err )
2016-12-23 14:09:12 -05:00
if hostconfig . ShmSize != 134217728 {
t . Fatalf ( "Expected a valid ShmSize, got %d" , hostconfig . ShmSize )
}
}
func TestParseRestartPolicy ( t * testing . T ) {
2023-08-28 05:23:37 -04:00
tests := [ ] struct {
input string
expected container . RestartPolicy
expectedErr string
} {
{
input : "" ,
2016-12-23 14:09:12 -05:00
} ,
2023-08-28 05:23:37 -04:00
{
input : "no" ,
expected : container . RestartPolicy {
2023-08-28 06:06:35 -04:00
Name : container . RestartPolicyDisabled ,
2023-08-28 05:23:37 -04:00
} ,
} ,
2023-08-28 05:46:49 -04:00
{
input : ":1" ,
expectedErr : "invalid restart policy format: no policy provided before colon" ,
} ,
2023-08-28 05:23:37 -04:00
{
input : "always" ,
expected : container . RestartPolicy {
2023-08-28 06:06:35 -04:00
Name : container . RestartPolicyAlways ,
2023-08-28 05:23:37 -04:00
} ,
} ,
{
input : "always:1" ,
expected : container . RestartPolicy {
2023-08-28 06:06:35 -04:00
Name : container . RestartPolicyAlways ,
2023-08-28 05:23:37 -04:00
MaximumRetryCount : 1 ,
} ,
} ,
{
input : "always:2:3" ,
expectedErr : "invalid restart policy format: maximum retry count must be an integer" ,
} ,
{
input : "on-failure:1" ,
expected : container . RestartPolicy {
2023-08-28 06:06:35 -04:00
Name : container . RestartPolicyOnFailure ,
2023-08-28 05:23:37 -04:00
MaximumRetryCount : 1 ,
} ,
} ,
{
input : "on-failure:invalid" ,
expectedErr : "invalid restart policy format: maximum retry count must be an integer" ,
} ,
{
input : "unless-stopped" ,
expected : container . RestartPolicy {
2023-08-28 06:06:35 -04:00
Name : container . RestartPolicyUnlessStopped ,
2023-08-28 05:23:37 -04:00
} ,
} ,
{
input : "unless-stopped:invalid" ,
expectedErr : "invalid restart policy format: maximum retry count must be an integer" ,
2016-12-23 14:09:12 -05:00
} ,
}
2023-08-28 05:23:37 -04:00
for _ , tc := range tests {
tc := tc
t . Run ( tc . input , func ( t * testing . T ) {
_ , hostConfig , _ , err := parseRun ( [ ] string { "--restart=" + tc . input , "img" , "cmd" } )
if tc . expectedErr != "" {
assert . Check ( t , is . Error ( err , tc . expectedErr ) )
2023-08-28 05:46:49 -04:00
assert . Check ( t , is . Nil ( hostConfig ) )
2023-08-28 05:23:37 -04:00
} else {
2023-08-28 05:46:49 -04:00
assert . NilError ( t , err )
2023-08-28 05:23:37 -04:00
assert . Check ( t , is . DeepEqual ( hostConfig . RestartPolicy , tc . expected ) )
}
} )
2016-12-23 14:09:12 -05:00
}
}
Don't use AutoRemove on older daemons
Docker 1.13 moves the `--rm` flag to the daemon,
through an AutoRemove option in HostConfig.
When using API 1.24 and under, AutoRemove should not be
used, even if the daemon is version 1.13 or above and
"supports" this feature.
This patch fixes a situation where an 1.13 client,
talking to an 1.13 daemon, but using the 1.24 API
version, still set the AutoRemove property.
As a result, both the client _and_ the daemon
were attempting to remove the container, resulting
in an error:
ERRO[0000] error removing container: Error response from daemon:
removal of container ce0976ad22495c7cbe9487752ea32721a282164862db036b2f3377bd07461c3a
is already in progress
In addition, the validation of conflicting options
is moved from `docker run` to `opts.parse()`, so
that conflicting options are also detected when
running `docker create` and `docker start` separately.
To resolve the issue, the `AutoRemove` option is now
always set to `false` both by the client and the
daemon, if API version 1.24 or under is used.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-01-12 19:05:39 -05:00
func TestParseRestartPolicyAutoRemove ( t * testing . T ) {
expected := "Conflicting options: --restart and --rm"
cli/command/container: suppress dogsled warnings
```
cli/command/container/opts_test.go:68:2: declaration has 3 blank identifiers (dogsled)
_, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
^
cli/command/container/opts_test.go:542:2: declaration has 3 blank identifiers (dogsled)
_, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"})
^
cli/command/container/opts_test.go:603:2: declaration has 3 blank identifiers (dogsled)
_, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"})
^
```
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-10-29 09:04:33 -04:00
_ , _ , _ , err := parseRun ( [ ] string { "--rm" , "--restart=always" , "img" , "cmd" } ) //nolint:dogsled
Don't use AutoRemove on older daemons
Docker 1.13 moves the `--rm` flag to the daemon,
through an AutoRemove option in HostConfig.
When using API 1.24 and under, AutoRemove should not be
used, even if the daemon is version 1.13 or above and
"supports" this feature.
This patch fixes a situation where an 1.13 client,
talking to an 1.13 daemon, but using the 1.24 API
version, still set the AutoRemove property.
As a result, both the client _and_ the daemon
were attempting to remove the container, resulting
in an error:
ERRO[0000] error removing container: Error response from daemon:
removal of container ce0976ad22495c7cbe9487752ea32721a282164862db036b2f3377bd07461c3a
is already in progress
In addition, the validation of conflicting options
is moved from `docker run` to `opts.parse()`, so
that conflicting options are also detected when
running `docker create` and `docker start` separately.
To resolve the issue, the `AutoRemove` option is now
always set to `false` both by the client and the
daemon, if API version 1.24 or under is used.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-01-12 19:05:39 -05:00
if err == nil || err . Error ( ) != expected {
t . Fatalf ( "Expected error %v, but got none" , expected )
}
}
2016-12-23 14:09:12 -05:00
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 {
2017-02-20 21:26:06 -05:00
t . Fatalf ( "--health-cmd: timeout = %s" , health . Timeout )
2016-12-23 14:09:12 -05:00
}
checkError ( "--no-healthcheck conflicts with --health-* options" ,
"--no-healthcheck" , "--health-cmd=/check.sh -q" , "img" , "cmd" )
2023-07-06 15:05:34 -04:00
health = checkOk ( "--health-timeout=2s" , "--health-retries=3" , "--health-interval=4.5s" , "--health-start-period=5s" , "--health-start-interval=1s" , "img" , "cmd" )
if health . Timeout != 2 * time . Second || health . Retries != 3 || health . Interval != 4500 * time . Millisecond || health . StartPeriod != 5 * time . Second || health . StartInterval != 1 * time . Second {
2016-12-23 14:09:12 -05:00
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 ) {
2020-06-26 09:36:49 -04:00
skip . If ( t , runtime . GOOS != "linux" ) // Windows and macOS validate server-side
2016-12-23 14:09:12 -05:00
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 {
2019-01-07 18:34:33 -05:00
if _ , err := validateDevice ( path , runtime . GOOS ) ; err != nil {
2016-12-23 14:09:12 -05:00
t . Fatalf ( "ValidateDevice(`%q`) should succeed: error %q" , path , err )
}
}
for path , expectedError := range invalid {
2019-01-07 18:34:33 -05:00
if _ , err := validateDevice ( path , runtime . GOOS ) ; err == nil {
2016-12-23 14:09:12 -05:00
t . Fatalf ( "ValidateDevice(`%q`) should have failed validation" , path )
2023-11-20 07:54:53 -05:00
} else if err . Error ( ) != expectedError {
t . Fatalf ( "ValidateDevice(`%q`) error should contain %q, got %q" , path , expectedError , err . Error ( ) )
2016-12-23 14:09:12 -05:00
}
}
}
2019-04-04 17:43:43 -04:00
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 )
}
}
2020-01-14 10:44:47 -05:00
2019-06-21 16:11:48 -04:00
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 )
}
}
2020-01-14 10:44:47 -05:00
}