foreman_opentofu 0.0.1

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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +134 -0
  4. data/Rakefile +30 -0
  5. data/app/controllers/api/v2/tf_states_controller.rb +52 -0
  6. data/app/controllers/concerns/foreman_opentofu/compute_resources_vms_controller.rb +17 -0
  7. data/app/controllers/concerns/foreman_opentofu/controller/parameters/compute_resource.rb +42 -0
  8. data/app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb +89 -0
  9. data/app/models/concerns/orchestration/tofu/compute.rb +24 -0
  10. data/app/models/foreman_opentofu/compute_vm.rb +99 -0
  11. data/app/models/foreman_opentofu/opentofu_vm_commands.rb +88 -0
  12. data/app/models/foreman_opentofu/tf_state.rb +8 -0
  13. data/app/models/foreman_opentofu/tofu.rb +77 -0
  14. data/app/overrides/compute_resources/remove_virtual_machines_tab.rb +8 -0
  15. data/app/services/foreman_opentofu/app_wrapper.rb +107 -0
  16. data/app/services/foreman_opentofu/compute_fetcher.rb +51 -0
  17. data/app/services/foreman_opentofu/opentofu_executer.rb +78 -0
  18. data/app/services/foreman_opentofu/provider_type.rb +30 -0
  19. data/app/services/foreman_opentofu/provider_type_manager.rb +36 -0
  20. data/app/views/compute_resources/form/_tofu.html.erb +9 -0
  21. data/app/views/compute_resources/show/_tofu.html.erb +8 -0
  22. data/app/views/compute_resources_vms/form/tofu/_base.html.erb +13 -0
  23. data/app/views/compute_resources_vms/form/tofu/_dynamic_attrs.html.erb +18 -0
  24. data/app/views/compute_resources_vms/form/tofu/_network.html.erb +5 -0
  25. data/app/views/compute_resources_vms/index/_tofu.html.erb +0 -0
  26. data/app/views/templates/provisioning/nutanix_provision_host.erb +51 -0
  27. data/app/views/templates/provisioning/ovirt_provision_host.erb +68 -0
  28. data/config/initializers/compute_attrs.rb +19 -0
  29. data/config/nutanix.json +27 -0
  30. data/config/ovirt.json +18 -0
  31. data/config/routes.rb +13 -0
  32. data/db/migrate/20250625192757_create_tf_state.foreman_opentofu.rb +10 -0
  33. data/db/seeds.d/71_provisioning_templates.rb +9 -0
  34. data/lib/foreman_opentofu/engine.rb +75 -0
  35. data/lib/foreman_opentofu/provider_types/nutanix.rb +6 -0
  36. data/lib/foreman_opentofu/provider_types/ovirt.rb +2 -0
  37. data/lib/foreman_opentofu/provider_types.rb +3 -0
  38. data/lib/foreman_opentofu/version.rb +3 -0
  39. data/lib/foreman_opentofu.rb +6 -0
  40. data/lib/tasks/foreman_opentofu_tasks.rake +30 -0
  41. data/locale/Makefile +73 -0
  42. data/locale/en/foreman_opentofu.po +19 -0
  43. data/locale/foreman_opentofu.pot +19 -0
  44. data/locale/gemspec.rb +2 -0
  45. data/test/controllers/api/v2/tf_states_controller_test.rb +67 -0
  46. data/test/factories/compute_resources.rb +26 -0
  47. data/test/factories/foreman_opentofu_factories.rb +7 -0
  48. data/test/factories/tf_state_factories.rb +7 -0
  49. data/test/models/foreman_opentofu/opentofu_vm_commands_test.rb +147 -0
  50. data/test/models/foreman_opentofu_test.rb +43 -0
  51. data/test/services/app_wrapper_test.rb +31 -0
  52. data/test/services/opentofu_executer_test.rb +62 -0
  53. data/test/test_plugin_helper.rb +6 -0
  54. metadata +119 -0
