foreman_acd 0.6.0 → 0.7.0

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_acd/ansible_playbooks_controller.rb +75 -0
  3. data/app/controllers/foreman_acd/app_instances_controller.rb +19 -2
  4. data/app/controllers/foreman_acd/concerns/ansible_playbook_parameters.rb +1 -1
  5. data/app/controllers/foreman_acd/remote_execution_controller.rb +37 -21
  6. data/app/lib/actions/foreman_acd/deploy_all_hosts.rb +12 -7
  7. data/app/lib/actions/foreman_acd/run_configurator.rb +10 -7
  8. data/app/models/concerns/foreman_acd/host_managed_extensions.rb +2 -2
  9. data/app/models/foreman_acd/acd_provider.rb +7 -1
  10. data/app/models/foreman_acd/ansible_playbook.rb +2 -1
  11. data/app/models/foreman_acd/app_instance.rb +1 -1
  12. data/app/services/foreman_acd/acd_proxy_proxy_selector.rb +17 -0
  13. data/app/services/foreman_acd/app_configurator.rb +59 -15
  14. data/app/services/foreman_acd/app_deployer.rb +8 -2
  15. data/app/services/foreman_acd/inventory_creator.rb +14 -0
  16. data/app/views/foreman_acd/ansible_playbooks/_form.html.erb +41 -7
  17. data/app/views/foreman_acd/app_definitions/_form.html.erb +1 -1
  18. data/app/views/foreman_acd/app_instances/_form.html.erb +1 -1
  19. data/app/views/foreman_acd/app_instances/index.html.erb +5 -3
  20. data/app/views/foreman_acd/app_instances/report.html.erb +1 -1
  21. data/app/views/templates/job/run_acd_ansible_playbook.erb +1 -1
  22. data/config/routes.rb +3 -0
  23. data/db/migrate/20210316151145_add_git_commit_to_ansible_playbooks.rb +8 -0
  24. data/db/migrate/20210503122809_add_git_url_to_ansible_playbooks.rb +8 -0
  25. data/db/seeds.d/75-job_templates.rb +1 -1
  26. data/lib/foreman_acd.rb +12 -0
  27. data/lib/foreman_acd/engine.rb +26 -4
  28. data/lib/foreman_acd/plugin.rb +0 -9
  29. data/lib/foreman_acd/version.rb +1 -1
  30. data/lib/tasks/foreman_acd_tasks.rake +0 -12
  31. data/package.json +8 -8
  32. data/test/controllers/ansible_playbooks_controller_test.rb +1 -1
  33. data/test/controllers/app_instances_controller_test.rb +8 -3
  34. data/test/controllers/ui_acd_controller_test.rb +22 -6
  35. data/test/factories/foreman_acd_factories.rb +18 -4
  36. data/test/models/acd_provider_test.rb +37 -0
  37. data/test/models/ansible_playbook_test.rb +11 -0
  38. data/test/models/app_definition_test.rb +1 -1
  39. data/test/models/app_instance_test.rb +2 -0
  40. data/test/models/concerns/host_extensions_test.rb +26 -0
  41. data/test/models/foreman_host_test.rb +12 -0
  42. data/webpack/__mocks__/foremanReact/API.js +2 -0
  43. data/webpack/__mocks__/foremanReact/common/I18n.js +3 -0
  44. data/webpack/__mocks__/foremanReact/common/helpers.js +2 -0
  45. data/webpack/__mocks__/foremanReact/components/ForemanModal.js +7 -0
  46. data/webpack/__mocks__/foremanReact/components/common/forms/CommonForm.js +2 -0
  47. data/webpack/__mocks__/foremanReact/components/common/forms/TextInput.js +2 -0
  48. data/webpack/__mocks__/foremanReact/components/hosts/powerStatus.js +1 -0
  49. data/webpack/__snapshots__/helper.test.js.snap +14 -0
  50. data/webpack/components/ApplicationDefinition/ApplicationDefinition.js +1 -1
  51. data/webpack/components/ApplicationDefinition/__fixtures__/applicationDefinitionConfData_1.fixtures.js +288 -0
  52. data/webpack/components/ApplicationDefinition/__fixtures__/applicationDefinitionReducer.fixtures.js +79 -0
  53. data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinition.test.js +25 -0
  54. data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinitionReducer.test.js +119 -0
  55. data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinitionSelectors.test.js +41 -0
  56. data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinition.test.js.snap +200 -0
  57. data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinitionReducer.test.js.snap +3033 -0
  58. data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinitionSelectors.test.js.snap +299 -0
  59. data/webpack/components/ApplicationDefinition/components/AnsiblePlaybookSelector.js +1 -0
  60. data/webpack/components/ApplicationDefinition/components/__tests__/AnsiblePlaybookSelector.test.js +41 -0
  61. data/webpack/components/ApplicationDefinition/components/__tests__/__snapshots__/AnsiblePlaybookSelector.test.js.snap +121 -0
  62. data/webpack/components/ApplicationInstance/ApplicationInstance.js +3 -5
  63. data/webpack/components/ApplicationInstance/__fixtures__/applicationInstanceConfData_1.fixtures.js +263 -0
  64. data/webpack/components/ApplicationInstance/__fixtures__/applicationInstanceReducer.fixtures.js +78 -0
  65. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstance.test.js +23 -0
  66. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstanceReducer.test.js +119 -0
  67. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstanceSelectors.test.js +44 -0
  68. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstance.test.js.snap +209 -0
  69. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstanceReducer.test.js.snap +2719 -0
  70. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstanceSelectors.test.js.snap +276 -0
  71. data/webpack/components/ApplicationInstanceReport/__fixtures__/applicationInstanceReportData_1.fixtures.js +349 -0
  72. data/webpack/components/ApplicationInstanceReport/__fixtures__/applicationInstanceReportReducer.fixtures.js +20 -0
  73. data/webpack/components/ApplicationInstanceReport/__tests__/ApplicationInstanceReport.test.js +47 -0
  74. data/webpack/components/ApplicationInstanceReport/__tests__/ApplicationInstanceReportReducer.test.js +41 -0
  75. data/webpack/components/ApplicationInstanceReport/__tests__/ApplicationInstanceReportSelectors.test.js +26 -0
  76. data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReport.test.js.snap +130 -0
  77. data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReportReducer.test.js.snap +718 -0
  78. data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReportSelectors.test.js.snap +347 -0
  79. data/webpack/components/ApplicationInstanceReport/components/__tests__/ReportViewer.test.js +24 -0
  80. data/webpack/components/ApplicationInstanceReport/components/__tests__/__snapshots__/ReportViewer.test.js.snap +24 -0
  81. data/webpack/components/ParameterSelection/ParameterSelectionReducer.js +2 -21
  82. data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionData_1.fixtures.js +116 -84
  83. data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionReducer.fixtures.js +10 -4
  84. data/webpack/components/ParameterSelection/__tests__/ParameterSelection.test.js +36 -46
  85. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionReducer.test.js +31 -25
  86. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionSelectors.test.js +6 -6
  87. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelection.test.js.snap +2 -126
  88. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionReducer.test.js.snap +1483 -872
  89. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionSelectors.test.js.snap +117 -79
  90. data/webpack/components/SyncGitRepo/SyncGitRepo.js +210 -0
  91. data/webpack/components/SyncGitRepo/SyncGitRepo.scss +1 -0
  92. data/webpack/components/SyncGitRepo/SyncGitRepoActions.js +124 -0
  93. data/webpack/components/SyncGitRepo/SyncGitRepoConstants.js +9 -0
  94. data/webpack/components/SyncGitRepo/SyncGitRepoReducer.js +80 -0
  95. data/webpack/components/SyncGitRepo/SyncGitRepoSelectors.js +6 -0
  96. data/webpack/components/SyncGitRepo/__fixtures__/syncGitRepoConfData_1.fixtures.js +7 -0
  97. data/webpack/components/SyncGitRepo/__fixtures__/syncGitRepoReducer.fixtures.js +44 -0
  98. data/webpack/components/SyncGitRepo/__tests__/SyncGitRepo.test.js +27 -0
  99. data/webpack/components/SyncGitRepo/__tests__/SyncGitRepoReducer.test.js +95 -0
  100. data/webpack/components/SyncGitRepo/__tests__/SyncGitRepoSelectors.test.js +32 -0
  101. data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepo.test.js.snap +30 -0
  102. data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepoReducer.test.js.snap +137 -0
  103. data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepoSelectors.test.js.snap +13 -0
  104. data/webpack/components/SyncGitRepo/components/FormTextInput.js +42 -0
  105. data/webpack/components/SyncGitRepo/components/ScmTypeSelector.js +46 -0
  106. data/webpack/components/SyncGitRepo/index.js +28 -0
  107. data/webpack/components/common/ExtTextInput.js +43 -0
  108. data/webpack/components/common/__tests__/EditTableEntry.test.js +53 -0
  109. data/webpack/components/common/__tests__/LockTableEntry.test.js +35 -0
  110. data/webpack/components/common/__tests__/__snapshots__/DeleteTableEntry.test.js.snap +2 -2
  111. data/webpack/components/common/__tests__/__snapshots__/EditTableEntry.test.js.snap +81 -0
  112. data/webpack/components/common/__tests__/__snapshots__/LockTableEntry.test.js.snap +60 -0
  113. data/webpack/helper.js +15 -1
  114. data/webpack/helper.test.js +37 -0
  115. data/webpack/index.js +2 -0
  116. data/webpack/reducer.js +4 -0
  117. metadata +92 -11
  118. data/webpack/components/common/EasyHeaderFormatter.js +0 -18
  119. data/webpack/components/common/__tests__/__snapshots__/AddParameter.test.js.snap +0 -35
  120. data/webpack/components/common/__tests__/__snapshots__/DeleteParameter.test.js.snap +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 809d34cc7063d66a26a1c0c913cd95287a6329cce55a75c1684046bb47b1afe1
