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 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