@@ -0,0 +1,107 @@
1
+ module ForemanOpentofu
2
+ class AppWrapper
3
+ attr_reader :workdir, :planfile, :conffile
4
+
5
+ # TODO: for future versions
6
+ # - manage temp-work-dir; problem: no auto-remove after finished :-(
7
+ # - handle ENVVars if applicable
8
+ # - handle stderr and stdout separately
9
+ # - use JSON-output for easier parsing
10
+ # - do we need locking or has the object be atomic
11
+
12
+ def initialize(workdir)
13
+ @workdir = workdir
14
+ @planfile = File.join(workdir, 'plan.bin')
15
+ @conffile = File.join(workdir, 'main.tf')
16
+ end
17
+
18
+ def base_command
19
+ 'tofu'
20
+ end
21
+
22
+ def default_params
23
+ [
24
+ '-no-color',
25
+ ]
26
+ end
27
+
28
+ # optional: specify block to access command-pipe (object of class `IO`), e.g.
29
+ # tofu.init do |t|
30
+ # until t.eof? do
31
+ # puts("Message from Tofu: #{t.gets}")
32
+ # end
33
+ # end
34
+ def init(params = [], &block)
35
+ tofu_execute('init', ['-input=false'].concat(parse_params(params)), &block)
36
+ end
37
+
38
+ def plan(params = [])
39
+ tofu_execute('plan', ["-out=#{planfile}"].concat(parse_params(params)))
40
+ end
41
+
42
+ def apply(params = [])
43
+ tofu_execute('apply', ['-auto-approve'].concat(parse_params(params)))
44
+ end
45
+
46
+ def destroy(params = [])
47
+ tofu_execute('destroy', ['-auto-approve'].concat(parse_params(params)))
48
+ end
49
+
50
+ def output(params = [])
51
+ JSON.parse(tofu_execute('output', ['-json'].concat(parse_params(params))))
52
+ end
53
+
54
+ # TODO: find better name ;-)
55
+ def show_plan(params = [])
56
+ JSON.parse(tofu_execute('show', ['-json', planfile].concat(parse_params(params))))
57
+ end
58
+
59
+ def main_configuration
60
+ File.read(conffile)
61
+ end
62
+
63
+ def main_configuration=(config)
64
+ File.write(conffile, config)
65
+ end
66
+
67
+ private
68
+
69
+ def parse_params(params)
70
+ params.is_a?(String) ? [params] : params
71
+ end
72
+
73
+ def tofu_execute(action, params = [], &block)
74
+ execute [base_command, action].concat(default_params).concat(params), &block
75
+ end
76
+
77
+ def command(cmd)
78
+ cmd.map { |item| "'#{item}'" }.append('2>&1').join(' ')
79
+ end
80
+
81
+ def execute(cmd)
82
+ output = nil
83
+ # quote cmdline parameters and add stderr to stdout
84
+ commandline = command(cmd)
85
+ Dir.chdir(workdir) do
86
+ Rails.logger.debug "Start command: #{commandline.inspect}"
87
+ IO.popen(commandline, 'r+') do |pipe|
88
+ if block_given?
89
+ yield pipe
90
+ else
91
+ output = pipe.read
92
+ end
93
+ end
94
+ end
95
+ ret = $CHILD_STATUS
96
+ Rails.logger.info "#{cmd} returned #{ret.inspect}"
97
+ Rails.logger.debug output.to_s
98
+ unless ret.success?
99
+ Rails.logger.error "Command failed with output: #{output}"
100
+ # TODO: do we need to use a specific exception-type here?
101
+ raise "command failed with code #{ret.exitstatus}:\n#{output}"
102
+ end
103
+
104
+ output
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,51 @@
1
+ module ForemanOpentofu
2
+ class ComputeFetcher
3
+ def self.fetch_subnets(compute_resource)
4
+ return [] unless compute_resource == 'Nutanix'
5
+ attrs = []
6
+ Dir.mktmpdir('opentofu_') do |dir|
7
+ tofu = ForemanOpentofu::AppWrapper.new(dir)
8
+ tofu.main_configuration = tf_content(compute_resource)
9
+ tofu.init
10
+ tofu.apply
11
+ attrs = tofu.output('subnets')
12
+ end
13
+ attrs.map { |h| OpenStruct.new(h) }
14
+ rescue StandardError => e
15
+ Rails.logger.error("Failed to fetch subnets: #{e}")
16
+ []
17
+ end
18
+
19
+ def self.tf_content(compute_resource)
20
+ <<~HCL
21
+ terraform {
22
+ required_providers {
23
+ nutanix = {
24
+ source = "nutanix/nutanix"
25
+ version = ">=1.6.0"
26
+ }
27
+ }
28
+ }
29
+
30
+ provider "nutanix" {
31
+ username = "#{compute_resource.user}"
32
+ password = "#{compute_resource.password}"
33
+ endpoint = "#{compute_resource.url}"
34
+ insecure = true
35
+ }
36
+
37
+ # Fetch all subnets
38
+ data "nutanix_subnets" "all" {}
39
+
40
+ output "subnets" {
41
+ value = [
42
+ for s in data.nutanix_subnets.all.entities : {
43
+ id = s.metadata.uuid
44
+ name = s.name
45
+ }
46
+ ]
47
+ }
48
+ HCL
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,78 @@
1
+ require 'json'
2
+
3
+ module ForemanOpentofu
4
+ class OpentofuExecuter
5
+ def initialize(*args)
6
+ @compute_resource = args[0]
7
+ @cr_attrs = args[1] || {}
8
+ end
9
+
10
+ def run(mode = 'create')
11
+ Dir.mktmpdir('opentofu_') do |dir|
12
+ tofu = AppWrapper.new(dir)
13
+ @use_backend = %w[create destroy output].include?(mode)
14
+ tofu.main_configuration = render_template
15
+ tofu.init
16
+ run_mode(tofu, mode)
17
+ end
18
+ end
19
+
20
+ def run_mode(tofu, mode = 'new')
21
+ @use_backend = true
22
+ case mode
23
+ when 'new'
24
+ tofu.plan
25
+ tofu.show_plan
26
+ when 'test_connection'
27
+ tofu.plan
28
+ when 'create'
29
+ tofu.plan
30
+ tofu.apply
31
+ attrs = tofu.output('vm_attrs')
32
+ ForemanOpentofu::TfState.find_by(name: @cr_attrs['name'])&.update(uuid: attrs['identity'])
33
+ attrs
34
+ when 'output'
35
+ tofu.output('vm_attrs')
36
+ when 'destroy'
37
+ tofu.destroy
38
+ else
39
+ raise "Please select one of the modes: 'new', 'test_connection', 'create' or 'destroy'"
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def render_template
46
+ template = provision_template
47
+ scope = Foreman::Renderer.get_scope(source: template)
48
+ source = Foreman::Renderer.get_source(template: template)
49
+ scope.instance_variable_set(:@compute_resource, @compute_resource)
50
+ scope.instance_variable_set(:@cr_attrs, @cr_attrs) if @cr_attrs
51
+ scope.instance_variable_set(:@use_backend, @use_backend)
52
+ rendered_template = Foreman::Renderer::UnsafeModeRenderer.render(source, scope)
53
+ raise ::Foreman::Exception, N_('Unable to render provisioning template') unless rendered_template
54
+
55
+ rendered_template
56
+ end
57
+
58
+ def provision_template
59
+ name = ''
60
+ provider = @compute_resource.opentofu_provider
61
+ case provider
62
+ when 'nutanix'
63
+ name = Setting[:provision_nutanix_host_template]
64
+ when 'ovirt'
65
+ name = Setting[:provision_ovirt_host_template]
66
+ when 'vsphere'
67
+ name = Setting[:provision_vsphere_host_template]
68
+ end
69
+ template = ProvisioningTemplate.unscoped.find_by(name: name)
70
+ unless template
71
+ raise ::Foreman::Exception.new(N_('Unable to find template specified by %s setting'),
72
+ name)
73
+ end
74
+
75
+ template
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,30 @@
1
+ module ForemanOpentofu
2
+ class ProviderType
3
+ attr_reader :id, :name
4
+
5
+ def initialize(id)
6
+ @id = id.to_sym
7
+ @name = id.capitalize
8
+ end
9
+
10
+ # returns hash of available-attributes with attr-name as key
11
+ def available_attributes
12
+ raise "No available-attributes found for #{name}" unless attributes?
13
+
14
+ attributes&.index_by { |e| e['name'] }
15
+ end
16
+
17
+ def attributes?
18
+ CR_ATTRS.key? id.to_s
19
+ end
20
+
21
+ def attributes(group = nil)
22
+ return nil unless CR_ATTRS.key? id.to_s
23
+
24
+ a = CR_ATTRS[id.to_s]
25
+ return a if group.nil?
26
+
27
+ a.select { |e| e['group'] == group }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ module ForemanOpentofu
2
+ class ProviderTypeManager
3
+ @defined_provider_types = {}
4
+
5
+ class << self
6
+ private :new
7
+ attr_reader :defined_provider_types
8
+
9
+ # Plugin constructor
10
+ def register(id, &block)
11
+ defined_prov_type = find_defined(id)
12
+ return if defined_prov_type.present?
13
+
14
+ defined_prov_type = ::ForemanOpentofu::ProviderType.new(id)
15
+ defined_prov_type.instance_eval(&block) if block_given?
16
+ @defined_provider_types[id.to_s] = defined_prov_type
17
+ end
18
+
19
+ def find(provider_type)
20
+ find_defined(provider_type)
21
+ end
22
+
23
+ def find_defined(provider_type)
24
+ @defined_provider_types[provider_type.to_s]
25
+ end
26
+
27
+ def enabled_provider_type_names
28
+ @defined_provider_types.values.map(&:name)
29
+ end
30
+
31
+ def enabled_provider_types
32
+ @defined_provider_types.values
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ <%= field_set_tag _("Common fields"), :id => "compute_ressource_common_field_set" do %>
2
+ <%= select_f f, :opentofu_provider, ForemanOpentofu::ProviderTypeManager.enabled_provider_types, :id, :name %>
3
+ <%= text_f f, :url, :help_block => _("127.0.0.1") %>
4
+ <%= text_f f, :user , :help_block => _("e.g. admin") %>
5
+ <%= password_f f, :password %>
6
+ <% end %>
7
+ <div class="col-md-offset-2">
8
+ <%= test_connection_button_f(f, (true)) %>
9
+ </div>
@@ -0,0 +1,8 @@
1
+ <tr>
2
+ <td><%= _('OpenTofu Provider') %></td>
3
+ <td><%= @compute_resource.opentofu_provider %></td>
4
+ </tr>
5
+ <tr>
6
+ <td><%= _('URL') %></td>
7
+ <td><%= @compute_resource.url %></td>
8
+ </tr>
@@ -0,0 +1,13 @@
1
+ <% provider = compute_resource.tofu_provider %>
2
+ <% if provider.attributes.present? %>
3
+ <% vm_attrs = provider.attributes('vm') %>
4
+ <% disk_attrs = provider.attributes('disk') %>
5
+ <%= field_set_tag _("VM Config") do %>
6
+ <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: vm_attrs } %>
7
+ <% end %>
8
+
9
+ <%= field_set_tag _("Storage") do %>
10
+ <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: disk_attrs } %>
11
+ <% end %>
12
+ <% end %>
13
+
@@ -0,0 +1,18 @@
1
+ <% attrs.each do |attr|
2
+ label = attr.fetch('label', attr['name'].humanize) %>
3
+
4
+ <% case attr["type"] %>
5
+ <% when 'string' %>
6
+ <%= text_f f, attr["name"].to_sym, label: _(label), label_help: attr['help'] %>
7
+ <% when 'bool' %>
8
+ <%= checkbox_f f, attr["name"].to_sym, label: _(label), label_help: attr['help'] %>
9
+ <% when 'number' %>
10
+ <%= counter_f f, attr["name"].to_sym, label: _(label), label_help: attr['help'] %>
11
+ <% when 'select' %>
12
+ <% if attr.key? 'options' %>
13
+ <%= select_f f, attr["name"].to_sym, attr.fetch('options', []), :to_s, :to_s, { :include_blank => true }, label: _(label), label_help: attr['help'] %>
14
+ <% else %>
15
+ <%= select_f f, attr["name"].to_sym, (options || []), :id, :name, { :include_blank => true }, label: _(label), label_help: attr['help'] %>
16
+ <% end %>
17
+ <% end %>
18
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <% provider = compute_resource.opentofu_provider.to_s %>
2
+ <% if CR_ATTRS.key?(provider) %>
3
+ <% nic_attrs = CR_ATTRS[provider].select { |a| a["group"] == "nic" } %>
4
+ <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: nic_attrs, options: ForemanOpentofu::ComputeFetcher.fetch_subnets(compute_resource) } %>
5
+ <% end %>
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ <%#
3
+ kind: script
4
+ name: Nutanix provision - host
5
+ model: ProvisioningTemplate
6
+ description: |
7
+ nutanix opentofu script to create vm resource
8
+ -%>
9
+ <%-
10
+ host_name = @cr_attrs['name'] || 'test'
11
+ %>
12
+
13
+ terraform {
14
+ required_providers {
15
+ nutanix = {
16
+ source = "nutanix/nutanix"
17
+ }
18
+ }
19
+ <% if @use_backend %>
20
+ backend "http" {
21
+ address = "<%= Setting[:foreman_url] %>/api/v2/tf_states/<%= host_name %>"
22
+ username = "admin"
23
+ password = "changeme"
24
+ }
25
+ <% end %>
26
+ }
27
+
28
+ provider "nutanix" {
29
+ username = "<%= @compute_resource['user'] %>"
30
+ password = "<%= @compute_resource['password'] %>"
31
+ endpoint = "<%= @compute_resource['url'] %>"
32
+ insecure = true
33
+ }
34
+
35
+ data "nutanix_clusters" "clusters" {}
36
+
37
+ resource "nutanix_virtual_machine" "vm" {
38
+ cluster_uuid = data.nutanix_clusters.clusters.entities.0.metadata.uuid
39
+ name = "<%= host_name %>"
40
+
41
+ <%= vm_attributes(['name', 'cluster_uuid']) %>
42
+
43
+ }
44
+
45
+ output "vm_attrs" {
46
+ value = {
47
+ identity = nutanix_virtual_machine.vm.id
48
+ mac = nutanix_virtual_machine.vm.nic_list[0].mac_address
49
+ }
50
+ }
51
+
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ <%#
3
+ kind: script
4
+ name: Ovirt provision - host
5
+ model: ProvisioningTemplate
6
+ description: |
7
+ nutanix opentofu script to create vm resource
8
+ -%>
9
+ <%-
10
+ host_name = @cr_attrs['name'] || 'test'
11
+ %>
12
+
13
+ terraform {
14
+ required_providers {
15
+ ovirt = {
16
+ source = "ovirt/ovirt"
17
+ }
18
+ }
19
+ <% if @use_backend %>
20
+ backend "http" {
21
+ address = "<%= Setting[:foreman_url] %>/api/v2/tf_states/<%= host_name %>"
22
+ username = "admin"
23
+ password = "changeme"
24
+ }
25
+ <% end %>
26
+ }
27
+
28
+ provider "ovirt" {
29
+ username = "<%= @compute_resource['user'] %>"
30
+ password = "<%= @compute_resource['password'] %>"
31
+ url = "<%= @compute_resource['url'] %>"
32
+ tls_insecure = true
33
+ }
34
+
35
+ data "ovirt_blank_template" "blank" {
36
+ }
37
+
38
+ resource "ovirt_vm" "vm" {
39
+ name = "<%= host_name %>"
40
+ cluster_id = "2ad616ea-4b66-11f0-964d-901b0ecbd584"
41
+ template_id = data.ovirt_blank_template.blank.id
42
+ cpu_cores = <%= @cr_attrs['cpu_cores'] || 1 %>
43
+ cpu_sockets = <%= @cr_attrs['cpu_sockets'] || 1 %>
44
+ cpu_threads = <%= @cr_attrs['cpu_threads'] || 1 %>
45
+ memory = <%= @cr_attrs['memory'] || 400000 %>
46
+ maximum_memory = <%= @cr_attrs['maximum_memory'] || 4000000 %>
47
+ memory_ballooning = <%= @cr_attrs['memory_ballooning'] || false %>
48
+ }
49
+ resource "ovirt_vm" "vm" {
50
+ name = "<%= host_name %>"
51
+ cluster_id = "2ad616ea-4b66-11f0-964d-901b0ecbd584"
52
+ template_id = data.ovirt_blank_template.blank.id
53
+ <%= "cpu_cores = #{@cr_attrs['cpu_cores'] || 1}" %>
54
+ <%= "cpu_sockets = #{@cr_attrs['cpu_sockets'] || 1}" %>
55
+ <%= "cpu_threads = #{@cr_attrs['cpu_threads'] || 1}" %>
56
+ <%= "cpu_mode = \"#{@cr_attrs['cpu_mode']}\"" if @cr_attrs['cpu_mode'].present? %>
57
+ <%= "memory = #{@cr_attrs['memory'] || 400000}" %>
58
+ <%= "maximum_memory = #{@cr_attrs['maximum_memory'] || 4000000}" %>
59
+ <%= "memory_ballooning = #{@cr_attrs['memory_ballooning']}" if @cr_attrs.key?('memory_ballooning') %>
60
+ <%= "id = \"#{@cr_attrs['id']}\"" if @cr_attrs['id'].present? %>
61
+ <%= "os_type = \"#{@cr_attrs['os_type']}\"" if @cr_attrs['os_type'].present? %>
62
+ <%= "soundcard_enabled = #{@cr_attrs['soundcard_enabled']}" if @cr_attrs.key?('soundcard_enabled') %>
63
+ <%= "vm_type = \"#{@cr_attrs['vm_type']}\"" if @cr_attrs['vm_type'].present? %>
64
+ }
65
+
66
+ output "uuid" {
67
+ value = ovirt_vm.vm
68
+ }
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ CONFIG_DIR = File.expand_path(File.join(__dir__, '..'))
4
+
5
+ # rubocop:disable Style/MutableConstant
6
+ CR_ATTRS = {}
7
+ # rubocop:enable Style/MutableConstant
8
+
9
+ Dir["#{CONFIG_DIR}/*.{json,yaml,yml}"].each do |filename|
10
+ next unless File.exist?(filename)
11
+
12
+ file_content = File.read(filename)
13
+ provider = File.basename(filename, '.*')
14
+ CR_ATTRS[provider] = case File.extname(filename)
15
+ when /\.json/i then JSON.parse(file_content)
16
+ when /\.ya?ml/i then YAML.safe_load(file_content)
17
+ else next
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ [
2
+ { "name": "num_sockets", "type": "number", "group": "vm", "mandatory": false,
3
+ "label": "Sockets" },
4
+ { "name": "num_vcpus_per_socket", "type": "number", "group": "vm", "mandatory": false,
5
+ "label": "Cores per socket" },
6
+ { "name": "memory_size_mib", "type": "number", "group": "vm", "mandatory": false,
7
+ "label": "Memory (MB)" },
8
+ { "name": "enable_cpu_passthrough", "label": "CPU Passthrough Enable", "type": "bool", "group": "vm", "mandatory": false },
9
+ { "name": "num_vnuma_nodes", "type": "number", "group": "vm", "mandatory": false,
10
+ "label": "vNUMA Nodes", "help": "Number of vNUMA nodes. 0 means vNUMA is disabled." },
11
+ { "name": "boot_type", "type": "select", "group": "vm", "mandatory": false,
12
+ "label": "Firmware", "options": [ "UEFI", "LEGACY", "SECURE_BOOT" ] },
13
+ { "name": "power_state", "type": "select", "group": "vm", "mandatory": false,
14
+ "options": [ "ON" , "OFF" ] },
15
+ { "name": "use_hot_add", "type": "bool", "group": "vm", "mandatory": false,
16
+ "help": "Use Hot Add when modifying VM resources. Passing value false will result in VM reboots. Default value is true." },
17
+ { "name": "vga_console_enabled", "type": "bool", "group": "vm", "mandatory": false,
18
+ "label": "VGA Console Enable" },
19
+ { "name": "disk_size_mib", "type": "number", "group": "disk", "mandatory": true,
20
+ "label": "Size (MB)" },
21
+ { "name": "nic_type", "type": "select", "group": "nic", "mandatory": false,
22
+ "label": "NIC Type", "options": [ "NORMAL_NIC", "DIRECT_NIC", "NETWORK_FUNCTION_NIC" ] },
23
+ { "name": "model", "type": "select", "group": "nic", "mandatory": true,
24
+ "options": [ "VIRTIO", "E1000" ] },
25
+ { "name": "subnet_uuid", "type": "select", "group": "nic", "mandatory": true,
26
+ "label": "Subnet" }
27
+ ]
data/config/ovirt.json ADDED
@@ -0,0 +1,18 @@
1
+ [
2
+ { "name": "clone", "type": "bool", "group": "vm" },
3
+ { "name": "cluster_id", "type": "string", "group": "vm" },
4
+ { "name": "cpu_cores", "type": "number", "group": "vm" },
5
+ { "name": "cpu_mode", "type": "string", "group": "vm" },
6
+ { "name": "cpu_sockets", "type": "number", "group": "vm" },
7
+ { "name": "cpu_threads", "type": "number", "group": "vm" },
8
+ { "name": "id", "type": "string", "group": "vm" },
9
+ { "name": "maximum_memory", "type": "number", "group": "vm" },
10
+ { "name": "memory", "type": "number", "group": "vm" },
11
+ { "name": "memory_ballooning", "type": "bool", "group": "vm" },
12
+ { "name": "name", "type": "string", "group": "vm" },
13
+ { "name": "os_type", "type": "string", "group": "vm" },
14
+ { "name": "soundcard_enabled", "type": "bool", "group": "vm" },
15
+ { "name": "template_id", "type": "string", "group": "vm" },
16
+ { "name": "vm_type", "type": "string", "group": "vm" }
17
+ ]
18
+
data/config/routes.rb ADDED
@@ -0,0 +1,13 @@
1
+ Rails.application.routes.draw do
2
+ namespace :api do
3
+ scope '(:apiv)',
4
+ module: :v2,
5
+ defaults: { apiv: 'v2' },
6
+ apiv: /v1|v2/,
7
+ constraints: ApiConstraints.new(version: 2, default: true) do
8
+ match 'tf_states/:name', to: 'tf_states#create', via: :post, constraints: { name: %r{[^/]+} }
9
+ match 'tf_states/:name', to: 'tf_states#show', via: :get, constraints: { name: %r{[^/]+} }
10
+ match 'tf_states/:name', to: 'tf_states#destroy', via: :delete, constraints: { name: %r{[^/]+} }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ class CreateTfState < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :tf_states do |t|
4
+ t.string :name, limit: 255
5
+ t.string :uuid, limit: 255
6
+ t.text :state
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ User.as_anonymous_admin do
4
+ ProvisioningTemplate.without_auditing do
5
+ SeedHelper.import_templates(
6
+ Dir[File.join("#{ForemanOpentofu::Engine.root}/app/views/templates/provisioning/**/*.erb")]
7
+ )
8
+ end
9
+ end
@@ -0,0 +1,75 @@
1
+ module ForemanOpentofu
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ForemanOpentofu
4
+ engine_name 'foreman_opentofu'
5
+
6
+ # Add any db migrations
7
+ initializer 'foreman_opentofu.load_app_instance_data' do |app|
8
+ ForemanOpentofu::Engine.paths['db/migrate'].existent.each do |path|
9
+ app.config.paths['db/migrate'] << path
10
+ end
11
+
12
+ app.config.autoload_paths += Dir["#{config.root}/app/services/foreman_opentofu"]
13
+ end
14
+
15
+ initializer 'foreman_opentofu.register_plugin', before: :finisher_hook do |app|
16
+ app.reloader.to_prepare do
17
+ Foreman::Plugin.register :foreman_opentofu do
18
+ requires_foreman '>= 3.0'
19
+ register_gettext
20
+
21
+ extend_template_helpers ForemanOpentofu::Concerns::BaseTemplateScopeExtensions
22
+ # Add Global files for extending foreman-core components and routes
23
+ # Register Nutanix compute resource in foreman
24
+ compute_resource ForemanOpentofu::Tofu
25
+ settings do
26
+ category :opentofu, N_('Opentofu') do
27
+ templates = lambda {
28
+ Hash[ProvisioningTemplate.where(template_kind: TemplateKind.where(name: 'script')).map do |temp|
29
+ [temp[:name], temp[:name]]
30
+ end ]
31
+ }
32
+
33
+ setting 'provision_nutanix_host_template',
34
+ type: :string,
35
+ collection: templates,
36
+ default: 'Nutanix provision - host',
37
+ full_name: N_('Nutanix Host provision template'),
38
+ description: N_('Opentofu script template to use for Nutanix based host provisioning')
39
+ setting 'provision_ovirt_host_template',
40
+ type: :string,
41
+ collection: templates,
42
+ default: 'Ovirt provision - host',
43
+ full_name: N_('Ovirt Host provision template'),
44
+ description: N_('Opentofu script template to use for Ovirt based host provisioning')
45
+ setting 'provision_vsphere_host_template',
46
+ type: :string,
47
+ collection: templates,
48
+ default: 'Vsphere provision - host',
49
+ full_name: N_('Vsphere Host provision template'),
50
+ description: N_('Opentofu script template to use for Vsphere based host provisioning')
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ config.autoload_paths << File.expand_path('../lib', __dir__)
58
+ # Include concerns in this config.to_prepare block
59
+ config.to_prepare do
60
+ ::ComputeResourcesController.include ForemanOpentofu::Controller::Parameters::ComputeResource
61
+ ::ComputeResourcesVmsController.include ForemanOpentofu::ComputeResourcesVmsController
62
+ ::Host::Managed.include Orchestration::Tofu::Compute
63
+ rescue StandardError => e
64
+ Rails.logger.warn "ForemanOpentofu: skipping engine hook (#{e})"
65
+ end
66
+
67
+ load 'foreman_opentofu/provider_types.rb'
68
+
69
+ rake_tasks do
70
+ Rake::Task['db:seed'].enhance do
71
+ ForemanOpentofu::Engine.load_seed
72
+ end
73
+ end
74
+ end
75
+ end