foreman_ansible 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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 }