foreman_ansible 1.0 → 1.1

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -27
  3. data/app/controllers/ansible_roles_controller.rb +55 -0
  4. data/app/controllers/api/v2/ansible_roles_controller.rb +52 -0
  5. data/app/controllers/foreman_ansible/concerns/hosts_controller_extensions.rb +11 -10
  6. data/app/helpers/foreman_ansible/ansible_plugin_helper.rb +8 -0
  7. data/app/helpers/foreman_ansible/ansible_reports_helper.rb +6 -4
  8. data/app/helpers/foreman_ansible/ansible_roles_helper.rb +17 -3
  9. data/app/helpers/foreman_ansible/hosts_helper_extensions.rb +4 -3
  10. data/app/lib/actions/foreman_ansible/play_host_roles.rb +56 -0
  11. data/app/lib/actions/foreman_ansible/play_hosts_roles.rb +26 -0
  12. data/app/lib/proxy_api/ansible.rb +28 -0
  13. data/app/models/ansible_role.rb +8 -0
  14. data/app/models/concerns/foreman_ansible/has_many_ansible_roles.rb +13 -0
  15. data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +5 -1
  16. data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +19 -0
  17. data/app/models/host_ansible_role.rb +0 -2
  18. data/app/models/hostgroup_ansible_role.rb +8 -0
  19. data/app/overrides/ansible_roles_tab.rb +2 -2
  20. data/app/overrides/hostgroup_ansible_roles_tab.rb +14 -0
  21. data/app/services/foreman_ansible/api_roles_importer.rb +16 -0
  22. data/app/services/foreman_ansible/fact_importer.rb +2 -1
  23. data/app/services/foreman_ansible/inventory_creator.rb +37 -5
  24. data/app/services/foreman_ansible/playbook_creator.rb +3 -4
  25. data/app/services/foreman_ansible/proxy_selector.rb +19 -0
  26. data/app/services/foreman_ansible/roles_importer.rb +42 -15
  27. data/app/services/foreman_ansible/ui_roles_importer.rb +26 -0
  28. data/app/views/ansible_roles/import.html.erb +51 -0
  29. data/app/views/ansible_roles/index.html.erb +30 -0
  30. data/app/views/ansible_roles/welcome.html.erb +14 -0
  31. data/app/views/api/v2/ansible_roles/import.json.rabl +3 -0
  32. data/app/views/api/v2/ansible_roles/index.json.rabl +3 -0
  33. data/app/views/api/v2/ansible_roles/obsolete.json.rabl +3 -0
  34. data/app/views/api/v2/ansible_roles/show.json.rabl +3 -0
  35. data/app/views/foreman_ansible/ansible_roles/_select_tab_content.html.erb +6 -0
  36. data/app/views/foreman_ansible/{hosts/_tab_title.html.erb → ansible_roles/_select_tab_title.html.erb} +0 -0
  37. data/config/routes.rb +25 -2
  38. data/db/migrate/20160705082036_create_ansible_role.rb +2 -1
  39. data/db/migrate/20160706074540_create_join_table_hosts_ansible_roles.rb +1 -0
  40. data/db/migrate/20160707195442_create_host_ansible_roles.rb +1 -0
  41. data/db/migrate/20160729094457_add_columns_to_ansible_role.rb +12 -0
  42. data/db/migrate/20160802153302_create_join_table_hostgroup_ansible_roles.rb +10 -0
  43. data/db/migrate/20160805094233_add_primary_key_hostgroup_ansible_roles.rb +6 -0
  44. data/db/seeds.d/{62-ansible_proxy_feature.rb → 62_ansible_proxy_feature.rb} +0 -0
  45. data/lib/foreman_ansible.rb +1 -0
  46. data/lib/foreman_ansible/engine.rb +54 -4
  47. data/lib/foreman_ansible/version.rb +1 -1
  48. data/locale/en/foreman_ansible.po +139 -2
  49. data/locale/foreman_ansible.pot +200 -8
  50. data/test/factories/ansible_proxy.rb +7 -0
  51. data/test/factories/ansible_roles.rb +2 -2
  52. data/test/fixtures/ansible_permissions.yml +11 -0
  53. data/test/functional/ansible_roles_controller_test.rb +16 -0
  54. data/test/functional/api/v2/ansible_roles_controller_test.rb +24 -0
  55. data/test/functional/hosts_controller_test.rb +65 -24
  56. data/test/support/fixture_support.rb +25 -0
  57. data/test/support/foreman_tasks/task.rb +47 -0
  58. data/test/support/foreman_test_helper_additions.rb +22 -0
  59. data/test/test_plugin_helper.rb +5 -2
  60. data/test/unit/ansible_role_test.rb +6 -2
  61. data/test/unit/concerns/host_managed_extensions_test.rb +37 -0
  62. data/test/unit/concerns/hostgroup_extensions_test.rb +36 -0
  63. data/test/unit/fact_importer_test.rb +11 -8
  64. data/test/unit/fact_parser_test.rb +2 -0
  65. data/test/unit/fact_sparser_test.rb +1 -0
  66. data/test/unit/helpers/foreman_ansible/ansible_reports_helper_test.rb +1 -0
  67. data/test/unit/host_ansible_role_test.rb +10 -0
  68. data/test/unit/hostgroup_ansible_role_test.rb +19 -0
  69. data/test/unit/lib/foreman_ansible_core/playbook_runner_test.rb +28 -0
  70. data/test/unit/lib/proxy_api/ansible_test.rb +23 -0
  71. data/test/unit/services/api_roles_importer_test.rb +28 -0
  72. data/test/unit/services/proxy_selector_test.rb +28 -0
  73. data/test/unit/services/roles_importer_test.rb +17 -0
  74. data/test/unit/services/ui_roles_importer_test.rb +30 -0
  75. metadata +108 -14
  76. data/app/jobs/foreman_ansible/run_playbook_job.rb +0 -28
  77. data/app/services/foreman_ansible/role_player.rb +0 -26
  78. data/app/views/foreman_ansible/hosts/_tab_content.html.erb +0 -4
  79. data/lib/tasks/foreman_ansible_tasks.rake +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fc4ac60019a9a765f9d6952ef2353dd4c67ddac3
