Repository: containous/traefik (go)

Issue:
CLI parsing error
In this guide: https://github.com/containous/traefik/edit/v2.0/docs/content/user-guides/crd-acme/index.md

I think there is a mistake, cause the flag `--certificatesresolvers.default.acme.tlschallenge` is missing the `=true` value.

At least in my test server it won´t work without that, I get this error:

`time="2019-09-09T18:21:05Z" level=error msg="Unable to obtain ACME certificate for domains \"my.domain\": cannot get ACME client ACME challenge not specified, please select TLS or HTTP or DNS Challenge" providerName=default.acme routerName=traefik/traefik-c4832920e2dbeca43c rule="Host(`my.domain`)"`

Unless I am missing something, I think this should be updated in the doc.

Pull request:
fix: boolean flag parsing with map.
### What does this PR do?

Fix boolean (or pointer) flag parsing when the flag is related to map.

Example:
With the flag `--certificatesresolvers.default.acme.tlschallenge` the related key for the type is  `--certificatesresolvers.<name>.acme.tlschallenge`, so the name (`default`) must be neutralized to allow matching with the type.

### Motivation

Fixes #5333

### More

- [x] Added/updated tests
- [ ] ~~Added/updated documentation~~


Patch:
diff --git a/pkg/config/flag/flagparser.go b/pkg/config/flag/flagparser.go
index 5638e7649..5a66ef89a 100644
--- a/pkg/config/flag/flagparser.go
+++ b/pkg/config/flag/flagparser.go
@@ -3,6 +3,7 @@ package flag
 import (
 	"fmt"
 	"reflect"
+	"regexp"
 	"strings"
 
 	"github.com/containous/traefik/v2/pkg/config/parser"
@@ -80,8 +81,8 @@ func (f *flagSet) parseOne() (bool, error) {
 		return true, nil
 	}
 
-	n := strings.ToLower(name)
-	if f.flagTypes[n] == reflect.Bool || f.flagTypes[n] == reflect.Ptr {
+	flagType := f.getFlagType(name)
+	if flagType == reflect.Bool || flagType == reflect.Ptr {
 		f.setValue(name, "true")
 		return true, nil
 	}
@@ -111,10 +112,30 @@ func (f *flagSet) setValue(name string, value string) {
 	}
 
 	v, ok := f.values[key]
-	if ok && f.flagTypes[strings.ToLower(name)] == reflect.Slice {
+	if ok && f.getFlagType(name) == reflect.Slice {
 		f.values[key] = v + "," + value
 		return
 	}
 
 	f.values[key] = value
 }
+
+func (f *flagSet) getFlagType(name string) reflect.Kind {
+	neutral := strings.ToLower(name)
+
+	kind, ok := f.flagTypes[neutral]
+	if ok {
+		return kind
+	}
+
+	for n, k := range f.flagTypes {
+		if strings.Contains(n, parser.MapNamePlaceholder) {
+			p := strings.NewReplacer(".", `\.`, parser.MapNamePlaceholder, `([^.]+)`).Replace(n)
+			if regexp.MustCompile(p).MatchString(neutral) {
+				return k
+			}
+		}
+	}
+
+	return reflect.Invalid
+}


Test patch:
diff --git a/pkg/config/flag/flagparser_test.go b/pkg/config/flag/flagparser_test.go
index 8c45fdaf5..461dfad70 100644
--- a/pkg/config/flag/flagparser_test.go
+++ b/pkg/config/flag/flagparser_test.go
@@ -250,7 +250,7 @@ func TestParse(t *testing.T) {
 			},
 		},
 		{
-			desc: "map struct with sub-map case senstitive",
+			desc: "map struct with sub-map case sensitive",
 			args: []string{"--foo.Name1.bar.name2.value=firstValue", "--foo.naMe1.bar.name2.value=secondValue"},
 			element: &struct {
 				Foo map[string]struct {
@@ -273,6 +273,20 @@ func TestParse(t *testing.T) {
 				"traefik.foo.Name1.bar.name2.value": "secondValue",
 			},
 		},
+		{
+			desc: "pointer of struct and map without explicit value",
+			args: []string{"--foo.default.bar.fuu"},
+			element: &struct {
+				Foo map[string]struct {
+					Bar *struct {
+						Fuu *struct{ Value string }
+					}
+				}
+			}{},
+			expected: map[string]string{
+				"traefik.foo.default.bar.fuu": "true",
+			},
+		},
 		{
 			desc: "slice with several flags 2 and different cases.",
 			args: []string{"--foo", "bar", "--Foo", "baz"},