Skip to content

Commit

Permalink
INFRA-3218 Initial commit of boolean comparators
Browse files Browse the repository at this point in the history
Initial commit of greater_than, equal_or_greater_than, equal_to,
equal_or_less_than, and less_than comparator functions and their unit
tests.
  • Loading branch information
greatflyingsteve committed Apr 9, 2024
1 parent ed615a9 commit 28f5ed7
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 0 deletions.
64 changes: 64 additions & 0 deletions lib/puppet/functions/ipcalc/equal_or_greater_than.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

require 'ipaddr'

Puppet::Functions.create_function(:"ipcalc::equal_or_greater_than") do
# Compare two IP addresses and return a Boolean indicating whether the first operand is equal to
# (or possibly greater than) the second. Contextually, this makes the most sense when using
# Puppet's dotted function notation, because the function name sits where a comparison operator
# would normally go. This will work with either plain addresses or CIDR-notation addresses; if a
# plain address is supplied, a full-width netmask is assumed. That is, '127.0.0.1' and
# '127.0.0.1/32' are equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask
# IS assessed in the comparison, but is only relevant if the remainder of the address is exactly
# equivalent down to the start of the netmask. If addresses of mixed families are given, an error
# is raised and the catalog will fail. There is no obvious implied relation between the two, and
# even the underlying Ruby implementation will refuse to make comparisons between addresses of
# different families.
# @param first
# The first address for comparison. If no netmask is given, full-width is assumed.
# @param second
# The second address for comparison. If no netmask is given, full-width is assumed.
# @return [Boolean]
# `true` if the first operand is equal to (or greater than) the second, or `false` if it's less
# than the second.
# @example Use with dotted function notation
# $first_ip.equal_or_greater_than($second_ip) ? {
# true => { 'We did it, go team, first operand is equal or bigger' },
# default => { 'I\'m afraid I have some bad news...' },
# }
dispatch :equal_or_greater_than do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V4', :second
return_type 'Boolean'
end

dispatch :equal_or_greater_than do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V6', :second
return_type 'Boolean'
end

# Refuse to handle addresses in different families. There isn't a clear precedence between the
# two families; if you need to establish an order, you can use type comparison to handle this in
# your Puppet code.
argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V6', :second
end

argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V4', :second
end

def equal_or_greater_than(first, second)
first_addr = IPAddr.new(first)
second_addr = IPAddr.new(second)

first_addr >= second_addr
end

def mixed_families(*)
'both addresses must be in the same family'
end
end
64 changes: 64 additions & 0 deletions lib/puppet/functions/ipcalc/equal_or_less_than.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

require 'ipaddr'

Puppet::Functions.create_function(:"ipcalc::equal_or_less_than") do
# Compare two IP addresses and return a Boolean indicating whether the first operand is equal to
# (or possibly less than) the second. Contextually, this makes the most sense when using
# Puppet's dotted function notation, because the function name sits where a comparison operator
# would normally go. This will work with either plain addresses or CIDR-notation addresses; if a
# plain address is supplied, a full-width netmask is assumed. That is, '127.0.0.1' and
# '127.0.0.1/32' are equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask
# IS assessed in the comparison, but is only relevant if the remainder of the address is exactly
# equivalent down to the start of the netmask. If addresses of mixed families are given, an error
# is raised and the catalog will fail. There is no obvious implied relation between the two, and
# even the underlying Ruby implementation will refuse to make comparisons between addresses of
# different families.
# @param first
# The first address for comparison. If no netmask is given, full-width is assumed.
# @param second
# The second address for comparison. If no netmask is given, full-width is assumed.
# @return [Boolean]
# `true` if the first operand is equal to (or less than) the second, or `false` if it's greater
# than the second.
# @example Use with dotted function notation
# $first_ip.equal_or_less_than($second_ip) ? {
# true => { 'We did it, go team, first operand is equal or smaller' },
# default => { 'I\'m afraid I have some bad news...' },
# }
dispatch :equal_or_less_than do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V4', :second
return_type 'Boolean'
end

dispatch :equal_or_less_than do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V6', :second
return_type 'Boolean'
end

# Refuse to handle addresses in different families. There isn't a clear precedence between the
# two families; if you need to establish an order, you can use type comparison to handle this in
# your Puppet code.
argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V6', :second
end

argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V4', :second
end

def equal_or_less_than(first, second)
first_addr = IPAddr.new(first)
second_addr = IPAddr.new(second)

first_addr <= second_addr
end

def mixed_families(*)
'both addresses must be in the same family'
end
end
66 changes: 66 additions & 0 deletions lib/puppet/functions/ipcalc/equal_to.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

require 'ipaddr'

Puppet::Functions.create_function(:"ipcalc::equal_to") do
# Compare two IP addresses and return a Boolean indicating whether the first operand is equal to
# the second. This differs from comparing the address Strings in that it compares native 32-bit
# (or for IPv6, 128-bit) binary values, regardless of their human-readable representations. For
# IPv4 addresses this is usually not much of a problem, as representations are simpler and
# zero-padded values are relatively rare (enough that Stdlib::IP::Address::V4 filters them out as
# invalid). For IPv6 addresses, however, IPv6's collapsed notation can be a very serious problem,
# and this function allows IPv6 addresses with un-collapsed runs of zeroes, leading zeroes, etc.
# to be compared meaningfully without needing to worry about normalizing their human-readable
# representations first.
#
# Contextually, this function makes the most sense when using Puppet's dotted function notation,
# because the function name sits where a comparison operator would normally go. This will work
# with either plain addresses or CIDR-notation addresses; if a plain address is supplied, a
# full-width netmask is assumed. That is, '127.0.0.1' and '127.0.0.1/32' are equivalent, just as
# 'fe80::1' and 'fe80::1/128' are equivalent. The netmask IS assessed in the comparison, but is
# only relevant if the remainder of the address is exactly equivalent down to the start of the
# netmask. Addresses of mixed families are always considered unequal.
# @param first
# The first address for comparison. If no netmask is given, full-width is assumed.
# @param second
# The second address for comparison. If no netmask is given, full-width is assumed.
# @return [Boolean]
# `true` if the operands are equal, or `false` otherwise.
# @example Use with dotted function notation
# $first_ip.equal_to($second_ip) ? {
# true => { 'We did it, go team, the operands are equal' },
# default => { 'I\'m afraid I have some bad news...' },
# }
dispatch :equal_to do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V4', :second
return_type 'Boolean'
end

dispatch :equal_to do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V6', :second
return_type 'Boolean'
end

dispatch :mixed_families do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V6', :second
end

dispatch :mixed_families do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V4', :second
end

def equal_to(first, second)
first_addr = IPAddr.new(first)
second_addr = IPAddr.new(second)

first_addr == second_addr
end

def mixed_families(*)
false
end
end
63 changes: 63 additions & 0 deletions lib/puppet/functions/ipcalc/greater_than.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'ipaddr'

Puppet::Functions.create_function(:"ipcalc::greater_than") do
# Compare two IP addresses and return a Boolean indicating whether the first operand is greater
# than the second. Contextually, this makes the most sense when using Puppet's dotted function
# notation, because the function name sits where a comparison operator would normally go. This
# will work with either plain addresses or CIDR-notation addresses; if a plain address is
# supplied, a full-width netmask is assumed. That is, '127.0.0.1' and '127.0.0.1/32' are
# equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask IS assessed in the
# comparison, but is only relevant if the remainder of the address is exactly equivalent down to
# the start of the netmask. If addresses of mixed families are given, an error is raised and the
# catalog will fail. There is no obvious implied relation between the two, and even the
# underlying Ruby implementation will refuse to make comparisons between addresses of different
# families.
# @param first
# The first address for comparison. If no netmask is given, full-width is assumed.
# @param second
# The second address for comparison. If no netmask is given, full-width is assumed.
# @return [Boolean]
# `true` if the first operand is greater than the second, or `false` otherwise.
# @example Use with dotted function notation
# $first_ip.greater_than($second_ip) ? {
# true => { 'We did it, go team, first operand is bigger' },
# default => { 'I\'m afraid I have some bad news...' },
# }
dispatch :greater_than do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V4', :second
return_type 'Boolean'
end

dispatch :greater_than do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V6', :second
return_type 'Boolean'
end

# Refuse to handle addresses in different families. There isn't a clear precedence between the
# two families; if you need to establish an order, you can use type comparison to handle this in
# your Puppet code.
argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V6', :second
end

argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V4', :second
end

def greater_than(first, second)
first_addr = IPAddr.new(first)
second_addr = IPAddr.new(second)

first_addr > second_addr
end