4
- data.tar.gz: 1cdc990b070ea857835c74822b021340640bac56
3
+ metadata.gz: 7394d81e94f7dd6bfda57b0a539adc09a1c068e0
4
+ data.tar.gz: 87475c40db6d3a594517de1443878ffe107b85f6
5
5
  SHA512:
6
- metadata.gz: 6658351477f153ae2b6b42356ba9cb75515fbfeae8a8a40040e2c80d458b127adb2a2a37171b42dc54ca73b135cfd207aa804d190ad6a63df5b8ae8f211805b7
7
- data.tar.gz: 20f1305a896c46529bc488044f3a3284feb09290845ecbdd333be2ad616473051aa2160cacca849656164bd8c91b654e063a35b4ad9a9edeaa2e257db37cee4c
6
+ metadata.gz: 48d0c08d791d3543d6cbf2341c6a3ed66ba9c0d4b9a8c9e5bbcc0344444ef9c8c94db91ae4b7bf193ef915c75d9162470877140c46cd9d3c44e110d3593d809b
7
+ data.tar.gz: 79df637cc5ce51f5f6d9ee40ae0587b18c4dc9f339ae0edd4d813dd97fcfe1965bc589d7c176fcabcbd202cd47d75a839695ed652a9636ff32e37175164e168b
data/Rakefile CHANGED
@@ -4,37 +4,16 @@ begin
4
4
  rescue LoadError
5
5
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
6
  end
