foreman_ansible 7.0.4 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/ansible_playbooks_controller.rb +66 -0
  3. data/app/jobs/sync_playbooks.rb +25 -0
  4. data/app/lib/proxy_api/ansible.rb +13 -0
  5. data/app/services/foreman_ansible/import_playbooks_error_notification.rb +38 -0
  6. data/app/services/foreman_ansible/import_playbooks_success_notification.rb +33 -0
  7. data/app/services/foreman_ansible/playbooks_importer.rb +73 -0
  8. data/app/services/foreman_ansible/proxy_api.rb +3 -4
  9. data/app/views/ansible_roles/index.html.erb +2 -0
  10. data/app/views/api/v2/ansible_playbooks/sync.json.rabl +5 -0
  11. data/app/views/foreman_ansible/job_templates/ansible_windows_updates.erb +160 -0
  12. data/app/views/foreman_ansible/job_templates/configure_cloud_connector_-_ansible_default.erb +37 -0
  13. data/config/routes.rb +7 -0
  14. data/db/seeds.d/90_notification_blueprints.rb +14 -0
  15. data/lib/foreman_ansible/register.rb +3 -1
  16. data/lib/foreman_ansible/remote_execution.rb +6 -0
  17. data/lib/foreman_ansible/version.rb +1 -1
  18. data/test/fixtures/playbooks_example_output.json +1 -0
  19. data/test/fixtures/sample_playbooks.json +10 -0
  20. data/test/functional/api/v2/ansible_playbooks_controller_test.rb +65 -0
  21. data/test/functional/hosts_controller_test.rb +2 -2
  22. data/test/unit/import_playbooks_test.rb +51 -0
  23. data/test/unit/lib/proxy_api/ansible_test.rb +6 -0
  24. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +58 -75
  25. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +44 -58
  26. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +32 -55
  27. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +1 -1
  28. data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +29 -42
  29. data/webpack/components/AnsibleRolesAndVariables/AnsibleRolesAndVariables.js +27 -38
  30. data/webpack/components/AnsibleRolesAndVariables/AnsibleRolesAndVariablesActions.js +2 -1
  31. data/webpack/components/AnsibleRolesAndVariables/AnsibleRolesAndVariablesConstants.js +1 -0
  32. data/webpack/components/AnsibleRolesAndVariables/AnsibleRolesAndVariablesSelectors.js +6 -0
  33. data/webpack/components/AnsibleRolesAndVariables/index.js +7 -1
  34. data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -2
  35. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +9 -6
  36. data/webpack/helpers/pageParamsHelper.js +3 -3
  37. metadata +46 -31
  38. data/webpack/components/withPagination.js +0 -0
  39. data/webpack/helpers/paginationHelper.js +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6974b31e097d2cb11b28ed665e3346b9b6b2b08111cc3a7701c062aeb41c6e09
4
- data.tar.gz: 13d80c394e6e050d0b9009e86ce1a8b0f500e65c678e6d7b8596b286ae70489a
3
+ metadata.gz: dc6d250a74d7e811600edfd0f78551286d8800644512d4346fd6d5794fe451f3
4
+ data.tar.gz: f7057a56a7e5fda90510ab9aa13b70b59efa90f07a8e7fb3460140102e36d390
5
5
  SHA512:
