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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d9930905b911888013c949cc42e140daed748c4
4
- data.tar.gz: 62f9e18006e75d4be30d9bf263cdb81edbbf4b05
3
+ metadata.gz: 651683d29fb40dbd94af8c41c17aac05d338da63
4
+ data.tar.gz: 08b2873008325af3d6108f2cebe5cfef7e3a3198
5
5
  SHA512:
6
- metadata.gz: 468ae9f121f50e441d2d1317bd87a79f0b547aa4f7fba0c4c6ce18a63e7538813ac02301530757fedaeef7c8c7f95bb54007ad25b5c75771dfc91bd6f8ff4fd5
7
- data.tar.gz: 676edd7606ac3c52c5d195ff2550ac0a610ad7e22b3987fdec4093a79a7bc538b71e9a7f66ef616e53a5d9e0215ced797b0ff94b2f569c16cdb221660eb42cb1
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
- [Foreman](http://theforeman.org) integration with Ansible. For now, it's just importing facts and not meant for general use.
4
-
5
- **Warning - this project hasn't been released yet, and might change significantly. Please don't use in production**
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("Merging facts for '#{host}': added #{@counters[:added]} facts")
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! { |fact_name, fact_value| !fact_value.nil? }
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
- next unless missing_facts.include?(fact_fqn)
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 ||= (facts.keys + FactSparser.sparse(@original_facts).keys) - db_facts.keys
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.maximum(:id, :group => 'name')
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 find_fact_name(name, parent)
62
- return FactName.find(fact_names[name]) if fact_names[name].present?
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
- host.fact_values.send(method, :value => value, :fact_name => fact_name)
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 ||= facts[:ansible_product_name] ||
24
- facts[:facter_virtual] ||
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 = facts[:ansible_domain] ||
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
- def get_interfaces
42
- # Move ansibles default interface first in the list of interfaces since
43
- # Foreman picks the first one that is usable. If ansible has no
44
- # preference otherwise at least sort the list.
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
- facts[:ansible_default_ipv4]['interface']
47
- pref ? (facts[:ansible_interfaces] - [pref]).unshift(pref) :
48
- facts[:ansible_interfaces].sort
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.gsub!(/-/, '_') # virbr1-nic -> virbr1_nic
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
- def sparse(hash, options = {} )
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) || Hash.new
15
+ end.reduce(:merge) || {}
10
16
  end
11
17
 
12
- def unsparse(hash, options={})
13
- ret = Hash.new
14
- sparse(hash).each do |k, v|
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
- key = k.to_s.split(options.fetch(:separator, FactName::SEPARATOR))
17
- current = (current[key.shift] ||= Hash.new) until key.size <= 1
18
- current[key.first] = v
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
@@ -1,4 +1,5 @@
1
1
  require 'foreman_ansible/engine'
2
2
 
3
+ # Module required to start the Foreman Rails engine
3
4
  module ForemanAnsible
4
5
  end
@@ -1,4 +1,5 @@
1
1
  module ForemanAnsible
2
+ # This engine connects ForemanAnsible with Foreman core
2
3
  class Engine < ::Rails::Engine
3
4
  engine_name 'foreman_ansible'
4
5
 
@@ -1,3 +1,6 @@
1
+ # Specify the version to be picked up in the foreman_ansible.gemspec
2
+ # This way other parts of Foreman can just call ForemanAnsible::VERSION
3
+ # and detect what version the plugin is running.
1
4
  module ForemanAnsible
2
- VERSION = '0.2.1'
5
+ VERSION = '0.2.2'
3
6
  end
@@ -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.1
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: 2015-12-04 00:00:00.000000000 Z
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