7
- begin
8
- require 'rdoc/task'
9
- rescue LoadError
10
- require 'rdoc/rdoc'
11
- require 'rake/rdoctask'
12
- RDoc::Task = Rake::RDocTask
13
- end
14
-
15
- RDoc::Task.new(:rdoc) do |rdoc|
16
- rdoc.rdoc_dir = 'rdoc'
17
- rdoc.title = 'ForemanAnsible'
18
- rdoc.options << '--line-numbers'
19
- rdoc.rdoc_files.include('README.rdoc')
20
- rdoc.rdoc_files.include('lib/**/*.rb')
21
- end
22
-
23
- APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
24
-
25
- Bundler::GemHelper.install_tasks
26
7
 
27
8
  require 'rake/testtask'
28
-
29
- Rake::TestTask.new(:test) do |t|
30
- t.libs << 'lib'
31
- t.libs << 'test'
32
- t.pattern = 'test/**/*_test.rb'
33
- t.verbose = false
9
+ Rake::TestTask.new("test:core") do |test|
10
+ test_dir = File.join(File.dirname(__FILE__), 'test/lib')
11
+ test.pattern = "#{test_dir}/**/*_test.rb"
12
+ test.libs << test_dir
13
+ test.verbose = false
14
+ test.warning = false
34
15
  end
35
16
 
36
- task :default => :test
37
-
38
17
  begin
39
18
  require 'rubocop/rake_task'
40
19
  RuboCop::RakeTask.new
@@ -0,0 +1,55 @@
1
+ # UI controller for ansible roles
2
+ class AnsibleRolesController < ::ApplicationController
3
+ include Foreman::Controller::AutoCompleteSearch
4
+
5
+ before_action :find_resource, :only => [:destroy]
6
+ before_action :find_proxy, :only => [:import]
7
+ before_action :create_importer, :only => [:import, :confirm_import]
8
+
9
+ def index
10
+ @ansible_roles = resource_base.search_for(params[:search],
11
+ :order => params[:order]).
12
+ paginate(:page => params[:page],
13
+ :per_page => params[:per_page])
14
+ end
15
+
16
+ def destroy
17
+ if @ansible_role.destroy
18
+ process_success
19
+ else
20
+ process_error
21
+ end
22
+ end
23
+
24
+ def import
25
+ changed = @importer.import!
26
+ if changed.values.all?(&:empty?)
27
+ notice no_changed_roles_message
28
+ redirect_to ansible_roles_path
29
+ else
30
+ render :locals => { :changed => changed }
31
+ end
32
+ end
33
+
34
+ def confirm_import
35
+ @importer.finish_import(params[:changed])
36
+ notice _('Import of roles successfully finished.')
37
+ redirect_to ansible_roles_path
38
+ end
39
+
40
+ private
41
+
42
+ def find_proxy
43
+ return nil unless params[:proxy]
44
+ @proxy = SmartProxy.authorized(:view_smart_proxies).find(params[:proxy])
45
+ end
46
+
47
+ def create_importer
48
+ @importer = ForemanAnsible::UiRolesImporter.new(@proxy)
49
+ end
50
+
51
+ def no_changed_roles_message
52
+ return _('No changes in roles detected.') unless @proxy.present?
53
+ _('No changes in roles detected on %s.') % @proxy.name
54
+ end
55
+ end
@@ -0,0 +1,52 @@
1
+ module Api
2
+ module V2
3
+ # API controller for Ansible Roles
4
+ class AnsibleRolesController < ::Api::V2::BaseController
5
+ include ::Api::Version2
6
+
7
+ before_action :find_resource, :only => [:show, :destroy]
8
+ before_action :find_proxy, :only => [:import, :obsolete]
9
+ before_action :create_importer, :only => [:import, :obsolete]
10
+
11
+ api :GET, '/ansible/ansible_roles/:id', N_('Show role')
12
+ param :id, :identifier, :required => true
13
+ def show
14
+ end
15
+
16
+ api :GET, '/ansible/ansible_roles', N_('List Ansible roles')
17
+ param_group :search_and_pagination, ::Api::V2::BaseController
18
+ def index
19
+ @ansible_roles = resource_scope_for_index
20
+ end
21
+
22
+ api :DELETE, '/ansible/ansible_roles/:id', N_('Deletes Ansible role')
23
+ param :id, :identifier, :required => true
24
+ def destroy
25
+ process_response @ansible_role.destroy
26
+ end
27
+
28
+ api :POST, '/ansible/ansible_roles/import', N_('Import Ansible roles')
29
+ param :proxy, Hash, N_('Smart Proxy to import from')
30
+ def import
31
+ @imported = @importer.import!
32
+ end
33
+
34
+ api :POST, '/ansible/ansible_roles/obsolete', N_('Obsolete Ansible roles')
35
+ param :proxy, Hash, N_('Smart Proxy to import from')
36
+ def obsolete
37
+ @obsoleted = @importer.obsolete!
38
+ end
39
+
40
+ private
41
+
42
+ def find_proxy
43
+ return nil unless params[:proxy]
44
+ @proxy = SmartProxy.authorized(:view_smart_proxies).find(params[:proxy])
45
+ end
46
+
47
+ def create_importer
48
+ @importer = ForemanAnsible::ApiRolesImporter.new(@proxy)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -3,23 +3,24 @@ module ForemanAnsible
3
3
  # Extra methods to enforce Ansible roles on a host or multiple hosts
