Skip to content

Commit 0ad28ea

Browse files
authored
Merge pull request #66 from github/kpaulisse-fact-override-refactor
Refactor the fact override code for the API
2 parents 0ca8b6c + c39fa48 commit 0ad28ea

11 files changed

Lines changed: 249 additions & 157 deletions

File tree

doc/dev/api/v1.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ The `OctocatalogDiff::API::V1::Catalog` object represents a compiled catalog and
3333
The `OctocatalogDiff::API::V1::Diff` object represents a difference between two catalogs and supports several methods to get information about the difference.
3434

3535
[Read more about the `OctocatalogDiff::API::V1::Diff` object](/doc/dev/api/v1/objects/diff.md)
36+
37+
#### OctocatalogDiff::API::V1::FactOverride
38+
39+
The `OctocatalogDiff::API::V1::FactOverride` object represents a user-supplied fact that will be used when compiling a catalog.
40+
41+
[Read more about the `OctocatalogDiff::API::V1::FactOverride` object](/doc/dev/api/v1/objects/fact_override.md)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# octocatalog-diff v1 API documentation: OctocatalogDiff::API::V1::FactOverride
2+
3+
## Overview
4+
5+
`OctocatalogDiff::API::V1::FactOverride` is an object that represents a user-supplied fact that will be used when compiling a catalog.
6+
7+
## Constructor
8+
9+
#### `#new(<Hash>)`
10+
11+
The hash must contain the following keys:
12+
13+
- `:fact_name` (String) - The name of the fact (e.g. `operatingsystem` or `ipaddress`)
14+
- `:value` (?) - The value of the fact
15+
16+
## Methods
17+
18+
#### `#fact_name` (String)
19+
20+
Returns the name of the fact as supplied in the constructor.
21+
22+
#### `#value` (?)
23+
24+
Returns the value of the fact as supplied in the constructor.

lib/octocatalog-diff/api/v1.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require_relative 'v1/catalog-diff'
66
require_relative 'v1/config'
77
require_relative 'v1/diff'
8+
require_relative 'v1/fact_override'
89

910
module OctocatalogDiff
1011
module API
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
require 'json'
4+
5+
module OctocatalogDiff
6+
module API
7+
module V1
8+
# Sets up the override of a fact during catalog compilation.
9+
class FactOverride
10+
# Accessors
11+
attr_reader :fact_name, :value
12+
13+
# Constructor: Accepts a key and value.
14+
# @param input [Hash] Must contain :fact_name and :value
15+
def initialize(input)
16+
@fact_name = input.fetch(:fact_name)
17+
@value = input.fetch(:value)
18+
end
19+
20+
# Retrieve the fact_name as #key (essentially an alias)
21+
# @return [String] Value of @fact_name
22+
def key
23+
@fact_name
24+
end
25+
end
26+
end
27+
end
28+
end

lib/octocatalog-diff/cli.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
require_relative 'api/v1'
44
require_relative 'catalog-util/cached_master_directory'
5-
require_relative 'errors'
6-
require_relative 'util/catalogs'
7-
require_relative 'version'
8-
95
require_relative 'cli/diffs'
6+
require_relative 'cli/fact_override'
107
require_relative 'cli/options'
118
require_relative 'cli/printer'
12-
require_relative 'cli/helpers/fact_override'
9+
require_relative 'errors'
10+
require_relative 'util/catalogs'
11+
require_relative 'version'
1312

