foreman_ansible 2.1.0 → 2.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 (35) hide show
  1. checksums.yaml +5 -5
  2. data/app/controllers/api/v2/ansible_roles_controller.rb +10 -5
  3. data/app/controllers/foreman_ansible/api/v2/hostgroups_controller_extensions.rb +1 -1
  4. data/app/controllers/foreman_ansible/api/v2/hosts_controller_extensions.rb +1 -1
  5. data/app/models/ansible_role.rb +9 -0
  6. data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +1 -0
  7. data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +1 -0
  8. data/app/models/host_ansible_role.rb +0 -1
  9. data/app/models/setting/ansible.rb +15 -0
  10. data/app/services/foreman_ansible/insights_notification_builder.rb +64 -0
  11. data/app/services/foreman_ansible/insights_plan_runner.rb +17 -2
  12. data/app/services/foreman_ansible/inventory_creator.rb +10 -0
  13. data/app/services/foreman_ansible/renderer_methods.rb +10 -3
  14. data/app/services/foreman_ansible/ui_roles_importer.rb +0 -2
  15. data/app/views/api/v2/ansible_roles/show.json.rabl +1 -1
  16. data/app/views/foreman_ansible/job_templates/package_action_-_ansible_default.erb +4 -0
  17. data/app/views/foreman_ansible/job_templates/power_action_-_ansible_default.erb +2 -2
  18. data/app/views/foreman_ansible/job_templates/puppet_run_once_-_ansible_default.erb +1 -1
  19. data/app/views/foreman_ansible/job_templates/run_command_-_ansible_default.erb +2 -1
  20. data/app/views/foreman_ansible/job_templates/service_action_-_ansible_default.erb +1 -1
  21. data/db/migrate/20180410125416_rename_ansible_job_categories.rb +30 -0
  22. data/db/seeds.d/75_job_templates.rb +18 -16
  23. data/db/seeds.d/90_notification_blueprints.rb +17 -0
  24. data/lib/foreman_ansible/engine.rb +1 -2
  25. data/lib/foreman_ansible/register.rb +1 -0
  26. data/lib/foreman_ansible/remote_execution.rb +4 -1
  27. data/lib/foreman_ansible/version.rb +1 -1
  28. data/locale/en/foreman_ansible.po +130 -7
  29. data/locale/foreman_ansible.pot +224 -34
  30. data/test/fixtures/insights_playbook.yaml +79 -0
  31. data/test/unit/services/insights_plan_runner_test.rb +36 -0
  32. data/test/unit/services/inventory_creator_test.rb +18 -0
  33. metadata +32 -47
  34. data/webpack/components/ReportJsonViewer.js +0 -17
  35. data/webpack/index.js +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 11936ad8fbecb22faf149473256064f197414c8b
4
- data.tar.gz: f54a4994feab57e29e2a974d04137be329105b5b
2
+ SHA256:
3
+ metadata.gz: 7fa908b35593b08f7be264f7a9d23ef2357858ab24faea00711b06084a4f9c4a
4
+ data.tar.gz: 7d1ae5b38052fd0b620170316ccf295181ff02c7d92712db15a95e2c3ab56bb5
5
5
  SHA512:
6
- metadata.gz: 6446ec9cb86573ed497ee010b2aa05b1e36b62e3cc9a9cd97afdfc2fd2df6620baed984339fa52f0cffe169b8ba1e38e7b3aff9f42a663e8ce3ed4ac0b89cb34
7
- data.tar.gz: 3b188ae9c37bcc7a1921a5ac008263a03c55867b322c5c4bb71bb67bf2347d6feed7b73513ad8cf2ec1b53fd82ea2b397ea8ed1d1afc0e861f0b422bb5491fcf
6
+ metadata.gz: 0c0fc2260f5762f172b3445567069b48bc7a021129d9aa8ae94ff4eef7d28cadd7f93fa8302e6143561545fc2b327a51e5f1400942ad853bed4262c431f5d751
7
+ data.tar.gz: d01daf2b7f4559c9e4ec4e819f6d4d99ce4570f59d3e4b99a2e074b3c16046817634846f534a08c46ec0c4aadbd92489897fd02a8c5f6b37e428605fa10daad3
@@ -4,33 +4,38 @@ module Api
4
4
  class AnsibleRolesController < ::Api::V2::BaseController
5
5
  include ::Api::Version2
6
6
 
7
+ resource_description do
8
+ api_version 'v2'
9
+ api_base_url '/ansible/api'
10
+ end
11
+
7
12
  before_action :find_resource, :only => [:show, :destroy]
