foreman_acd 0.6.0 → 0.7.0

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