foreman_ansible 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of foreman_ansible might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/app/services/foreman_ansible/fact_importer.rb +21 -11
- data/app/services/foreman_ansible/fact_parser.rb +28 -15
- data/app/services/foreman_ansible/fact_sparser.rb +19 -8
- data/lib/foreman_ansible.rb +1 -0
- data/lib/foreman_ansible/engine.rb +1 -0
- data/lib/foreman_ansible/version.rb +4 -1
- data/test/unit/fact_parser_test.rb +56 -0
- data/test/unit/fact_sparser_test.rb +23 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 651683d29fb40dbd94af8c41c17aac05d338da63
|
4
|
+
data.tar.gz: 08b2873008325af3d6108f2cebe5cfef7e3a3198
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e2d6381c31429e26137a351e8125f7475a9328f3fa58f46b5b59d60f0590f7ff4f85be8d72b11bc822c4db3711e77497c49b30c1395745374787a73a0808a78
|
7
|
+
data.tar.gz: 8e6cdf7a6c43694f79a1587fd10cc30b4d74543ea329dff02628d2c03e0aa7a9b9272abf1c353e1c52b4a7eef1a5c769d6509d5f7eb0ed379677d487dd6d8f93
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Foreman Ansible :arrow_forward:
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
## Features
|
4
|
+
* Import facts
|
5
|
+
* Monitor playbook and ansible runs runtime
|
6
|
+
* Sends Ansible reports to Foreman that contain what changed on your system after an ansible run.
|
6
7
|
|
7
8
|
## Basic usage
|
8
9
|
|
@@ -1,4 +1,7 @@
|
|
1
1
|
module ForemanAnsible
|
2
|
+
# Override methods from Foreman app/services/fact_importer so that Ansible
|
3
|
+
# facts are recognized in Foreman as ForemanAnsible facts. It supports
|
4
|
+
# nested facts.
|
2
5
|
class FactImporter < ::FactImporter
|
3
6
|
def fact_name_class
|
4
7
|
ForemanAnsible::FactName
|
@@ -16,17 +19,16 @@ module ForemanAnsible
|
|
16
19
|
def add_new_facts
|
17
20
|
@counters[:added] = 0
|
18
21
|
add_missing_facts(FactSparser.unsparse(@original_facts))
|
19
|
-
logger.debug(
|
22
|
+
logger.debug(
|
23
|
+
"Merging facts for '#{host}': added #{@counters[:added]} facts")
|
20
24
|
end
|
21
25
|
|
22
26
|
def add_missing_facts(imported_facts, parent = nil, prefix = '')
|
23
|
-
imported_facts.select! { |
|
27
|
+
imported_facts.select! { |_fact_name, fact_value| !fact_value.nil? }
|
24
28
|
|
25
29
|
imported_facts.each do |imported_name, imported_value|
|
26
30
|
fact_fqn = fact_fqn(imported_name, prefix)
|
27
|
-
|
28
|
-
fact_name = find_fact_name(fact_fqn, parent)
|
29
|
-
|
31
|
+
fact_name = find_or_create_fact_name(fact_fqn, parent, imported_value)
|
30
32
|
add_fact_value(imported_value, fact_name)
|
31
33
|
add_compose_fact(imported_value, fact_name, fact_fqn)
|
32
34
|
end
|
@@ -44,12 +46,14 @@ module ForemanAnsible
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def missing_facts
|
47
|
-
@missing_facts ||=
|
49
|
+
@missing_facts ||= facts.keys +
|
50
|
+
FactSparser.sparse(@original_facts).keys -
|
51
|
+
db_facts.keys
|
48
52
|
end
|
49
53
|
|
50
54
|
# Returns pairs [id, fact_name]
|
51
55
|
def fact_names
|
52
|
-
@fact_names ||= fact_name_class.
|
56
|
+
@fact_names ||= fact_name_class.group('name').maximum(:id)
|
53
57
|
end
|
54
58
|
|
55
59
|
# Fact fully qualified name contains an unambiguous name for a fact
|
@@ -58,18 +62,24 @@ module ForemanAnsible
|
|
58
62
|
prefix.empty? ? name : prefix + FactName::SEPARATOR + name
|
59
63
|
end
|
60
64
|
|
61
|
-
def
|
62
|
-
return
|
65
|
+
def find_or_create_fact_name(name, parent, fact_value)
|
66
|
+
return fact_name_class.find(fact_names[name]) if fact_names[name].present?
|
63
67
|
fact_name_class.create!(:name => name,
|
64
68
|
:parent => parent,
|
65
|
-
:compose => compose)
|
69
|
+
:compose => compose?(fact_value))
|
66
70
|
end
|
67
71
|
|
68
72
|
def add_fact_value(value, fact_name)
|
73
|
+
return unless missing_facts.include?(fact_name.name)
|
69
74
|
method = host.new_record? ? :build : :create!
|
70
|
-
|
75
|
+
#value = nil if compose?(value)
|
76
|
+
host.fact_values.send(method, :value => value.to_s, :fact_name => fact_name)
|
71
77
|
@counters[:added] += 1
|
72
78
|
end
|
73
79
|
|
80
|
+
def compose?(fact_value)
|
81
|
+
fact_value.is_a?(Hash) ||
|
82
|
+
fact_value.is_a?(Array) && fact_value.first.is_a?(Hash)
|
83
|
+
end
|
74
84
|
end
|
75
85
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
module ForemanAnsible
|
2
|
+
# Override methods from Foreman app/services/fact_parser so that facts
|
3
|
+
# representing host properties are understood when they come from Ansible.
|
2
4
|
class FactParser < ::FactParser
|
3
5
|
attr_reader :facts
|
4
6
|
|
@@ -20,17 +22,13 @@ module ForemanAnsible
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def model
|
23
|
-
name
|
24
|
-
|
25
|
-
facts[:facter_productname] ||
|
26
|
-
facts[:facter_model]
|
25
|
+
name = detect_fact([:ansible_product_name, :facter_virtual,
|
26
|
+
:facter_productname, :facter_model])
|
27
27
|
Model.where(:name => name.strip).first_or_create unless name.blank?
|
28
28
|
end
|
29
29
|
|
30
30
|
def domain
|
31
|
-
name =
|
32
|
-
facts[:facter_domain] ||
|
33
|
-
facts[:ohai_domain]
|
31
|
+
name = detect_fact([:ansible_domain, :facter_domain, :ohai_domain])
|
34
32
|
Domain.where(:name => name).first_or_create unless name.blank?
|
35
33
|
end
|
36
34
|
|
@@ -38,18 +36,24 @@ module ForemanAnsible
|
|
38
36
|
true
|
39
37
|
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
# Move ansible's default interface first in the list of interfaces since
|
40
|
+
# Foreman picks the first one that is usable. If ansible has no
|
41
|
+
# preference otherwise at least sort the list.
|
42
|
+
#
|
43
|
+
# This method overrides app/services/fact_parser.rb on Foreman and returns
|
44
|
+
# an array of interface names, ['eth0', 'wlan1', etc...]
|
45
|
+
def get_interfaces # rubocop:disable Style/AccessorMethodName
|
45
46
|
pref = facts[:ansible_default_ipv4] &&
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
facts[:ansible_default_ipv4]['interface']
|
48
|
+
if pref.present?
|
49
|
+
(facts[:ansible_interfaces] - [pref]).unshift(pref)
|
50
|
+
else
|
51
|
+
facts[:ansible_interfaces].sort
|
52
|
+
end
|
49
53
|
end
|
50
54
|
|
51
55
|
def get_facts_for_interface(interface)
|
52
|
-
interface.
|
56
|
+
interface.tr!('-', '_') # virbr1-nic -> virbr1_nic
|
53
57
|
interface_facts = facts[:"ansible_#{interface}"]
|
54
58
|
ipaddress = ip_from_interface(interface)
|
55
59
|
HashWithIndifferentAccess[interface_facts.merge(:ipaddress => ipaddress)]
|
@@ -87,5 +91,14 @@ module ForemanAnsible
|
|
87
91
|
def os_description
|
88
92
|
facts[:ansible_lsb] && facts[:ansible_lsb]['description']
|
89
93
|
end
|
94
|
+
|
95
|
+
# Returns first non-empty fact. Needed to check for empty strings.
|
96
|
+
def detect_fact(fact_names)
|
97
|
+
facts[
|
98
|
+
fact_names.detect do |fact_name|
|
99
|
+
facts[fact_name].present?
|
100
|
+
end
|
101
|
+
]
|
102
|
+
end
|
90
103
|
end
|
91
104
|
end
|
@@ -1,21 +1,32 @@
|
|
1
1
|
module ForemanAnsible
|
2
|
+
# See sparse and unsparse documentation
|
2
3
|
class FactSparser
|
3
4
|
class << self
|
4
|
-
|
5
|
+
# Sparses facts, so that it converts a facts hash
|
6
|
+
# { operatingsystem : { major: 20, name : 'fedora' }
|
7
|
+
# into
|
8
|
+
# { operatingsystem::major: 20,
|
9
|
+
# operatingsystem::name: 'fedora' }
|
10
|
+
def sparse(hash, options = {})
|
5
11
|
hash.map do |k, v|
|
6
12
|
prefix = options.fetch(:prefix, []) + [k]
|
7
13
|
next sparse(v, options.merge(:prefix => prefix)) if v.is_a? Hash
|
8
14
|
{ prefix.join(options.fetch(:separator, FactName::SEPARATOR)) => v }
|
9
|
-
end.reduce(:merge) ||
|
15
|
+
end.reduce(:merge) || {}
|
10
16
|
end
|
11
17
|
|
12
|
-
|
13
|
-
|
14
|
-
|
18
|
+
# Unsparses facts, so that it converts a hash with facts
|
19
|
+
# { operatingsystem::major: 20,
|
20
|
+
# operatingsystem::name: 'fedora' }
|
21
|
+
# into
|
22
|
+
# { operatingsystem : { major: 20, name: 'fedora' } }
|
23
|
+
def unsparse(facts_hash)
|
24
|
+
ret = {}
|
25
|
+
sparse(facts_hash).each do |full_name, value|
|
15
26
|
current = ret
|
16
|
-
|
17
|
-
current = (current[
|
18
|
-
current[
|
27
|
+
fact_name = full_name.to_s.split(FactName::SEPARATOR)
|
28
|
+
current = (current[fact_name.shift] ||= {}) until fact_name.size <= 1
|
29
|
+
current[fact_name.first] = value
|
19
30
|
end
|
20
31
|
ret
|
21
32
|
end
|
data/lib/foreman_ansible.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
module ForemanAnsible
|
4
|
+
class FactParserTest < ActiveSupport::TestCase
|
5
|
+
setup do
|
6
|
+
facts_json = HashWithIndifferentAccess.new(JSON.parse(sample_facts_file))
|
7
|
+
@facts_importer = ForemanAnsible::FactParser.new(facts_json)
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'finds facter domain even if ansible_domain is empty' do
|
11
|
+
expect_where(Domain, @facts_importer.facts[:facter_domain])
|
12
|
+
@facts_importer.domain
|
13
|
+
end
|
14
|
+
|
15
|
+
test 'finds model' do
|
16
|
+
expect_where(Model, @facts_importer.facts[:ansible_product_name])
|
17
|
+
@facts_importer.model
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'finds architecture' do
|
21
|
+
expect_where(Architecture, @facts_importer.facts[:ansible_architecture])
|
22
|
+
@facts_importer.architecture
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'does not set environment' do
|
26
|
+
refute @facts_importer.environment
|
27
|
+
end
|
28
|
+
|
29
|
+
test 'creates operatingsystem from operating system options' do
|
30
|
+
sample_mock = mock
|
31
|
+
major_fact = @facts_importer.facts['ansible_distribution_major_version']
|
32
|
+
_, minor_fact = @facts_importer.
|
33
|
+
facts['ansible_distribution_version'].split('.')
|
34
|
+
Operatingsystem.expects(:where).
|
35
|
+
with(:name => @facts_importer.facts['ansible_distribution'],
|
36
|
+
:major => major_fact, :minor => minor_fact || '').
|
37
|
+
returns(sample_mock)
|
38
|
+
sample_mock.expects(:first)
|
39
|
+
@facts_importer.operatingsystem
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def expect_where(model, fact_name)
|
45
|
+
sample_mock = mock
|
46
|
+
model.expects(:where).
|
47
|
+
with(:name => fact_name).
|
48
|
+
returns(sample_mock)
|
49
|
+
sample_mock.expects(:first_or_create)
|
50
|
+
end
|
51
|
+
|
52
|
+
def sample_facts_file
|
53
|
+
File.read(File.join(Engine.root, 'test', 'fixtures', 'sample_facts.json'))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
module ForemanAnsible
|
4
|
+
class FactSparserTest < ActiveSupport::TestCase
|
5
|
+
setup do
|
6
|
+
@original_os_facts = { 'operatingsystem' => { 'major' => 20, 'minor' => 1,
|
7
|
+
'name' => 'Fedora' } }
|
8
|
+
@sparsed_os_facts = { 'operatingsystem::major' => 20,
|
9
|
+
'operatingsystem::minor' => 1,
|
10
|
+
'operatingsystem::name' => 'Fedora'}
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'sparses simple hash' do
|
14
|
+
assert_equal @sparsed_os_facts,
|
15
|
+
ForemanAnsible::FactSparser.sparse(@original_os_facts)
|
16
|
+
end
|
17
|
+
|
18
|
+
test 'unsparse simple hash' do
|
19
|
+
assert_equal @original_os_facts,
|
20
|
+
ForemanAnsible::FactSparser.unsparse(@sparsed_os_facts)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_ansible
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Lobato Garcia
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubocop
|
@@ -48,6 +48,8 @@ files:
|
|
48
48
|
- locale/gemspec.rb
|
49
49
|
- test/fixtures/sample_facts.json
|
50
50
|
- test/test_plugin_helper.rb
|
51
|
+
- test/unit/fact_parser_test.rb
|
52
|
+
- test/unit/fact_sparser_test.rb
|
51
53
|
homepage: https://github.com/theforeman/foreman_ansible
|
52
54
|
licenses:
|
53
55
|
- GPL-3
|
@@ -75,3 +77,5 @@ summary: Ansible integration with Foreman (theforeman.org)
|
|
75
77
|
test_files:
|
76
78
|
- test/fixtures/sample_facts.json
|
77
79
|
- test/test_plugin_helper.rb
|
80
|
+
- test/unit/fact_parser_test.rb
|
81
|
+
- test/unit/fact_sparser_test.rb
|