6
- metadata.gz: df9ff9f909efd6602b11ac585fe8b3768b3168a869232038d4c92a5b1d2acdf635a9ce0834bfff2385aed1475e63c8361894b06b297eb38ec41adbccf8ea67ba
7
- data.tar.gz: 68dc31b736c3ff6b787bf49fb4d24b1563b042d9845b8e8e122cfc87f50b8db97981add7b8b3ba06f91792c04460ccc0a08c6fc0193cc36fe5daaf6bba0e6e80
6
+ metadata.gz: 99dbfada521e9075ae164312cdf3eecb85e663beca47e558b5d3fd61cde68bb2a632e29f80ca9dcbf5519be3122a15b6c2777f4400b6bd685c84f2488a02baf4
7
+ data.tar.gz: b8a5fde3a9bb62aaf578b5d08ef24afe6bd9ddc2ac7af33b732a6c8eadfeb077dc92f0db86496975f44d3b9ad0c59acdb176f1722e964de5a4afb7a7c826182c
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V2
5
+ # API controller for Ansible Roles
6
+ class AnsiblePlaybooksController < ::Api::V2::BaseController
7
+ include ::Api::Version2
8
+ include ::ForemanAnsible::ProxyAPI
9
+
10
+ before_action :find_proxy, only: [:fetch, :sync]
11
+
12
+ resource_description do
13
+ api_version 'v2'
14
+ api_base_url '/ansible/api'
15
+ end
16
+
17
+ api :PUT, '/ansible_playbooks/sync', N_('Sync Ansible playbooks')
18
+ param :proxy_id, :identifier, :required => true, :desc => N_('Smart Proxy to sync from')
19
+ param :playbooks_names, Array, N_('Ansible playbooks names to be synced')
20
+ def sync
21
+ @task = plan_ansible_sync(@proxy.id, playbooks_names)
22
+ end
23
+
24
+ api :GET, '/ansible_playbooks/fetch', N_('Fetch Ansible playbooks available to be synced')
25
+ param :proxy_id, :identifier, N_('Smart Proxy to fetch from'),
26
+ :required => true
27
+ def fetch
28
+ fetched = fetch_playbooks_names
29
+ render :json => { :results => { :playbooks_names => fetched } }
30
+ end
31
+
32
+ def action_permission
33
+ case params[:action]
34
+ when 'sync', 'fetch'
35
+ :import
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ def plan_ansible_sync(proxy_id, playbooks_names)
42
+ ForemanTasks.async_task(ImportPlaybooksJob::Async::SyncPlaybooks, proxy_id, playbooks_names)
43
+ end
44
+
45
+ private
46
+
47
+ def find_proxy
48
+ unless params[:proxy_id]
49
+ msg = _('Smart proxy id is required')
50
+ render_error('custom_error', :status => :unprocessable_entity, :locals => { :message => msg })
51
+ return false
52
+ end
53
+ @proxy = SmartProxy.find(params[:proxy_id])
54
+ end
55
+
56
+ def fetch_playbooks_names
57
+ proxy_api = find_proxy_api(@proxy)
58
+ proxy_api.playbooks_names if @proxy
59
+ end
60
+
61
+ def playbooks_names
62
+ params.fetch(:playbooks_names, [])
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ module ImportPlaybooksJob
2
+ module Async
3
+ class SyncPlaybooks < ::Actions::EntryAction
4
+ def plan(proxy_id, playbooks_names)
5
+ plan_self(proxy_id: proxy_id, playbooks_names: playbooks_names)
6
+ end
7
+
8
+ def run
9
+ playbooks_importer = ForemanAnsible::PlaybooksImporter.new(proxy)
10
+ output[:result] = playbooks_importer.import_playbooks(playbooks_names)
11
+ ForemanAnsible::ImportPlaybooksSuccessNotification.deliver!(task)
12
+ rescue StandardError => e
13
+ ForemanAnsible::ImportPlaybooksErrorNotification.new(e, task).deliver!
14
+ end
15
+
16
+ def proxy
17
+ SmartProxy.find(input[:proxy_id])
18
+ end
19
+
20
+ def playbooks_names
21
+ input[:playbooks_names]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -40,5 +40,18 @@ module ProxyAPI
40
40
  raise ProxyException.new(url, e,
41
41
  N_('Unable to get roles/variables from Ansible'))
42
42
  end
43
+
44
+ def playbooks_names
45
+ parse(get('playbooks_names'))
46
+ rescue *PROXY_ERRORS => e
47
+ raise ProxyException.new(url, e, N_('Unable to get playbook\'s names from Ansible'))
48
+ end
49
+
50
+ def playbooks(playbooks_names = [])
51
+ playbooks_names = playbooks_names.join(',')
52
+ parse(get("playbooks/#{playbooks_names}"))
53
+ rescue *PROXY_ERRORS => e
54
+ raise ProxyException.new(url, e, N_('Unable to get playbooks from Ansible'))
55
+ end
43
56
  end
44
57
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanAnsible
4
+ class ImportPlaybooksErrorNotification < ::UINotifications::Base
5
+ private
6
+
7
+ def initialize(error, task)
8
+ @job_error = error
9
+ @subject = task
10
+ end
11
+
12
+ def create
13
+ ::Notification.create!(
14
+ :audience => Notification::AUDIENCE_USER,
15
+ :notification_blueprint => blueprint,
16
+ :initiator => initiator,
17
+ :message => message,
18
+ :subject => subject,
19
+ :actions => {
20
+ :links => links
21
+ }
22
+ )
23
+ end
24
+
25
+ def blueprint
26
+ name = 'Sync_playbooks_failed'
27
+ @blueprint ||= NotificationBlueprint.unscoped.find_by(:name => name)
28
+ end
29
+
30
+ def message
31
+ _("Failed to import playbooks Due to: #{@job_error}")
32
+ end
33
+
34
+ def links
35
+ [{ :href => Rails.application.routes.url_helpers.foreman_tasks_task_path(:id => subject.id), :title => N_('Task Details') }]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanAnsible
4
+ class ImportPlaybooksSuccessNotification < ::UINotifications::Base
5
+ private
6
+
7
+ def create
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 = 'Sync_playbooks_successfully'
22
+ @blueprint ||= NotificationBlueprint.unscoped.find_by(:name => name)
23
+ end
24
+
25
+ def message
26
+ blueprint.message
27
+ end
28
+
29
+ def links
30
+ [{ :href => Rails.application.routes.url_helpers.foreman_tasks_task_path(:id => subject.id), :title => N_('Task Details') }]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanAnsible
4
+ # Imports playbooks from smart proxy
5
+ class PlaybooksImporter
6
+ include ::ForemanAnsible::ProxyAPI
7
+ delegate :playbooks, :playbooks_names, :to => :proxy_api
8
+
9
+ def initialize(proxy = nil)
10
+ @ansible_proxy = proxy
11
+ end
12
+
13
+ def import_playbooks(playbooks_names)
14
+ playbooks = playbooks(playbooks_names)
15
+ result = { created: {}, updated: {} }
16
+ playbooks.each do |playbook|
17
+ parsed_playbook = parse_playbook playbook
18
+ job_template_id = JobTemplate.where(name: parsed_playbook[:name]).pick(:id)
19
+ if job_template_id.present?
20
+ updated = update_job_template(job_template_id, parsed_playbook)
21
+ result[:updated].merge!(updated) unless updated.nil?
22
+ else
23
+ result[:created].merge!(create_job_template(parsed_playbook))
24
+ end
25
+ end
26
+ result
27
+ end
28
+
29
+ def parse_playbook(playbook)
30
+ content = playbook['playbooks_content']
31
+ {
32
+ name: playbook['name'],
33
+ playbook_content: metadata(playbook['name']) + content,
34
+ vars: get_vars(content)
35
+ }
36
+ end
37
+
38
+ def metadata(playbook_name)
39
+ <<~END_HEREDOC
40
+ <%#
41
+ name: #{playbook_name}
42
+ snippet: false
43
+ job_category: Ansible Playbook - Imported
44
+ provider_type: Ansible
45
+ kind: job_template
46
+ model: JobTemplate
47
+ %>
48
+ END_HEREDOC
49
+ end
50
+
51
+ def get_vars(playbook_content)
52
+ YAML.safe_load(playbook_content).map { |play| play['vars'] }
53
+ end
54
+
55
+ def create_job_template(playbook)
56
+ job_template = JobTemplate.create(name: playbook[:name], template: playbook[:playbook_content], job_category: 'Ansible Playbook - Imported', provider_type: 'Ansible')
57
+ # TODO: Add support for creating template inputs
58
+ job_template.organizations = Organization.unscoped.all
59
+ job_template.locations = Location.unscoped.all
60
+ job_template.save
61
+ { job_template.id => job_template.name }
62
+ end
63
+
64
+ def update_job_template(job_template_id, playbook)
65
+ # TODO: Add support for updating template inputs
66
+ inputs = []
67
+ job_template = JobTemplate.find(job_template_id)
68
+ should_update = !playbook[:playbook_content].eql?(job_template.template)
69
+ job_template.template = playbook[:playbook_content] if should_update
70
+ { job_template.id => job_template.name } unless inputs.empty? && !should_update
71
+ end
72
+ end
73
+ end
@@ -8,16 +8,15 @@ module ForemanAnsible
8
8
  included do
