Skip to content

Commit c066ae3

Browse files
authored
Merge pull request #81 from puppetlabs/CAT-1731-add-rules-tests
(CAT-1731) add rules tests
2 parents f8e4636 + edeea19 commit c066ae3

File tree

3 files changed

+354
-137
lines changed

3 files changed

+354
-137
lines changed

.vscode/extensions.json

Lines changed: 0 additions & 6 deletions
This file was deleted.

lib/puppet/type/node_group.rb

Lines changed: 72 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -35,82 +35,90 @@
3535
end
3636
newproperty(:rule, array_matching: :all) do
3737
desc 'Match conditions for this group'
38+
# rubocop:disable Metrics/CyclomaticComplexity
39+
# rubocop:disable Metrics/PerceivedComplexity
3840
def should
3941
case @resource[:purge_behavior]
40-
when :rule, :all
41-
super
42-
else
42+
# only do something special when we are asked to merge, otherwise, fall back to the default
43+
when :none
44+
# rule grammar
45+
# condition : [ {bool} {condition}+ ] | [ "not" {condition} ] | {operation}
46+
# bool : "and" | "or"
47+
# operation : [ {operator} {fact-path} {value} ]
48+
# operator : "=" | "~" | ">" | ">=" | "<" | "<=" | "<:"
49+
# fact-path : {field-name} | [ {path-type} {field-name} {path-component}+ ]
50+
# path-type : "trusted" | "fact"
51+
# path-component : field-name | number
52+
# field-name : string
53+
# value : string
54+
4355
a = @resource.property(:rule).retrieve || {}
4456
b = shouldorig
45-
aorig = a.map(&:clone)
46-
atmp = a.map(&:clone)
47-
# check if the node classifer has any rules defined before attempting merge.
48-
if a.length >= 2
49-
if (a[0] == 'or') && (a[1][0] == 'or') || (a[1][0] == 'and')
50-
# Merging both rules and pinned nodes
51-
if (b[0] == 'or') && (b[1][0] == 'or') || (b[1][0] == 'and')
52-
# b has rules to merge
53-
rules = (atmp[1] + b[1].drop(1)).uniq
54-
atmp[1] = rules
55-
pinned = (b[2, b.length] + atmp[2, atmp.length]).uniq
56-
merged = (atmp + pinned).uniq
57-
elsif ((b[0] == 'and') || (b[0] == 'or')) && PuppetX::Node_manager::Common.factcheck(b)
58-
# b only has rules to merge
59-
rules = (atmp[1] + b.drop(1)).uniq
60-
atmp[1] = rules
61-
merged = atmp
62-
else
63-
pinned = (b[1, b.length] + atmp[2, atmp.length]).uniq
64-
merged = (atmp + pinned).uniq
65-
end
66-
elsif (b[0] == 'or') && (b[1][0] == 'or') || (b[1][0] == 'and')
67-
# Merging both rules and pinned nodes
68-
rules = b[1] # no rules to merge on a side
69-
pinned = (b[2, b.length] + a[1, a.length]).uniq
70-
merged = (b + pinned).uniq
71-
elsif ((a[0] == 'and') || (a[0] == 'or')) && PuppetX::Node_manager::Common.factcheck(a)
72-
# a only has fact rules
73-
if (b[0] == 'or') && !PuppetX::Node_manager::Common.factcheck(b)
74-
# b only has pinned nodes
75-
rules = atmp
76-
temp = ['or']
77-
temp[1] = atmp
78-
merged = (temp + b[1, b.length]).uniq
79-
else
80-
# b only has rules
81-
merged = (a + b.drop(1)).uniq
82-
end
83-
elsif (a[0] == 'or') && (a[1][1] == 'name')
84-
# a only has pinned nodes
85-
if (b[0] == 'or') && !PuppetX::Node_manager::Common.factcheck(b)
86-
# b only has pinned nodes
87-
merged = (b + a.drop(1)).uniq
88-
else
89-
# b only has rules
90-
temp = ['or']
91-
temp[1] = b
92-
merged = (temp + atmp[1, atmp.length]).uniq
93-
end
94-
else
95-
# default fall back.
96-
merged = (b + a.drop(1)).uniq
97-
end
98-
if merged == aorig
99-
# values are the same, returning orginal value"
100-
aorig
101-
else
102-
merged
103-
end
104-
else
105-
b
57+
58+
# extract all pinned nodes if any
59+
# pinned nodes are in the form ['=', 'name', <hostname>]
60+
apinned = []
61+
a_without_pinned = a
62+
if a[0] == 'or'
63+
apinned = a.select { |item| (item[0] == '=') && (item[1] == 'name') }
64+
a_without_pinned = a.select { |item| (item[0] != '=') || (item[1] != 'name') }
65+
end
66+
bpinned = []
67+
b_without_pinned = b
68+
merged = []
69+
70+
(return b.uniq.select { |item| (item != ['or'] && item != ['and']) } if a == [''])
71+
(return a.uniq.select { |item| (item != ['or'] && item != ['and']) } if b == [''])
72+
73+
if b[0] == 'or'
74+
bpinned = b.select { |item| (item[0] == '=') && (item[1] == 'name') }
75+
b_without_pinned = b.select { |item| (item[0] != '=') || (item[1] != 'name') }
10676
end
77+
78+
merged = if ((a[0] == 'and') || (a[0] == 'or')) && a[0] == b[0]
79+
# if a and b start with the same 'and' or 'or' clause, we can just combine them
80+
if a[0] == 'or'
81+
(['or'] + a_without_pinned.drop(1) + b_without_pinned.drop(1) + apinned + bpinned).uniq
82+
elsif apinned.length.positive? || bpinned.length.positive?
83+
# must both be 'and' clauses
84+
(['or'] + [a_without_pinned + b_without_pinned.drop(1)] + apinned + bpinned).uniq
85+
# we have pinned nodes
86+
else
87+
# no pinned nodes and one top level 'and' clause, just combine them.
88+
a_without_pinned + b_without_pinned.drop(1)
89+
end
90+
elsif a_without_pinned[0] == 'and' && b_without_pinned[0] == 'or'
91+
# first clause of a and b aren't equal
92+
# a first clause is one of and/or/not/operator
93+
# b first clause is one of and/or/not/operator
94+
# if a starts with `and` and b starts with `or`, create a top level `or` clause, nest a under it and append the rest of b
95+
if a_without_pinned.length == 2
96+
(['or'] + a_without_pinned[1] + b_without_pinned.drop(1) + apinned + bpinned)
97+
else
98+
(['or'] + [a_without_pinned] + b_without_pinned.drop(1) + apinned + bpinned)
99+
end
100+
# special case of a only having one subclause
101+
elsif a_without_pinned[0] == 'or'
102+
(a_without_pinned + [b_without_pinned] + apinned + bpinned).uniq
103+
elsif b_without_pinned[0] == 'or'
104+
# if b starts with 'or', we want to be sure to drop that.
105+
(['or'] + [a_without_pinned] + b_without_pinned.drop(1) + apinned + bpinned)
106+
else
107+
(['or'] + [a_without_pinned] + [b_without_pinned] + apinned + bpinned)
108+
end
109+
# ensure rules are unique at the top level and remove any empty rule sets
110+
merged.uniq.select { |item| (item != ['or'] && item != ['and']) }
111+
else
112+
super
107113
end
108114
end
109115

110116
def insync?(is)
111117
is == should
112118
end
113119
end
120+
# rubocop:enable Metrics/CyclomaticComplexity
121+
# rubocop:enable Metrics/PerceivedComplexity
114122
newproperty(:environment) do
115123
desc 'Environment for this group'
116124
defaultto :production

0 commit comments

Comments
 (0)