Skip to content

Commit e1eb4de

Browse files
Merge pull request #80 from jonathannewman/CAT-1731-add-rules-tests
(CAT-1731) improve handling of pinned nodes
2 parents 537bf15 + 7456c65 commit e1eb4de

File tree

2 files changed

+350
-161
lines changed

2 files changed

+350
-161
lines changed

lib/puppet/type/node_group.rb

+76-60
Original file line numberDiff line numberDiff line change
@@ -37,73 +37,89 @@
3737
desc 'Match conditions for this group'
3838
def should
3939
case @resource[:purge_behavior]
40-
when :rule, :all
41-
super
42-
else
40+
# only do something special when we are asked to merge, otherwise, fall back to the default
41+
when :none
42+
# rule grammar
43+
# condition : [ {bool} {condition}+ ] | [ "not" {condition} ] | {operation}
44+
# bool : "and" | "or"
45+
# operation : [ {operator} {fact-path} {value} ]
46+
# operator : "=" | "~" | ">" | ">=" | "<" | "<=" | "<:"
47+
# fact-path : {field-name} | [ {path-type} {field-name} {path-component}+ ]
48+
# path-type : "trusted" | "fact"
49+
# path-component : field-name | number
50+
# field-name : string
51+
# value : string
52+
4353
a = @resource.property(:rule).retrieve || {}
4454
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
55+
56+
# extract all pinned nodes if any
57+
# pinned nodes are in the form ['=', 'name', <hostname>]
58+
apinned = []
59+
a_without_pinned = a
60+
if (a[0] == 'or')
61+
apinned = a.select {|item| (item[0] == '=') && (item[1] == 'name')}
62+
a_without_pinned = a.select { |item| (item[0] != '=') || (item[1] != 'name') }
63+
end
64+
bpinned = []
65+
b_without_pinned = b
66+
merged = []
67+
if (a == [""])
68+
# ensure rules are unique at the top level and remove any empty rule sets
69+
return b.uniq.select { |item| (item != ['or'] && item != ['and'] )}
70+
elsif (b == [""])
71+
# ensure rules are unique at the top level and remove any empty rule sets
72+
return a.uniq.select { |item| (item != ['or'] && item != ['and'] )}
73+
end
74+
75+
if (b[0] == 'or')
76+
bpinned = b.select {|item| (item[0] == '=') && (item[1] == 'name')}
77+
b_without_pinned = b.select { |item| (item[0] != '=') || (item[1] != 'name') }
78+
end
79+
80+
if (((a[0] == 'and') || (a[0] == 'or')) && a[0] == b[0])
81+
# if a and b start with the same 'and' or 'or' clause, we can just combine them
82+
if (a[0] == 'or')
83+
merged = (['or'] + a_without_pinned.drop(1) + b_without_pinned.drop(1) + apinned + bpinned).uniq
84+
else
85+
# must both be 'and' clauses
86+
if (apinned.length > 0 || bpinned.length > 0)
87+
# we have pinned nodes
88+
merged = (['or'] + [a_without_pinned + b_without_pinned.drop(1)] + apinned + bpinned).uniq
89+
else
90+
# no pinned nodes and one top level 'and' clause, just combine them.
91+
merged = a_without_pinned + b_without_pinned.drop(1)
9392
end
94-
else
95-
# default fall back.
96-
merged = (b + a.drop(1)).uniq
9793
end
98-
if merged == aorig
99-
# values are the same, returning orginal value"
100-
aorig
94+
else
95+
# first clause of a and b aren't equal
96+
# a first clause is one of and/or/not/operator
97+
# b first clause is one of and/or/not/operator
98+
# 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
99+
if (a_without_pinned[0] == 'and' && b_without_pinned[0] == 'or')
100+
# special case of a only having one subclause
101+
if (a_without_pinned.length == 2)
102+
merged = (['or'] + a_without_pinned[1] + b_without_pinned.drop(1) + apinned + bpinned)
103+
else
104+
merged = (['or'] + [a_without_pinned] + b_without_pinned.drop(1) + apinned + bpinned)
105+
end
101106
else
102-
merged
107+
if (a_without_pinned[0] == 'or')
108+
merged = (a_without_pinned + [b_without_pinned] + apinned + bpinned).uniq
109+
else
110+
# if b starts with 'or', we want to be sure to drop that.
111+
if (b_without_pinned[0] == 'or')
112+
merged = (['or'] + [a_without_pinned] + b_without_pinned.drop(1) + apinned + bpinned)
113+
else
114+
merged = (['or'] + [a_without_pinned] + [b_without_pinned] + apinned + bpinned)
115+
end
116+
end
103117
end
104-
else
105-
b
106118
end
119+
# ensure rules are unique at the top level and remove any empty rule sets
120+
merged.uniq.select { |item| (item != ['or'] && item != ['and'] )}
121+
else
122+
super
107123
end
108124
end
109125

0 commit comments

Comments
 (0)