9
9
  attr_reader :ansible_proxy
10
10
 
11
- def find_proxy_api
11
+ def find_proxy_api(ansible_proxy)
12
12
  if ansible_proxy.blank?
13
13
  raise ::Foreman::Exception.new(N_('Proxy not found'))
14
14
  end
15
- @proxy_api = ::ProxyAPI::Ansible.new(:url => ansible_proxy.url)
15
+ ::ProxyAPI::Ansible.new(:url => ansible_proxy.url)
16
16
  end
17
17
 
18
18
  def proxy_api
19
- return @proxy_api if @proxy_api
20
- find_proxy_api
19
+ @proxy_api ||= find_proxy_api(ansible_proxy)
21
20
  end
22
21
  end
23
22
  end
@@ -9,6 +9,7 @@
9
9
  <th class="col-md-6"><%= sort :name, :as => s_("Role|Name") %></th>
10
10
  <th class="col-md-2"><%= _("Hostgroups") %></th>
11
11
  <th class="col-md-2"><%= _("Hosts") %></th>
12
+ <th class="col-md-2"><%= _("Variables") %></th>
12
13
  <th class="col-md-2"><%= sort :updated_at, :as => _("Imported at") %></th>
13
14
  <th class="col-md-2"><%= _("Actions") %></th>
14
15
  </tr>
@@ -19,6 +20,7 @@
19
20
  <td class="ellipsis"><%= role.name %></td>
20
21
  <td class="ellipsis"><%= role.hostgroups.count %></td>
21
22
  <td class="ellipsis"><%= link_to role.hosts.count, hosts_path(:search => "ansible_role = #{role.name}")%></td>
23
+ <td class="ellipsis"><%= link_to(role.ansible_variables.count, ansible_variables_path(:search => "ansible_role = #{role}")) %></td>
22
24
  <td class="ellipsis"><%= import_time role %></td>
23
25
  <td>
