foreman_fog_proxmox 0.15.0 → 0.16.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_fog_proxmox/proxmox_vm.js +54 -33
  3. data/app/assets/javascripts/foreman_fog_proxmox/proxmox_vm_server.js +25 -5
  4. data/app/assets/stylesheets/foreman_fog_proxmox/accordion.css +21 -0
  5. data/app/helpers/proxmox_compute_resources_helper.rb +1 -1
  6. data/app/helpers/proxmox_form_helper.rb +2 -2
  7. data/app/helpers/proxmox_vm_attrs_helper.rb +131 -0
  8. data/app/helpers/proxmox_vm_cloudinit_helper.rb +1 -1
  9. data/app/helpers/proxmox_vm_interfaces_helper.rb +3 -3
  10. data/app/helpers/proxmox_vm_volumes_helper.rb +3 -3
  11. data/app/models/concerns/fog_extensions/proxmox/node.rb +1 -1
  12. data/app/models/foreman_fog_proxmox/proxmox.rb +1 -1
  13. data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +3 -3
  14. data/app/models/foreman_fog_proxmox/proxmox_console.rb +1 -1
  15. data/app/models/foreman_fog_proxmox/proxmox_images.rb +5 -0
  16. data/app/models/foreman_fog_proxmox/proxmox_version.rb +1 -1
  17. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +5 -2
  18. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
  19. data/app/overrides/compute_resources_vms/form/add_from_profile_to_compute_attributes_form.rb +8 -0
  20. data/app/overrides/compute_resources_vms/form/add_react_component_to_host.rb +25 -0
  21. data/app/overrides/compute_resources_vms/form/update_react_component_to_host_form.rb +25 -0
  22. data/app/views/compute_resources_vms/form/proxmox/_add_react_component_to_host_form.html.erb +5 -0
  23. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +21 -21
  24. data/app/views/compute_resources_vms/form/proxmox/_update_react_component_to_host_form.html.erb +26 -0
  25. data/app/views/compute_resources_vms/form/proxmox/container/_config.html.erb +17 -13
  26. data/app/views/compute_resources_vms/form/proxmox/server/_config.html.erb +20 -20
  27. data/app/views/compute_resources_vms/form/proxmox/server/_volume_hard_disk.html.erb +4 -1
  28. data/config/routes.rb +3 -3
  29. data/lib/foreman_fog_proxmox/version.rb +1 -1
  30. data/package.json +42 -0
  31. data/test/factories/proxmox_factory.rb +7 -7
  32. data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +1 -1
  33. data/test/unit/foreman_fog_proxmox/proxmox_interfaces_test.rb +6 -6
  34. data/webpack/components/GeneralTabContent.js +107 -0
  35. data/webpack/components/ProxmoxComputeSelectors.js +141 -0
  36. data/webpack/components/ProxmoxContainer/MountPoint.js +91 -0
  37. data/webpack/components/ProxmoxContainer/ProxmoxContainerHardware.js +85 -0
  38. data/webpack/components/ProxmoxContainer/ProxmoxContainerNetwork.js +179 -0
  39. data/webpack/components/ProxmoxContainer/ProxmoxContainerOptions.js +104 -0
  40. data/webpack/components/ProxmoxContainer/ProxmoxContainerStorage.js +194 -0
  41. data/webpack/components/ProxmoxContainer/components/NetworkInterface.js +193 -0
  42. data/webpack/components/ProxmoxServer/ProxmoxServerHardware.js +204 -0
  43. data/webpack/components/ProxmoxServer/ProxmoxServerNetwork.js +161 -0
  44. data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +105 -0
  45. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +272 -0
  46. data/webpack/components/ProxmoxServer/components/CDRom.js +149 -0
  47. data/webpack/components/ProxmoxServer/components/CPUFlagsModal.js +88 -0
  48. data/webpack/components/ProxmoxServer/components/HardDisk.js +143 -0
  49. data/webpack/components/ProxmoxServer/components/NetworkInterface.js +150 -0
  50. data/webpack/components/ProxmoxStoragesUtils.js +50 -0
  51. data/webpack/components/ProxmoxVmType.js +256 -0
  52. data/webpack/components/ProxmoxVmUtils.js +62 -0
  53. data/webpack/components/common/FormInputs.js +143 -0
  54. data/webpack/global_index.js +15 -0
  55. data/webpack/index.js +7 -0
  56. metadata +50 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b41c2a927bcce7383e1fd55c588247e935e0c6e11ba967fe6628db72a80730df
4
- data.tar.gz: ce7634ce750c8317bc28906e4394888d50b66931b9c77586f3af90c339ca07c2
3
+ metadata.gz: 66a00f7786605946bb2388d497b7a2f512fb29fcf00a12f35bc7059e8b0825f4
4
+ data.tar.gz: 3e2bf00ae5430cad3c5927ba4636582cd78d83cefa7065088fa4458da638b173
5
5
  SHA512:
6
- metadata.gz: 65a3b9ac47bb382cc0c2b30430151a9db92bf64dcc9c64ed6c9fffb1fbb5dc525316d3475a7c5d88c21d30e3ffb8ad8eaf6ef5d6676cff53cfbd43dd5a086378
7
- data.tar.gz: e210dad89239964322d0dc1b5f3a326a11f449ada2ad61d23f87f2047d421454020aa4b14ffed1efc173f31fd7cb0c68d92a76a9df0757133c810993a49a5a85
6
+ metadata.gz: 9534979808ea0e6ad262d696497189b3129e6594478601649d74d475ef1cee060898988e662d50db447f04257b6e6cdcfa6d2d144fea0ef498e66c3a899bd140
7
+ data.tar.gz: 981cfaa3815ce58182a465490541051b4a79612f0153e3419142bbf5e3c7e58c68b7758b2cac8eaa4bae31a428717d239315695b6847f2d1d207b04f26ff8a41
@@ -15,8 +15,6 @@
15
15
  // You should have received a copy of the GNU General Public License
16
16
  // along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
17
17
 
18
- //= require jquery-ui/widgets/accordion
19
-
20
18
  $(document).ready(vmTypeSelected);
21
19
 