8
13
  before_action :find_proxy, :only => [:import, :obsolete]
9
14
  before_action :create_importer, :only => [:import, :obsolete]
10
15
 
11
- api :GET, '/ansible/ansible_roles/:id', N_('Show role')
16
+ api :GET, '/ansible_roles/:id', N_('Show role')
12
17
  param :id, :identifier, :required => true
13
18
  def show; end
14
19
 
15
- api :GET, '/ansible/ansible_roles', N_('List Ansible roles')
20
+ api :GET, '/ansible_roles', N_('List Ansible roles')
16
21
  param_group :search_and_pagination, ::Api::V2::BaseController
17
22
  def index
18
23
  @ansible_roles = resource_scope_for_index
19
24
  end
20
25
 
21
- api :DELETE, '/ansible/ansible_roles/:id', N_('Deletes Ansible role')
26
+ api :DELETE, '/ansible_roles/:id', N_('Deletes Ansible role')
22
27
  param :id, :identifier, :required => true
23
28
  def destroy
24
29
  process_response @ansible_role.destroy
25
30
  end
26
31
 
27
- api :POST, '/ansible/ansible_roles/import', N_('Import Ansible roles')
32
+ api :POST, '/ansible_roles/import', N_('Import Ansible roles')
28
33
  param :proxy, Hash, N_('Smart Proxy to import from')
29
34
  def import
30
35
  @imported = @importer.import!
31
36
  end
32
37
 
33
- api :POST, '/ansible/ansible_roles/obsolete', N_('Obsolete Ansible roles')
38
+ api :POST, '/ansible_roles/obsolete', N_('Obsolete Ansible roles')
34
39
  param :proxy, Hash, N_('Smart Proxy to import from')
35
40
  def obsolete
36
41
  @obsoleted = @importer.obsolete!
@@ -27,7 +27,7 @@ module ForemanAnsible
27
27
  def multiple_play_roles
28
28
  find_multiple
29
29
  composer = job_composer(:ansible_run_host,
30
- @hostgroups.map(&:hosts).flatten.uniq)
30
+ @hostgroups.map(&:host_ids).flatten.uniq)
31
31
  process_response composer.trigger!, composer.job_invocation
32
32
  end
33
33
  end
@@ -21,7 +21,7 @@ module ForemanAnsible
21
21
  param :id, Array, :required => true
22
22
 
23
23
  def multiple_play_roles
24
- composer = job_composer(:ansible_run_host, @host)
24
+ composer = job_composer(:ansible_run_host, @host.pluck(:id))
25
25
  process_response composer.trigger!, composer.job_invocation
26
26
  end
27
27
  end
@@ -1,5 +1,6 @@
1
1
  # Simple model to store basic info about the Ansible role
2
2
  class AnsibleRole < ApplicationRecord
3
+ audited
3
4
  include Authorizable
4
5
 
5
6
  self.include_root_in_json = false
@@ -12,6 +13,14 @@ class AnsibleRole < ApplicationRecord
12
13
 
13
14
  scoped_search :on => :name, :complete_value => true
14
15
  scoped_search :on => :updated_at
16
+ scoped_search :relation => :hosts,
17
+ :on => :id, :rename => :host_id, :only_explicit => true
18
+ scoped_search :relation => :hosts,
19
+ :on => :name, :rename => :host, :only_explicit => true
20
+ scoped_search :relation => :hostgroups,
21
+ :on => :id, :rename => :hostgroup_id, :only_explicit => true
22
+ scoped_search :relation => :hostgroups,
23
+ :on => :name, :rename => :hostgroup, :only_explicit => true
15
24
 
16
25
  # Methods to be allowed in any template with safemode enabled
17
26
  class Jail < Safemode::Jail
@@ -13,6 +13,7 @@ module ForemanAnsible
13
13
 
14
14
  before_provision :play_ansible_roles
15
15
  include ForemanAnsible::HasManyAnsibleRoles
16
+ audit_associations :ansible_roles
16
17
 
17
18
  def inherited_ansible_roles
18
19
  return [] unless hostgroup
@@ -8,6 +8,7 @@ module ForemanAnsible
8
8
  has_many :ansible_roles, :through => :hostgroup_ansible_roles,
9
9
  :dependent => :destroy
10
10
  include ForemanAnsible::HasManyAnsibleRoles
11
+ audit_associations :ansible_roles
11
12
 
12
13
  def inherited_ansible_roles