24
26
  <%
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ object @task
4
+
5
+ attributes :id, :action, :state, :result, :start_at
@@ -0,0 +1,160 @@
1
+ <%#
2
+ name: Manage Windows Updates - Ansible Default
3
+ snippet: false
4
+ template_inputs:
5
+ - name: reject_list
6
+ required: false
7
+ input_type: user
8
+ description: "A list of update titles or KB numbers that can be used to specify
9
+ which updates are to be excluded from installation.\r\nIf an available update
10
+ does match one of the entries, then it is skipped and not installed.\r\nEach entry
11
+ can either be the KB article or Update title as a regex according to the PowerShell
12
+ regex rules."
13
+ advanced: false
14
+ - name: category_names
15
+ required: false
16
+ input_type: user
17
+ description: "A scalar or list of categories to install updates from. To get the
18
+ list of categories, run the module with state=searched. The category must be the
19
+ full category string, but is case insensitive.\r\nSome possible categories are
20
+ Application, Connectors, Critical Updates, Definition Updates, Developer Kits,
21
+ Feature Packs, Guidance, Security Updates, Service Packs, Tools, Update Rollups
22
+ and Updates.\r\n\r\nDefault is '[\"CriticalUpdates\", \"SecurityUpdates\", \"UpdateRollups\"]'"
23
+ advanced: false
24
+ - name: log_path
25
+ required: false
26
+ input_type: user
27
+ description: If set, win_updates will append update progress to the specified file.
28
+ The directory must already exist.
29
+ advanced: false
30
+ - name: reboot
31
+ required: false
32
+ input_type: user
33
+ description: "Ansible will automatically reboot the remote host if it is required
34
+ and continue to install updates after the reboot.\r\nThis can be used instead
35
+ of using a win_reboot task after this one and ensures all updates for that category
36
+ is installed in one go.\r\nAsync does not work when reboot=true.\r\n\r\nDefault
37
+ is 'false'"
38
+ options: "false\r\ntrue"
39
+ advanced: false
40
+ - name: reboot_timeout
41
+ required: false
42
+ input_type: user
43
+ description: "The time in seconds to wait until the host is back online from a reboot.\r\nThis
44
+ is only used if reboot=true and a reboot is required.\r\n\r\nDefault is '1200'"
45
+ advanced: false
46
+ - name: server_selection
47
+ required: false
48
+ input_type: user
49
+ description: "Defines the Windows Update source catalog.\r\ndefault Use the default
50
+ search source. For many systems default is set to the Microsoft Windows Update
51
+ catalog. Systems participating in Windows Server Update Services (WSUS), Systems
52
+ Center Configuration Manager (SCCM), or similar corporate update server environments
53
+ may default to those managed update sources instead of the Windows Update catalog.\r\nmanaged_server
54
+ Use a managed server catalog. For environments utilizing Windows Server Update
55
+ Services (WSUS), Systems Center Configuration Manager (SCCM), or similar corporate
56
+ update servers, this option selects the defined corporate update source.\r\nwindows_update
57
+ Use the Microsoft Windows Update catalog.\r\n\r\nDefault is 'default'"
58
+ options: "default\r\nmanaged_server\r\nwindows_update"
59
+ advanced: false
60
+ - name: state
61
+ required: false
62
+ input_type: user
63
+ description: "Controls whether found updates are downloaded or installed or listed\r\nThis
64
+ module also supports Ansible check mode, which has the same effect as setting
65
+ state=searched\r\n\r\nDefault is 'searched'"
66
+ options: "installed\r\nsearched\r\ndownloaded"
67
+ advanced: false
68
+ - name: use_scheduled_task
69
+ required: false
70
+ input_type: user
71
+ description: "Will not auto elevate the remote process with become and use a scheduled
72
+ task instead.\r\nSet this to true when using this module with async on Server
73
+ 2008, 2008 R2, or Windows 7, or on Server 2008 that is not authenticated with
74
+ basic or credssp.\r\nCan also be set to true on newer hosts where become does
75
+ not work due to further privilege restrictions from the OS defaults."
76
+ options: "false\r\ntrue"
77
+ advanced: false
78
+ - name: whitelist
79
+ required: false
80
+ input_type: user
81
+ description: "A list of update titles or KB numbers that can be used to specify
82
+ which updates are to be searched or installed.\r\nIf an available update does
83
+ not match one of the entries, then it is skipped and not installed.\r\nEach entry
84
+ can either be the KB article or Update title as a regex according to the PowerShell
85
+ regex rules.\r\nThe whitelist is only validated on updates that were found based
86
+ on category_names. It will not force the module to install an update if it was
87
+ not in the category specified."
88
+ advanced: false
89
+ model: JobTemplate
90
+ job_category: Ansible Playbook
91
+ provider_type: Ansible
92
+ kind: job_template
93
+ organizations:
94
+ - My_Organization
95
+ locations:
96
+ - My_Location
97
+ %>
98
+
99
+ - hosts: all
100
+ vars:
101
+ updates: []
102
+ <% if input('state').blank? %>
103
+ state: searched
104
+ <% else %>
105
+ state: <%= input('state') %>
106
+ <% end %>
107
+
108
+ tasks:
109
+ - name: "{{ state | replace('ed','') | capitalize }} Windows Updates"
110
+ win_updates:
111
+ <% unless input('reject_list').blank? %>
112
+ blacklist: <%= input('reject_list') %>
113
+ <% end -%>
114
+ <% if input('category_names').blank? %>
115
+ category_names: ["CriticalUpdates", "SecurityUpdates", "UpdateRollups"]
116
+ <% else %>
117
+ category_names: <%= input('category_names') %>
118
+ <% end %>
119
+ <% unless input('log_path').blank? %>
120
+ log_path: <%= input('log_path') %>
121
+ <% end -%>
122
+ <% if input('reboot').blank? %>
123
+ reboot: false
124
+ <% else %>
125
+ reboot: <%= input('reboot') %>
126
+ <% end %>
127
+ <% if input('reboot_timeout').blank? %>
128
+ reboot_timeout: 1200
129
+ <% else %>
130
+ reboot_timeout: <%= input('reboot_timeout') %>
131
+ <% end -%>
132
+ <% if input('server_selection').blank? %>
133
+ server_selection: default
134
+ <% else %>
135
+ server_selection: <%= input('server_selection') %>
136
+ <% end %>
137
+ state: "{{ state }}"
138
+ <% if input('use_scheduled_task').blank? %>
139
+ use_scheduled_task: false
140
+ <% else %>
141
+ use_scheduled_task: <%= input('use_scheduled_task') %>
142
+ <% end %>
143
+ <% if !input('whitelist').blank? %>
144
+ whitelist: <%= input('whitelist') %>
145
+ <% end -%>
146
+
147
+ register: found_updates
148
+
149
+ - name: "Get all {{ state }} updates"
150
+ set_fact:
151
+ updates: "{{ updates + [ current_update ] }}"
152
+ vars:
153
+ current_update: "{{ item.value.title }}"
154
+ loop: "{{ found_updates.updates | dict2items }}"
155
+ no_log: true
156
+
157
+ - name: "List {{ state }} Windows Updates"
158
+ debug:
159
+ msg: "{{ item }}"
160
+ loop: "{{ updates }}"
@@ -0,0 +1,37 @@
1
+ <%#
2
+ name: Configure Cloud Connector
3
+ snippet: false
4
+ template_inputs:
5
+ - name: satellite_user
6
+ required: true
7
+ input_type: user
8
+ advanced: false
9
+ value_type: plain
10
+ hidden_value: false
11
+ - name: satellite_password
12
+ required: true
13
+ input_type: user
14
+ advanced: false
15
+ value_type: plain
16
+ hidden_value: true
17
+ - name: http_proxy
18
+ required: false
19
+ input_type: user
20
+ advanced: true
21
+ value_type: plain
22
+ hidden_value: false
23
+ description: You can specify a HTTP proxy address that should be used for Cloud Connector connection to the cloud.redhat.com. Note that it must be HTTP proxy, not HTTPS. The tunelling of SSL (secured web socket connection) in SSL (HTTPS proxy) is currently unsupported.
24
+ model: JobTemplate
25
+ job_category: Maintenance Operations
26
+ description_format: "%{template_name}"
27
+ provider_type: Ansible
28
+ kind: job_template
29
+ feature: ansible_configure_cloud_connector
30
+ %>
31
+
32
+ ---
33
+ - hosts: all
34
+ vars:
35
+ satellite_url: "<%= foreman_server_url %>"
36
+ roles:
37
+ - project-receptor.satellite_receptor_installer
data/config/routes.rb CHANGED
@@ -85,6 +85,13 @@ Rails.application.routes.draw do
85
85
  end
