foreman_acd 0.7.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/app/controllers/foreman_acd/ansible_playbooks_controller.rb +17 -2
  4. data/app/controllers/foreman_acd/app_definitions_controller.rb +104 -7
  5. data/app/controllers/foreman_acd/app_instances_controller.rb +15 -30
  6. data/app/controllers/foreman_acd/concerns/app_instance_mixins.rb +36 -0
  7. data/app/controllers/ui_acd_controller.rb +42 -3
  8. data/app/lib/actions/foreman_acd/run_configurator.rb +1 -0
  9. data/app/models/concerns/foreman_acd/host_managed_extensions.rb +40 -27
  10. data/app/models/foreman_acd/app_instance.rb +47 -2
  11. data/app/models/foreman_acd/foreman_host.rb +8 -0
  12. data/app/services/foreman_acd/app_deployer.rb +19 -2
  13. data/app/services/foreman_acd/inventory_creator.rb +11 -1
  14. data/app/views/foreman_acd/app_definitions/_form.html.erb +4 -0
  15. data/app/views/foreman_acd/app_definitions/import.html.erb +20 -1
  16. data/app/views/foreman_acd/app_definitions/index.html.erb +3 -6
  17. data/app/views/foreman_acd/app_instances/_form.html.erb +4 -0
  18. data/app/views/foreman_acd/app_instances/index.html.erb +15 -11
  19. data/app/views/foreman_acd/app_instances/report.html.erb +7 -2
  20. data/app/views/ui_acd/host_report.json.rabl +4 -0
  21. data/app/views/ui_acd/report_data.json.rabl +10 -0
  22. data/app/views/ui_acd/validate_hostname.json.rabl +6 -0
  23. data/config/routes.rb +3 -0
  24. data/db/migrate/20210818125913_add_is_existing_host_to_foreman_host.rb +8 -0
  25. data/db/migrate/20210902110645_add_initial_configure_task.rb +8 -0
  26. data/lib/foreman_acd/plugin.rb +9 -9
  27. data/lib/foreman_acd/version.rb +1 -1
  28. data/lib/foreman_acd.rb +27 -9
  29. data/package.json +8 -25
  30. data/test/controllers/ui_acd_controller_test.rb +4 -1
  31. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +2 -0
  32. data/webpack/__snapshots__/helper.test.js.snap +1 -1
  33. data/webpack/components/ApplicationDefinition/ApplicationDefinition.js +34 -10
  34. data/webpack/components/ApplicationDefinition/ApplicationDefinitionActions.js +12 -0
  35. data/webpack/components/ApplicationDefinition/ApplicationDefinitionConstants.js +1 -0
  36. data/webpack/components/ApplicationDefinition/ApplicationDefinitionReducer.js +30 -9
  37. data/webpack/components/ApplicationDefinition/ApplicationDefinitionSelectors.js +4 -0
  38. data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinition.test.js +1 -0
  39. data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinitionSelectors.test.js +12 -0
  40. data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinition.test.js.snap +31 -5
  41. data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinitionSelectors.test.js.snap +8 -0
  42. data/webpack/components/ApplicationDefinition/components/AnsiblePlaybookSelector.js +1 -1
  43. data/webpack/components/ApplicationDefinition/components/__tests__/__snapshots__/AnsiblePlaybookSelector.test.js.snap +3 -3
  44. data/webpack/components/ApplicationDefinition/index.js +8 -0
  45. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImport.js +214 -0
  46. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImport.scss +1 -0
  47. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportActions.js +161 -0
  48. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportConstants.js +6 -0
  49. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportReducer.js +79 -0
  50. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportSelectors.js +8 -0
  51. data/webpack/components/ApplicationDefinitionImport/__fixtures__/applicationDefinitionImportConfData_1.fixtures.js +129 -0
  52. data/webpack/components/ApplicationDefinitionImport/__fixtures__/applicationDefinitionImportReducer.fixtures.js +29 -0
  53. data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImport.test.js +20 -0
  54. data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImportReducer.test.js +43 -0
  55. data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImportSelectors.test.js +29 -0
  56. data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImport.test.js.snap +62 -0
  57. data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImportReducer.test.js.snap +362 -0
  58. data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImportSelectors.test.js.snap +130 -0
  59. data/webpack/components/ApplicationDefinitionImport/index.js +32 -0
  60. data/webpack/components/ApplicationInstance/ApplicationInstance.js +102 -26
  61. data/webpack/components/ApplicationInstance/ApplicationInstanceActions.js +118 -6
  62. data/webpack/components/ApplicationInstance/ApplicationInstanceConstants.js +4 -0
  63. data/webpack/components/ApplicationInstance/ApplicationInstanceHelper.js +15 -0
  64. data/webpack/components/ApplicationInstance/ApplicationInstanceReducer.js +71 -30
  65. data/webpack/components/ApplicationInstance/ApplicationInstanceSelectors.js +4 -0
  66. data/webpack/components/ApplicationInstance/__fixtures__/applicationInstanceReducer.fixtures.js +2 -0
  67. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstance.test.js +1 -0
  68. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstanceReducer.test.js +12 -0
  69. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstanceSelectors.test.js +12 -0
  70. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstance.test.js.snap +98 -7
  71. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstanceReducer.test.js.snap +271 -0
  72. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstanceSelectors.test.js.snap +8 -0
  73. data/webpack/components/ApplicationInstance/components/AppDefinitionSelector.js +1 -0
  74. data/webpack/components/ApplicationInstance/components/ServiceCounter.js +1 -1
  75. data/webpack/components/ApplicationInstance/helper.js +0 -0
  76. data/webpack/components/ApplicationInstance/index.js +8 -0
  77. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.js +81 -6
  78. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportActions.js +35 -1
  79. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportConstants.js +3 -0
  80. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportReducer.js +19 -0
  81. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportSelectors.js +4 -0
  82. data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReport.test.js.snap +1 -124
  83. data/webpack/components/ApplicationInstanceReport/index.js +8 -1
  84. data/webpack/components/ExistingHostSelection/ExistingHostSelection.js +104 -0
  85. data/webpack/components/ExistingHostSelection/ExistingHostSelection.scss +15 -0
  86. data/webpack/components/ExistingHostSelection/ExistingHostSelectionActions.js +71 -0
  87. data/webpack/components/ExistingHostSelection/ExistingHostSelectionConstants.js +4 -0
  88. data/webpack/components/ExistingHostSelection/ExistingHostSelectionHelper.js +0 -0
  89. data/webpack/components/ExistingHostSelection/ExistingHostSelectionReducer.js +90 -0
  90. data/webpack/components/ExistingHostSelection/ExistingHostSelectionSelectors.js +8 -0
  91. data/webpack/components/ExistingHostSelection/__fixtures__/existingHostSelectionConfData_1.fixtures.js +191 -0
  92. data/webpack/components/ExistingHostSelection/__fixtures__/existingHostSelectionReducer.fixtures.js +203 -0
  93. data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelection.test.js +19 -0
  94. data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelectionReducer.test.js +59 -0
  95. data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelectionSelectors.test.js +36 -0
  96. data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelection.test.js.snap +35 -0
  97. data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelectionReducer.test.js.snap +614 -0
  98. data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelectionSelectors.test.js.snap +27 -0
  99. data/webpack/components/ExistingHostSelection/components/ServiceSelector.js +48 -0
  100. data/webpack/components/ExistingHostSelection/components/__tests__/ServiceSelector.test.js +35 -0
  101. data/webpack/components/ExistingHostSelection/components/__tests__/__snapshots__/ServiceSelector.test.js.snap +77 -0
  102. data/webpack/components/ExistingHostSelection/index.js +26 -0
  103. data/webpack/components/ParameterSelection/ParameterSelection.js +103 -1
  104. data/webpack/components/ParameterSelection/ParameterSelection.scss +7 -0
  105. data/webpack/components/ParameterSelection/ParameterSelectionActions.js +46 -4
  106. data/webpack/components/ParameterSelection/ParameterSelectionConstants.js +2 -0
  107. data/webpack/components/ParameterSelection/ParameterSelectionHelper.js +5 -1
  108. data/webpack/components/ParameterSelection/ParameterSelectionReducer.js +52 -11
  109. data/webpack/components/ParameterSelection/ParameterSelectionSelectors.js +2 -0
  110. data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionData_1.fixtures.js +8 -0
  111. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionReducer.test.js +2 -0
  112. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionSelectors.test.js +6 -0
  113. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelection.test.js.snap +96 -0
  114. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionReducer.test.js.snap +117 -17
  115. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionSelectors.test.js.snap +13 -0
  116. data/webpack/components/ParameterSelection/index.js +4 -1
  117. data/webpack/components/SyncGitRepo/SyncGitRepo.js +2 -10
  118. data/webpack/components/SyncGitRepo/SyncGitRepoActions.js +2 -3
  119. data/webpack/components/SyncGitRepo/SyncGitRepoConstants.js +0 -1
  120. data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepo.test.js.snap +1 -0
  121. data/webpack/components/SyncGitRepo/components/FormTextInput.js +1 -1
  122. data/webpack/components/SyncGitRepo/components/ScmTypeSelector.js +3 -2
  123. data/webpack/components/common/DeleteTableEntry.js +16 -2
  124. data/webpack/components/common/__tests__/__snapshots__/DeleteTableEntry.test.js.snap +38 -0
  125. data/webpack/helper.js +21 -1
  126. data/webpack/helper.test.js +20 -1
  127. data/webpack/index.js +5 -0
  128. data/webpack/js-yaml.js +3874 -0
  129. data/webpack/reducer.js +13 -2
  130. data/webpack/test_setup.js +0 -2
  131. metadata +46 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b616c7ba21eea339af8509f618585dcd949ddead6ecdec57278a62c751e4fd2
