sort.Strings(res)
return res, nil
}
+
+// TransitivelyDetectedGroups returns all groups which depend on "detected"
+// groups, either directly or by depending on groups which transitively depend
+// on "detected" groups.
+func TransitivelyDetectedGroups(groups map[string][]string) []string {
+ work := make(map[string][]string)
+ for k, v := range groups {
+ work[k] = v
+ }
+
+ // Mark all groups which contain "detected" groups as long as new
+ // (transitive) "detected" groups are found.
+ detected := make(map[string]bool)
+ for {
+ change := false
+ for group, members := range work {
+ for _, x := range members {
+ if !detected[x] && !strings.HasPrefix(x,
+ GroupDetectedPrefix) {
+ continue
+ }
+ detected[strings.TrimSuffix(group,
+ GroupRemoveSuffix)] = true
+ delete(work, group)
+ change = true
+ }
+ }
+ if !change {
+ break
+ }
+ }
+
+ var res []string
+ for x := range detected {
+ res = append(res, x)
+ }
+ sort.Strings(res)
+ return res
+}
"group2:remove": {
"remove",
},
+ "group3": {
+ "host1.example.org",
+ },
+ "group3:remove": {
+ "host2",
+ },
"all_except_some": {
"all",
},
[]string{
"all",
"group",
+ "group3",
"host1.example.org",
"remove",
},
[]string{
"all",
"detected_mips",
+ "group3",
"host1.example.org",
"remove",
},
})
}
}
+
+func TestTransitivelyDetectedGroups(t *testing.T) {
+ tests := []struct {
+ name string
+ groups map[string][]string
+ exp []string
+ }{
+
+ {
+ "no detected",
+ map[string][]string{
+ "group-a": {
+ "a",
+ "b",
+ "group-b",
+ },
+ "group-a:remove": {
+ "d",
+ },
+ "group-b": {
+ "c",
+ "d",
+ },
+ },
+ nil,
+ },
+
+ {
+ "detected as direct member",
+ map[string][]string{
+ "group-a": {
+ "a",
+ "b",
+ "detected_foo",
+ },
+ "group-b": {
+ "c",
+ "d",
+ },
+ },
+ []string{
+ "group-a",
+ },
+ },
+
+ {
+ "detected as direct :remove member",
+ map[string][]string{
+ "group-a": {
+ "a",
+ "b",
+ "group-b",
+ },
+ "group-a:remove": {
+ "d",
+ "detected_foo",
+ },
+ "group-b": {
+ "c",
+ "d",
+ },
+ },
+ []string{
+ "group-a",
+ },
+ },
+
+ {
+ "detected as transitive member",
+ map[string][]string{
+ "group-a": {
+ "group-b",
+ },
+ "group-b": {
+ "group-c",
+ },
+ "group-c": {
+ "group-d",
+ "detected_bar",
+ },
+ "group-d": {
+ "group-e",
+ },
+ "group-e": {
+ "detected_foo",
+ },
+ "group-f": {
+ "a",
+ "b",
+ },
+ },
+ []string{
+ "group-a",
+ "group-b",
+ "group-c",
+ "group-d",
+ "group-e",
+ },
+ },
+
+ {
+ "detected as transitive :remove member",
+ map[string][]string{
+ "group-a": {
+ "group-b",
+ },
+ "group-b": {
+ "group-c",
+ },
+ "group-c": {
+ "group-d",
+ },
+ "group-d": {
+ "group-e",
+ },
+ "group-e": {
+ "all",
+ },
+ "group-e:remove": {
+ "detected_foo",
+ },
+ "group-f": {
+ "a",
+ "b",
+ },
+ },
+ []string{
+ "group-a",
+ "group-b",
+ "group-c",
+ "group-d",
+ "group-e",
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ res := TransitivelyDetectedGroups(tc.groups)
+ testutil.AssertEqual(t, "res", res, tc.exp)
+ })
+ }
+}
func hostsToSync(names []string, allHosts *config.Hosts,
allGroups map[string][]string) ([]*config.Host, error) {
+ detectedMap := make(map[string]bool)
+ for _, x := range config.TransitivelyDetectedGroups(allGroups) {
+ detectedMap[x] = true
+ }
+
+ const detectedErr = `
+
+Groups depending on "detected" groups cannot be used to select hosts as these
+are only available after the hosts were contacted.
+`
+
nameMap := make(map[string]bool)
for _, x := range names {
+ if detectedMap[x] {
+ return nil, fmt.Errorf(
+ "group %q depends on \"detected\" groups%s",
+ x, detectedErr)
+ }
nameMap[x] = true
}
nameMatched := make(map[string]bool)
nameMatched[host.Name] = true
}
- // TODO: don't permit groups which contain "detected" groups
- // because these are not available yet
groups, err := config.ResolveHostGroups(host.Name,
allGroups, nil)
if err != nil {
Groups: []string{
"all",
"group",
+ "group3",
"remove",
"host1.example.org",
},
},
},
[]string{
- "host1.example.org: <nil> 3 host groups: all group host1.example.org remove",
+ "host1.example.org: <nil> 3 host groups: all group group3 host1.example.org remove",
"host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
},
nil,
Groups: []string{
"all",
"group",
+ "group3",
"remove",
"host1.example.org",
},
t.Fatal(err)
}
+ const errMsg = `
+
+Groups depending on "detected" groups cannot be used to select hosts as these
+are only available after the hosts were contacted.
+`
+
tests := []struct {
name string
names []string
{
"group: single name",
- []string{"group"},
+ []string{"group3"},
[]*config.Host{
allHosts.Map["host1.example.org"],
},
},
{
"group: multiple names",
- []string{"group", "group2"},
+ []string{"group3", "group2"},
[]*config.Host{
allHosts.Map["host1.example.org"],
allHosts.Map["host2"],
},
{
"group: multiple identical names",
- []string{"group", "group2", "group"},
+ []string{"group3", "group2", "group3"},
[]*config.Host{
allHosts.Map["host1.example.org"],
allHosts.Map["host2"],
},
{
"group: multiple names, including unknown",
- []string{"group", "group2", "unknown-group"},
+ []string{"group3", "group2", "unknown-group"},
nil,
fmt.Errorf("hosts/groups not found: \"unknown-group\""),
},
nil,
},
+ {
+ "group: single name (detected)",
+ []string{"group"},
+ nil,
+ fmt.Errorf(`group "group" depends on "detected" groups` + errMsg),
+ },
+ {
+ "group: multiple names (detected)",
+ []string{"group", "group2"},
+ nil,
+ fmt.Errorf(`group "group" depends on "detected" groups` + errMsg),
+ },
+
{
"\"all\" and name",
[]string{"all", "group2"},
group2:remove:
- remove
+group3:
+ - host1.example.org
+group3:remove:
+ - host2
+
all_except_some:
- all
all_except_some:remove: