Skip to content
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

Add EquivalentArrayNoDatatypes filter #254

Merged
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
23 changes: 21 additions & 2 deletions doc/advanced-filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Here is the list of available filters and an explanation of each:

#### Description

When the `AbsentFile` filter is enabled, if any file is `ensure => absent` in the *new* catalog, then changes to any other parameters will be suppressed.
When the `AbsentFile` filter is enabled, if any file is `ensure => absent` in the _new_ catalog, then changes to any other parameters will be suppressed.

Consider that a file resource is declared as follows in two catalogs:

Expand Down Expand Up @@ -71,6 +71,25 @@ Wouldn't it be nice if the meaningless information didn't appear, and all you sa
+ absent
```

## Equivalent Array (not considering datatypes)

#### Usage

```
--filters EquivalentArrayNoDatatypes
```

#### Description

In an array, ignore changes where the old and new arrays are "equivalent" as described below. This is useful when octocatalog-diff is comparing changes between a catalog with stringified values and a catalog with non-stringified values.

The following are considered equivalent when this filter is engaged:

- Stringified integers (`[0, 1]` and `['0', '1']`)
- Stringified floats (`[0.0, 1.0]` and `['0.0', '1.0']`)
- Numerically-equal integers and floats (`[0, 1]` and `[0.0, 1.0]`)
- Symbols and corresponding strings (`[:foo, :bar]` and `[':foo', ':bar']`)

## JSON

#### Usage
Expand Down Expand Up @@ -105,7 +124,7 @@ New: { "notify": [ "Service[foo]" ] }
This filter will suppress differences for the value of a parameter when:

- The value in one catalog is an object, AND
- The value in the other catalog is an array containing *only* that same object
- The value in the other catalog is an array containing _only_ that same object

## YAML

Expand Down
3 changes: 2 additions & 1 deletion lib/octocatalog-diff/catalog-diff/filter.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require_relative '../api/v1/diff'
require_relative 'filter/absent_file'
require_relative 'filter/compilation_dir'
require_relative 'filter/equivalent_array_no_datatypes'
require_relative 'filter/json'
require_relative 'filter/single_item_array'
require_relative 'filter/yaml'
Expand All @@ -14,7 +15,7 @@ class Filter
attr_accessor :logger

# List the available filters here (by class name) for use in the validator method.
AVAILABLE_FILTERS = %w(AbsentFile CompilationDir JSON SingleItemArray YAML).freeze
AVAILABLE_FILTERS = %w(AbsentFile CompilationDir EquivalentArrayNoDatatypes JSON SingleItemArray YAML).freeze

# Public: Determine whether a particular filter exists. This can be used to validate
# a user-submitted filter.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

require_relative '../filter'

module OctocatalogDiff
module CatalogDiff
class Filter
# Filter out changes in parameters where the elements of an array are the
# same values but different data types. For example, this would filter out
# the following diffs:
# Exec[some command] =>
# parameters =>
# returns =>
# - ["0", "1"]
# + [0, 1]
class EquivalentArrayNoDatatypes < OctocatalogDiff::CatalogDiff::Filter
# Public: Implement the filter for arrays that have the same elements
# but possibly different data types.
#
# @param diff [OctocatalogDiff::API::V1::Diff] Difference
# @param _options [Hash] Additional options (there are none for this filter)
# @return [Boolean] true if this should be filtered out, false otherwise
def filtered?(diff, _options = {})
# Skip additions or removals - focus only on changes
return false unless diff.change?
old_value = diff.old_value
new_value = diff.new_value

# Skip unless both the old and new values are arrays.
return false unless old_value.is_a?(Array) && new_value.is_a?(Array)

# Avoid generating comparable values if the arrays are a different
# size, because there's no possible way that they are equivalent.
return false unless old_value.size == new_value.size

# Generate and then compare the comparable arrays.
old_value.map { |x| comparable_value(x) } == new_value.map { |x| comparable_value(x) }
end

# Private: Get a more easily comparable value for an array element.
# Integers, floats, and strings that look like integers or floats become
# floats, and symbols are converted to string representation.
#
# @param input [any] Value to convert
# @return [any] "Comparable" value
def comparable_value(input)
# Any string that looks like a number is converted to a float.
if input.is_a?(String) && input =~ /\A-?(([0-9]*\.[0-9]+)|([0-9]+))\z/
return input.to_f
end

# Any number is converted to a float
return input.to_f if input.is_a?(Integer) || input.is_a?(Float)

# Symbols are converted to ":xxx" strings.
return ":#{input}" if input.is_a?(Symbol)

# Everything else is unconverted.
input
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"document_type": "Catalog",
"data": {
"tags": [
"settings"
],
"name": "my.rspec.node",
"version": "production",
"environment": "production",
"resources": [
{
"type": "Exec",
"title": "run-my-command 1",
"file": "/environments/production/modules/foo/manifests/init.pp",
"line": 10,
"exported": false,
"parameters": {
"path": "/usr/bin",
"command": "id",
"returns": "0"
}
},
{
"type": "Exec",
"title": "run-my-command 2",
"file": "/environments/production/modules/foo/manifests/init.pp",
"line": 10,
"exported": false,
"parameters": {
"path": "/usr/bin",
"command": "id",
"returns": ["0", "1"]
}
},
{
"type": "Exec",
"title": "run-my-command 3",
"file": "/environments/production/modules/foo/manifests/init.pp",
"line": 10,
"exported": false,
"parameters": {
"path": "/usr/bin",
"command": "id",
"returns": ["0", "1", "2"]
}
}
],
"classes": [
"settings"
]
},
"metadata": {
"api_version": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"document_type": "Catalog",
"data": {
"tags": [
"settings"
],
"name": "my.rspec.node",
"version": "production",
"environment": "production",
"resources": [
{
"type": "Exec",
"title": "run-my-command 1",
"file": "/environments/production/modules/foo/manifests/init.pp",
"line": 10,
"exported": false,
"parameters": {
"path": "/usr/bin",
"command": "id",
"returns": 0
}
},
{
"type": "Exec",
"title": "run-my-command 2",
"file": "/environments/production/modules/foo/manifests/init.pp",
"line": 10,
"exported": false,
"parameters": {
"path": "/usr/bin",
"command": "id",
"returns": [0, 1]
}
},
{
"type": "Exec",
"title": "run-my-command 3",
"file": "/environments/production/modules/foo/manifests/init.pp",
"line": 10,
"exported": false,
"parameters": {
"path": "/usr/bin",
"command": "id",
"returns": [0, 1, 2, 3]
}
}
],
"classes": [
"settings"
]
},
"metadata": {
"api_version": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

require_relative 'integration_helper'

describe 'equivalent array no datatypes filter integration' do
context 'with default behavior' do
before(:all) do
@result = OctocatalogDiff::Integration.integration(
spec_catalog_old: 'filter-equivalent-array-1.json',
spec_catalog_new: 'filter-equivalent-array-2.json'
)
end

it 'should succeed' do
expect(@result[:exitcode]).not_to eq(-1), "Internal error: #{@result[:exception]}\n#{@result[:logs]}"
expect(@result[:exitcode]).to eq(2), "Runtime error: #{@result[:logs]}"
end

it 'should not suppress equivalent-but-for-data-type arrays' do
diffs = OctocatalogDiff::Spec.remove_file_and_line(@result[:diffs])
expect(diffs.size).to eq(3), diffs.inspect
expect(diffs[0][1..3]).to eq(["Exec\frun-my-command 1\fparameters\freturns", '0'.inspect, 0])
expect(diffs[1][1..3]).to eq(["Exec\frun-my-command 2\fparameters\freturns", %w[0 1], [0, 1]])
expect(diffs[2][1..3]).to eq(["Exec\frun-my-command 3\fparameters\freturns", %w[0 1 2], [0, 1, 2, 3]])
end
end

context 'with equivalent array no datatypes filter engaged' do
before(:all) do
@result = OctocatalogDiff::Integration.integration(
spec_catalog_old: 'filter-equivalent-array-1.json',
spec_catalog_new: 'filter-equivalent-array-2.json',
argv: ['--filters', 'EquivalentArrayNoDatatypes']
)
end

it 'should succeed' do
expect(@result[:exitcode]).not_to eq(-1), "Internal error: #{@result[:exception]}\n#{@result[:logs]}"
expect(@result[:exitcode]).to eq(2), "Runtime error: #{@result[:logs]}"
end

it 'should suppress equivalent-but-for-data-type arrays' do
diffs = OctocatalogDiff::Spec.remove_file_and_line(@result[:diffs])
expect(diffs.size).to eq(2), diffs.inspect
# '0' => 0 is not suppressed because it's not an array
expect(diffs[0][1..3]).to eq(["Exec\frun-my-command 1\fparameters\freturns", '0'.inspect, 0])
# %w[0 1] => [0, 1] is suppressed
# %w[0 1 2] => [0, 1, 2, 3] is not suppressed because it's not equivalent
expect(diffs[1][1..3]).to eq(["Exec\frun-my-command 3\fparameters\freturns", %w[0 1 2], [0, 1, 2, 3]])
end
end
end
Loading