foreman_ansible 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|