the_foreman_proxmox 0.3.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +102 -0
  4. data/Rakefile +47 -0
  5. data/app/assets/javascripts/the_foreman_proxmox/proxmox.js +110 -0
  6. data/app/controllers/concerns/the_foreman_proxmox/controller/parameters/compute_resource.rb +41 -0
  7. data/app/controllers/the_foreman_proxmox/compute_resources_controller.rb +38 -0
  8. data/app/helpers/proxmox_compute_helper.rb +154 -0
  9. data/app/helpers/proxmox_compute_selectors_helper.rb +148 -0
  10. data/app/models/concerns/fog_extensions/proxmox/disk.rb +29 -0
  11. data/app/models/concerns/fog_extensions/proxmox/server.rb +82 -0
  12. data/app/models/concerns/fog_extensions/proxmox/server_config.rb +49 -0
  13. data/app/models/concerns/fog_extensions/proxmox/volume.rb +29 -0
  14. data/app/models/the_foreman_proxmox/options_select.rb +36 -0
  15. data/app/models/the_foreman_proxmox/proxmox.rb +394 -0
  16. data/app/views/api/v2/compute_resources/proxmox.json.rabl +1 -0
  17. data/app/views/compute_resources/form/_proxmox.html.erb +24 -0
  18. data/app/views/compute_resources/show/_proxmox.html.erb +21 -0
  19. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +45 -0
  20. data/app/views/compute_resources_vms/form/proxmox/_config.html.erb +55 -0
  21. data/app/views/compute_resources_vms/form/proxmox/_network.html.erb +24 -0
  22. data/app/views/compute_resources_vms/form/proxmox/_volume.html.erb +23 -0
  23. data/app/views/compute_resources_vms/index/_proxmox.html.erb +45 -0
  24. data/app/views/compute_resources_vms/show/_proxmox.html.erb +36 -0
  25. data/app/views/dashboard/_the_foreman_proxmox_widget.erb +19 -0
  26. data/app/views/images/form/_proxmox.html.erb +4 -0
  27. data/config/routes.rb +24 -0
  28. data/lib/tasks/the_foreman_proxmox_tasks.rake +47 -0
  29. data/lib/the_foreman_proxmox.rb +23 -0
  30. data/lib/the_foreman_proxmox/engine.rb +92 -0
  31. data/lib/the_foreman_proxmox/version.rb +22 -0
  32. data/locale/Makefile +60 -0
  33. data/locale/en/foreman_proxmox.po +19 -0
  34. data/locale/gemspec.rb +2 -0
  35. data/locale/the_foreman_proxmox.pot +19 -0
  36. data/test/factories/the_foreman_proxmox_factories.rb +33 -0
  37. data/test/helpers/proxmox_compute_helper_test.rb +130 -0
  38. data/test/test_plugin_helper.rb +15 -0
  39. data/test/unit/the_foreman_proxmox_test.rb +11 -0
  40. metadata +143 -0
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox 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
+ # TheForemanProxmox 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 TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module ProxmoxComputeSelectorsHelper
21
+ def proxmox_controllers_map
22
+ [TheForemanProxmox::OptionsSelect.new(id:'ide', name: 'IDE', range: 3),
23
+ TheForemanProxmox::OptionsSelect.new(id:'sata', name: 'SATA', range: 5),
24
+ TheForemanProxmox::OptionsSelect.new(id:'scsi', name: 'SCSI', range: 13),
25
+ TheForemanProxmox::OptionsSelect.new(name:'VirtIO Block', id: 'virtio', range: 15)]
26
+ end
27
+
28
+ def proxmox_operating_systems_map
29
+ [OpenStruct.new(id: 'other', name: 'Unspecified OS'),
30
+ OpenStruct.new(id: 'wxp', name: 'Microsoft Windows XP'),
31
+ OpenStruct.new(id: 'w2k', name: 'Microsoft Windows 2000'),
32
+ OpenStruct.new(id: 'w2k3', name: 'Microsoft Windows 2003'),
33
+ OpenStruct.new(id: 'w2k8', name: 'Microsoft Windows 2008'),
34
+ OpenStruct.new(id: 'wvista', name: 'Microsoft Windows Vista'),
35
+ OpenStruct.new(id: 'win7', name: 'Microsoft Windows 7'),
36
+ OpenStruct.new(id: 'win8', name: 'Microsoft Windows 8/2012/2012r2'),
37
+ OpenStruct.new(id: 'win10', name: 'Microsoft Windows 10/2016'),
38
+ OpenStruct.new(id: 'l24', name: 'Linux 2.4 Kernel'),
39
+ OpenStruct.new(id: 'l26', name: 'Linux 2.6/3.X + Kernel'),
40
+ OpenStruct.new(id: 'solaris', name: 'Solaris/OpenSolaris/OpenIndiania kernel')]
41
+ end
42
+
43
+ def proxmox_vgas_map
44
+ [OpenStruct.new(id: 'std', name: 'Standard VGA'),
45
+ OpenStruct.new(id: 'vmware', name: 'Vmware compatible'),
46
+ OpenStruct.new(id: 'qxl', name: 'SPICE'),
47
+ OpenStruct.new(id: 'qxl2', name: 'SPICE 2 monnitors'),
48
+ OpenStruct.new(id: 'qxl3', name: 'SPICE 3 monnitors'),
49
+ OpenStruct.new(id: 'qxl4', name: 'SPICE 4 monnitors'),
50
+ OpenStruct.new(id: 'serial0', name: 'Serial terminal 0'),
51
+ OpenStruct.new(id: 'serial1', name: 'Serial terminal 1'),
52
+ OpenStruct.new(id: 'serial2', name: 'Serial terminal 2'),
53
+ OpenStruct.new(id: 'serial3', name: 'Serial terminal 3')]
54
+ end
55
+
56
+ def proxmox_keyboards_map
57
+ [OpenStruct.new(id: 'de', name: 'Deutsch'),
58
+ OpenStruct.new(id: 'de-ch', name: 'Deutsch (Swiss)'),
59
+ OpenStruct.new(id: 'da', name: 'Danish'),
60
+ OpenStruct.new(id: 'en-gb', name: 'English (UK)'),
61
+ OpenStruct.new(id: 'en-us', name: 'English (US)'),
62
+ OpenStruct.new(id: 'es', name: 'Spanish'),
63
+ OpenStruct.new(id: 'fi', name: 'Finnish'),
64
+ OpenStruct.new(id: 'fr', name: 'French'),
65
+ OpenStruct.new(id: 'fr-be', name: 'French (Belgium)'),
66
+ OpenStruct.new(id: 'fr-ca', name: 'French (Canadian)'),
67
+ OpenStruct.new(id: 'fr-ch', name: 'French (Swiss)'),
68
+ OpenStruct.new(id: 'hu', name: 'Hungarian'),
69
+ OpenStruct.new(id: 'is', name: 'Israelian'),
70
+ OpenStruct.new(id: 'it', name: 'Italian'),
71
+ OpenStruct.new(id: 'ja', name: 'Japanese'),
72
+ OpenStruct.new(id: 'lt', name: 'Lituanian'),
73
+ OpenStruct.new(id: 'mk', name: ''),
74
+ OpenStruct.new(id: 'nl', name: 'Nederland'),
75
+ OpenStruct.new(id: 'no', name: 'Norway'),
76
+ OpenStruct.new(id: 'pl', name: 'Polish'),
77
+ OpenStruct.new(id: 'pt', name: 'Portugese'),
78
+ OpenStruct.new(id: 'pt-br', name: 'Portugese (Brasilian)'),
79
+ OpenStruct.new(id: 'sv', name: 'Sv'),
80
+ OpenStruct.new(id: 'sl', name: 'Slovakian'),
81
+ OpenStruct.new(id: 'tr', name: 'Tr')]
82
+ end
83
+
84
+ def get_controller(id)
85
+ proxmox_controllers_map.find { |controller| controller.id == id}
86
+ end
87
+
88
+ def proxmox_max_device(id)
89
+ options_select = get_controller(id)
90
+ options_select ? options_select.range : 1
91
+ end
92
+
93
+ def proxmox_caches_map
94
+ [OpenStruct.new(id: 'directsync', name: 'Direct sync'),
95
+ OpenStruct.new(id: 'writethrough', name: 'Write through'),
96
+ OpenStruct.new(id: 'writeback', name: 'Write back'),
97
+ OpenStruct.new(id: 'unsafe', name: 'Write back unsafe'),
98
+ OpenStruct.new(id: 'none', name: 'No cache')]
99
+ end
100
+
101
+ def proxmox_cpus_map
102
+ [OpenStruct.new(id: '486', name: '486'),
103
+ OpenStruct.new(id: 'athlon', name: 'athlon'),
104
+ OpenStruct.new(id: 'core2duo', name: 'core2duo'),
105
+ OpenStruct.new(id: 'coreduo', name: 'coreduo'),
106
+ OpenStruct.new(id: 'kvm32', name: 'kvm32'),
107
+ OpenStruct.new(id: 'kvm64', name: '(Default) kvm64'),
108
+ OpenStruct.new(id: 'pentium', name: 'pentium'),
109
+ OpenStruct.new(id: 'pentium2', name: 'pentium2'),
110
+ OpenStruct.new(id: 'pentium3', name: 'pentium3'),
111
+ OpenStruct.new(id: 'phenom', name: 'phenom'),
112
+ OpenStruct.new(id: 'qemu32', name: 'qemu32'),
113
+ OpenStruct.new(id: 'qemu64', name: 'qemu64'),
114
+ OpenStruct.new(id: 'Conroe', name: 'Conroe'),
115
+ OpenStruct.new(id: 'Penryn', name: 'Penryn'),
116
+ OpenStruct.new(id: 'Nehalem', name: 'Nehalem'),
117
+ OpenStruct.new(id: 'Westmere', name: 'Westmere'),
118
+ OpenStruct.new(id: 'SandyBridge', name: 'SandyBridge'),
119
+ OpenStruct.new(id: 'IvyBridge', name: 'IvyBridge'),
120
+ OpenStruct.new(id: 'Haswell', name: 'Haswell'),
121
+ OpenStruct.new(id: 'Haswell-noTSX', name: 'Haswell-noTSX'),
122
+ OpenStruct.new(id: 'Broadwell', name: 'Broadwell'),
123
+ OpenStruct.new(id: 'Broadwell-noTSX', name: 'Broadwell-noTSX'),
124
+ OpenStruct.new(id: 'Skylake-Client', name: 'Skylake-Client'),
125
+ OpenStruct.new(id: 'Opteron_G1', name: 'Opteron_G1'),
126
+ OpenStruct.new(id: 'Opteron_G2', name: 'Opteron_G2'),
127
+ OpenStruct.new(id: 'Opteron_G3', name: 'Opteron_G3'),
128
+ OpenStruct.new(id: 'Opteron_G4', name: 'Opteron_G4'),
129
+ OpenStruct.new(id: 'Opteron_G5', name: 'Opteron_G5'),
130
+ OpenStruct.new(id: 'host', name: 'host')]
131
+ end
132
+
133
+ def proxmox_scsihw_map
134
+ [OpenStruct.new(id: 'lsi', name: 'lsi'),
135
+ OpenStruct.new(id: 'lsi53c810', name: 'lsi53c810'),
136
+ OpenStruct.new(id: 'megasas', name: 'megasas'),
137
+ OpenStruct.new(id: 'virtio-scsi-pci', name: 'virtio-scsi-pci'),
138
+ OpenStruct.new(id: 'virtio-scsi-single', name: 'virtio-scsi-single'),
139
+ OpenStruct.new(id: 'pvscsi', name: 'pvscsi')]
140
+ end
141
+
142
+ def proxmox_networkcards_map
143
+ [OpenStruct.new(id: 'e1000', name: 'Intel E1000'),
144
+ OpenStruct.new(id: 'virtio', name: 'VirtIO (paravirtualized)'),
145
+ OpenStruct.new(id: 'rtl8139', name: 'Realtek RTL8139'),
146
+ OpenStruct.new(id: 'vmxnet3', name: 'VMware vmxnet3')]
147
+ end
148
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of ForemanProxmox.
6
+
7
+ # TheForemanProxmox 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
+ # TheForemanProxmox 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 TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module FogExtensions
21
+ module Proxmox
22
+ module Disk
23
+ extend ActiveSupport::Concern
24
+ def templated?
25
+ volid ? volid.match(/^([\w-]+)[:]base-(\d+)-disk-(\d+)/) : false
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox 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
+ # TheForemanProxmox 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 TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module FogExtensions
21
+ module Proxmox
22
+ module Server
23
+ extend ActiveSupport::Concern
24
+ attr_accessor :image_id, :templated
25
+ def to_s
26
+ name
27
+ end
28
+ def persisted?
29
+ !!identity && !!uptime
30
+ end
31
+ def start
32
+ action('start')
33
+ end
34
+ def stop
35
+ action('stop')
36
+ end
37
+ def reboot
38
+ stop
39
+ start
40
+ end
41
+ def reset
42
+ reboot
43
+ end
44
+ def mac
45
+ config.mac_addresses.first
46
+ end
47
+ def memory
48
+ config.memory * 1024 * 1024
49
+ end
50
+ def state
51
+ qmpstatus
52
+ end
53
+ def description
54
+ config.description
55
+ end
56
+ def vm_description
57
+ "Name=#{name}, vmid=#{vmid}"
58
+ end
59
+ def select_nic(fog_nics, nic)
60
+ fog_nics.find {|fog_nic| fog_nic.identity.to_s == nic.identifier}
61
+ end
62
+ def interfaces
63
+ config.interfaces
64
+ end
65
+ def volumes
66
+ config.disks.reject { |disk| disk.cdrom? }
67
+ end
68
+ def disks
69
+ config.disks.collect { |disk| disk.id+': '+disk.volid }
70
+ end
71
+ def vga
72
+ config.vga
73
+ end
74
+ def interfaces_attributes=(attrs); end
75
+ def volumes_attributes=(attrs); end
76
+ def config_attributes=(attrs); end
77
+ def templated?
78
+ volumes.any? { |volume| volume.templated? }
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox 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
+ # TheForemanProxmox 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 TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require 'fog/proxmox/helpers/cpu_helper'
21
+
22
+ module FogExtensions
23
+ module Proxmox
24
+ module ServerConfig
25
+ extend ActiveSupport::Concern
26
+ def cpu_type
27
+ Fog::Proxmox::CpuHelper.extract_type(cpu)
28
+ end
29
+ def spectre
30
+ Fog::Proxmox::CpuHelper.extract_spectre(cpu)
31
+ end
32
+ def pcid
33
+ Fog::Proxmox::CpuHelper.extract_pcid(cpu)
34
+ end
35
+ def cdrom
36
+ %w[none cdrom].include?(disks.cdrom.volid) ? disks.cdrom.volid : 'image'
37
+ end
38
+ def cdrom_storage
39
+ disks.cdrom.storage
40
+ end
41
+ def cdrom_iso
42
+ disks.cdrom.volid
43
+ end
44
+ def cdrom_image
45
+ %w[none cdrom].include?(disks.cdrom.volid) ? disks.cdrom.volid : 'image'
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox 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
+ # TheForemanProxmox 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 TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module FogExtensions
21
+ module Proxmox
22
+ module Volume
23
+ extend ActiveSupport::Concern
24
+ def templated?
25
+ volid ? volid.match(/^([\w-]+)[:]base-(\d+)-disk-(\d+)/) : false
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox 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
+ # TheForemanProxmox 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 TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module TheForemanProxmox
21
+ class OptionsSelect
22
+ attr_accessor :id
23
+ attr_accessor :name
24
+ attr_accessor :range
25
+
26
+ def to_s
27
+ id
28
+ end
29
+
30
+ def initialize(args = {})
31
+ @id = args[:id]
32
+ @name = args[:name]
33
+ @range = args[:range]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheTheForemanProxmox.
6
+
7
+ # TheTheForemanProxmox 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
+ # TheTheForemanProxmox 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 TheTheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require 'fog/proxmox'
21
+
22
+ module TheForemanProxmox
23
+ class Proxmox < ComputeResource
24
+ include ProxmoxComputeHelper
25
+ validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true
26
+ validates :user, :format => { :with => /(\w+)[@]{1}(\w+)/ }, :presence => true
27
+ validates :password, :presence => true
28
+ before_create :test_connection
29
+ attr_accessor :node
30
+
31
+ def provided_attributes
32
+ super.merge(
33
+ :mac => :mac
34
+ )
35
+ end
36
+
37
+ def self.provider_friendly_name
38
+ "Proxmox"
39
+ end
40
+
41
+ def capabilities
42
+ [:build, :new_volume, :image]
43
+ end
44
+
45
+ def self.model_name
46
+ ComputeResource.model_name
47
+ end
48
+
49
+ def credentials_valid?
50
+ errors[:url].empty? && errors[:user].empty? && errors[:user].include?('@') && errors[:password].empty? && node
51
+ end
52
+
53
+ def test_connection(options = {})
54
+ super
55
+ credentials_valid?
56
+ rescue => e
57
+ errors[:base] << e.message
58
+ errors[:url] << e.message
59
+ end
60
+
61
+ def nodes
62
+ nodes = client.nodes.all
63
+ nodes.sort_by(&:node)
64
+ end
65
+
66
+ def pools
67
+ pools = identity_client.pools.all
68
+ pools.sort_by(&:poolid)
69
+ end
70
+
71
+ def storages
72
+ storages = node.storages.list_by_content_type 'images'
73
+ storages.sort_by(&:storage)
74
+ end
75
+
76
+ def storages_isos
77
+ storages = node.storages.list_by_content_type 'iso'
78
+ storages.sort_by(&:storage)
79
+ end
80
+
81
+ def isos(storage_id)
82
+ storage = node.storages.find_by_id storage_id if storage_id
83
+ storage.volumes.list_by_content_type('iso').sort_by(&:volid) if storage
84
+ end
85
+
86
+ def associated_host(vm)
87
+ associate_by('mac', vm.mac)
88
+ end
89
+
90
+ def bridges
91
+ node = network_client.nodes.all.first
92
+ bridges = node.networks.all(type: 'bridge')
93
+ bridges.sort_by(&:iface)
94
+ end
95
+
96
+ def available_images
97
+ templates.collect { |template| OpenStruct.new(id: template.vmid) }
98
+ end
99
+
100
+ def templates
101
+ storage = storages.first
102
+ images = storage.volumes.list_by_content_type('images')
103
+ images.select { |image| image.templated? }
104
+ end
105
+
106
+ def template(vmid)
107
+ find_vm_by_uuid(vmid)
108
+ end
109
+
110
+ def host_compute_attrs(host)
111
+ super.tap do |attrs|
112
+ ostype = host.compute_attributes['config_attributes']['ostype']
113
+ raise Foreman::Exception.new("Operating system family #{host.operatingsystem.type} is not consistent with #{ostype}") unless compute_os_types(host).include?(ostype)
114
+ end
115
+ end
116
+
117
+ def host_interfaces_attrs(host)
118
+ host.interfaces.select(&:physical?).each.with_index.reduce({}) do |hash, (nic, index)|
119
+ raise ::Foreman::Exception.new N_("Identifier interface[#{index}] required.") if nic.identifier.empty?
120
+ raise ::Foreman::Exception.new N_("Invalid identifier interface[#{index}]. Must be net[n] with n integer >= 0") unless Fog::Proxmox::ControllerHelper.valid?(Fog::Compute::Proxmox::Interface::NAME,nic.identifier)
121
+ hash.merge(index.to_s => nic.compute_attributes.merge(id: nic.identifier, ip: nic.ip, ip6: nic.ip6))
122
+ end
123
+ end
124
+
125
+ def new_volume(attr = {})
126
+ opts = volume_defaults('scsi',1).merge(attr.to_h).deep_symbolize_keys
127
+ opts[:size] = opts[:size].to_s
128
+ Fog::Compute::Proxmox::Disk.new(opts)
129
+ end
130
+
131
+ def new_interface(attr = {})
132
+ opts = interface_defaults.merge(attr.to_h).deep_symbolize_keys
133
+ Fog::Compute::Proxmox::Interface.new(opts)
134
+ end
135
+
136
+ def vm_compute_attributes(vm)
137
+ vm_attrs = vm.attributes rescue {}
138
+ vm_attrs = vm_attrs.reject{|k,v| k == :id }
139
+ vm_attrs = set_vm_volumes_attributes(vm, vm_attrs)
140
+ vm_attrs = set_vm_interfaces_attributes(vm, vm_attrs)
141
+ vm_attrs
142
+ end
143
+
144
+ def set_vm_volumes_attributes(vm, vm_attrs)
145
+ if vm.config.respond_to?(:volumes)
146
+ volumes = vm.config.volumes || []
147
+ vm_attrs[:volumes_attributes] = Hash[volumes.each_with_index.map { |volume, idx| [idx.to_s, volume.attributes] }]
148
+ end
149
+ vm_attrs
150
+ end
151
+
152
+ def set_vm_interfaces_attributes(vm, vm_attrs)
153
+ if vm.config.respond_to?(:interfaces)
154
+ interfaces = vm.config.interfaces || []
155
+ vm_attrs[:interfaces_attributes] = Hash[interfaces.each_with_index.map { |interface, idx| [idx.to_s, interface.attributes] }]
156
+ end
157
+ vm_attrs
158
+ end
159
+
160
+ def new_vm(attr = {})
161
+ vm = node.servers.new(vm_instance_defaults.merge(parse_vm(attr)))
162
+ logger.debug("new_vm() vm.config=#{vm.config.inspect}")
163
+ vm
164
+ end
165
+
166
+ def create_vm(args = {})
167
+ vmid = args[:vmid]
168
+ raise ::Foreman::Exception.new N_("invalid vmid=#{vmid}") unless node.servers.id_valid?(vmid)
169
+ image_id = args[:image_id]
170
+ node = get_cluster_node args
171
+ if image_id
172
+ logger.debug("create_vm(): clone #{image_id} in #{vmid}")
173
+ image = node.servers.get image_id
174
+ image.clone(vmid)
175
+ else
176
+ logger.debug("create_vm(): #{args}")
177
+ convert_sizes(args)
178
+ node.servers.create(parse_vm(args))
179
+ end
180
+ vm = find_vm_by_uuid(vmid)
181
+ vm
182
+ rescue => e
183
+ logger.warn "failed to create vm: #{e}"
184
+ destroy_vm vm.id if vm
185
+ raise e
186
+ end
187
+
188
+ def find_vm_by_uuid(uuid)
189
+ node.servers.get(uuid)
190
+ rescue Fog::Errors::Error => e
191
+ Foreman::Logging.exception("Failed retrieving proxmox vm by vmid=#{uuid}", e)
192
+ raise(ActiveRecord::RecordNotFound)
193
+ end
194
+
195
+ def supports_update?
196
+ true
197
+ end
198
+
199
+ def update_required?(old_attrs, new_attrs)
200
+ return true if super(old_attrs, new_attrs)
201
+
202
+ new_attrs[:interfaces_attributes].each do |key, interface|
203
+ return true if (interface[:id].blank? || interface[:_delete] == '1') && key != 'new_interfaces' #ignore the template
204
+ end if new_attrs[:interfaces_attributes]
205
+
206
+ new_attrs[:volumes_attributes].each do |key, volume|
207
+ return true if (volume[:id].blank? || volume[:_delete] == '1') && key != 'new_volumes' #ignore the template
208
+ end if new_attrs[:volumes_attributes]
209
+
210
+ false
211
+ end
212
+
213
+ def editable_network_interfaces?
214
+ true
215
+ end
216
+
217
+ def user_data_supported?
218
+ true
219
+ end
220
+
221
+ def image_exists?(image)
222
+ find_vm_by_uuid(image)
223
+ end
224
+
225
+ def save_vm(uuid, attr)
226
+ vm = find_vm_by_uuid(uuid)
227
+ logger.debug("save_vm(): #{attr}")
228
+ templated = attr[:templated]
229
+ if (templated == '1' && !vm.templated?)
230
+ vm.template
231
+ else
232
+ merged = vm.config.attributes.merge!(parse_vm(attr).symbolize_keys).deep_symbolize_keys
233
+ filtered = merged.reject { |key,value| %w[node vmid].include?(key) || [:templated,:image_id].include?(key) || value.to_s.empty? }
234
+ vm.update(filtered)
235
+ end
236
+ end
237
+
238
+ def next_vmid
239
+ node.servers.next_id
240
+ end
241
+
242
+ def node
243
+ @node ||= get_cluster_node
244
+ end
245
+
246
+ def ssl_certs
247
+ self.attrs[:ssl_certs]
248
+ end
249
+
250
+ def ssl_certs=(value)
251
+ self.attrs[:ssl_certs] = value
252
+ end
253
+
254
+ def certs_to_store
255
+ return if ssl_certs.blank?
256
+ store = OpenSSL::X509::Store.new
257
+ ssl_certs.split(/(?=-----BEGIN)/).each do |cert|
258
+ x509_cert = OpenSSL::X509::Certificate.new cert
259
+ store.add_cert x509_cert
260
+ end
261
+ store
262
+ rescue => e
263
+ logger.error(e)
264
+ raise ::Foreman::Exception.new N_("Unable to store X509 certificates")
265
+ end
266
+
267
+ def ssl_verify_peer
268
+ self.attrs[:ssl_verify_peer].blank? ? false : Foreman::Cast.to_bool(self.attrs[:ssl_verify_peer])
269
+ end
270
+
271
+ def ssl_verify_peer=(value)
272
+ self.attrs[:ssl_verify_peer] = value
273
+ end
274
+
275
+ def connection_options
276
+ opts = http_proxy ? {proxy: http_proxy.full_url} : {disable_proxy: 1}
277
+ opts.store(:ssl_verify_peer, ssl_verify_peer)
278
+ opts.store(:ssl_cert_store, certs_to_store) if Foreman::Cast.to_bool(ssl_verify_peer)
279
+ opts
280
+ end
281
+
282
+ def console(uuid)
283
+ vm = find_vm_by_uuid(uuid)
284
+ if vm.config.type_console == 'vnc'
285
+ vnc_console = vm.start_console(websocket: 1)
286
+ WsProxy.start(:host => host, :host_port => vnc_console['port'], :password => vnc_console['ticket']).merge(:name => vm.name, :type => vm.config.type_console)
287
+ else
288
+ raise ::Foreman::Exception.new(N_("%s console is not supported at this time"), vm.config.type_console)
289
+ end
290
+ end
291
+
292
+ private
293
+
294
+ def fog_credentials
295
+ { pve_url: url,
296
+ pve_username: user,
297
+ pve_password: password,
298
+ connection_options: connection_options }
299
+ end
300
+
301
+ def client
302
+ @client ||= ::Fog::Compute::Proxmox.new(fog_credentials)
303
+ end
304
+
305
+ def identity_client
306
+ @identity_client ||= ::Fog::Identity::Proxmox.new(fog_credentials)
307
+ end
308
+
309
+ def network_client
310
+ @network_client ||= ::Fog::Network::Proxmox.new(fog_credentials)
311
+ end
312
+
313
+ def disconnect
314
+ client.terminate if @client
315
+ @client = nil
316
+ identity_client.terminate if @identity_client
317
+ @identity_client = nil
318
+ network_client.terminate if @network_client
319
+ @network_client = nil
320
+ end
321
+
322
+ def vm_instance_defaults
323
+ super.merge(
324
+ vmid: next_vmid,
325
+ type: 'qemu',
326
+ node: node.to_s,
327
+ cores: 1,
328
+ sockets: 1,
329
+ kvm: 0,
330
+ vga: 'std',
331
+ memory: 512 * MEGA,
332
+ ostype: 'l26',
333
+ keyboard: 'en-us',
334
+ cpu: 'kvm64',
335
+ scsihw: 'virtio-scsi-pci',
336
+ ide2: "none,media=cdrom",
337
+ templated: 0
338
+ ).merge(Fog::Proxmox::DiskHelper.flatten(volume_defaults)).merge(Fog::Proxmox::NicHelper.flatten(interface_defaults))
339
+ end
340
+
341
+ def volume_defaults(controller = 'scsi', device = 0)
342
+ id = "#{controller}#{device}"
343
+ { id: id, storage: storages.first.to_s, size: (8 * GIGA), options: { cache: 'none' } }
344
+ end
345
+
346
+ def interface_defaults(id = 'net0')
347
+ { id: id, model: 'virtio', bridge: bridges.first.to_s }
348
+ end
349
+
350
+ def get_cluster_node(args = {})
351
+ test_connection
352
+ args.empty? ? client.nodes.first : client.nodes.find_by_id(args[:node]) if errors.empty?
353
+ end
354
+
355
+ def compute_os_types(host)
356
+ os_linux_types_mapping(host).empty? ? os_windows_types_mapping(host) : os_linux_types_mapping(host)
357
+ end
358
+
359
+ def available_operating_systems
360
+ operating_systems = %w[other solaris]
361
+ operating_systems += available_linux_operating_systems
362
+ operating_systems += available_windows_operating_systems
363
+ operating_systems
364
+ end
365
+
366
+ def available_linux_operating_systems
367
+ %w[l24 l26]
368
+ end
369
+
370
+ def available_windows_operating_systems
371
+ %w[wxp w2k w2k3 w2k8 wvista win7 win8 win10]
372
+ end
373
+
374
+ def os_linux_types_mapping(host)
375
+ %w[Debian Redhat Suse Altlinux Archlinux CoreOs Gentoo].include?(host.operatingsystem.type) ? available_linux_operating_systems : []
376
+ end
377
+
378
+ def os_windows_types_mapping(host)
379
+ %w[Windows].include?(host.operatingsystem.type) ? available_windows_operating_systems : []
380
+ end
381
+
382
+ def convert_sizes(args)
383
+ args['config_attributes']['memory'] = (args['config_attributes']['memory'].to_i / MEGA).to_s
384
+ args['config_attributes']['min_memory'] = (args['config_attributes']['min_memory'].to_i / MEGA).to_s
385
+ args['config_attributes']['shares'] = (args['config_attributes']['shares'].to_i / MEGA).to_s
386
+ args['volumes_attributes'].each_value { |value| value['size'] = (value['size'].to_i / GIGA).to_s }
387
+ end
388
+
389
+ def host
390
+ URI.parse(url).host
391
+ end
392
+
393
+ end
394
+ end