4
4
  module HostsControllerExtensions
5
5
  extend ActiveSupport::Concern
6
+ include ForemanTasks::Triggers
6
7
 
7
8
  def play_roles
8
9
  find_resource
9
- RolePlayer.new(@host).play
10
- notice(_('Ansible roles running on background: ') +
11
- @host.ansible_roles.map(&:name).join(', '))
12
- redirect_to :back
10
+ task = async_task(::Actions::ForemanAnsible::PlayHostRoles, @host)
11
+ redirect_to task
12
+ rescue Foreman::Exception => e
13
+ error e.message
14
+ redirect_to host_path(@host)
13
15
  end
14
16
 
15
17
  def multiple_play_roles
16
18
  find_multiple
17
- @hosts.each do |host|
18
- RolePlayer.new(host).play
19
- end
20
- notice(_('Ansible roles running on background for hosts: ') +
21
- @hosts.map(&:name).join(', '))
22
- redirect_to :hosts
19
+ task = async_task(::Actions::ForemanAnsible::PlayHostsRoles, @hosts)
20
+ redirect_to task
21
+ rescue Foreman::Exception => e
22
+ error e.message
23
+ redirect_to hosts_path
23
24
  end
24
25
 
25
26
  private
@@ -0,0 +1,8 @@
1
+ module ForemanAnsible
2
+ # General helper for foreman_ansible
3
+ module AnsiblePluginHelper
4
+ def ansible_doc_url
5
+ 'http://theforeman.org/plugins/foreman_ansible/1.x/index.html'
6
+ end
7
+ end
8
+ end
@@ -12,10 +12,12 @@ module ForemanAnsible
12
12
 
13
13
  def ansible_module_message(log)
14
14
  paragraph_style = 'margin:0px;font-family:Menlo,Monaco,Consolas,monospace'
15
- JSON.parse(log.message.value).except('invocation').map do |name, value|
16
- next if value.blank?
17
- "<p style=#{paragraph_style}>#{name}: #{value}</p>"
18
- end.join.html_safe
15
+ safe_join(
16
+ JSON.parse(log.message.value).except('invocation').map do |name, value|
17
+ next if value.blank?
18
+ content_tag(:p, "#{name}: #{value}", :style => paragraph_style)
19
+ end
20
+ )
19
21
  end
20
22
 
21
23
  def ansible_report?(log)
@@ -1,9 +1,23 @@
1
1
  module ForemanAnsible
2
2
  # Small convenience to list all roles in the UI
3
3
  module AnsibleRolesHelper