4
- data.tar.gz: d32aa387545b3fac503996354baae6704c30118a923d95706668895cad6440f5
3
+ metadata.gz: 7b616c7ba21eea339af8509f618585dcd949ddead6ecdec57278a62c751e4fd2
4
+ data.tar.gz: 75c7d2e2c07d306dee127bc50c6b300934f90066bc4a491e49f17f65ac6b7c24
5
5
  SHA512:
6
- metadata.gz: 996b0c911e71e045b232b3d6b27776eaa9c7e6c52f23f78d29c911f899d4a60024990fa8866e2f401e2d91f1c2a0c06a7264f16ffc2e4bd0b97c0e794a0a7c63
7
- data.tar.gz: 445dc99ec810e7fbbdf33d2c1e69b3a60942c58938212105033e472981bee85e626ce51d7e2f36359bbfb9148dca240fff134d183481d1d8312a3e9f933f4815
6
+ metadata.gz: bba62637508bbbd4e35772a55ad1126fea7a8cb3c1658c6f734b02ccf03d44048db40fa621bb51e796220668e7a49e8b03c3169a77b818f6dbbde117d09edfe2
7
+ data.tar.gz: a6e4ba1bb97250e8bcebe6180abcf4755859b73828febda64353b47021acadcc33fd99e2ab4b45ac965e5d643c368bbfb47aee421a80979c7a4c69354a18b297
@@ -7,6 +7,7 @@ module ForemanAcd
7
7
  include ::ForemanAcd::Concerns::AnsiblePlaybookParameters