4
- data.tar.gz: 75c7d2e2c07d306dee127bc50c6b300934f90066bc4a491e49f17f65ac6b7c24
3
+ metadata.gz: a14a2b2d7f2353e0a908bf3b0de9ea3126fff402e1003f9bea648afe260c73c0
4
+ data.tar.gz: 6eecc24dc7be78b4ddc04da2b50a525dd95a2b358647d8701e1ea6479eef5332
5
5
  SHA512:
6
- metadata.gz: bba62637508bbbd4e35772a55ad1126fea7a8cb3c1658c6f734b02ccf03d44048db40fa621bb51e796220668e7a49e8b03c3169a77b818f6dbbde117d09edfe2
7
- data.tar.gz: a6e4ba1bb97250e8bcebe6180abcf4755859b73828febda64353b47021acadcc33fd99e2ab4b45ac965e5d643c368bbfb47aee421a80979c7a4c69354a18b297
6
+ metadata.gz: 372bb6339ec6ff38e80dbc8df019083ff76a660c64cd23b40ab7da5b6c035617e7395f3c2738a04f21dd327c796ca959cb250ef6bbbbfb4b23d1220177ffab6f
7
+ data.tar.gz: 5a68e1cb910e7fca00e0f44172ff391a93557a7c9febbb8eb40a2711471c7d67bfc29ea5c70e763a7b94aa7c732e406b42d2d36daa05971f950824c6eae10d80
data/README.md CHANGED
@@ -1,5 +1,3 @@
1
- [![Build Status master](https://travis-ci.org/ATIX-AG/foreman_acd.svg?branch=master)](https://travis-ci.org/ATIX-AG/foreman_acd)
2
-
3
1
  # Foreman Application Centric Deployment
4
2
 
5
3
  A Foreman plugin providing application centric deployment and a self-service portal.
@@ -55,6 +53,10 @@ This plugin aims to setup all six hosts and to deploy the application.
55
53
 
56
54
  :warning: This plugin is still in development.
57
55
 
56
+ ## Documentation
57
+
58
+ See [Application Centric Deployment Guide](https://docs.theforeman.org/nightly/Application_Centric_Deployment/index-foreman-el.html)
59
+
58
60
  ## Installation
59
61
 
60
62
  See the [installation](https://theforeman.org/plugins/#2.Installation) chapter of the Foreman plugins documentation on how to install Foreman plugins.
@@ -88,7 +90,7 @@ You need to refresh the smart proxy features in *Infrastructure > Smart Proxies
88
90
  ### Ansible Playbook
89
91
 
90
92
  * Copy (or checkout a git repository) an Ansible playbook.
91
- Store it in `/etc/foreman/plugins/foreman_acd/ansible-playbooks/` so that SELinux is able to read it.
93
+ Store it in `/var/lib/foreman/foreman_acd/ansible-playbooks/` so that SELinux is able to read it.
92
94
  * Add a new Ansible Playbook via *Applications > Ansible Playbooks*.
93
95
  * Specify the path to the Ansible playbook and name of the playbook file. (e.g. `site.yml`).
94
96
  * Save it and press *Import group variables* for this newly created Ansible playbook.
@@ -117,8 +119,6 @@ All Foreman parameters require a value.
117
119
 
118
120
  ## TODO
119
121
 
120
- * Add `git` support for the Ansible playbooks.
121
- * Provide application templates which contains application definition and the required Ansible-playbook.
122
122
  * Add Saltstack support to configure the application.
123
123
  * Extend the Foreman parameter and value validation.
124
124
 
@@ -77,9 +77,20 @@ module ForemanAcd
77
77
  logger.info("HTTP Proxy used: #{git.config['http.proxy']}")
78
78
  end
79
79
 
80
+ if sync_params[:git_commit].empty?
81
+ if ForemanAcd.proxy_setting.present?
82
+ err_msg = _('Please set the Git Branch/Tag/Commit. This setting is necessary if a HTTP proxy is used!')
83
+ raise StandardError.new err_msg
84
+ else
85
+ commit = Git.ls_remote(sync_params[:git_url])['head'][:sha]
86
+ end
87
+ else
88
+ commit = sync_params[:git_commit]
89
+ end
90
+
80
91
  git.add_remote('origin', sync_params[:git_url])
81
92
  git.fetch
82
- git.checkout(sync_params[:git_commit])
93
+ git.checkout(commit)
83
94
 
84
95
  session[:git_path] = git.dir.path
85
96
  rescue StandardError => e
@@ -189,7 +200,11 @@ module ForemanAcd
189
200
  end
190
201
 
191
202
  def remove_ansible_dir(dirpath)
192
- FileUtils.remove_dir(dirpath) if Dir.exist?(dirpath)
203
+ unless dirpath.start_with? ForemanAcd.ansible_playbook_path
204
+ logger.error("Sorry, the directory #{dirpath} is not within #{ForemanAcd.ansible_playbook_path} and will therefore not be cleaned up!")
205
+ return false
206
+ end
207
+ FileUtils.rm_rf(dirpath) if Dir.exist?(dirpath)
193
208
  end
194
209
 
195
210
  def ansible_playbook_full_path(dirname)
@@ -9,7 +9,7 @@ module ForemanAcd
9
9
  before_action :find_resource, :only => [:edit, :update, :destroy, :export]
10
10
  before_action :read_hostgroups, :only => [:edit, :new, :import]
11
11
  before_action :read_ansible_playbooks, :only => [:edit, :new]
12
- before_action :handle_file_upload, :only => [:create]
12
+ before_action :assign_ansible_playbook, :only => [:create]
13
13
 
14
14
  def index
15
15
  @app_definitions = resource_base.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page])
@@ -37,6 +37,9 @@ module ForemanAcd
37
37
  process_error
38
38
  end
39
39
  rescue StandardError, ValidationError => e
40
+ if params[:foreman_acd_app_definition_import].present?
41
+ AnsiblePlaybook.find(params[:foreman_acd_app_definition][:acd_ansible_playbook_id]).delete if params[:foreman_acd_app_definition][:acd_ansible_playbook_id].present?
42
+ end
40
43
  redirect_to new_app_definition_path, :flash => { :error => _(e.message) }
41
44
  end
42
45
  end
@@ -75,16 +78,110 @@ module ForemanAcd
75
78
  end
76
79
 
77
80
  def export
78
- filename = "#{@app_definition.name}.yaml"
79
- data = JSON.parse(@app_definition.services).to_yaml
80
- send_data data, :type => 'text/yaml', :disposition => 'attachment', :filename => filename
81
+ dir_path = "#{Dir.mktmpdir}/#{@app_definition.name}.tar"
82
+ `tar -cvf "#{dir_path}" "#{@app_definition.ansible_playbook.path}" #{export_app_template_data.path}`
83
+ logger.info("Successfully created application template tar file for #{@app_definition.name}")
84
+ send_file dir_path
85
+ rescue StandardError => e
86
+ logger.info("Export of #{@app_definition.name} failed with the error: #{e}")
87
+ redirect_to app_definitions_path, :flash => { :error => _(e.message) }
88
+ end
89
+
90
+ def handle_file_upload
91
+ return unless params[:app_definition_file] && (raw_directory = params[:app_definition_file])
92
+ begin
93
+ dir = Dir.mktmpdir
94
+ untar_import_directory(raw_directory, dir)
95
+ ansible_file = Dir.glob("#{dir}/tmp/*.yaml")
96
+ data = JSON.parse(YAML.load_file(ansible_file[0]).to_json) if ansible_file
97
+ ansible_playbook_import = data.find { |d| d if d['ansible_playbook'] }
98
+
99
+ session[:ansible_playbook_params] = { :dir => dir, :ansible_playbook => ansible_playbook_import['ansible_playbook'] }
100
+ render :json => { :ansible_services => create_ansible_services(data, ansible_playbook_import) }, :status => :ok
101
+ rescue StandardError => e
102
+ render :json => { :status => 'error', :message => e }, :status => :internal_server_error
103
+ end
81
104
  end
82
105
 
83
106
  private
84
107
 
85
- def handle_file_upload
86
- return unless params[:foreman_acd_app_definition] && (raw_file = params[:foreman_acd_app_definition][:app_definition_file])
87
- params[:foreman_acd_app_definition][:services] = YAML.load_file(raw_file.tempfile).to_json
108
+ def export_app_template_data
109
+ file = Tempfile.open([@app_definition.name, '.yaml'])
110
+ data = JSON.parse(@app_definition.services).append(:ansible_playbook => @app_definition.ansible_playbook.attributes.except('id', 'created_at', 'updated_at').as_json).to_yaml
111
+ file.write(data)
112
+ file.close
113
+ logger.info("Successfully created yaml file for app_template data: #{file.path}")
114
+ file
115
+ rescue StandardError => e
116
+ logger.info("Creation of app template data failed: #{e}")
117
+ redirect_to app_definitions_path, :flash => { :error => _(e.message) }
118
+ end
119
+
120
+ def rename_path_name(dir_path)
121
+ return dir_path unless Dir.exist?(dir_path)
122
+ ind = 1
123
+ loop do
124
+ path = dir_path
125
+ path += ind.to_s
126
+ return path unless Dir.exist?(path)
127
+ ind += 1
128
+ end
129
+ end
130
+
131
+ def create_ansible_playbook(dir, ansible_playbook, dir_path)
132
+ FileUtils.cp_r "#{dir}/#{ansible_playbook['path']}/.", dir_path
133
+ ansible_playbook['path'] = dir_path
134
+ ansible_playbook['name'] = File.basename(dir_path)
135
+ new_playbook = AnsiblePlaybook.create(ansible_playbook)
136
+ new_playbook
137
+ rescue StandardError => e
138
+ logger.info("Error while creating AnsiblePlaybook: #{e}")
139
+ end
140
+
141
+ def create_ansible_services(data, ansible_playbook_import)
142
+ ansible_services = []
143
+ session[:data_services] = data - [ansible_playbook_import]
144
+ session[:data_services].each do |d|
145
+ ansible_services.append({ :id => d['id'], :value => d['name'] })
146
+ end
147
+ ansible_services
148
+ end
149
+
150
+ def untar_import_directory(directory, dir)
151
+ `tar -xvf #{directory.path} -C #{dir}`
152
+ rescue StandardError => e
153
+ logger.info("Failed to untar imported directory: #{e}")
154
+ end
155
+
156
+ def assign_ansible_playbook
157
+ return unless params[:foreman_acd_app_definition_import]
158
+ dir_path = "#{ForemanAcd.ansible_playbook_path}/#{session[:ansible_playbook_params][:ansible_playbook]['name'].split(/\W+/).join('_')}"
159
+
160
+ # Append path and name of ansible with n + 1 if ansible_playbook with same name or name[n] exists
161
+ ansible_playbook = create_ansible_playbook(session[:ansible_playbook_params][:dir], session[:ansible_playbook_params][:ansible_playbook], rename_path_name(dir_path))
162
+ params[:foreman_acd_app_definition][:acd_ansible_playbook_id] = ansible_playbook.id
163
+
164
+ begin
165
+ services = JSON.parse(params[:foreman_acd_app_definition_import][:services])
166
+ flag = 0
167
+ session[:data_services].each do |d|
168
+ hostgroup = services.find { |service| service['name'] == d['name'] }['hostgroup']
169
+ if hostgroup == ''
170
+ flag += 1
171
+ break
172
+ else
173
+ d['hostgroup'] = hostgroup
174
+ end
175
+ end
176
+ params[:foreman_acd_app_definition][:services] = session[:data_services].to_json
177
+ rescue StandardError => e
178
+ ansible_playbook&.destroy
179
+ redirect_to({ :action => 'import' }, :error => _("Hostgroups are not configured properly: #{e}"))
180
+ else
181
+ ansible_playbook&.destroy
182
+ redirect_to({ :action => 'import' }, :error => _('Some services are not assigned Hostgroups')) if flag.positive?
183
+ end
184
+ session[:data_services] = nil
88
185
  end
89
186
  end
90
187
  end
@@ -3,8 +3,9 @@
3
3
  module ForemanAcd
4
4
  # Application Instance Controller
5
5
  class AppInstancesController < ::ForemanAcd::ApplicationController
6
- include Foreman::Controller::AutoCompleteSearch
6
+ include ::Foreman::Controller::AutoCompleteSearch
7
7
  include ::ForemanAcd::Concerns::AppInstanceParameters
8
+ include ::ForemanAcd::Concerns::AppInstanceMixins
8
9
 
9
10
  before_action :find_resource, :only => [:edit, :update, :destroy_with_hosts, :deploy, :report]
10
11
  before_action :read_applications, :only => [:new, :edit]
@@ -73,13 +74,17 @@ module ForemanAcd
73
74
 
74
75
  def deploy
75
76
  value = false
77
+ @app_instance.update!({ :last_deploy_task_id => nil,
78
+ :initial_configure_task_id => nil })
79
+ @app_instance.foreman_hosts.each { |f| f.update!(:last_progress_report => nil) }
76
80
  @app_instance.clean_all_hosts if params[:delete_hosts]
77
81
  value = safe_deploy? if params[:safe_deploy]
78
82
  session.delete(:remember_hosts)
79
83
  logger.info('Run async foreman task to deploy hosts')
80
84
  async_task = ForemanTasks.async_task(::Actions::ForemanAcd::DeployAllHosts, @app_instance, value)
81
85
  @app_instance.update!(:last_deploy_task => async_task)
82
- process_success(:success_msg => _('Started task to deploy hosts for %s') % @app_instance)
86
+
87
+ redirect_to report_app_instance_path, :success => _('Started task to deploy hosts for %s') % @app_instance
83
88
  rescue StandardError => e
84
89
  error_msg = "Error happend while deploying hosts of #{@app_instance}: #{e.message}"
85
90
  logger.error("#{error_msg} - #{e.class}\n#{e.backtrace.join($INPUT_RECORD_SEPARATOR)}")
@@ -92,7 +97,7 @@ module ForemanAcd
92
97
  end
93
98
 
94
99
  def report
95
- @report_hosts = collect_host_report_data
100
+ @report_hosts = collect_host_report_data(@app_instance)
96
101
  logger.debug("app instance host details: #{@report_hosts.inspect}")
97
102
  end
98
103
 
@@ -112,8 +117,12 @@ module ForemanAcd
112
117
  # Store hosts if updated for safe deploy
113
118
  session[:remember_hosts] << updated_host.id if updated_host.updated_at != old_host.updated_at
114
119
  else
115
- @app_instance.foreman_hosts.create(:hostname => h['hostname'], :service => h['service'], :description => h['description'],
116
- :foremanParameters => JSON.dump(h['foremanParameters']), :ansibleParameters => JSON.dump(h['ansibleParameters']))
120
+ @app_instance.foreman_hosts.create(:hostname => h['hostname'],
121
+ :service => h['service'],
122
+ :description => h['description'],
123
+ :is_existing_host => h['isExistingHost'],
124
+ :foremanParameters => JSON.dump(h['foremanParameters']),
125
+ :ansibleParameters => JSON.dump(h['ansibleParameters']))
117
126
  # Store new hosts for safe deploy
118
127
  session[:remember_hosts] << @app_instance.foreman_hosts.find_by(:hostname => h['hostname']).id
119
128
  end
@@ -132,6 +141,7 @@ module ForemanAcd
132
141
  :hostname => h.hostname,
133
142
  :service => h.service,
134
143
  :description => h.description,
144
+ :isExistingHost => h.is_existing_host,
135
145
  :foremanParameters => JSON.parse(h.foremanParameters),
136
146
  :ansibleParameters => JSON.parse(h.ansibleParameters)
137
147
  }
@@ -152,30 +162,5 @@ module ForemanAcd
152
162
  def read_applications
153
163
  @applications = AppDefinition.all.map { |elem| { elem.id => elem.name } }.reduce({}) { |h, v| h.merge v }
154
164
  end
155
-
156
- def collect_host_report_data
157
- report_data = []
158
-
159
- @app_instance.foreman_hosts.each do |foreman_host|
160
- a_host = {
161
- :id => nil,
162
- :name => foreman_host.hostname,
163
- :build => nil,
164
- :hostUrl => nil,
165
- :progress_report => foreman_host.last_progress_report.empty? ? [] : JSON.parse(foreman_host.last_progress_report)
166
- }
167
-
168
- if foreman_host.host.present?
169
- a_host.update({
170
- :id => foreman_host.host.id,
171
- :build => foreman_host.host.build,
172
- :hostUrl => host_path(foreman_host.host),
173
- :powerStatusUrl => power_api_host_path(foreman_host.host)
174
- })
175
- end
176
- report_data << a_host
177
- end
178
- report_data
179
- end
180
165
  end
181
166
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanAcd
4
+ module Concerns
5
+ # Shared code for AppInstance API and UI controller
6
+ module AppInstanceMixins
7
+ extend ActiveSupport::Concern
8
+
9
+ def collect_host_report_data(app_instance)
10
+ report_data = []
11
+
12
+ app_instance.foreman_hosts.each do |foreman_host|
13
+ a_host = {
14
+ :id => nil,
15
+ :name => foreman_host.hostname,
16
+ :build => nil,
17
+ :hostUrl => nil,
18
+ :progress_report => foreman_host.last_progress_report.empty? ? [] : JSON.parse(foreman_host.last_progress_report)
19
+ }
20
+
21
+ if foreman_host.host.present?
22
+ a_host.update({
23
+ :id => foreman_host.host.id,
24
+ :build => foreman_host.host.build,
25
+ :hostUrl => host_path(foreman_host.host),
26
+ :isExistingHost => foreman_host.is_existing_host,
27
+ :powerStatusUrl => power_api_host_path(foreman_host.host)
28
+ })
29
+ end
30
+ report_data << OpenStruct.new(a_host)
31
+ end
32
+ report_data
33
+ end
34
+ end
35
+ end
36
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  # Controller to create JSON data to be used in react app
4
4
  class UiAcdController < ::Api::V2::BaseController
5
+ include ::ForemanAcd::Concerns::AppInstanceMixins
6
+
5
7
  def app
6
8
  @app_data = {}
7
9
  app_definition = ForemanAcd::AppDefinition.find(params[:id])
@@ -16,9 +18,17 @@ class UiAcdController < ::Api::V2::BaseController
16
18
  @ansible_data = collect_ansible_data(params['id'])
17
19
  end
18
20
 
21
+ def report_data
22
+ @report_data = collect_report_data(params['id'])
23
+ end
24
+
25
+ def validate_hostname
26
+ @host_validation = hostname_duplicate?(params['appDefId'].to_i, params['serviceId'].to_i, params['hostname'])
27
+ end
28
+
19
29
  def action_permission
20
30
  case params[:action]
21
- when 'app', 'foreman_data', 'ansible_data'
31
+ when 'app', 'foreman_data', 'ansible_data', 'validate_hostname'
22
32
  :view
23
33
  else
24
34
  super
@@ -30,17 +40,46 @@ class UiAcdController < ::Api::V2::BaseController
30
40
  def collect_foreman_data(hostgroup_id)
31
41
  hg = Hostgroup.find(hostgroup_id)
32
42
  fdata = OpenStruct.new(
33
- :environments => Environment.all,
34
- :lifecycle_environments => Katello::KTEnvironment.all,
35
43
  :domains => Domain.all,
36
44
  :computeprofiles => ComputeProfile.all,
37
45
  :hostgroup_id => hg.id,
38
46
  :ptables => hg&.operatingsystem&.ptables
39
47
  )
48
+
49
+ fdata[:environments] = Environment.all if defined?(ForemanPuppet)
50
+ fdata[:lifecycle_environments] = Katello::KTEnvironment.all if defined?(Katello)
51
+
40
52
  fdata
41
53
  end
42
54
 
43
55
  def collect_ansible_data(playbook_id)
44
56
  ForemanAcd::AnsiblePlaybook.find(playbook_id).as_unified_structobj
45
57
  end
58
+
59
+ def collect_report_data(app_instance_id)
60
+ app_instance = ForemanAcd::AppInstance.find(app_instance_id)
61
+
62
+ report_data = {
63
+ :hosts => collect_host_report_data(app_instance),
64
+ :deploymentState => app_instance.deployment_state.to_s,
65
+ :initialConfigureState => app_instance.initial_configure_state.to_s
66
+ }
67
+ report_data['initialConfigureJobUrl'] = job_invocation_path(app_instance.initial_configure_job) unless app_instance.initial_configure_job.nil?
68
+
69
+ OpenStruct.new(report_data)
70
+ end
71
+
72
+ def hostname_duplicate?(app_def_id, service_id, hostname)
73
+ app_definition = ForemanAcd::AppDefinition.find(app_def_id)
74
+ service_data = JSON.parse(app_definition.services).select { |k| k['id'] == service_id }.first
75
+ domain_name = Hostgroup.find(service_data['hostgroup']).domain.name
76
+ validation_hostname = "#{hostname}.#{domain_name}"
77
+
78
+ vdata = OpenStruct.new(
79
+ :hostname => hostname,
80
+ :fqdn => validation_hostname,
81
+ :result => Host.find_by(:name => validation_hostname).nil?
82
+ )
83
+ vdata
84
+ end
46
85
  end
@@ -19,6 +19,7 @@ module Actions
19
19
  if result.success
20
20
  ::Foreman::Logging.logger('foreman_acd').info "Creating job to configure the app #{app_instance}"
21
21
  job.trigger!
22
+ output[:configure_job_id] = job.job_invocation.id
22
23
  else
23
24
  ::Foreman::Logging.logger('foreman_acd').error "Could not create the job to configure the app #{app_instance}: #{result.error}"
24
25
  end
@@ -5,47 +5,60 @@ module ForemanAcd
5
5
  module HostManagedExtensions
6
6
  extend ActiveSupport::Concern
7
7
 
8
- RUN_CONFIGURATOR_DELAY = 240
9
-
10
8
  def self.prepended(base)
11
9
  base.instance_eval do
12
- before_provision :initiate_acd_app_configurator, :if => :deployed_via_acd?
10
+ before_provision :initiate_acd_app_configurator_after_host_deployment, :if => :deployed_via_acd?
11
+ before_destroy :check_deletable, :prepend => true, :if => :deployed_via_acd?
12
+
13
+ has_many :app_instances, :through => :foreman_hosts, :class_name => 'ForemanAcd::AppInstance'
14
+
15
+ scoped_search :relation => :app_instances,
16
+ :on => :name,
17
+ :rename => :acd_app_instance,
18
+ :only_explicit => true,
19
+ :complete_value => true,
20
+ :operators => ['= '],
21
+ :ext_method => :find_by_acd_app_instance_name
22
+ end
23
+
24
+ base.singleton_class.prepend ClassMethods
25
+ end
26
+
27
+ # New class methods for Host::Managed
28
+ module ClassMethods
29
+ def find_by_acd_app_instance_name(_key, operator, acd_instance_name)
30
+ cond = sanitize_sql_for_conditions(["acd_app_instances.name #{operator} ?", value_to_sql(operator, acd_instance_name)])
31
+ hosts = ForemanAcd::AppInstance.where(cond).joins(:foreman_hosts).pluck(:host_id)
32
+ if hosts.empty?
33
+ { :condition => '1=0' }
34
+ else
35
+ { :conditions => Host::Managed.arel_table[:id].in(hosts).to_sql }
36
+ end
13
37
  end
14
38
  end
15
39
 
16
40
  def deployed_via_acd?
17
- find_app_instance_host
18
- @app_instance_host.present?
41
+ find_foreman_host
42
+ @foreman_host.present?
19
43
  end
20
44
 
21
45
  private
22
46
 
23
- def find_app_instance_host
24
- @app_instance_host = ForemanAcd::ForemanHost.find_by(:host_id => id)
47
+ def check_deletable
48
+ return if @foreman_host.blank?
49
+ ::Foreman::Logging.logger('foreman_acd').warn "Could not delete host '#{name}' because it is used in Applications > App Instances '#{@foreman_host.app_instance.name}'"
50
+ raise _("Could not delete host '%{host_name}' because it is used in Applications > App Instances '%{app_instance_name}'") % {
51
+ :host_name => name, :app_instance_name => @foreman_host.app_instance.name
52
+ }
25
53
  end
26
54
 
27
- def initiate_acd_app_configurator
28
- return false if @app_instance_host.blank?
29
-
30
- run_it = true
31
- @app_instance_host.app_instance.foreman_hosts.each do |foreman_host|
32
- # if there is ONE host, which is still in build phase we don't let the app_configuator run
33
- next unless foreman_host.host.build?
34
- ::Foreman::Logging.logger('foreman_acd').info("Another host (#{foreman_host.host.name} is still in build-phase. Wait for it...")
35
- run_it = false
36
- break
37
- end
38
-
39
- return unless run_it
40
-
41
- ::Foreman::Logging.logger('foreman_acd').info("All hosts of app (#{@app_instance_host.app_instance.name}) were built. Schedule app configurator...")
42
- start_acd_app_configurator
55
+ def find_foreman_host
56
+ @foreman_host = ForemanAcd::ForemanHost.find_by(:host_id => id)
43
57
  end
44
58
 
45
- def start_acd_app_configurator
46
- ForemanTasks.delay(::Actions::ForemanAcd::RunConfigurator,
47
- { :start_at => Time.zone.now + RUN_CONFIGURATOR_DELAY },
48
- @app_instance_host.app_instance)
59
+ def initiate_acd_app_configurator_after_host_deployment
60
+ return if @foreman_host.blank?
61
+ ForemanAcd.initiate_acd_app_configurator(@foreman_host.app_instance)
49
62
  end
50
63
  end
51
64
  end
@@ -11,6 +11,7 @@ module ForemanAcd
11
11
 
12
12
  self.table_name = 'acd_app_instances'
13
13
  belongs_to :last_deploy_task, :class_name => 'ForemanTasks::Task'
14
+ belongs_to :initial_configure_task, :class_name => 'ForemanTasks::Task'
14
15
  validates :name, :presence => true, :uniqueness => true
15
16
  validates :app_definition, :presence => true
16
17
  belongs_to :app_definition, :inverse_of => :app_instances
@@ -32,7 +33,7 @@ module ForemanAcd
32
33
  end
33
34
 
34
35
  def clean_all_hosts
35
- remember_host_ids = foreman_hosts.map(&:host_id)
36
+ remember_host_ids = foreman_hosts.select(&:fresh_host?).map(&:host_id)
36
37
 
37
38
  # Clean the app instance association first
38
39
  foreman_hosts.update_all(:host_id => nil)
@@ -43,12 +44,56 @@ module ForemanAcd
43
44
 
44
45
  def clean_hosts_by_id(ids = [])
45
46
  # Clean the app instance association first
46
- foreman_hosts.where(:host_id => ids).update_all(:host_id => nil)
47
+ foreman_hosts.where(:host_id => ids, :is_existing_host => false).update_all(:host_id => nil)
47
48
 
48
49
  # Remove all hosts afterwards
49
50
  delete_hosts(ids)
50
51
  end
51
52
 
53
+ def hosts_deployment_finished?
54
+ return true if all_hosts_deployed?
55
+
56
+ ::Foreman::Logging.logger('foreman_acd').info('Another host is still in build-phase. Wait for it...')
57
+ false
58
+ end
59
+
60
+ def deployment_state
61
+ return :new if last_deploy_task.nil?
62
+ return :initiated if !last_deploy_task.nil? && last_deploy_task.ended_at.nil?
63
+
64
+ state = if all_hosts_deployed?
65
+ :finished
66
+ elsif last_deploy_task.ended_at? && last_deploy_task.result != 'success'
67
+ :failed
68
+ else
69
+ :pending
70
+ end
71
+ state
72
+ end
73
+
74
+ def initial_configure_job
75
+ return nil if initial_configure_task.nil?
76
+ return JobInvocation.find(initial_configure_task.output['configure_job_id']) if initial_configure_task.output.key?('configure_job_id') &&
77
+ !initial_configure_task.output['configure_job_id'].nil?
78
+ nil
79
+ end
80
+
81
+ def initial_configure_state
82
+ return :unconfigured if initial_configure_job.nil? && initial_configure_task.nil?
83
+ return :scheduled if initial_configure_job.nil?
84
+ return :pending unless initial_configure_job.finished?
85
+ initial_configure_job.status_label.to_sym
86
+ end
87
+
88
+ private
89
+
90
+ def all_hosts_deployed?
91
+ foreman_hosts.each do |foreman_host|
92
+ return false if foreman_host.host.nil? || foreman_host.host.build?
93
+ end
94
+ true
95
+ end
96
+
52
97
  def delete_hosts(ids = [])
53
98
  return if ids.empty?
54
99
  ids.each do |host_id|
@@ -16,6 +16,14 @@ module ForemanAcd
16
16
  _('Foreman Host')
17
17
  end
18
18
 
19
+ def fresh_host?
20
+ !is_existing_host
21
+ end
22
+
23
+ def existing_host?
24
+ is_existing_host
25
+ end
26
+
19
27
  def self.permission_name
20
28
  'foreman_hosts'
21
29
  end
@@ -22,8 +22,17 @@ module ForemanAcd
22
22
 
23
23
  foreman_hosts.each do |foreman_host|
24
24
  service_data = services.select { |k| k['id'] == foreman_host.service.to_i }.first
25
- host_params = set_host_params(foreman_host, service_data)
26
25
 
26
+ # Handle already deployed hosts
27
+ if foreman_host.existing_host?
28
+ domain = Hostgroup.find(service_data['hostgroup']).domain.name
29
+ fqdn = "#{foreman_host.hostname}.#{domain}"
30
+ h = Host.find_by(:name => fqdn)
31
+ foreman_host.update!(:host_id => h.id)
32
+ next
33
+ end
34
+
35
+ host_params = set_host_params(foreman_host, service_data)
27
36
  host = foreman_host.host.presence
28
37
 
29
38
  is_rebuild = false
@@ -45,7 +54,10 @@ module ForemanAcd
45
54
  end
46
55
 
47
56
  # REMOVE ME (but very nice for testing)
48
- # host.mac = "00:11:22:33:44:55"
57
+ # prng = Random.new
58
+ # x = prng.rand(100)
59
+ # y = prng.rand(100)
60
+ # host.mac = "00:11:22:33:#{x}:#{y}"
49
61
 
50
62
  apply_compute_profile(host)
51
63
  host.suggest_default_pxe_loader
@@ -73,6 +85,11 @@ module ForemanAcd
73
85
  output << msg
74
86
  end
75
87
  end
88
+
89
+ # Try to start the configuration, too. In case of a app instance including only already deployed hosts
90
+ # this would start the configuration job then.
91
+ ForemanAcd.initiate_acd_app_configurator(@app_instance)
92
+
76
93
  output
77
94
  end
78
95