4
- def ansible_roles
5
- ForemanAnsible::RolesImporter.import!
6
- AnsibleRole.all
4
+ def ansible_proxy_links(hash, classes = nil)
5
+ links = SmartProxy.with_features('Ansible').map do |proxy|
6
+ display_link_if_authorized(_('Import from %s') % proxy.name,
7
+ hash.merge(:proxy => proxy),
8
+ :class => classes)
9
+ end.flatten
10
+ links.unshift display_link_if_authorized(_('Import from Foreman host'),
11
+ hash,
12
+ :class => classes)
13
+ end
14
+
15
+ def ansible_proxy_import(hash)
16
+ select_action_button(_('Import'), {}, ansible_proxy_links(hash))
17
+ end
18
+
19
+ def import_time(role)
20
+ _('%s ago') % time_ago_in_words(role.updated_at)
7
21
  end
8
22
  end
9
23
  end
@@ -11,9 +11,10 @@ module ForemanAnsible
11
11
  def host_title_actions_with_run_ansible_roles(*args)
12
12
  button = link_to(
13
13
  icon_text('play', ' ' + _('Ansible roles'), :kind => 'fa'),
14
- play_roles_ansible_host_path(:id => args.first.id),
14
+ play_roles_host_path(:id => args.first.id),
15
15
  :id => :ansible_roles_button,
16
- :class => 'btn btn-default')
16
+ :class => 'btn btn-default'
17
+ )
17
18
  title_actions(button_group(button)) if args.first.ansible_roles.present?
18
19
  host_title_actions_without_run_ansible_roles(*args)
19
20
  end
@@ -21,7 +22,7 @@ module ForemanAnsible
21
22
  def multiple_actions_with_run_ansible_roles
22
23
  multiple_actions_without_run_ansible_roles +
23
24
  [[_('Play Ansible roles'),
24
- multiple_play_roles_ansible_hosts_path,
25
+ multiple_play_roles_hosts_path,
25
26
  false]]
26
27
  end
27
28
  end
@@ -0,0 +1,56 @@
1
+ module Actions
2
+ module ForemanAnsible
3
+ # Actions that initiaztes the playbook run for roles assigned to
4
+ # the host. It doest that either locally or via a proxy when available.
5
+ class PlayHostRoles < Actions::EntryAction
6
+ include ::Actions::Helpers::WithContinuousOutput
7
+ include ::Actions::Helpers::WithDelegatedAction
8
+
9
+ def plan(host, proxy_selector = ::ForemanAnsible::ProxySelector.new)
10
+ input[:host] = { :id => host.id, :name => host.fqdn }
11
+ proxy = proxy_selector.determine_proxy(host)
12
+ inventory_creator = ::ForemanAnsible::InventoryCreator.new([host])
13
+ role_names = host.all_ansible_roles.map(&:name)
14
+ playbook_creator = ::ForemanAnsible::PlaybookCreator.new(role_names)
15
+ plan_delegated_action(proxy, ::ForemanAnsibleCore::Actions::RunPlaybook,
16
+ :inventory => inventory_creator.to_hash.to_json,
17
+ :playbook => playbook_creator.roles_playbook)
18
+ plan_self
19
+ end
20
+
21
+ def finalize
22
+ if delegated_output[:exit_status].to_s != '0'
23
+ error! _('Playbook execution failed')
24
+ end
25
+ end
26
+
27
+ def rescue_strategy
28
+ ::Dynflow::Action::Rescue::Fail
29
+ end
30
+
31
+ def humanized_input
32
+ _('on host %{name}') % { :name => input.fetch(:host, {})[:name] }
33
+ end
34
+
35
+ def humanized_name
36
+ _('Play Ansible roles')
37
+ end
38
+
39
+ def humanized_output
40
+ continuous_output.humanize
41
+ end
42
+
43
+ def continuous_output_providers
44
+ super << self
45
+ end
46
+
47
+ def fill_continuous_output(continuous_output)
48
+ delegated_output.fetch('result', []).each do |raw_output|
49
+ continuous_output.add_raw_output(raw_output)
50
+ end
51
+ rescue => e
52
+ continuous_output.add_exception(_('Error loading data from proxy'), e)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ module Actions
2
+ module ForemanAnsible
3
+ # Bulk action for running ansible roles
4
+ class PlayHostsRoles < Actions::ActionWithSubPlans
5
+ def plan(hosts)
6
+ plan_self(:host_ids => hosts.map(&:id))
7
+ end
8
+
9
+ def create_sub_plans
10
+ proxy_selector = ::ForemanAnsible::ProxySelector.new
11
+ input[:host_ids].map do |host_id|
12
+ host = Host.find(host_id)
13
+ trigger(PlayHostRoles, host, proxy_selector)
14
+ end
15
+ end
16
+
17
+ def rescue_strategy
18
+ ::Dynflow::Action::Rescue::Fail
19
+ end
20
+
21
+ def humanized_name
22
+ _('Bulk play Ansible roles')
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module ProxyAPI
2
+ # ProxyAPI for Ansible
3
+ class Ansible < ::ProxyAPI::Resource
4
+ def initialize(args)
5
+ @url = args[:url] + '/ansible/'
6
+ super args
7
+ end
8
+
9
+ PROXY_ERRORS = [
10
+ Errno::ECONNREFUSED,
11
+ SocketError,
12
+ Timeout::Error,
13
+ Errno::EINVAL,
14
+ Errno::ECONNRESET,
15
+ EOFError,
16
+ Net::HTTPBadResponse,
17
+ Net::HTTPHeaderSyntaxError,
18
+ Net::ProtocolError,
19
+ RestClient::ResourceNotFound
20
+ ].freeze
21
+
22
+ def roles
23
+ parse(get('roles'))
24
+ rescue *PROXY_ERRORS => e
25
+ raise ProxyException.new(url, e, N_('Unable to get roles from Ansible'))
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,14 @@
1
1
  # Simple model to store basic info about the Ansible role
