diff --git a/internal/opts/opts.go b/internal/opts/opts.go index 71a01731201ae..d518fbb690106 100644 --- a/internal/opts/opts.go +++ b/internal/opts/opts.go @@ -1,6 +1,7 @@ package opts import ( + "errors" "fmt" "strconv" "strings" @@ -17,10 +18,12 @@ type SetOpts struct { // internal map, by splitting on '='. func (opts *SetOpts) Set(value string) error { k, v, found := strings.Cut(value, "=") + if k == "" { + return errors.New("invalid option name: " + value) + } var isSet bool if !found { isSet = true - k = value } else { var err error isSet, err = strconv.ParseBool(v) diff --git a/internal/opts/opts_test.go b/internal/opts/opts_test.go index 954c5baab3b5e..70263d75aef16 100644 --- a/internal/opts/opts_test.go +++ b/internal/opts/opts_test.go @@ -14,15 +14,20 @@ func TestSetOpts(t *testing.T) { assert.NilError(t, o.Set("feature-b=true")) assert.NilError(t, o.Set("feature-c=0")) assert.NilError(t, o.Set("feature-d=false")) + assert.NilError(t, o.Set("feature-e")) - expected := "map[feature-a:true feature-b:true feature-c:false feature-d:false]" + expected := "map[feature-a:true feature-b:true feature-c:false feature-d:false feature-e:true]" assert.Check(t, is.Equal(expected, o.String())) - expectedValue := map[string]bool{"feature-a": true, "feature-b": true, "feature-c": false, "feature-d": false} + expectedValue := map[string]bool{"feature-a": true, "feature-b": true, "feature-c": false, "feature-d": false, "feature-e": true} assert.Check(t, is.DeepEqual(expectedValue, o.GetAll())) err := o.Set("feature=not-a-bool") assert.Check(t, is.Error(err, `strconv.ParseBool: parsing "not-a-bool": invalid syntax`)) + err = o.Set("feature=") + assert.Check(t, is.Error(err, `strconv.ParseBool: parsing "": invalid syntax`)) + err = o.Set("=true") + assert.Check(t, is.Error(err, `invalid option name: =true`)) } func TestNamedSetOpts(t *testing.T) { @@ -34,13 +39,18 @@ func TestNamedSetOpts(t *testing.T) { assert.NilError(t, o.Set("feature-b=true")) assert.NilError(t, o.Set("feature-c=0")) assert.NilError(t, o.Set("feature-d=false")) + assert.NilError(t, o.Set("feature-e")) - expected := "map[feature-a:true feature-b:true feature-c:false feature-d:false]" + expected := "map[feature-a:true feature-b:true feature-c:false feature-d:false feature-e:true]" assert.Check(t, is.Equal(expected, o.String())) - expectedValue := map[string]bool{"feature-a": true, "feature-b": true, "feature-c": false, "feature-d": false} + expectedValue := map[string]bool{"feature-a": true, "feature-b": true, "feature-c": false, "feature-d": false, "feature-e": true} assert.Check(t, is.DeepEqual(expectedValue, o.GetAll())) err := o.Set("feature=not-a-bool") assert.Check(t, is.Error(err, `strconv.ParseBool: parsing "not-a-bool": invalid syntax`)) + err = o.Set("feature=") + assert.Check(t, is.Error(err, `strconv.ParseBool: parsing "": invalid syntax`)) + err = o.Set("=true") + assert.Check(t, is.Error(err, `invalid option name: =true`)) } diff --git a/man/dockerd.8.md b/man/dockerd.8.md index 47391497c4c16..c93f809991db4 100644 --- a/man/dockerd.8.md +++ b/man/dockerd.8.md @@ -31,7 +31,7 @@ dockerd - Enable daemon mode [**--exec-opt**[=*[]*]] [**--exec-root**[=*/var/run/docker*]] [**--experimental**[=**false**]] -[**--feature**[=*NAME*=**true**|**false**] +[**--feature**[=*NAME*[=**true**|**false**]] [**--fixed-cidr**[=*FIXED-CIDR*]] [**--fixed-cidr-v6**[=*FIXED-CIDR-V6*]] [**-G**|**--group**[=*docker*]] @@ -223,13 +223,13 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru **--experimental**="" Enable the daemon experimental features. -**--feature**=*NAME*=**true**|**false** - Enable or disable feature feature in the daemon. This option corresponds +**--feature**=*NAME*[=**true**|**false**] + Enable or disable a feature in the daemon. This option corresponds with the "features" field in the daemon.json configuration file. Using both the command-line option and the "features" field in the configuration file produces an error. The feature option can be specified multiple times to configure multiple features. - Usage example: `--feature containerd-snapshotter=true` + Usage example: `--feature containerd-snapshotter` or `--feature containerd-snapshotter=true`. **--fixed-cidr**="" IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in