22
20
  function vmTypeSelected() {
@@ -25,13 +23,21 @@ function vmTypeSelected() {
25
23
  var host_uuid = $("input[id='host_uuid']").val();
26
24
  var new_vm = host_uuid == undefined;
27
25
  var fieldsets = [];
26
+ var fieldconfig = [];
28
27
  fieldsets.push({id: 'config_advanced_options', toggle: true, new_vm: new_vm, selected: selected});
29
28
  fieldsets.push({id: 'config_ext', toggle: true, new_vm: new_vm, selected: selected});
30
29
  fieldsets.push({id: 'volume', toggle: true, new_vm: new_vm, selected: selected});
31
30
  fieldsets.push({id: 'network', toggle: true, new_vm: true, selected: selected});
31
+ fieldconfig.push({id: 'config_options', new_vm: new_vm, selected: selected});
32
+ fieldconfig.push({id: 'config_cpu', new_vm: new_vm, selected: selected});
33
+ fieldconfig.push({id: 'config_memory', new_vm: new_vm, selected: selected});
34
+ fieldconfig.push({id: 'config_cdrom', new_vm: new_vm, selected: selected});
35
+ fieldconfig.push({id: 'config_os', new_vm: new_vm, selected: selected});
36
+ fieldconfig.push({id: 'config_dns', new_vm: new_vm, selected: selected});
32
37
  fieldsets.forEach(toggleFieldsets);
33
- toggleAccordion(selected);
34
38
  toggleVolumes(selected);
39
+ fieldconfig.forEach(toggleConfigs);
40
+ toggleAccordions();
35
41
  return false;
36
42
  }
37
43
 
@@ -131,7 +137,7 @@ function enableFieldset(fieldsetId, fieldset) {
131
137
  input_hidden_id(fieldsetId).removeAttr('disabled');
132
138
  }
133
139
 
134
- function disableFieldset(fieldsetId, fieldset) {
140
+ function disableFieldset(fieldsetId, fieldset) {
135
141
  if (fieldset.toggle && fieldset.new_vm){
136
142
  fieldset_id(fieldsetId, fieldset).hide();
137
143
  }
@@ -139,57 +145,72 @@ function disableFieldset(fieldsetId, fieldset) {
139
145
  input_hidden_id(fieldsetId).attr('disabled','disabled');
140
146
  }
141
147
 
142
- function enableConfigOptions(fieldsetId) {
143
- var field = $("#" + fieldsetId + "_advanced_options");
144
- field.accordion({collapsible : true, heightStyle: "content"});
145
- field.removeClass('disabled').find("*").prop("disabled", false);
146
- field.removeClass('hide');
148
+ function toggleFieldset(fieldsetId, fieldset, type1, type2) {
149
+ type1 === type2 ? enableFieldset(fieldsetId, fieldset) : disableFieldset(fieldsetId, fieldset);
147
150
  }
148
151
 
149
- function disableConfigOptions(fieldsetId) {
150
- var field = $("#" + fieldsetId + "_advanced_options");
151
- field.addClass('disabled').find("*").prop("disabled", true);
152
- field.addClass('hide');
152
+ function input_hidden_id(volume_id){
153
+ return $("div[id^='"+ volume_id +"_volumes']" + " + input:hidden");
153
154
  }
154
155
 
155
- function toggleConfigOptions(fieldsetId, type1, type2) {
156
- if (type1 === type2) {
157
- enableConfigOptions(fieldsetId);
158
- } else {
159
- disableConfigOptions(fieldsetId);
160
- }
156
+ function fieldset_id(fieldsetId, fieldset){
157
+ return $("fieldset[id^='" + fieldsetId + "_"+fieldset.id+"']");
161
158
  }
162
159
 
163
- function toggleAccordion(selected){
160
+ function fieldsets(type){
161
+ return type === 'qemu' ? ['server'] : ['container'];
162
+ }
163
+
164
+ function toggleFieldsets(fieldset){
165
+ var removable_input_hidden = $("div.removable-item[style='display: none;']" + " + input:hidden");
166
+ removable_input_hidden.attr('disabled','disabled');
164
167
  ['qemu', 'lxc'].forEach(function(type){
165
168
  fieldsets(type).forEach(function(fieldsetId){
166
- toggleConfigOptions(fieldsetId, selected, type);
169
+ toggleFieldset(fieldsetId, fieldset, fieldset.selected, type);
167
170
  });
168
171
  });
169
172
  }
170
173
 
171
- function toggleFieldset(fieldsetId, fieldset, type1, type2) {
172
- type1 === type2 ? enableFieldset(fieldsetId, fieldset) : disableFieldset(fieldsetId, fieldset);
174
+ function toggleAccordions() {
175
+ $('.accordion-content').hide();
176
+ $('.accordion-section').off('click').on('click', function(event) {
177
+ var $content = $(this).find('.accordion-content');
178
+ $content.slideToggle();
179
+ $(this).toggleClass('active');
180
+ $('.accordion-content').not($content).slideUp();
181
+ $('.accordion-section').not($(this)).removeClass('active');
182
+ event.stopPropagation();
183
+ });
184
+ $('.accordion-content').on('click', function(event) {
185
+ event.stopPropagation();
186
+ });
173
187
  }
174
188
 
175
- function input_hidden_id(volume_id){
176
- return $("div[id^='"+ volume_id +"_volumes']" + " + input:hidden");
189
+ function enableConfig(fieldsetId, fieldset) {
190
+ fieldset_id(fieldsetId, fieldset).removeClass('hide');
191
+ fieldset_id(fieldsetId, fieldset).removeAttr('disabled');
192
+ input_hidden_id(fieldsetId).removeAttr('disabled');
193
+
177
194
  }
178
195
 
179
- function fieldset_id(fieldsetId, fieldset){
180
- return $("fieldset[id^='" + fieldsetId + "_"+fieldset.id+"']");
196
+ function disableConfig(fieldsetId, fieldset) {
197
+ fieldset_id(fieldsetId, fieldset).addClass('hide');
198
+ fieldset_id(fieldsetId, fieldset).attr('disabled','disabled');
199
+ input_hidden_id(fieldsetId).attr('disabled','disabled');
181
200
  }
182
201
 
183
- function fieldsets(type){
184
- return type === 'qemu' ? ['server'] : ['container'];
202
+ function toggleConfig(fieldsetId, fieldset, type1, type2) {
203
+ if (type1 === type2) {
204
+ enableConfig(fieldsetId, fieldset);
205
+ } else {
206
+ disableConfig(fieldsetId, fieldset);
207
+ }
185
208
  }
186
209
 
187
- function toggleFieldsets(fieldset){
188
- var removable_input_hidden = $("div.removable-item[style='display: none;']" + " + input:hidden");
189
- removable_input_hidden.attr('disabled','disabled');
210
+ function toggleConfigs(fieldset){
190
211
  ['qemu', 'lxc'].forEach(function(type){
191
212
  fieldsets(type).forEach(function(fieldsetId){
192
- toggleFieldset(fieldsetId, fieldset, fieldset.selected, type);
213
+ toggleConfig(fieldsetId, fieldset, fieldset.selected, type);
193
214
  });
194
215
  });
195
216
  }
@@ -32,7 +32,7 @@ function getIndex(item) {
32
32
  }
33
33
 
34
34
  function isProfile() {
35
- return $(volumesAttributesSelector(true,0,'id')) !== undefined;
35
+ return $(volumesAttributesSelector(true,0,'id')).val() !== undefined;
36
36
  }
37
37
 
38
38
  function controllerSelected(item) {
@@ -42,23 +42,43 @@ function controllerSelected(item) {
42
42
  var profile = isProfile();
43
43
  var device_selector = volumesAttributesSelector(profile,index,'device');
44
44
  var id_selector = volumesAttributesSelector(profile,index,'id');
45
- $(device_selector).attr('data-soft-max', max);
46
- var device = $(device_selector).limitedSpinner('value');
45
+ var device = $(device_selector).val();
47
46
  var id = controller + device;
47
+ validateDeviceLimit(device_selector, max);
48
48
  $(id_selector).val(id);
49
49
  }
50
50
 
51
51
  function deviceSelected(item) {
52
- var device = $(item).limitedSpinner('value');
52
+ var device = $(item).val();
53
53
  var index = getIndex(item);
54
54
  var profile = isProfile();
55
55
  var controller_selector = volumesAttributesSelector(profile,index,'controller');
56
56
  var id_selector = volumesAttributesSelector(profile,index,'id');
57
57
  var controller = $(controller_selector).val();
58
58
  var id = controller + device;
59
+ var max = computeControllerMaxDevice(controller);
60
+ validateDeviceLimit(item, max);
59
61
  $(id_selector).val(id);
60
62
  }
61
63
 
64
+ function validateDeviceLimit(item, maxLimit) {
65
+ var warningContainer = $(item).closest('form').find('.warning-container');
66
+ var deviceNumber = $(item).val();
67
+ var submitButton = $(item).closest('form').find('[data-disable-with]');
68
+ if (!isNaN(maxLimit) && !isNaN(deviceNumber) && deviceNumber > maxLimit) {
69
+ if (warningContainer.length === 0) {
70
+ warningContainer = $('<div class="warning-container" style="color: red;"><span style="color: red;">&#9888;</span> Warning: Value exceeds the maximum limit of ' + maxLimit + '.</div>');
71
+ $(item).closest('.col-md-4').append(warningContainer);
72
+ }
73
+ warningContainer.show();
74
+ submitButton.prop('disabled', true);
75
+ } else {
76
+ warningContainer.hide();
77
+ warningContainer.remove();
78
+ submitButton.prop('disabled', false);
79
+ }
80
+ }
81
+
62
82
  function computeControllerMaxDevice(controller) {
63
83
  switch (controller) {
64
84
  case 'ide':
@@ -72,4 +92,4 @@ function computeControllerMaxDevice(controller) {
72
92
  default:
73
93
  return 1;
74
94
  }
75
- }
95
+ }
@@ -0,0 +1,21 @@
1
+ .accordion-section {
2
+ cursor: pointer;
3
+ }
4
+
5
+ .accordion-section > legend::before {
6
+ content: "\f054";
7
+ font-family: "Font Awesome 5 Free";
8
+ position: relative;
9
+ left: -1em;
10
+ top: 50%;
11
+ transform: translateY(-50%);
12
+ }
13
+
14
+ .accordion-section.active > legend::before {
15
+ content: "\f078";
16
+ font-family: "Font Awesome 5 Free";
17
+ position: relative;
18
+ left: -1em;
19
+ top: 50%;
20
+ transform: translateY(-50%);
21
+ }
@@ -34,7 +34,7 @@ module ProxmoxComputeResourcesHelper
34
34
  def cluster_nodes(compute_resource)
35
35
  nodes = compute_resource.nodes ? compute_resource.nodes.collect(&:node) : []
36
36
  rescue ::Foreman::Exception => e
37
- return [] if e.message == 'User token expired'
37
+ [] if e.message == 'User token expired'
38
38
  rescue StandardError => e
39
39
  logger.warn("failed to get cluster nodes: #{e}")
40
40
  raise e
@@ -30,7 +30,7 @@ module ProxmoxFormHelper
30
30
  addClass options, 'form-control'
31
31
  pass = f.password_field(attr, options) +
32
32
  tag(:span, '', class: 'glyphicon glyphicon-warning-sign input-addon', title: 'Caps lock ON',
33
- style: 'display:none')
33
+ style: 'display:none')
34
34
  if unset_button
35
35
  button = link_to_function(icon_text('edit', '', :kind => 'pficon'), 'toggle_input_group(this)',
36
36
  :id => 'disable-pass-btn', :class => 'btn btn-default', :title => _('Change the password'))
@@ -56,7 +56,7 @@ style: 'display:none')
56
56
  options[:form_builder_attrs] ||= {}
57
57
 
58
58
  content_tag(:div, :class => "#{options[:type]}_#{association}_fields_template form_template",
59
- :style => 'display: none;') do
59
+ :style => 'display: none;') do
60
60
  form_builder.fields_for(association, options[:object],
61
61
  :child_index => "new_#{options[:type]}_#{association}") do |f|
62
62
  render(:partial => options[:partial], :layout => options[:layout],
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of ForemanFogProxmox.
6
+
7
+ # ForemanFogProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # ForemanFogProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require 'fog/proxmox/helpers/disk_helper'
21
+ require 'fog/proxmox/helpers/nic_helper'
22
+ require 'fog/proxmox/helpers/cpu_helper'
23
+ require 'foreman_fog_proxmox/value'
24
+ require 'foreman_fog_proxmox/hash_collection'
25
+
26
+ # Convert a foreman form server hash into a fog-proxmox server attributes hash
27
+ module ProxmoxVmAttrsHelper
28
+ def object_to_attributes_hash(vms, from_profile, start_checked)
29
+ param_scope = from_profile ? "compute_attribute[vm_attrs]" : "host[compute_attributes]"
30
+ vm_h = ActiveSupport::HashWithIndifferentAccess.new
31
+ keys = [:vmid, :node_id, :type, :pool]
32
+ main = vms.attributes.select { |key, _value| keys.include? key }
33
+ vms.config.all_attributes.each do |key, value|
34
+ camel_key = key.to_s.include?('_') ? snake_to_camel(key.to_s).to_sym : key
35
+ vm_h[camel_key] = { :name => "#{param_scope}[config_attributes][#{key}]", :value => value } unless keys.include? key
36
+ end
37
+ main.each do |key, value|
38
+ camel_key = key.to_s.include?('_') ? snake_to_camel(key.to_s).to_sym : key
39
+ vm_h[camel_key] = { :name => "#{param_scope}[#{key}]", :value => value } if keys.include? key
40
+ end
41
+
42
+ vm_h.merge!(additional_attrs(vms, param_scope, start_checked))
43
+ vm_h[:interfaces] = network_attrs(param_scope, vms.interfaces)
44
+ vm_h[:disks] = volumes_attrs(param_scope, vms.volumes)
45
+ vm_h.merge(cpu_flags_attrs(param_scope, vms.config))
46
+ end
47
+
48
+ def cpu_flags_attrs(param_scope, config)
49
+ flag_attrs = ActiveSupport::HashWithIndifferentAccess.new
50
+ Fog::Proxmox::CpuHelper.flags.each do |key, _val|
51
+ flag_attrs.merge!({ key => { :name => "#{param_scope}[config_attributes][#{key}]", :value => config.public_send(key) } })
52
+ end
53
+ flag_attrs
54
+ end
55
+
56
+ def volumes_attrs(param_scope, volumes)
57
+ vol_attrs = []
58
+ volumes.each_with_index do |vol, id|
59
+ keys = []
60
+ type = ""
61
+ if vol.rootfs?
62
+ keys = ['id', 'volid', 'storage', 'size', 'storage_type']
63
+ type = 'rootfs'
64
+ elsif vol.hard_disk?
65
+ keys = ['id', 'volid', 'storage_type', 'storage', 'controller', 'device', 'cache', 'size']
66
+ type = 'hard_disk'
67
+ elsif vol.cdrom?
68
+ keys = ['id', 'storage_type', 'cdrom', 'storage', 'volid']
69
+ type = 'cdrom'
70
+ elsif vol.cloud_init?
71
+ keys = ['id', 'volid', 'storage_type', 'storage', 'controller', 'device']
72
+ type = 'cloud_init'
73
+ elsif vol.mount_point?
74
+ keys = ['id', 'volid', 'storage_type', 'storage', 'device', 'mp', 'size']
75
+ type = 'mount_point'
76
+ end
77
+ vol_attrs << { :name => type, :value => vol_keys(param_scope, keys, vol, id) }
78
+ end
79
+ vol_attrs
80
+ end
81
+
82
+ def vol_keys(param_scope, keys, vol, id)
83
+ attrs = ActiveSupport::HashWithIndifferentAccess.new
84
+ keys.each do |key|
85
+ camel_key = key.to_s.include?('_') ? snake_to_camel(key.to_s).to_sym : key
86
+ attrs[camel_key] = { :name => "#{param_scope}[volumes_attributes][#{id}][#{key}]", :value => vol.public_send(key) }
87
+ end
88
+ attrs
89
+ end
90
+
91
+ def network_attrs(param_scope, interfaces)
92
+ networks_attrs = []
93
+ interfaces.each_with_index do |interface, id|
94
+ attrs = ActiveSupport::HashWithIndifferentAccess.new
95
+ interface.all_attributes.each do |key, value|
96
+ camel_key = key.to_s.include?('_') ? snake_to_camel(key.to_s).to_sym : key
97
+ attrs[camel_key] = { :name => "#{param_scope}[interfaces_attributes][#{id}][#{key}]", :value => value }
98
+ end
99
+ networks_attrs << { :name => 'interface', :value => attrs }
100
+ end
101
+ networks_attrs
102
+ end
103
+
104
+ def additional_attrs(vms, param_scope, start_checked)
105
+ attributes = {
106
+ pool: vms.pool,
107
+ image_id: vms.image_id,
108
+ cpu_type: vms.config.cpu_type,
109
+ nameserver: vms.config.nameserver,
110
+ searchdomain: vms.config.searchdomain,
111
+ hostname: vms.config.hostname,
112
+ ostemplate_storage: vms.ostemplate_storage,
113
+ ostemplate_file: vms.ostemplate_file,
114
+ start_after_create: vms.start_after_create,
115
+ templated: vms.templated,
116
+ }
117
+ vms_keys = [:pool, :start_after_create]
118
+ extra_attrs = ActiveSupport::HashWithIndifferentAccess.new
119
+ attributes.each do |key, value|
120
+ camel_key = key.to_s.include?('_') ? snake_to_camel(key.to_s).to_sym : key
121
+ nested_key = vms_keys.include?(key) ? key : "config_attributes[#{key}]"
122
+ value = start_checked if key == :start_after_create
123
+ extra_attrs[camel_key] = { name: "#{param_scope}[#{nested_key}]", value: value }
124
+ end
125
+ extra_attrs
126
+ end
127
+
128
+ def snake_to_camel(str)
129
+ str.split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
130
+ end
131
+ end
@@ -48,7 +48,7 @@ module ProxmoxVmCloudinitHelper
48
48
  wd = create_temp_directory(ssh)
49
49
 
50
50
  configs.each do |config|
51
- config_file = ssh.run(%(echo "#{config[1]}" >> "#{wd}/#{config[0]}"))
51
+ config_file = ssh.run(%(echo '#{config[1]}' >> "#{wd}/#{config[0]}"))
52
52
  unless config_file.first.status.zero?
53
53
  delete_temp_dir(ssh, wd)
54
54
  raise ::Foreman::Exception, "Failed to create file #{config[0]}: #{config_file.first.stdout}"
@@ -56,13 +56,13 @@ module ProxmoxVmInterfacesHelper
56
56
  end
57
57
 
58
58
  def interface_common_typed_keys(type)
59
- ['id', type == 'qemu' ? 'macaddr' : 'hwaddr']
59
+ ['id', (type == 'qemu') ? 'macaddr' : 'hwaddr']
60
60
  end
61
61
 
62
62
  def compute_dhcps(interface_attributes_h)
63
- interface_attributes_h[:dhcp] = interface_attributes_h[:ip] == 'dhcp' ? '1' : '0'
63
+ interface_attributes_h[:dhcp] = (interface_attributes_h[:ip] == 'dhcp') ? '1' : '0'
64
64
  interface_attributes_h[:ip] = '' if interface_attributes_h[:dhcp] == '1'
65
- interface_attributes_h[:dhcp6] = interface_attributes_h[:ip6] == 'dhcp' ? '1' : '0'
65
+ interface_attributes_h[:dhcp6] = (interface_attributes_h[:ip6] == 'dhcp') ? '1' : '0'
66
66
  interface_attributes_h[:ip6] = '' if interface_attributes_h[:dhcp6] == '1'
67
67
  end
68
68
 
@@ -52,9 +52,9 @@ module ProxmoxVmVolumesHelper
52
52
  logger.debug(format(_('parse_hard_disk_volume(): args=%<args>s'), args: args))
53
53
  disk = {}
54
54
  disk[:id] = args['id'] if args.key?('id')
55
- disk[:volid] = args['volid'] if args.key?('volid')
56
- disk[:storage] = args['storage'].to_s if args.key?('storage')
57
- disk[:size] = args['size'].to_i if args.key?('size')
55
+ disk[:volid] = args['volid'] if args.key?('volid') && !args['volid'].empty?
56
+ disk[:storage] = args['storage'].to_s if args.key?('storage') && !args['storage'].empty?
57
+ disk[:size] = args['size'].to_i if args.key?('size') && !args['size'].empty?
58
58
  add_disk_options(disk, args) unless args.key?('options')
59
59
  disk[:options] = args['options'] if args.key?('options')
60
60
  disk.key?(:storage) ? disk : {}
@@ -31,7 +31,7 @@ module FogExtensions
31
31
  # We need to explicitly use the base 'each' method here on the page,
32
32
  # otherwise we get infinite recursion
33
33
  base_each = Fog::Collection.instance_method(:each)
34
- base_each.bind(page).call(&block)
34
+ base_each.bind_call(page, &block)
35
35
  end
36
36
  end
37
37
  self
@@ -181,7 +181,7 @@ module ForemanFogProxmox
181
181
  your server cannot connect to Proxmox due to network issues."
182
182
  end
183
183
 
184
- def host
184
+ def proxmox_host
185
185
  URI.parse(url).host
186
186
  end
187
187
  end
@@ -30,7 +30,7 @@ module ForemanFogProxmox
30
30
  unless compute_os_types(host).include?(ostype)
31
31
  raise ::Foreman::Exception,
32
32
  format(_('Operating system family %<type>s is not consistent with %<ostype>s'), type: host.operatingsystem.type,
33
- ostype: ostype)
33
+ ostype: ostype)
34
34
  end
35
35
  end
36
36
  super
@@ -42,8 +42,8 @@ ostype: ostype)
42
42
 
43
43
  def interface_compute_attributes(interface_attributes)
44
44
  vm_attrs = ForemanFogProxmox::HashCollection.new_hash_reject_keys(interface_attributes, [:identifier, :macaddr, :hwaddr])
45
- vm_attrs[:dhcp] = interface_attributes[:ip] == 'dhcp' ? '1' : '0'
46
- vm_attrs[:dhcp6] = interface_attributes[:ip6] == 'dhcp' ? '1' : '0'
45
+ vm_attrs[:dhcp] = (interface_attributes[:ip] == 'dhcp') ? '1' : '0'
46
+ vm_attrs[:dhcp6] = (interface_attributes[:ip6] == 'dhcp') ? '1' : '0'
47
47
  vm_attrs
48
48
  end
49
49
 
@@ -33,7 +33,7 @@ module ForemanFogProxmox
33
33
  vnc_console = vm.start_console(options)
34
34
  vmid = extract_vmid(uuid).to_i
35
35
  vnc_host_port = vnc_console['port'].to_i + vmid
36
- WsProxy.start(:host => host, :host_port => vnc_host_port, :password => vnc_console['ticket']).merge(
36
+ WsProxy.start(:host => proxmox_host, :host_port => vnc_host_port, :password => vnc_console['ticket']).merge(
37
37
  :name => vm.name, :type => type_console
38
38
  )
39
39
  rescue StandardError => e
@@ -48,9 +48,14 @@ module ForemanFogProxmox
48
48
  volumes = []
49
49
  nodes.each do |node|
50
50
  storages(node.node).each do |storage|
51
+ # fetches volumes of QEMU servers for images
51
52
  volumes += storage.volumes.list_by_content_type('images')
53
+
54
+ # fetches volumes of KVM containers for images
55
+ volumes += storage.volumes.list_by_content_type('rootdir')
52
56
  end
53
57
  end
58
+ # for creating image, only list volumes which are templated
54
59
  volumes.select(&:template?)
55
60
  end
56
61
 
@@ -36,7 +36,7 @@ module ForemanFogProxmox
36
36
  v = identity_client.read_version if identity_client
37
37
  v ? v['version'] : 'Unknown'
38
38
  rescue ::Foreman::Exception => e
39
- return 'Unkown' if e.message == 'User token expired'
39
+ 'Unkown' if e.message == 'User token expired'
40
40
  rescue StandardError => e
41
41
  logger.warn("failed to get identity client version: #{e}")
42
42
  raise e
@@ -32,10 +32,9 @@ module ForemanFogProxmox
32
32
  end
33
33
 
34
34
  def create_vm(args = {})
35
- vmid = args[:vmid].to_i
36
35
  type = args[:type]
37
36
  node = client.nodes.get(args[:node_id])
38
- vmid = node.servers.next_id.to_i if vmid < 1
37
+ vmid = args[:vmid] = assign_vmid(args[:vmid].to_i, node)
39
38
  raise ::Foreman::Exception, format(N_('invalid vmid=%<vmid>s'), vmid: vmid) unless node.servers.id_valid?(vmid)
40
39
 
41
40
  image_id = args[:image_id]
@@ -55,6 +54,10 @@ module ForemanFogProxmox
55
54
  raise e
56
55
  end
57
56
 
57
+ def assign_vmid(vmid, node)
58
+ (vmid < 1) ? node.servers.next_id : vmid
59
+ end
60
+
58
61
  def compute_clone_attributes(args, container, type)
59
62
  args = parse_cloudinit_config(args) if args[:user_data]
60
63
  parsed_args = parse_typed_vm(args, type)
@@ -191,7 +191,7 @@ module ForemanFogProxmox
191
191
  new_attr_type ||= type
192
192
  logger.debug("new_typed_vm(#{type}): new_attr_type=#{new_attr_type}")
193
193
  logger.debug("new_typed_vm(#{type}): new_attr=#{new_attr}'")
194
- options = !new_attr.key?('vmid') || ForemanFogProxmox::Value.empty?(new_attr['vmid']) ? vm_typed_instance_defaults(type).merge(new_attr).merge(type: type) : new_attr
194
+ options = (!new_attr.key?('vmid') || ForemanFogProxmox::Value.empty?(new_attr['vmid'])) ? vm_typed_instance_defaults(type).merge(new_attr).merge(type: type) : new_attr
195
195
  logger.debug("new_typed_vm(#{type}): options=#{options}")
196
196
  vm_h = parse_typed_vm(options, type).deep_symbolize_keys
197
197
  logger.debug("new_typed_vm(#{type}): vm_h=#{vm_h}")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright 2018 Tristan Robert
2
4
 
3
5
  # This file is part of ForemanFogProxmox.
@@ -15,6 +17,12 @@
15
17
  # You should have received a copy of the GNU General Public License
16
18
  # along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
17
19
 
20
+ Deface::Override.new(
21
+ virtual_path: 'compute_attributes/_compute_form',
22
+ name: 'remove_networks_and_volumes_partial',
23
+ remove: "erb[loud]:contains('compute_resources_vms/form/networks'), erb[loud]:contains('compute_resources_vms/form/volumes')"
24
+ )
25
+
18
26
  Deface::Override.new(
19
27
  :virtual_path => 'compute_attributes/_form',
20
28
  :name => 'add_from_profile_to_compute_attributes_form',
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of ForemanFogProxmox.
6
+
7
+ # ForemanFogProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # ForemanFogProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ Deface::Override.new(
21
+ virtual_path: 'hosts/_form',
22
+ name: 'add_react_component_to_virtual_machine_tab',
23
+ insert_after: "li.active",
24
+ partial: 'compute_resources_vms/form/proxmox/add_react_component_to_host_form'
25
+ )
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of ForemanFogProxmox.
6
+
7
+ # ForemanFogProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # ForemanFogProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ Deface::Override.new(
21
+ :virtual_path => 'hosts/_compute',
22
+ :name => 'update_react_component_to_virtual_machine_tab',
23
+ :replace => "erb[loud]:contains('hosts/compute_detail')",
24
+ :partial => 'compute_resources_vms/form/proxmox/update_react_component_to_host_form'
25
+ )
@@ -0,0 +1,5 @@
1
+ <% content_for(:javascripts) do %>
2
+ <%= webpacked_plugins_js_for :foreman_fog_proxmox %>
3
+ <%= javascript_include_tag 'foreman_fog_proxmox/proxmox_vm', "data-turbolinks-track" => true %>
4
+ <% end %>
5
+ <%= react_component('ProxmoxVmType', { registerComp: true }) unless @host.managed %>