diff --git a/internal/template/groupby_test.go b/internal/template/groupby_test.go index e34f9ca2..f591e0e4 100644 --- a/internal/template/groupby_test.go +++ b/internal/template/groupby_test.go @@ -29,7 +29,8 @@ var groupByContainers = []*context.RuntimeContainer{ ID: "3", }, { - ID: "4", + Env: map[string]string{}, + ID: "4", }, } @@ -170,6 +171,10 @@ func TestGroupByMulti(t *testing.T) { }, ID: "3", }, + { + Env: map[string]string{}, + ID: "4", + }, } groups, _ := groupByMulti(containers, "Env.VIRTUAL_HOST", ",") diff --git a/internal/template/reflect.go b/internal/template/reflect.go index f329f28d..47e76eed 100644 --- a/internal/template/reflect.go +++ b/internal/template/reflect.go @@ -41,7 +41,29 @@ func deepGetImpl(v reflect.Value, path []string) interface{} { case reflect.Struct: return deepGetImpl(v.FieldByName(path[0]), path[1:]) case reflect.Map: - return deepGetImpl(v.MapIndex(reflect.ValueOf(path[0])), path[1:]) + // If the first part of the path is a key in the map, we use it directly + if mapValue := v.MapIndex(reflect.ValueOf(path[0])); mapValue.IsValid() { + return deepGetImpl(mapValue, path[1:]) + } + + // If the first part of the path is not a key in the map, we try to find a valid key by joining the path parts + var builder strings.Builder + for i, pathPart := range path { + if i > 0 { + builder.WriteString(".") + } + builder.WriteString(pathPart) + joinedPath := builder.String() + + if mapValue := v.MapIndex(reflect.ValueOf(joinedPath)); mapValue.IsValid() { + if i == len(path) { + return mapValue.Interface() + } + return deepGetImpl(mapValue, path[i+1:]) + } + } + + return nil case reflect.Slice, reflect.Array: i, err := parseAllocateInt(path[0]) if err != nil { diff --git a/internal/template/reflect_test.go b/internal/template/reflect_test.go index 2c43eb3b..dac9c4ee 100644 --- a/internal/template/reflect_test.go +++ b/internal/template/reflect_test.go @@ -62,6 +62,14 @@ func TestDeepGet(t *testing.T) { path string want interface{} }{ + { + "map of string", + map[string]string{ + "Env": "quux", + }, + "Env", + "quux", + }, { "map key empty string", map[string]map[string]map[string]string{ @@ -74,6 +82,28 @@ func TestDeepGet(t *testing.T) { "...", "foo", }, + { + "map with dot in key", + map[string]map[string]string{ + "Env": { + "foo.bar.baz.qux": "quux", + }, + }, + "Env.foo.bar.baz.qux", + "quux", + }, + { + "nested maps with dot in keys", + map[string]map[string]map[string]string{ + "Env": { + "foo.bar": { + "baz.qux": "quux", + }, + }, + }, + "Env.foo.bar.baz.qux", + "quux", + }, {"struct", s, "X", "foo"}, {"pointer to struct", sp, "X", "foo"}, {"double pointer to struct", &sp, ".X", nil}, diff --git a/internal/template/sort_test.go b/internal/template/sort_test.go index 68a4e48d..b541fcdf 100644 --- a/internal/template/sort_test.go +++ b/internal/template/sort_test.go @@ -55,6 +55,9 @@ func TestSortObjectsByKeys(t *testing.T) { Env: map[string]string{ "VIRTUAL_HOST": "bar.localhost", }, + Labels: map[string]string{ + "com.docker.compose.container_number": "1", + }, ID: "11", } o1 := &context.RuntimeContainer{ @@ -62,6 +65,9 @@ func TestSortObjectsByKeys(t *testing.T) { Env: map[string]string{ "VIRTUAL_HOST": "foo.localhost", }, + Labels: map[string]string{ + "com.docker.compose.container_number": "11", + }, ID: "1", } o2 := &context.RuntimeContainer{ @@ -69,12 +75,16 @@ func TestSortObjectsByKeys(t *testing.T) { Env: map[string]string{ "VIRTUAL_HOST": "baz.localhost", }, - ID: "3", + Labels: map[string]string{}, + ID: "3", } o3 := &context.RuntimeContainer{ Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Env: map[string]string{}, - ID: "8", + Labels: map[string]string{ + "com.docker.compose.container_number": "2", + }, + ID: "8", } containers := []*context.RuntimeContainer{o0, o1, o2, o3} @@ -85,9 +95,11 @@ func TestSortObjectsByKeys(t *testing.T) { want []interface{} }{ {"Asc simple", sortObjectsByKeysAsc, "ID", []interface{}{o1, o2, o3, o0}}, - {"Asc complex", sortObjectsByKeysAsc, "Env.VIRTUAL_HOST", []interface{}{o3, o0, o2, o1}}, {"Desc simple", sortObjectsByKeysDesc, "ID", []interface{}{o0, o3, o2, o1}}, + {"Asc complex", sortObjectsByKeysAsc, "Env.VIRTUAL_HOST", []interface{}{o3, o0, o2, o1}}, {"Desc complex", sortObjectsByKeysDesc, "Env.VIRTUAL_HOST", []interface{}{o1, o2, o0, o3}}, + {"Asc complex w/ dots in key name", sortObjectsByKeysAsc, "Labels.com.docker.compose.container_number", []interface{}{o2, o0, o3, o1}}, + {"Desc complex w/ dots in key name", sortObjectsByKeysDesc, "Labels.com.docker.compose.container_number", []interface{}{o1, o3, o0, o2}}, {"Asc time", sortObjectsByKeysAsc, "Created", []interface{}{o3, o0, o2, o1}}, {"Desc time", sortObjectsByKeysDesc, "Created", []interface{}{o1, o2, o0, o3}}, } {