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.
- checksums.yaml +5 -5
- data/app/controllers/api/v2/ansible_roles_controller.rb +10 -5
- data/app/controllers/foreman_ansible/api/v2/hostgroups_controller_extensions.rb +1 -1
- data/app/controllers/foreman_ansible/api/v2/hosts_controller_extensions.rb +1 -1
- data/app/models/ansible_role.rb +9 -0
- data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +1 -0
- data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +1 -0
- data/app/models/host_ansible_role.rb +0 -1
- data/app/models/setting/ansible.rb +15 -0
- data/app/services/foreman_ansible/insights_notification_builder.rb +64 -0
- data/app/services/foreman_ansible/insights_plan_runner.rb +17 -2
- data/app/services/foreman_ansible/inventory_creator.rb +10 -0
- data/app/services/foreman_ansible/renderer_methods.rb +10 -3
- data/app/services/foreman_ansible/ui_roles_importer.rb +0 -2
- data/app/views/api/v2/ansible_roles/show.json.rabl +1 -1
- data/app/views/foreman_ansible/job_templates/package_action_-_ansible_default.erb +4 -0
- data/app/views/foreman_ansible/job_templates/power_action_-_ansible_default.erb +2 -2
- data/app/views/foreman_ansible/job_templates/puppet_run_once_-_ansible_default.erb +1 -1
- data/app/views/foreman_ansible/job_templates/run_command_-_ansible_default.erb +2 -1
- data/app/views/foreman_ansible/job_templates/service_action_-_ansible_default.erb +1 -1
- data/db/migrate/20180410125416_rename_ansible_job_categories.rb +30 -0
- data/db/seeds.d/75_job_templates.rb +18 -16
- data/db/seeds.d/90_notification_blueprints.rb +17 -0
- data/lib/foreman_ansible/engine.rb +1 -2
- data/lib/foreman_ansible/register.rb +1 -0
- data/lib/foreman_ansible/remote_execution.rb +4 -1
- data/lib/foreman_ansible/version.rb +1 -1
- data/locale/en/foreman_ansible.po +130 -7
- data/locale/foreman_ansible.pot +224 -34
- data/test/fixtures/insights_playbook.yaml +79 -0
- data/test/unit/services/insights_plan_runner_test.rb +36 -0
- data/test/unit/services/inventory_creator_test.rb +18 -0
- metadata +32 -47
- data/webpack/components/ReportJsonViewer.js +0 -17
- data/webpack/index.js +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7fa908b35593b08f7be264f7a9d23ef2357858ab24faea00711b06084a4f9c4a
|
|
4
|
+
data.tar.gz: 7d1ae5b38052fd0b620170316ccf295181ff02c7d92712db15a95e2c3ab56bb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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, '/
|
|
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, '/
|
|
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, '/
|
|
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, '/
|
|
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, '/
|
|
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(&:
|
|
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
|
data/app/models/ansible_role.rb
CHANGED
|
@@ -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
|
|
@@ -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|
|
|
@@ -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
|
-
|
|
36
|
-
YAML.safe_load(
|
|
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
|
|
15
|
-
|
|
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
|
|
@@ -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
|
-
'
|
|
25
|
+
'shutdown -r +1'
|
|
26
26
|
else
|
|
27
27
|
'shutdown -h now'
|
|
28
28
|
end %>
|
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 }
|