8
8
 
9
9
  before_action :find_resource, :only => [:edit, :update, :destroy, :import_vars]
10
+ after_action :delete_synced_repo, :only => [:new, :edit, :create, :update, :destroy, :index]
10
11
 
11
12
  def index
12
13
  @ansible_playbooks = resource_base.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page])
@@ -18,6 +19,11 @@ module ForemanAcd
18
19
 
19
20
  def create
20
21
  @ansible_playbook = AnsiblePlaybook.new(ansible_playbook_params)
22
+ if session[:git_path]
23
+ @ansible_playbook.update(:path => ansible_playbook_full_path(ansible_playbook_rename(@ansible_playbook[:name])))
24
+ FileUtils.mv(session[:git_path], @ansible_playbook[:path])
25
+ session[:git_path] = nil
26
+ end
21
27
  if @ansible_playbook.save
22
28
  process_success :success_msg => _("Successfully created %s. You need to press the \"Import groups\" button
23
29
  before this ansible playbook can be used in App Definitions!") % @ansible_playbook
@@ -29,6 +35,20 @@ module ForemanAcd
29
35
  def edit; end
30
36
 
31
37
  def update
38
+ # Move synced repo to new path if ansible_playbook name is changed
39
+ if !session[:git_path].nil? && ansible_playbook_params[:name] != @ansible_playbook[:name]
40
+ FileUtils.mv(@ansible_playbook[:path], ansible_playbook_full_path(ansible_playbook_rename(ansible_playbook_params[:name])))
41
+ @ansible_playbook.update(:path => ansible_playbook_full_path(ansible_playbook_rename(ansible_playbook_params[:name])))
42
+ session[:git_path] = nil
43
+
44
+ # Remove old version and copy new version of synced repository
45
+ elsif !session[:git_path].nil? && ansible_playbook_params[:name] == @ansible_playbook.name
46
+ remove_ansible_dir(@ansible_playbook[:path]) if @ansible_playbook.path
47
+ @ansible_playbook.update(:path => ansible_playbook_full_path(ansible_playbook_rename(@ansible_playbook[:name])))
48
+ FileUtils.mv(session[:git_path], @ansible_playbook[:path])
49
+ session[:git_path] = nil
50
+ end
51
+
32
52
  if @ansible_playbook.update(ansible_playbook_params)
33
53
  process_success
34
54
  else
@@ -44,10 +64,51 @@ module ForemanAcd
44
64
  end
45
65
  end
46
66
 
67
+ def sync_git_repo
68
+ @ansible_playbook = AnsiblePlaybook.new
69
+ sync_params = params[:ansible_playbook]
70
+ dir = Dir.mktmpdir
71
+
72
+ begin
73
+ git = Git.init(dir)
74
+
75
+ if ForemanAcd.proxy_setting.present?
76
+ git.config('http.proxy', ForemanAcd.proxy_setting)
77
+ logger.info("HTTP Proxy used: #{git.config['http.proxy']}")
78
+ end
79
+
80
+ git.add_remote('origin', sync_params[:git_url])
81
+ git.fetch
82
+ git.checkout(sync_params[:git_commit])
83
+
84
+ session[:git_path] = git.dir.path
85
+ rescue StandardError => e
86
+ logger.error("Failed to sync git repository: #{e}")
87
+ render :json => { :status => 'error', :message => e }, :status => :internal_server_error
88
+ end
89
+ end
90
+
91
+ # Remove abandoned synced git repositories
92
+ def delete_synced_repo
93
+ names = []
94
+ AnsiblePlaybook.all.each do |ansible_playbook|
95
+ names.push(ansible_playbook_rename(ansible_playbook.name))
96
+ end
97
+ names.push('.', '..')
98
+ return unless Dir.exist?(ForemanAcd.ansible_playbook_path)
99
+ Dir.foreach(ForemanAcd.ansible_playbook_path) do |dirname|
100
+ next if names.include? dirname
101
+ remove_ansible_dir(ansible_playbook_full_path(dirname))
102
+ logger.info("Successfully removed #{dirname}")
103
+ end
104
+ end
105
+
47
106
  def action_permission
48
107
  case params[:action]
49
108
  when 'import_vars'
50
109
  :import_vars
110
+ when 'sync_git_repo'
111
+ :sync_git_repo
51
112
  when 'grab'
52
113
  :grab
53
114
  else
@@ -120,5 +181,19 @@ module ForemanAcd
120
181
  process_error :error_msg => _(errors.join(' ')), :redirect => ansible_playbooks_path
121
182
  end
122
183
  end
184
+
185
+ private
186
+
187
+ def ansible_playbook_rename(name)
188
+ name.split(/\W+/).join('_')
189
+ end
190
+
191
+ def remove_ansible_dir(dirpath)
192
+ FileUtils.remove_dir(dirpath) if Dir.exist?(dirpath)
193
+ end
194
+
195
+ def ansible_playbook_full_path(dirname)
196
+ File.join(ForemanAcd.ansible_playbook_path, dirname)
197
+ end
123
198
  end
124
199
  end
@@ -72,10 +72,12 @@ module ForemanAcd
72
72
  end
73
73
 
74
74
  def deploy
75
+ value = false
75
76
  @app_instance.clean_all_hosts if params[:delete_hosts]
76
-
77
+ value = safe_deploy? if params[:safe_deploy]
78
+ session.delete(:remember_hosts)
77
79
  logger.info('Run async foreman task to deploy hosts')
78
- async_task = ForemanTasks.async_task(::Actions::ForemanAcd::DeployAllHosts, @app_instance)
80
+ async_task = ForemanTasks.async_task(::Actions::ForemanAcd::DeployAllHosts, @app_instance, value)
79
81
  @app_instance.update!(:last_deploy_task => async_task)
80
82
  process_success(:success_msg => _('Started task to deploy hosts for %s') % @app_instance)
81
83
  rescue StandardError => e
@@ -84,6 +86,11 @@ module ForemanAcd
84
86
  process_error :error_msg => error_msg
85
87
  end
86
88
 
89
+ def safe_deploy?
90
+ return false if session[:remember_hosts].empty?
91
+ session[:remember_hosts]
92
+ end
93
+
87
94
  def report
88
95
  @report_hosts = collect_host_report_data
89
96
  logger.debug("app instance host details: #{@report_hosts.inspect}")
@@ -91,14 +98,24 @@ module ForemanAcd
91
98
 
92
99
  def app_instance_has_foreman_hosts
93
100
  hosts = JSON.parse(@app_instance.hosts)
101
+ session[:remember_hosts] = []
94
102
  hosts.each do |h|
95
103
  if @app_instance.foreman_hosts.where(:hostname => h['hostname']).exists?
104
+ old_host = @app_instance.foreman_hosts.find_by(:hostname => h['hostname'])
105
+
96
106
  @app_instance.foreman_hosts.where(:hostname => h['hostname']).
97
107
  update(:service => h['service'], :description => h['description'],
98
108
  :foremanParameters => JSON.dump(h['foremanParameters']), :ansibleParameters => JSON.dump(h['ansibleParameters']))
109
+
110
+ updated_host = @app_instance.foreman_hosts.find_by(:hostname => h['hostname'])
111
+
112
+ # Store hosts if updated for safe deploy
113
+ session[:remember_hosts] << updated_host.id if updated_host.updated_at != old_host.updated_at
99
114
  else
100
115
  @app_instance.foreman_hosts.create(:hostname => h['hostname'], :service => h['service'], :description => h['description'],
101
116
  :foremanParameters => JSON.dump(h['foremanParameters']), :ansibleParameters => JSON.dump(h['ansibleParameters']))
117
+ # Store new hosts for safe deploy
118
+ session[:remember_hosts] << @app_instance.foreman_hosts.find_by(:hostname => h['hostname']).id
102
119
  end
103
120
  end
104
121
 
@@ -9,7 +9,7 @@ module ForemanAcd
9
9
  class_methods do
10
10
  def ansible_playbook_params_filter
11
11
  Foreman::ParameterFilter.new(::ForemanAcd::AnsiblePlaybook).tap do |filter|
12
- filter.permit(:name, :description, :scm_type, :path, :playfile, :location_ids => [], :organization_ids => [])
12
+ filter.permit(:name, :description, :scm_type, :path, :git_commit, :git_url, :playfile, :location_ids => [], :organization_ids => [])
13
13
  end
14
14
  end
15
15
  end
@@ -4,25 +4,40 @@ module ForemanAcd
4
4
  # Class to run remote execution jobs
5
5
  class RemoteExecutionController < JobInvocationsController
6
6
  def new
7
- jobs = init_configuration
8
- @composer = jobs.first
7
+ set_app_instance
8
+ result, job = init_configuration
9
+
10
+ if result.success == true
11
+ @composer = job
12
+ else
13
+ redirect_to(app_instances_path, :error => _("Coult not create remote execution job to configure the app '%{app_instance}': %{msg}") % {
14
+ :app_instance => @app_instance, :msg => result.error
15
+ })
16
+ end
9
17
  end
10
18
 
11
19
  def create
12
20
  customize_first = params[:customize] || false
13
- jobs = init_configuration
14
-
15
- if jobs.count == 1 && customize_first == false
16
- @composer = jobs.first
17
- @composer.trigger!
18
- redirect_to job_invocation_path(@composer.job_invocation)
19
- elsif customize_first == false
20
- jobs.each(&:trigger)
21
- redirect_to job_invocations_path
22
- else
23
- # redirect to the job itself if we want to customize the job
24
- @composer = jobs.first
25
- render :action => 'new'
21
+ begin
22
+ set_app_instance
23
+ result, job = init_configuration
24
+
25
+ unless result.success == true
26
+ return redirect_to(app_instances_path, :error => _("Coult not create remote execution job to configure the app '%{app_instance}': %{msg}") % {
27
+ :app_instance => @app_instance, :msg => result.error
28
+ })
29
+ end
30
+
31
+ @composer = job
32
+ if customize_first == false
33
+ @composer.trigger!
34
+ redirect_to job_invocation_path(@composer.job_invocation)
35
+ else
36
+ # redirect to the job itself if we want to customize the job
37
+ render :action => 'new'
38
+ end
39
+ rescue StandardError => e
40
+ redirect_to app_instances_path, :error => _("#{job}, #{e}")
26
41
  end
27
42
  end
28
43
 
@@ -34,13 +49,14 @@ module ForemanAcd
34
49
 
35
50
  private
36
51
 
37
- def init_configuration
38
- app_instance = ForemanAcd::AppInstance.find_by(:id => params[:id])
39
- app_configurator = ForemanAcd::AppConfigurator.new(app_instance)
52
+ def set_app_instance
53
+ @app_instance = ForemanAcd::AppInstance.find_by(:id => params[:id])
54
+ end
40
55
 
41
- jobs = app_configurator.configure
42
- logger.debug("Creating #{jobs.count} job(s) to configure the app #{app_instance}. Customize first: #{params[:customize]}")
43
- jobs
56
+ def init_configuration
57
+ logger.debug("Creating job to configure the app #{@app_instance}. Customize first: #{params[:customize]}")
58
+ app_configurator = ForemanAcd::AppConfigurator.new(@app_instance)
59
+ app_configurator.configure
44
60
  end
45
61
  end
46
62
  end
@@ -4,27 +4,32 @@ module Actions
4
4
  module ForemanAcd
5
5
  # DeployAllHosts implements a Foreman Task EntryAction
6
6
  class DeployAllHosts < Actions::EntryAction
7
- def plan(app_instance)
8
- action_subject(app_instance)
7
+ def plan(app_instance, safe_deploy)
8
+ action_subject(app_instance, :safe_deploy => safe_deploy)
9
9
  plan_self(:id => app_instance.id)
10
10
  end
11
11
 
12
12
  def run
13
13
  output[:status] = 'IN PROGRESS'
14
14
  app_instance = ::ForemanAcd::AppInstance.find(input.fetch(:id))
15
+ safe_deploy = input.fetch(:safe_deploy)
15
16
 
16
- # Goal: all or nothing
17
+ # Goal: all, safe_deploy or nothing
17
18
  begin
18
- ::Foreman::Logging.logger('foreman_acd').info "Start to deploy all hosts of the app #{app_instance}"
19
+ if safe_deploy
20
+ ::Foreman::Logging.logger('foreman_acd').info "Start to safe deploy hosts of the app #{app_instance}"
21
+ else
22
+ ::Foreman::Logging.logger('foreman_acd').info "Start to deploy all hosts of the app #{app_instance}"
23
+ end
19
24
  app_deployer = ::ForemanAcd::AppDeployer.new(app_instance)
20
- output[:data] = app_deployer.deploy
25
+ output[:data] = app_deployer.deploy(safe_deploy)
21
26
  output[:status] = 'SUCCESS'
22
27
  rescue StandardError => e
23
- ::Foreman::Logging.logger('foreman_acd').error "Error while deploying hosts for application instance. Clean up all other hosts: #{e}"
28
+ ::Foreman::Logging.logger('foreman_acd').error "Error while deploying hosts for application instance '#{app_instance.name}'. Clean up all other hosts: #{e}"
24
29
  app_instance.clean_all_hosts
25
30
 
26
- output[:error] = e.to_s
27
31
  output[:status] = 'FAILURE'
32
+ raise "Error while deploying hosts for application instance '#{app_instance.name}': (#{e.message})"
28
33
  end
29
34
  end
30
35
 
@@ -15,15 +15,18 @@ module Actions
15
15
  app_instance = ::ForemanAcd::AppInstance.find(input.fetch(:id))
16
16
  app_configurator = ::ForemanAcd::AppConfigurator.new(app_instance)
17
17
 
18
- jobs = app_configurator.configure
19
- ::Foreman::Logging.logger('foreman_acd').info "Creating #{jobs.count} job(s) to configure the app #{app_instance}"
20
-
21
- # TODO: would it be better to create a sub task for each job we need to run?
22
- jobs.each(&:trigger)
18
+ result, job = app_configurator.configure
19
+ if result.success
20
+ ::Foreman::Logging.logger('foreman_acd').info "Creating job to configure the app #{app_instance}"
21
+ job.trigger!
22
+ else
23
+ ::Foreman::Logging.logger('foreman_acd').error "Could not create the job to configure the app #{app_instance}: #{result.error}"
24
+ end
23
25
  rescue StandardError => e
24
- ::Foreman::Logging.logger('foreman_acd').error "Error while configuring application instance: #{e}"
25
- output[:error] = e.to_s
26
+ ::Foreman::Logging.logger('foreman_acd').error "Error while configuring application instance '#{app_instance.name}': #{e}"
27
+
26
28
  output[:status] = 'FAILURE'
29
+ raise "Error while configuring hosts via ansible playbook for application instance '#{app_instance.name}': (#{e.message})"
27
30
  end
28
31
  end
29
32
 
@@ -13,13 +13,13 @@ module ForemanAcd
13
13
  end
14
14
  end
15
15
 
16
- private
17
-
18
16
  def deployed_via_acd?
19
17
  find_app_instance_host
20
18
  @app_instance_host.present?
21
19
  end
22
20
 
21
+ private
22
+
23
23
  def find_app_instance_host
24
24
  @app_instance_host = ForemanAcd::ForemanHost.find_by(:host_id => id)
25
25
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ForemanAcd
4
4
  # Implement a RemoteExecutionProvider
5
- class AcdProvider < RemoteExecutionProvider
5
+ class AcdProvider < ::RemoteExecutionProvider
6
6
  class << self
7
7
  def supports_effective_user?
8
8
  true
@@ -25,6 +25,12 @@ module ForemanAcd
25
25
  def ssh_key_passphrase(_host); end
26
26
 
27
27
  def sudo_password(_host); end
28
+
29
+ # Workaround till infrastructure jobs on proxies are possible. See
30
+ # configure in services/foreman_acd/app_configurator.rb for more details.
31
+ # def required_proxy_selector_for(_template)
32
+ # AcdProxyProxySelector.new
33
+ # end
28
34
  end
29
35
  end
30
36
  end
@@ -12,6 +12,7 @@ module ForemanAcd
12
12
  self.table_name = 'acd_ansible_playbooks'
13
13
  has_many :app_definitions, :inverse_of => :ansible_playbook, :foreign_key => 'acd_ansible_playbook_id', :dependent => :restrict_with_error
14
14
  validates :name, :presence => true, :uniqueness => true
15
+ validates :scm_type, :presence => true
15
16
  scoped_search :on => :name
16
17
 
17
18
  default_scope do
@@ -44,7 +45,7 @@ module ForemanAcd
44
45
 
45
46
  def content
46
47
  case scm_type
47
- when 'directory'
48
+ when 'directory' || 'git'
48
49
  File.read(File.join(path, playfile))
49
50
  else
50
51
  raise NotImplementedError.new "scm_type #{scm_type.inspect} not supported!"
@@ -54,7 +54,7 @@ module ForemanAcd
54
54
  ids.each do |host_id|
55
55
  h = ::Host.find(host_id) unless host_id.nil?
56
56
  if h
57
- Katello::RegistrationManager.unregister_host(h, :unregistering => false) if ForemanAcd::Engine.with_katello?
57
+ Katello::RegistrationManager.unregister_host(h, :unregistering => false) if ForemanAcd.with_katello?
58
58
  h.destroy
59
59
  end
60
60
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanAcd
4
+ # AcdProxy Selector implementing a RemoteExecutionProxySelector which
5
+ # only returns the Smart Proxy of the given host
6
+ class AcdProxyProxySelector < ::RemoteExecutionProxySelector
7
+ def determine_proxy(*args)
8
+ host, _provider = args
9
+
10
+ # We already did the determine_proxy in app_configurator. We want that REX
11
+ # isn't doing the determination of the proxy twice.
12
+ # Therefore, we will just return the host, which is the proxy!
13
+
14
+ ::SmartProxy.find_by(:name => host.name)
15
+ end
16
+ end
17
+ end
@@ -19,12 +19,36 @@ module ForemanAcd
19
19
 
20
20
  begin
21
21
  proxy_hosts = {}
22
- jobs = []
22
+ job = nil
23
+ result = OpenStruct.new
23
24
 
24
25
  hosts = @app_instance.foreman_hosts
26
+
27
+ # Important: This uses the REX Proxy Selector and not
28
+ # the AcdProxySelection because the AcdProxySelector
29
+ # does not find the proxy but only return the given
30
+ # host. This is required because we want to run the
31
+ # ansible playbook for a group of hosts on the smart proxy.
32
+ # So the process need to be:
33
+ # 1. get the proxy which is required to connect to host a,b,c
34
+ # 2. run the job on the proxy
35
+ # In 2. we need to make sure that REX doesn't try to find the
36
+ # proxy which is necessary to connect to the proxy
25
37
  proxy_selector = RemoteExecutionProxySelector.new
26
38
  hosts.each do |h|
27
- proxy = proxy_selector.determine_proxy(h.host, 'ACD')
39
+ begin
40
+ unless h.host
41
+ result.success = false
42
+ result.error = 'App Instance is not deployed'
43
+ return [result, job]
44
+ end
45
+ proxy = proxy_selector.determine_proxy(h.host, 'ACD')
46
+ result.success = true
47
+ rescue NoMethodError => e
48
+ result.success = false
49
+ result.error = "#{e}, Install/Update smart-proxies for ACD"
50
+ return [result, job]
51
+ end
28
52
  proxy_hosts[proxy.name] = [] unless proxy_hosts.key?(proxy.name)
29
53
  proxy_hosts[proxy.name] << h
30
54
  end
@@ -32,23 +56,43 @@ module ForemanAcd
32
56
  # TODO: just for testing...
33
57
  # proxy_hosts = { Host.first.name => [ Host.first.id] }
34
58
 
35
- # we need to compose multiple jobs. for each proxy one job.
59
+ proxy_inventories = {}
60
+ proxy_host_ids = []
36
61
  proxy_hosts.each do |proxy_name, foreman_hosts|
37
- # create the inventory file
38
- inventory = ForemanAcd::InventoryCreator.new(@app_instance, foreman_hosts).create_inventory
39
- job_input['inventory'] = YAML.dump(inventory)
40
-
41
- composer = JobInvocationComposer.for_feature(
42
- :run_acd_ansible_playbook,
43
- [Host.find_by(:name => proxy_name).id],
44
- job_input.to_hash
45
- )
46
- jobs << composer
62
+ proxy_inventories[proxy_name] = ForemanAcd::InventoryCreator.new(@app_instance, foreman_hosts).create_inventory
63
+
64
+ # Workaround till infrastructure jobs on proxies are possible. See
65
+ # https://community.theforeman.org/t/infrastructure-roles/22001/33
66
+ #
67
+ # Why do we use 'foreman_hosts.first.host'? REX is 'host-centric'. During
68
+ # job execution, REX will try to find the proxy on which the job should be executed.
69
+ # We throw the first host at REX's feet so that the resolution of the
70
+ # proxy works (... which we have already done above).
71
+ # ACD on the smart proxy side will call the ansible-playbook for ALL hosts
72
+ # which are behind this smart proxy.
73
+ #
74
+ # Additionally, we disable that AcdProxyProxySelector is used.
75
+ # See required_proxy_selector_for in models/foreman_acd/acd_provider.rb
76
+ proxy_host_ids << foreman_hosts.first.host.id
77
+
78
+ # Actually, we want this:
79
+ # proxy_host_ids << Host.find_by(:name => proxy_name).id
47
80
  end
81
+
82
+ job_input['inventory'] = YAML.dump(proxy_inventories)
83
+ composer = JobInvocationComposer.for_feature(
84
+ :run_acd_ansible_playbook,
85
+ proxy_host_ids,
86
+ job_input.to_hash
87
+ )
88
+ job = composer
48
89
  rescue StandardError => e
49
- logger.error("Failed to configure hosts: #{e.class}: #{e.message}\n#{e.backtrace.join($INPUT_RECORD_SEPARATOR)}")
90
+ result.success = false
91
+ result.error = _('Failed to configure hosts: %{err_msg}' % { :err_msg => e.message })
92
+ logger.error('Failed to configure hosts: %{err_class}: %{err_msg}' % { :err_class => e.class, :err_msg => e.message })
93
+ job = nil
50
94
  end
51
- jobs
95
+ [result, job]
52
96
  end
53
97
  end
54
98
  end