86
86
  end
87
87
 
88
+ resources :ansible_playbooks, :only => [] do
89
+ collection do
90
+ get :fetch
91
+ put :sync
92
+ end
93
+ end
94
+
88
95
  resources :ansible_variables, :only => [:show, :index, :destroy, :update, :create] do
89
96
  collection do
90
97
  put :import
@@ -31,7 +31,21 @@ blueprints = [
31
31
  :name => 'Sync_roles_and_variables_failed',
32
32
  :message => 'DYNAMIC',
33
33
  :level => 'error'
34
+ },
35
+ {
36
+ :group => N_('Playbooks'),
37
+ :name => 'Sync_playbooks_successfully',
38
+ :message => N_('Import playbooks has finished successfully'),
39
+ :level => 'success'
40
+
41
+ },
42
+ {
43
+ :group => N_('Playbooks'),
44
+ :name => 'Sync_playbooks_failed',
45
+ :message => 'DYNAMIC',
46
+ :level => 'error'
34
47
  }
35
48
 
36
49
  ]
50
+
37
51
  blueprints.each { |blueprint| UINotifications::Seed.new(blueprint).configure }
@@ -158,6 +158,8 @@ Foreman::Plugin.register :foreman_ansible do
158
158
  :resource_type => 'Hostgroup'