13
14
  ancestors.reduce([]) do |roles, hostgroup|
@@ -1,6 +1,5 @@
1
1
  # Join model that hosts the connection between hosts and ansible_roles
2
2
  class HostAnsibleRole < ApplicationRecord
3
- audited :associated_with => :host, :allow_mass_assignment => true
4
3
  belongs_to_host
5
4
  belongs_to :ansible_role
6
5
 
@@ -73,6 +73,21 @@ class Setting
73
73
  'foreman_params["host_parameter"] in the playbooks.'),
74
74
  true,
75
75
  N_('Top level Ansible variables')
76
+ ),
77
+ set(
78
+ 'ansible_interval',
79
+ N_('Timeout (in minutes) when hosts should have reported.'),
80
+ '30',
81
+ N_('Ansible report timeout')
82
+ ),
83
+ set(
84
+ 'ansible_out_of_sync_disabled',
85
+ format(N_('Disable host configuration status turning to out of'\
86
+ ' sync for %{cfgmgmt} after report does not arrive within'\
87
+ ' configured interval'), :cfgmgmt => 'Ansible'),
88
+ false,
89
+ format(N_('%{cfgmgmt} out of sync disabled'),
90
+ :cfgmgmt => 'Ansible')
76
91
  )
77
92
  ].compact.each do |s|
78
93
  create(s.update(:category => 'Setting::Ansible'))
@@ -0,0 +1,64 @@
1
+ module ForemanAnsible
2
+ # A class that builds custom notificaton for REX job if it's insights
3
+ # remediation feature
4
+ # rubocop:disable LineLength
5
+ class InsightsNotificationBuilder < ::UINotifications::RemoteExecutionJobs::BaseJobFinish
6
+ # rubocop:enable LineLength
7
+ def deliver!
8
+ ::Notification.create!(
9
+ :audience => Notification::AUDIENCE_USER,
10
+ :notification_blueprint => blueprint,
11
+ :initiator => initiator,
12
+ :message => message,
13
+ :subject => subject,
14
+ :actions => {
15
+ :links => links
16
+ }
17
+ )
18
+ end
19
+
20
+ def blueprint
21
+ name = 'insights_remediation_successful'
22
+ @blueprint ||= NotificationBlueprint.unscoped.find_by(:name => name)
23
+ end
24
+
25
+ def hosts_count
26
+ @hosts_count ||= subject.template_invocations_hosts.size
27
+ end
28
+
29
+ def message
30
+ UINotifications::StringParser.new(blueprint.message,
31
+ :hosts_count => hosts_count)
32
+ end
33
+
34
+ def links
35
+ job_links + insights_links
36
+ end
37
+
38
+ def insights_links
39
+ pattern_template = subject.pattern_template_invocations.first
40
+ plan_id = pattern_template.input_values.
41
+ joins(:template_input).
42
+ where('template_inputs.name' => 'plan_id').
43
+ first.try(:value)
44
+ return [] if plan_id.nil?
45
+
46
+ [
47
+ {
48
+ :href => "/redhat_access/insights/planner/#{plan_id}",
49
+ :title => _('Remediation Plan')
50
+ }
51
+ ]
52
+ end
53
+
54
+ def job_links
55
+ UINotifications::URLResolver.new(
56
+ subject,
57
+ :links => [{
58
+ :path_method => :job_invocation_path,
59
+ :title => _('Job Details')
60
+ }]
61
+ ).actions[:links]
62
+ end
63
+ end
64
+ end
@@ -32,8 +32,23 @@ if defined?(RedhatAccess)
32
32
  "v3/maintenance/#{@plan_id}/playbook",
33
33
  get_ssl_options_for_org(@organization, nil)
34
34
  )
35
- response = resource.get
36
- YAML.safe_load(response.body)
35
+ @raw_playbook = resource.get.body
36
+ YAML.safe_load(@raw_playbook)
37
+ end
38
+
39
+ # To parse the disclaimer we iterate over the first lines of the
40
+ # playbook (all comments) until we get to a line that looks like
41
+ # "Generated by Red Hat Insights on..."
42
+ def parse_disclaimer(playbook = @raw_playbook)
43
+ return '' if playbook.blank?
44
+ disclaimer = []
45
+ playbook.split("\n").each do |line|
46
+ next if line == '---'
47
+ break unless line[0] == '#'
48
+ disclaimer << line
49
+ break if /Generated by Red Hat Insights on/ =~ line
50
+ end
51
+ disclaimer.join("\n")
37
52
  end
38
53
 
