Skip to content

Commit 28f5ed7

Browse files
INFRA-3218 Initial commit of boolean comparators
Initial commit of greater_than, equal_or_greater_than, equal_to, equal_or_less_than, and less_than comparator functions and their unit tests.
1 parent ed615a9 commit 28f5ed7

File tree

10 files changed

+394
-0
lines changed

10 files changed

+394
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
require 'ipaddr'
4+
5+
Puppet::Functions.create_function(:"ipcalc::equal_or_greater_than") do
6+
# Compare two IP addresses and return a Boolean indicating whether the first operand is equal to
7+
# (or possibly greater than) the second. Contextually, this makes the most sense when using
8+
# Puppet's dotted function notation, because the function name sits where a comparison operator
9+
# would normally go. This will work with either plain addresses or CIDR-notation addresses; if a
10+
# plain address is supplied, a full-width netmask is assumed. That is, '127.0.0.1' and
11+
# '127.0.0.1/32' are equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask
12+
# IS assessed in the comparison, but is only relevant if the remainder of the address is exactly
13+
# equivalent down to the start of the netmask. If addresses of mixed families are given, an error
14+
# is raised and the catalog will fail. There is no obvious implied relation between the two, and
15+
# even the underlying Ruby implementation will refuse to make comparisons between addresses of
16+
# different families.
17+
# @param first
18+
# The first address for comparison. If no netmask is given, full-width is assumed.
19+
# @param second
20+
# The second address for comparison. If no netmask is given, full-width is assumed.
21+
# @return [Boolean]
22+
# `true` if the first operand is equal to (or greater than) the second, or `false` if it's less
23+
# than the second.
24+
# @example Use with dotted function notation
25+
# $first_ip.equal_or_greater_than($second_ip) ? {
26+
# true => { 'We did it, go team, first operand is equal or bigger' },
27+
# default => { 'I\'m afraid I have some bad news...' },
28+
# }
29+
dispatch :equal_or_greater_than do
30+
param 'Stdlib::IP::Address::V4', :first
31+
param 'Stdlib::IP::Address::V4', :second
32+
return_type 'Boolean'
33+
end
34+
35+
dispatch :equal_or_greater_than do
36+
param 'Stdlib::IP::Address::V6', :first
37+
param 'Stdlib::IP::Address::V6', :second
38+
return_type 'Boolean'
39+
end
40+
41+
# Refuse to handle addresses in different families. There isn't a clear precedence between the
42+
# two families; if you need to establish an order, you can use type comparison to handle this in
43+
# your Puppet code.
44+
argument_mismatch :mixed_families do
45+
param 'Stdlib::IP::Address::V4', :first
46+
param 'Stdlib::IP::Address::V6', :second
47+
end
48+
49+
argument_mismatch :mixed_families do
50+
param 'Stdlib::IP::Address::V6', :first
51+
param 'Stdlib::IP::Address::V4', :second
52+
end
53+
54+
def equal_or_greater_than(first, second)
55+
first_addr = IPAddr.new(first)
56+
second_addr = IPAddr.new(second)
57+
58+
first_addr >= second_addr
59+
end
60+
61+
def mixed_families(*)
62+
'both addresses must be in the same family'
63+
end
64+
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
require 'ipaddr'
4+
5+
Puppet::Functions.create_function(:"ipcalc::equal_or_less_than") do
6+
# Compare two IP addresses and return a Boolean indicating whether the first operand is equal to
7+
# (or possibly less than) the second. Contextually, this makes the most sense when using
8+
# Puppet's dotted function notation, because the function name sits where a comparison operator
9+
# would normally go. This will work with either plain addresses or CIDR-notation addresses; if a
10+
# plain address is supplied, a full-width netmask is assumed. That is, '127.0.0.1' and
11+
# '127.0.0.1/32' are equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask
12+
# IS assessed in the comparison, but is only relevant if the remainder of the address is exactly
13+
# equivalent down to the start of the netmask. If addresses of mixed families are given, an error
14+
# is raised and the catalog will fail. There is no obvious implied relation between the two, and
15+
# even the underlying Ruby implementation will refuse to make comparisons between addresses of
16+
# different families.
17+
# @param first
18+
# The first address for comparison. If no netmask is given, full-width is assumed.
19+
# @param second
20+
# The second address for comparison. If no netmask is given, full-width is assumed.
21+
# @return [Boolean]
22+
# `true` if the first operand is equal to (or less than) the second, or `false` if it's greater
23+
# than the second.
24+
# @example Use with dotted function notation
25+
# $first_ip.equal_or_less_than($second_ip) ? {
26+
# true => { 'We did it, go team, first operand is equal or smaller' },
27+
# default => { 'I\'m afraid I have some bad news...' },
28+
# }
29+
dispatch :equal_or_less_than do
30+
param 'Stdlib::IP::Address::V4', :first
31+
param 'Stdlib::IP::Address::V4', :second
32+
return_type 'Boolean'
33+
end
34+
35+
dispatch :equal_or_less_than do
36+
param 'Stdlib::IP::Address::V6', :first
37+
param 'Stdlib::IP::Address::V6', :second
38+
return_type 'Boolean'
39+
end
40+
41+
# Refuse to handle addresses in different families. There isn't a clear precedence between the
42+
# two families; if you need to establish an order, you can use type comparison to handle this in
43+
# your Puppet code.
44+
argument_mismatch :mixed_families do
45+
param 'Stdlib::IP::Address::V4', :first
46+
param 'Stdlib::IP::Address::V6', :second
47+
end
48+
49+
argument_mismatch :mixed_families do
50+
param 'Stdlib::IP::Address::V6', :first
51+
param 'Stdlib::IP::Address::V4', :second
52+
end
53+
54+
def equal_or_less_than(first, second)
55+
first_addr = IPAddr.new(first)
56+
second_addr = IPAddr.new(second)
57+
58+
first_addr <= second_addr
59+
end
60+
61+
def mixed_families(*)
62+
'both addresses must be in the same family'
63+
end
64+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
require 'ipaddr'
4+
5+
Puppet::Functions.create_function(:"ipcalc::equal_to") do
6+
# Compare two IP addresses and return a Boolean indicating whether the first operand is equal to
7+
# the second. This differs from comparing the address Strings in that it compares native 32-bit
8+
# (or for IPv6, 128-bit) binary values, regardless of their human-readable representations. For
9+
# IPv4 addresses this is usually not much of a problem, as representations are simpler and
10+
# zero-padded values are relatively rare (enough that Stdlib::IP::Address::V4 filters them out as
11+
# invalid). For IPv6 addresses, however, IPv6's collapsed notation can be a very serious problem,
12+
# and this function allows IPv6 addresses with un-collapsed runs of zeroes, leading zeroes, etc.
13+
# to be compared meaningfully without needing to worry about normalizing their human-readable
14+
# representations first.
15+
#
16+
# Contextually, this function makes the most sense when using Puppet's dotted function notation,
17+
# because the function name sits where a comparison operator would normally go. This will work
18+
# with either plain addresses or CIDR-notation addresses; if a plain address is supplied, a
19+
# full-width netmask is assumed. That is, '127.0.0.1' and '127.0.0.1/32' are equivalent, just as
20+
# 'fe80::1' and 'fe80::1/128' are equivalent. The netmask IS assessed in the comparison, but is
21+
# only relevant if the remainder of the address is exactly equivalent down to the start of the
22+
# netmask. Addresses of mixed families are always considered unequal.
23+
# @param first
24+
# The first address for comparison. If no netmask is given, full-width is assumed.
25+
# @param second
26+
# The second address for comparison. If no netmask is given, full-width is assumed.
27+
# @return [Boolean]
28+
# `true` if the operands are equal, or `false` otherwise.
29+
# @example Use with dotted function notation
30+
# $first_ip.equal_to($second_ip) ? {
31+
# true => { 'We did it, go team, the operands are equal' },
32+
# default => { 'I\'m afraid I have some bad news...' },
33+
# }
34+
dispatch :equal_to do
35+
param 'Stdlib::IP::Address::V4', :first
36+
param 'Stdlib::IP::Address::V4', :second
37+
return_type 'Boolean'
38+
end
39+
40+
dispatch :equal_to do
41+
param 'Stdlib::IP::Address::V6', :first
42+
param 'Stdlib::IP::Address::V6', :second
43+
return_type 'Boolean'
44+
end
45+
46+
dispatch :mixed_families do
47+
param 'Stdlib::IP::Address::V4', :first
48+
param 'Stdlib::IP::Address::V6', :second
49+
end
50+
51+
dispatch :mixed_families do
52+
param 'Stdlib::IP::Address::V6', :first
53+
param 'Stdlib::IP::Address::V4', :second
54+
end
55+
56+
def equal_to(first, second)
57+
first_addr = IPAddr.new(first)
58+
second_addr = IPAddr.new(second)
59+
60+
first_addr == second_addr
61+
end
62+
63+
def mixed_families(*)
64+
false
65+
end
66+
end
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
3+
require 'ipaddr'
4+
5+
Puppet::Functions.create_function(:"ipcalc::greater_than") do
6+
# Compare two IP addresses and return a Boolean indicating whether the first operand is greater
7+
# than the second. Contextually, this makes the most sense when using Puppet's dotted function
8+
# notation, because the function name sits where a comparison operator would normally go. This
9+
# will work with either plain addresses or CIDR-notation addresses; if a plain address is
10+
# supplied, a full-width netmask is assumed. That is, '127.0.0.1' and '127.0.0.1/32' are
11+
# equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask IS assessed in the
12+
# comparison, but is only relevant if the remainder of the address is exactly equivalent down to
13+
# the start of the netmask. If addresses of mixed families are given, an error is raised and the
14+
# catalog will fail. There is no obvious implied relation between the two, and even the
15+
# underlying Ruby implementation will refuse to make comparisons between addresses of different
16+
# families.
17+
# @param first
18+
# The first address for comparison. If no netmask is given, full-width is assumed.
19+
# @param second
20+
# The second address for comparison. If no netmask is given, full-width is assumed.
21+
# @return [Boolean]
22+
# `true` if the first operand is greater than the second, or `false` otherwise.
23+
# @example Use with dotted function notation
24+
# $first_ip.greater_than($second_ip) ? {
25+
# true => { 'We did it, go team, first operand is bigger' },
26+
# default => { 'I\'m afraid I have some bad news...' },
27+
# }
28+
dispatch :greater_than do
29+
param 'Stdlib::IP::Address::V4', :first
30+
param 'Stdlib::IP::Address::V4', :second
31+
return_type 'Boolean'
32+
end
33+
34+
dispatch :greater_than do
35+
param 'Stdlib::IP::Address::V6', :first
36+
param 'Stdlib::IP::Address::V6', :second
37+
return_type 'Boolean'
38+
end
39+
40+
# Refuse to handle addresses in different families. There isn't a clear precedence between the
41+
# two families; if you need to establish an order, you can use type comparison to handle this in
42+
# your Puppet code.
43+
argument_mismatch :mixed_families do
44+
param 'Stdlib::IP::Address::V4', :first
45+
param 'Stdlib::IP::Address::V6', :second
46+
end
47+
48+
argument_mismatch :mixed_families do
49+
param 'Stdlib::IP::Address::V6', :first
50+
param 'Stdlib::IP::Address::V4', :second
51+
end
52+
53+
def greater_than(first, second)
54+
first_addr = IPAddr.new(first)
55+
second_addr = IPAddr.new(second)
56+
57+
first_addr > second_addr
58+
end
59+
60+
def mixed_families(*)
61+
'both addresses must be in the same family'
62+
end
63+
end
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
3+
require 'ipaddr'
4+
5+
Puppet::Functions.create_function(:"ipcalc::less_than") do
6+
# Compare two IP addresses and return a Boolean indicating whether the first operand is less than
7+
# the second. Contextually, this makes the most sense when using Puppet's dotted function
8+
# notation, because the function name sits where a comparison operator would normally go. This
9+
# will work with either plain addresses or CIDR-notation addresses; if a plain address is
10+
# supplied, a full-width netmask is assumed. That is, '127.0.0.1' and '127.0.0.1/32' are
11+
# equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask IS assessed in the
12+
# comparison, but is only relevant if the remainder of the address is exactly equivalent down to
13+
# the start of the netmask. If addresses of mixed families are given, an error is raised and the
14+
# catalog will fail. There is no obvious implied relation between the two, and even the
15+
# underlying Ruby implementation will refuse to make comparisons between addresses of different
16+
# families.
17+
# @param first
18+
# The first address for comparison. If no netmask is given, full-width is assumed.
19+
# @param second
20+
# The second address for comparison. If no netmask is given, full-width is assumed.
21+
# @return [Boolean]
22+
# `true` if the first operand is less than the second, or `false` otherwise.
23+
# @example Use with dotted function notation
24+
# $first_ip.less_than($second_ip) ? {
25+
# true => { 'We did it, go team, first operand is smaller' },
26+
# default => { 'I\'m afraid I have some bad news...' },
27+
# }
28+
dispatch :less_than do
29+
param 'Stdlib::IP::Address::V4', :first
30+
param 'Stdlib::IP::Address::V4', :second
31+
return_type 'Boolean'
32+
end
33+
34+
dispatch :less_than do
35+
param 'Stdlib::IP::Address::V6', :first
36+
param 'Stdlib::IP::Address::V6', :second
37+
return_type 'Boolean'
38+
end
39+
40+
# Refuse to handle addresses in different families. There isn't a clear precedence between the
41+
# two families; if you need to establish an order, you can use type comparison to handle this in
42+
# your Puppet code.
43+
argument_mismatch :mixed_families do
44+
param 'Stdlib::IP::Address::V4', :first
45+
param 'Stdlib::IP::Address::V6', :second
46+
end
47+
48+
argument_mismatch :mixed_families do
49+
param 'Stdlib::IP::Address::V6', :first
50+
param 'Stdlib::IP::Address::V4', :second
51+
end
52+
53+
def less_than(first, second)
54+
first_addr = IPAddr.new(first)
55+
second_addr = IPAddr.new(second)
56+
57+
first_addr < second_addr
58+
end
59+
60+
def mixed_families(*)
61+
'both addresses must be in the same family'
62+
end
63+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'ipcalc::equal_or_greater_than' do
6+
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(false) }
7+
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(true) }
8+
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.1').and_return(true) }
9+
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(false) }
10+
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(true) }
11+
it { is_expected.to run.with_params('fe80::1', 'fe80::1').and_return(true) }
12+
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
13+
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_raise_error(StandardError) }
14+
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_raise_error(StandardError) }
15+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'ipcalc::equal_or_less_than' do
6+
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(true) }
7+
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(false) }
8+
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.1').and_return(true) }
9+
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(true) }
10+
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(false) }
11+
it { is_expected.to run.with_params('fe80::1', 'fe80::1').and_return(true) }
12+
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
13+
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_raise_error(StandardError) }
14+
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_raise_error(StandardError) }
15+
end

spec/functions/equal_to_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'ipcalc::equal_to' do
6+
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(false) }
7+
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(false) }
8+
# Zero-padded values are rejected by type validation and by the underlying Ruby implementation
9+
it { is_expected.to run.with_params('127.00.0.1', '127.0.0.1').and_raise_error(StandardError) }
10+
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.1').and_return(true) }
11+
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(false) }
12+
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(false) }
13+
it { is_expected.to run.with_params('fe80:0::001', 'fe80::1').and_return(true) }
14+
it { is_expected.to run.with_params('fe80::1', 'fe80::1').and_return(true) }
15+
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
16+
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_return(false) }
17+
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_return(false) }
18+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'ipcalc::greater_than' do
6+
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(false) }
7+
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(true) }
8+
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(false) }
9+
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(true) }
10+
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
11+
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_raise_error(StandardError) }
12+
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_raise_error(StandardError) }
13+
end

0 commit comments

Comments
 (0)