159
159
  permission :generate_ansible_inventory,
160
160
  { :'api/v2/ansible_inventories' => [:schedule] }
161
+ permission :import_ansible_playbooks,
162
+ { :'api/v2/ansible_playbooks' => [:sync, :fetch] }
161
163
  end
162
164
 
163
165
  role 'Ansible Roles Manager',
@@ -167,7 +169,7 @@ Foreman::Plugin.register :foreman_ansible do
167
169
  :view_ansible_roles, :destroy_ansible_roles,
168
170
  :import_ansible_roles, :view_ansible_variables,
169
171
  :create_ansible_variables, :import_ansible_variables,
170
- :edit_ansible_variables, :destroy_ansible_variables]
172
+ :edit_ansible_variables, :destroy_ansible_variables, :import_ansible_playbooks]
171
173
 
172
174
  role 'Ansible Tower Inventory Reader',
173
175
  [:view_hosts, :view_hostgroups, :view_facts, :generate_report_templates, :generate_ansible_inventory,
@@ -48,6 +48,12 @@ module ForemanAnsible
48
48
  :description => N_('Upgrade Capsules on given Capsule server hosts'),
49
49
  :proxy_selector_override => ::RemoteExecutionProxySelector::INTERNAL_PROXY
50
50
  )
51
+ RemoteExecutionFeature.register(
52
+ :ansible_configure_cloud_connector,
53
+ N_('Configure Cloud Connector on given hosts'),
54
+ :description => N_('Configure Cloud Connector on given hosts'),
55
+ :proxy_selector_override => ::RemoteExecutionProxySelector::INTERNAL_PROXY
56
+ )
51
57
  end
52
58
  end
53
59
  end
@@ -4,5 +4,5 @@
4
4
  # This way other parts of Foreman can just call ForemanAnsible::VERSION
5
5
  # and detect what version the plugin is running.
6
6
  module ForemanAnsible
7
- VERSION = '7.0.4'
7
+ VERSION = '7.1.0'
8
8
  end