39
54
  # This method creates a hash like this:
@@ -94,6 +94,7 @@ module ForemanAnsible
94
94
  'ansible_become' => @template_invocation.effective_user,
95
95
  'ansible_user' => host_setting(host, 'remote_execution_ssh_user'),
96
96
  'ansible_ssh_pass' => rex_ssh_password(host),
97
+ 'ansible_ssh_private_key_file' => ansible_or_rex_ssh_private_key(host),
97
98
  'ansible_port' => host_setting(host, 'remote_execution_ssh_port')
98
99
  }
99
100
  # Backward compatibility for Ansible 1.x
@@ -115,6 +116,15 @@ module ForemanAnsible
115
116
  host_setting(host, 'remote_execution_ssh_password')
116
117
  end
117
118
 
119
+ def ansible_or_rex_ssh_private_key(host)
120
+ ansible_private_file = host_setting(host, 'ansible_ssh_private_key_file')
121
+ if !ansible_private_file.empty?
122
+ ansible_private_file
123
+ else
124
+ ForemanRemoteExecutionCore.settings[:ssh_identity_key_file]
125
+ end
126
+ end
127
+
118
128
  private
119
129
 
120
130
  def render_rabl(host, template)
@@ -9,13 +9,20 @@ module ForemanAnsible
9
9
  plan_id
10
10
  )
11
11
  rules = insights_plan.playbook
12
+ disclaimer = insights_plan.parse_disclaimer
12
13
  hostname_rules_relation = insights_plan.hostname_rules(rules)
13
14
  global_rules = insights_plan.rules_to_hash(rules)
14
- host_playbooks = hostname_rules_relation[@host.name].
15
- reduce([]) do |acc, cur|
15
+ host_playbooks = individual_host_playbooks(hostname_rules_relation,
16
+ global_rules)
17
+ "#{disclaimer}\n#{host_playbooks.to_yaml}"
18
+ end
19
+
20
+ private
21
+
22
+ def individual_host_playbooks(hostname_rules_relation, global_rules)
23
+ hostname_rules_relation[@host.name].reduce([]) do |acc, cur|
16
24
  acc << global_rules[cur]
17
25
  end
18
- host_playbooks.to_yaml
19
26
  end
20
27
  end
21
28
  end
@@ -11,7 +11,6 @@ module ForemanAnsible
11
11
  delete_old_roles changes['obsolete'] if changes['obsolete']
12
12
  end
13
13
 
14
- # rubocop:disable Performance/HashEachMethods
15
14
  def create_new_roles(changes)
16
15
  changes.values.each do |new_role|
17
16
  ::AnsibleRole.create(JSON.parse(new_role))
@@ -23,6 +22,5 @@ module ForemanAnsible
23
22
  ::AnsibleRole.find(JSON.parse(old_role)['id']).destroy
24
23
  end
25
24
  end
26
- # rubocop:enable Performance/HashEachMethods
27
25
  end
28
26
  end
@@ -1,3 +1,3 @@
1
1
  object @ansible_role
2
2
 
3
- attributes :name, :created_at, :updated_at
3
+ attributes :id, :name, :created_at, :updated_at
@@ -35,11 +35,15 @@ model: JobTemplate
35
35
  # For Windows targets use the win_package module instead.
36
36
  ---
37
37
  - hosts: all
38
+ <%- if input('pre_script').present? -%>
38
39
  pre_tasks:
39
40
  - shell: "<%= input('pre_script') %>"
41
+ <%- end -%>
40
42
  tasks:
41
43
  - package:
42
44
  name: <%= input('name') %>
43
45
  state: <%= input('state') %>
46
+ <%- if input('post_script').present? -%>
44
47
  post_tasks:
45
48
  - shell: "<%= input('post_script') %>"
49
+ <%- end -%>
@@ -1,6 +1,6 @@
1
1
  <%#
2
2
  name: Power Action - Ansible Default
3
- job_category: Power
3
+ job_category: Ansible Power
4
4
  description_format: "%{action} host"
5
5
  snippet: false
6
6
  template_inputs:
@@ -22,7 +22,7 @@ model: JobTemplate
22
22
  echo <%= input('action') %> host && sleep 3
23
23
  <%= case input('action')
24
24
  when 'restart'
25
- 'reboot'
25
+ 'shutdown -r +1'
26
26
  else
27
27
  'shutdown -h now'
28
28
  end %>
@@ -1,6 +1,6 @@
1
1
  <%#
2
2
  name: Puppet Run Once - Ansible Default
