Skip to content

(CAT-1731) add rules tests #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .vscode/extensions.json

This file was deleted.

136 changes: 72 additions & 64 deletions lib/puppet/type/node_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,82 +35,90 @@
end
newproperty(:rule, array_matching: :all) do
desc 'Match conditions for this group'
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def should
case @resource[:purge_behavior]
when :rule, :all
super
else
# only do something special when we are asked to merge, otherwise, fall back to the default
when :none
# rule grammar
# condition : [ {bool} {condition}+ ] | [ "not" {condition} ] | {operation}
# bool : "and" | "or"
# operation : [ {operator} {fact-path} {value} ]
# operator : "=" | "~" | ">" | ">=" | "<" | "<=" | "<:"
# fact-path : {field-name} | [ {path-type} {field-name} {path-component}+ ]
# path-type : "trusted" | "fact"
# path-component : field-name | number
# field-name : string
# value : string

a = @resource.property(:rule).retrieve || {}
b = shouldorig
aorig = a.map(&:clone)
atmp = a.map(&:clone)
# check if the node classifer has any rules defined before attempting merge.
if a.length >= 2
if (a[0] == 'or') && (a[1][0] == 'or') || (a[1][0] == 'and')
# Merging both rules and pinned nodes
if (b[0] == 'or') && (b[1][0] == 'or') || (b[1][0] == 'and')
# b has rules to merge
rules = (atmp[1] + b[1].drop(1)).uniq
atmp[1] = rules
pinned = (b[2, b.length] + atmp[2, atmp.length]).uniq
merged = (atmp + pinned).uniq
elsif ((b[0] == 'and') || (b[0] == 'or')) && PuppetX::Node_manager::Common.factcheck(b)
# b only has rules to merge
rules = (atmp[1] + b.drop(1)).uniq
atmp[1] = rules
merged = atmp
else
pinned = (b[1, b.length] + atmp[2, atmp.length]).uniq
merged = (atmp + pinned).uniq
end
elsif (b[0] == 'or') && (b[1][0] == 'or') || (b[1][0] == 'and')
# Merging both rules and pinned nodes
rules = b[1] # no rules to merge on a side
pinned = (b[2, b.length] + a[1, a.length]).uniq
merged = (b + pinned).uniq
elsif ((a[0] == 'and') || (a[0] == 'or')) && PuppetX::Node_manager::Common.factcheck(a)
# a only has fact rules
if (b[0] == 'or') && !PuppetX::Node_manager::Common.factcheck(b)
# b only has pinned nodes
rules = atmp
temp = ['or']
temp[1] = atmp
merged = (temp + b[1, b.length]).uniq
else
# b only has rules
merged = (a + b.drop(1)).uniq
end
elsif (a[0] == 'or') && (a[1][1] == 'name')
# a only has pinned nodes
if (b[0] == 'or') && !PuppetX::Node_manager::Common.factcheck(b)
# b only has pinned nodes
merged = (b + a.drop(1)).uniq
else
# b only has rules
temp = ['or']
temp[1] = b
merged = (temp + atmp[1, atmp.length]).uniq
end
else
# default fall back.
merged = (b + a.drop(1)).uniq
end
if merged == aorig
# values are the same, returning orginal value"
aorig
else
merged
end
else
b

# extract all pinned nodes if any
# pinned nodes are in the form ['=', 'name', <hostname>]
apinned = []
a_without_pinned = a
if a[0] == 'or'
apinned = a.select { |item| (item[0] == '=') && (item[1] == 'name') }
a_without_pinned = a.select { |item| (item[0] != '=') || (item[1] != 'name') }
end
bpinned = []
b_without_pinned = b
merged = []

(return b.uniq.select { |item| (item != ['or'] && item != ['and']) } if a == [''])
(return a.uniq.select { |item| (item != ['or'] && item != ['and']) } if b == [''])

if b[0] == 'or'
bpinned = b.select { |item| (item[0] == '=') && (item[1] == 'name') }
b_without_pinned = b.select { |item| (item[0] != '=') || (item[1] != 'name') }
end

merged = if ((a[0] == 'and') || (a[0] == 'or')) && a[0] == b[0]
# if a and b start with the same 'and' or 'or' clause, we can just combine them
if a[0] == 'or'
(['or'] + a_without_pinned.drop(1) + b_without_pinned.drop(1) + apinned + bpinned).uniq
elsif apinned.length.positive? || bpinned.length.positive?
# must both be 'and' clauses
(['or'] + [a_without_pinned + b_without_pinned.drop(1)] + apinned + bpinned).uniq
# we have pinned nodes
else
# no pinned nodes and one top level 'and' clause, just combine them.
a_without_pinned + b_without_pinned.drop(1)
end
elsif a_without_pinned[0] == 'and' && b_without_pinned[0] == 'or'
# first clause of a and b aren't equal
# a first clause is one of and/or/not/operator
# b first clause is one of and/or/not/operator
# 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
if a_without_pinned.length == 2
(['or'] + a_without_pinned[1] + b_without_pinned.drop(1) + apinned + bpinned)
else
(['or'] + [a_without_pinned] + b_without_pinned.drop(1) + apinned + bpinned)
end
# special case of a only having one subclause
elsif a_without_pinned[0] == 'or'
(a_without_pinned + [b_without_pinned] + apinned + bpinned).uniq
elsif b_without_pinned[0] == 'or'
# if b starts with 'or', we want to be sure to drop that.
(['or'] + [a_without_pinned] + b_without_pinned.drop(1) + apinned + bpinned)
else
(['or'] + [a_without_pinned] + [b_without_pinned] + apinned + bpinned)
end
# ensure rules are unique at the top level and remove any empty rule sets
merged.uniq.select { |item| (item != ['or'] && item != ['and']) }
else
super
end
end

def insync?(is)
is == should
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
newproperty(:environment) do
desc 'Environment for this group'
defaultto :production
Expand Down
Loading
Loading