1413
require 'logger'
1514
require 'socket'
@@ -137,7 +136,7 @@ def self.setup_fact_overrides(options)
137136
next unless o.is_a?(Array)
138137
next unless o.any?
139138
options[key] ||= []
140-
options[key].concat o.map { |x| OctocatalogDiff::Cli::Helpers::FactOverride.new(x) }
139+
options[key].concat o.map { |x| OctocatalogDiff::Cli::FactOverride.fact_override(x) }
141140
end
142141
end
143142

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# frozen_string_literal: true
2+
3+
require 'json'
4+
5+
require_relative '../api/v1'
6+
7+
module OctocatalogDiff
8+
class Cli
9+
# Helper methods for fact override parsing.
10+
class FactOverride
11+
# Public: Given an input string, construct the fact override object(s).
12+
#
13+
# @param input [String] Input in the format: key=(data type)value
14+
# @return [OctocatalogDiff::API::V1::FactOverride] Constructed override object
15+
def self.fact_override(input, key = nil)
16+
# Normally the input will be a string in the format key=(data type)value where the data
17+
# type is optional and the parentheses are literal. Example:
18+
# foo=1 (auto-determine data type - in this case it would be a fixnum)
19+
# foo=(fixnum)1 (will be a fixnum)
20+
# foo=(string)1 (will be '1' the string)
21+
# If input is not a string, we can still construct the object if the key is given.
22+
# That input would come directly from code and not from the command line, since inputs
23+
# from the command line are always strings.
24+
if key.nil? && input.is_a?(String)
25+
unless input.include?('=')
26+
raise ArgumentError, "Fact override '#{input}' is not in 'key=(data type)value' format"
27+
end
28+
key, raw_value = input.strip.split('=', 2)
29+
value = parsed_value(raw_value)
30+
OctocatalogDiff::API::V1::FactOverride.new(fact_name: key, value: value)
31+
elsif key.nil?
32+
message = "Define a key when the input is not a string (#{input.class} => #{input.inspect})"
33+
raise ArgumentError, message
34+
else
35+
OctocatalogDiff::API::V1::FactOverride.new(fact_name: key, value: input)
36+
end
37+
end
38+
39+
# Guess the datatype from a particular input
40+
# @param input [String] Input in string format
41+
# @return [?] Output in appropriate format
42+
def self.parsed_value(input)
43+
# If data type is explicitly given
44+
if input =~ /^\((\w+)\)(.*)$/m
45+
datatype = Regexp.last_match(1)
46+
value = Regexp.last_match(2)
47+
return convert_to_data_type(datatype.downcase, value)
48+
end
49+
50+
# Guess data type
51+
return input.to_i if input =~ /^-?\d+$/
52+
return input.to_f if input =~ /^-?\d*\.\d+$/
53+
return true if input.casecmp('true').zero?
54+
return false if input.casecmp('false').zero?
55+
input
56+
end
57+
58+
# Handle data type that's explicitly given
59+
# @param datatype [String] Data type (as a string)
60+
# @param value [String] Value given
61+
# @return [?] Value converted to specified data type
62+
def self.convert_to_data_type(datatype, value)
63+
return value if datatype == 'string'
64+
return parse_json(value) if datatype == 'json'
65+
return nil if datatype == 'nil'
66+
if datatype == 'fixnum'
67+
return Regexp.last_match(1).to_i if value =~ /^(-?\d+)$/
68+
raise ArgumentError, "Illegal fixnum '#{value}'"
69+
end
70+
if datatype == 'float'
71+
return Regexp.last_match(1).to_f if value =~ /^(-?\d*\.\d+)$/
72+
return Regexp.last_match(1).to_f if value =~ /^(-?\d+)$/
73+
raise ArgumentError, "Illegal float '#{value}'"
74+
end
75+
if datatype == 'boolean'
76+
return true if value.casecmp('true').zero?
77+
return false if value.casecmp('false').zero?
78+
raise ArgumentError, "Illegal boolean '#{value}'"
79+
end
80+
raise ArgumentError, "Unknown data type '#{datatype}'"
81+
end
82+
83+
# Parse JSON value
84+
# @param input [String] Input, hopefully in JSON format
85+
# @return [?] Output data structure
86+
def self.parse_json(input)
87+
JSON.parse(input)
88+
rescue JSON::ParserError => exc
89+
raise JSON::ParserError, "Failed to parse JSON: input=#{input} error=#{exc}"
90+
end
91+
end
92+
end
93+
end

lib/octocatalog-diff/cli/helpers/fact_override.rb

Lines changed: 0 additions & 98 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../spec_helper'
4+
5+
require OctocatalogDiff::Spec.require_path('/api/v1/fact_override')
6+
7+
describe OctocatalogDiff::API::V1::FactOverride do
8+
describe '#new' do
9+
it 'should raise error if fact_name is not supplied' do
10+
expect { described_class.new(value: 'bar') }.to raise_error(KeyError, /fact_name/)
11+
end
12+
13+
it 'should raise error if value is not supplied' do
14+
expect { described_class.new(fact_name: 'bar') }.to raise_error(KeyError, /value/)
15+
end
16+
end
17+
18+
describe '#fact_name' do
19+
it 'should return fact_name' do
20+
testobj = described_class.new(fact_name: 'foo', value: 'bar')
21+
expect(testobj.fact_name).to eq('foo')
22+
end
23+
end
24+
25+
describe '#key' do
26+
it 'should return fact_name' do
27+
testobj = described_class.new(fact_name: 'foo', value: 'bar')
28+
expect(testobj.key).to eq('foo')
29+
end
30+
end
31+
32+
describe '#value' do
33+
it 'should return value' do
34+
testobj = described_class.new(fact_name: 'foo', value: 'bar')
35+
expect(testobj.value).to eq('bar')
36+
end
37+
end
38+
end

spec/octocatalog-diff/tests/catalog-util/builddir_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require_relative '../spec_helper'
44
require OctocatalogDiff::Spec.require_path('/facts')
55
require OctocatalogDiff::Spec.require_path('/catalog-util/builddir')
6-
require OctocatalogDiff::Spec.require_path('/cli/helpers/fact_override')
6+
require OctocatalogDiff::Spec.require_path('/cli/fact_override')
77
require 'socket'
88
require 'yaml'
99

@@ -531,7 +531,7 @@
531531
context 'with fact overrides' do
532532
it 'should create and populate the fact file' do
533533
overrides_raw = %w(ipaddress=10.30.50.70 fizz=buzz jsontest=(json){"foo":"bar"})
534-
overrides = overrides_raw.map { |x| OctocatalogDiff::Cli::Helpers::FactOverride.new(x) }
534+
overrides = overrides_raw.map { |x| OctocatalogDiff::Cli::FactOverride.fact_override(x) }
535535
options = {
536536
basedir: OctocatalogDiff::Spec.fixture_path('repos/default'),
537537
fact_file: OctocatalogDiff::Spec.fixture_path('facts/valid-facts.yaml'),

0 commit comments

Comments
 (0)