3
- job_category: Puppet
3
+ job_category: Ansible Puppet
4
4
  description_format: 'Run Puppet once with "%{puppet_options}"'
5
5
  snippet: false
6
6
  template_inputs:
@@ -17,6 +17,7 @@ model: JobTemplate
17
17
  ---
18
18
  - hosts: all
19
19
  tasks:
20
- - command: <%= input('command') %>
20
+ - shell: |
21
+ <%= indent(8) { input('command') } %>
21
22
  register: out
22
23
  - debug: var=out
@@ -1,6 +1,6 @@
1
1
  <%#
2
2
  name: Service Action - Ansible Default
3
- job_category: Ansible Service
3
+ job_category: Ansible Services
4
4
  description_format: "%{state} service %{name}"
5
5
  snippet: false
6
6
  template_inputs:
@@ -0,0 +1,30 @@
1
+ class RenameAnsibleJobCategories < ActiveRecord::Migration[5.1]
2
+ def up
3
+ unless User.unscoped.find_by_login(User::ANONYMOUS_ADMIN)
4
+ puts "No ANONYMOUS_ADMIN found. Skipping renaming Ansible jobs"
5
+ return
6
+ end
7
+ User.as_anonymous_admin do
8
+ updated_templates = ['Power Action - Ansible Default',
9
+ 'Puppet Run Once - Ansible Default']
10
+ JobTemplate.without_auditing do
11
+ job_templates = JobTemplate.where(
12
+ :name => updated_templates
13
+ ).all
14
+ job_templates.each do |job_template|
15
+ next if job_template.job_category =~ /^Ansible/
16
+ job_template.job_category = "Ansible #{job_template.job_category}"
17
+ job_template.save_without_auditing
18
+ end
19
+
20
+ service_template = JobTemplate.where(
21
+ :name => 'Service Action - Ansible Default'
22
+ ).first
23
+ if service_template.present?
24
+ service_template.job_category = 'Ansible Services'
25
+ service_template.save_without_auditing
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,20 +1,22 @@
1
1
  User.as_anonymous_admin do
2
- if Rails.env.test? || File.basename($0) == 'rake'
3
- # If this file tries to import a template with a REX feature in a SeedsTest,
4
- # it will fail - the REX feature isn't registered on SeedsTest because
5
- # DatabaseCleaner truncates the db before every test.
6
- # During db:seed, we also want to know the feature is registered before
7
- # seeding the template
8
- ForemanAnsible::Engine.register_rex_feature
9
- end
10
- JobTemplate.without_auditing do
11
- Dir[File.join("#{ForemanAnsible::Engine.root}/app/views/foreman_ansible/"\
12
- 'job_templates/**/*.erb')].each do |template|
13
- sync = !Rails.env.test? && Setting[:remote_execution_sync_templates]
14
- JobTemplate.import_raw!(File.read(template),
15
- :default => true,
16
- :locked => true,
17
- :update => sync)
2
+ RemoteExecutionFeature.without_auditing do
3
+ if Rails.env.test? || File.basename($PROGRAM_NAME) == 'rake'
4
+ # If this file tries to import a template with a REX feature in a SeedsTest,
5
+ # it will fail - the REX feature isn't registered on SeedsTest because
6
+ # DatabaseCleaner truncates the db before every test.
7
+ # During db:seed, we also want to know the feature is registered before
8
+ # seeding the template
9
+ ForemanAnsible::Engine.register_rex_feature
10
+ end
11
+ JobTemplate.without_auditing do
12
+ Dir[File.join("#{ForemanAnsible::Engine.root}/app/views/foreman_ansible/"\
13
+ 'job_templates/**/*.erb')].each do |template|
14
+ sync = !Rails.env.test? && Setting[:remote_execution_sync_templates]
15
+ JobTemplate.import_raw!(File.read(template),
16
+ :default => true,
17
+ :locked => true,
18
+ :update => sync)
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -0,0 +1,17 @@
1
+ blueprints = [
2
+ {
3
+ :group => N_('Jobs'),
4
+ :name => 'insights_remediation_successful',
5
+ :message => N_('Insights remediation on %{hosts_count}' \
6
+ ' host(s) has finished successfully'),
7
+ :level => 'success',
8
+ :actions => {
9
+ :links => [
10
+ :path_method => :job_invocation_path,
11
+ :title => N_('Job Details')
12
+ ]
13
+ }
14
+ }
15
+ ]
16
+
17
+ blueprints.each { |blueprint| UINotifications::Seed.new(blueprint).configure }