def mixed_families(*)
'both addresses must be in the same family'
end
end
63 changes: 63 additions & 0 deletions lib/puppet/functions/ipcalc/less_than.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'ipaddr'

Puppet::Functions.create_function(:"ipcalc::less_than") do
# Compare two IP addresses and return a Boolean indicating whether the first operand is less than
# the second. Contextually, this makes the most sense when using Puppet's dotted function
# notation, because the function name sits where a comparison operator would normally go. This
# will work with either plain addresses or CIDR-notation addresses; if a plain address is
# supplied, a full-width netmask is assumed. That is, '127.0.0.1' and '127.0.0.1/32' are
# equivalent, just as 'fe80::1' and 'fe80::1/128' are equivalent. The netmask IS assessed in the
# comparison, but is only relevant if the remainder of the address is exactly equivalent down to
# the start of the netmask. If addresses of mixed families are given, an error is raised and the
# catalog will fail. There is no obvious implied relation between the two, and even the
# underlying Ruby implementation will refuse to make comparisons between addresses of different
# families.
# @param first
# The first address for comparison. If no netmask is given, full-width is assumed.
# @param second
# The second address for comparison. If no netmask is given, full-width is assumed.
# @return [Boolean]
# `true` if the first operand is less than the second, or `false` otherwise.
# @example Use with dotted function notation
# $first_ip.less_than($second_ip) ? {
# true => { 'We did it, go team, first operand is smaller' },
# default => { 'I\'m afraid I have some bad news...' },
# }
dispatch :less_than do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V4', :second
return_type 'Boolean'
end

dispatch :less_than do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V6', :second
return_type 'Boolean'
end

# Refuse to handle addresses in different families. There isn't a clear precedence between the
# two families; if you need to establish an order, you can use type comparison to handle this in
# your Puppet code.
argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V4', :first
param 'Stdlib::IP::Address::V6', :second
end

argument_mismatch :mixed_families do
param 'Stdlib::IP::Address::V6', :first
param 'Stdlib::IP::Address::V4', :second
end

def less_than(first, second)
first_addr = IPAddr.new(first)
second_addr = IPAddr.new(second)

first_addr < second_addr
end

def mixed_families(*)
'both addresses must be in the same family'
end
end
15 changes: 15 additions & 0 deletions spec/functions/equal_or_greater_than_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'ipcalc::equal_or_greater_than' do
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(false) }
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(true) }
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.1').and_return(true) }
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(false) }
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(true) }
it { is_expected.to run.with_params('fe80::1', 'fe80::1').and_return(true) }
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_raise_error(StandardError) }
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_raise_error(StandardError) }
end
15 changes: 15 additions & 0 deletions spec/functions/equal_or_less_than_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'ipcalc::equal_or_less_than' do
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(true) }
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(false) }
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.1').and_return(true) }
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(true) }
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(false) }
it { is_expected.to run.with_params('fe80::1', 'fe80::1').and_return(true) }
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_raise_error(StandardError) }
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_raise_error(StandardError) }
end
18 changes: 18 additions & 0 deletions spec/functions/equal_to_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'ipcalc::equal_to' do
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(false) }
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(false) }
# Zero-padded values are rejected by type validation and by the underlying Ruby implementation
it { is_expected.to run.with_params('127.00.0.1', '127.0.0.1').and_raise_error(StandardError) }
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.1').and_return(true) }
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(false) }
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(false) }
it { is_expected.to run.with_params('fe80:0::001', 'fe80::1').and_return(true) }
it { is_expected.to run.with_params('fe80::1', 'fe80::1').and_return(true) }
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_return(false) }
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_return(false) }
end
13 changes: 13 additions & 0 deletions spec/functions/greater_than_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'ipcalc::greater_than' do
it { is_expected.to run.with_params('127.0.0.1', '127.0.0.2').and_return(false) }
it { is_expected.to run.with_params('127.0.0.2', '127.0.0.1').and_return(true) }
it { is_expected.to run.with_params('fe80::1', 'fe80::2').and_return(false) }
it { is_expected.to run.with_params('fe80::2', 'fe80::1').and_return(true) }
it { is_expected.to run.with_params(nil).and_raise_error(StandardError) }
it { is_expected.to run.with_params('127.0.0.1', 'fe80::1').and_raise_error(StandardError) }
it { is_expected.to run.with_params('fe80::1', '127.0.0.1').and_raise_error(StandardError) }
end
Loading

0 comments on commit 28f5ed7

Please sign in to comment.