@@ -0,0 +1 @@
1
+ { "template": "<%#\n name: xprazak2.forklift_collection.foreman_provisioning.yml\n snippet: false\n job_category: Ansible Playbook - Imported\n provider_type: Ansible\n kind: job_template\n model: JobTemplate\n%>\n- hosts: all\n become: true\n vars:\n libvirt_tftp: true\n roles:\n - foreman\n - libvirt\n - foreman_provisioning\n"}
@@ -0,0 +1,10 @@
1
+ [
2
+ {
3
+ "name": "xprazak2.forklift_collection.foreman_provisioning.yml",
4
+ "playbooks_content": "- hosts: all\n become: true\n vars:\n libvirt_tftp: true\n roles:\n - foreman\n - libvirt\n - foreman_provisioning\n"
5
+ },
6
+ {
7
+ "name": "xprazak2.forklift_collection.collect_debug_draft.yml",
8
+ "playbooks_content": "---\n- hosts: all\n become: true\n vars:\n bats_output_dir: '/root/bats_results'\n remote_dir: \"/tmp/debug-{{ pipeline_type | default('foreman') }}-{{ pipeline_version | default('nightly') }}-{{ pipeline_os | default('el7') }}\"\n roles:\n - sos_report\n tasks:\n - name: \"Find bats files\"\n find:\n paths: \"{{ bats_output_dir }}\"\n patterns: \"*.tap\"\n register: bats_results\n\n - name: \"Copy bats results\"\n fetch:\n src: \"{{ item.path }}\"\n dest: \"{{ remote_dir }}\"\n with_items: \"{{ bats_results.files }}\"\n\n - name: \"Find smoker files\"\n find:\n paths: \"{{ smoker_output_dir }}\"\n patterns: \"*.xml\"\n register: smoker_results\n\n - name: \"Copy smoker results\"\n fetch:\n src: \"{{ item.path }}\"\n dest: \"{{ remote_dir }}\"\n with_items: \"{{ smoker_results.files }}\"\n"
9
+ }
10
+ ]
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ module Api
6
+ module V2
7
+ # Tests for the ansible playbooks controller
8
+ class AnsiblePlaybooksControllerTest < ActionController::TestCase
9
+ let(:ansible_proxy) { FactoryBot.create(:smart_proxy, :with_ansible) }
10
+ let(:proxy_api) { ::ProxyAPI::Ansible.new(url: ansible_proxy.url) }
11
+ let(:playbooks_names) { %w[xprazak2.forklift_collection.foreman_provisioning.yml xprazak2.forklift_collection.collect_debug_draft.yml] }
12
+ let(:importer_test) { mock('PlaybooksImporter') }
13
+
14
+ test 'should fetch with nil' do
15
+ AnsiblePlaybooksController.any_instance.stubs(:fetch_playbooks_names).returns(nil)
16
+
17
+ get :fetch, :params => {
18
+ :proxy_id => ansible_proxy.id
19
+ }, :session => set_session_user
20
+ response = JSON.parse(@response.body)
21
+ assert_empty response['results']['playbooks_names']
22
+ assert_response :success
23
+ end
24
+
25
+ test 'should fetch playbooks names' do
26
+ AnsiblePlaybooksController.any_instance.stubs(:fetch_playbooks_names).returns(playbooks_names)
27
+
28
+ get :fetch, :params => {
29
+ :proxy_id => ansible_proxy.id
30
+ }, :session => set_session_user
31
+ response = JSON.parse(@response.body)
32
+ assert_not_empty response['results']
33
+ assert_equal response['results']['playbooks_names'], playbooks_names
34
+ end
35
+
36
+ test 'should use correct proxy' do
37
+ AnsiblePlaybooksController.any_instance.expects(:find_proxy_api).with(ansible_proxy).returns(proxy_api)
38
+ proxy_api.expects(:playbooks_names).returns(playbooks_names)
39
+
40
+ get :fetch, :params => {
41
+ :proxy_id => ansible_proxy.id
42
+ }, :session => set_session_user
43
+ response = JSON.parse(@response.body)
44
+ assert_not_empty response['results']
45
+ assert_not_empty playbooks_names = response['results']['playbooks_names']
46
+ assert_equal 2, playbooks_names.count
47
+ end
48
+
49
+ test 'check plan and sync' do
50
+ task = mock('Foreman Task')
51
+ AnsiblePlaybooksController.any_instance.expects(:plan_ansible_sync).with(ansible_proxy.id, playbooks_names).returns(task)
52
+ task.stubs(:id).returns('123')
53
+ task.stubs(:action).returns('Import Playbooks')
54
+
55
+ put :sync, :params => {
56
+ :proxy_id => ansible_proxy.id,
57
+ :playbooks_names => playbooks_names
58
+ }, :session => set_session_user
59
+ response = JSON.parse(@response.body)
60
+ assert_equal '123', response['id']
61
+ assert_equal 'Import Playbooks', response['action']
62
+ end
63
+ end
64
+ end
65
+ end