2
2
  class AnsibleRole < ActiveRecord::Base
3
+ include Authorizable
4
+
5
+ self.include_root_in_json = false
3
6
  validates :name, :presence => true, :uniqueness => true
4
7
  has_many :host_ansible_roles
5
8
  has_many_hosts :through => :host_ansible_roles, :dependent => :destroy
9
+ has_many :hostgroup_ansible_roles
10
+ has_many :hostgroups, :through => :hostgroup_ansible_roles,
11
+ :dependent => :destroy
12
+
13
+ scoped_search :on => :name, :complete_value => true
6
14
  end
@@ -0,0 +1,13 @@
1
+ module ForemanAnsible
2
+ # Define common behaviors between models that have many Ansible roles,
3
+ # currently Host and Hostgroup. Takes care of inheritance, etc...
4
+ module HasManyAnsibleRoles
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def all_ansible_roles
9
+ (ansible_roles + inherited_ansible_roles).uniq
10
+ end
11
+ end
12
+ end
13
+ end
@@ -7,8 +7,12 @@ module ForemanAnsible
7
7
  has_many :host_ansible_roles, :foreign_key => :host_id
8
8
  has_many :ansible_roles, :through => :host_ansible_roles,
9
9
  :dependent => :destroy
10
+ include ForemanAnsible::HasManyAnsibleRoles
10
11
 
11
- attr_accessible :ansible_role_ids, :ansible_roles
12
+ def inherited_ansible_roles
13
+ return [] unless hostgroup
14
+ hostgroup.all_ansible_roles
15
+ end
12
16
  end
13
17
  end
14
18
  end
@@ -0,0 +1,19 @@
1
+ module ForemanAnsible
2
+ # Relations to make Hostgroup 'have' ansible roles
3
+ module HostgroupExtensions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :hostgroup_ansible_roles, :foreign_key => :hostgroup_id
8
+ has_many :ansible_roles, :through => :hostgroup_ansible_roles,
9
+ :dependent => :destroy
10
+ include ForemanAnsible::HasManyAnsibleRoles
11
+
12
+ def inherited_ansible_roles
13
+ ancestors.reduce([]) do |roles, hostgroup|
14
+ roles + hostgroup.ansible_roles
15
+ end.uniq
16
+ end
17
+ end
18
+ end
19
+ end