foreman_opentofu 0.0.3 → 0.0.4

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -13
  3. data/app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb +35 -0
  4. data/app/models/concerns/foreman_opentofu/vm_command_collection_normalization.rb +22 -0
  5. data/app/models/foreman_opentofu/opentofu_vm_commands.rb +10 -3
  6. data/app/models/foreman_opentofu/tofu.rb +13 -5
  7. data/app/overrides/compute_resources_vms/tofu_indexed_networks_fields.rb +7 -0
  8. data/app/overrides/compute_resources_vms/tofu_indexed_volumes_fields.rb +8 -0
  9. data/app/services/foreman_opentofu/app_wrapper.rb +15 -1
  10. data/app/services/foreman_opentofu/opentofu_executer.rb +5 -5
  11. data/app/services/foreman_opentofu/provider_type.rb +11 -1
  12. data/app/views/compute_resources_vms/form/tofu/_base.html.erb +0 -5
  13. data/app/views/compute_resources_vms/form/tofu/_volume.html.erb +2 -0
  14. data/app/views/foreman_opentofu/compute_resources_vms/_indexed_networks_fields.html.erb +47 -0
  15. data/app/views/foreman_opentofu/compute_resources_vms/_indexed_volumes_fields.html.erb +30 -0
  16. data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_interfaces_fields.html.erb +12 -0
  17. data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_volumes_fields.html.erb +11 -0
  18. data/app/views/templates/provisioning/hetzner_provision_host.erb +2 -0
  19. data/app/views/templates/provisioning/nutanix_provision_default.erb +3 -2
  20. data/lib/foreman_opentofu/provider_types/hetzner.rb +32 -0
  21. data/lib/foreman_opentofu/provider_types/nutanix.rb +26 -0
  22. data/lib/foreman_opentofu/version.rb +1 -1
  23. data/lib/foreman_opentofu.rb +4 -0
  24. data/selinux/Makefile +22 -0
  25. data/selinux/foreman_opentofu.fc +3 -0
  26. data/selinux/foreman_opentofu.if +0 -0
  27. data/selinux/foreman_opentofu.te +93 -0
  28. data/test/lib/foreman_opentofu/concerns/base_template_scope_extensions_test.rb +27 -0
  29. data/test/models/foreman_opentofu/opentofu_vm_commands_test.rb +27 -0
  30. data/test/services/app_wrapper_test.rb +13 -1
  31. data/test/services/foreman_opentofu/provider_type_test.rb +13 -0
  32. data/test/services/opentofu_executer_test.rb +50 -27
  33. metadata +13 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccfa3742aa9fe09edc20f685bf1a65f0f32aa34603572812fd250f64f84c7f60
4
- data.tar.gz: 632627c1dbecb3da3282fa29085647263f588eb99753db33d2f2ada53b57680c
3
+ metadata.gz: e3f7d22aa8036489bd3356eff61fb54608d15c1ced729e2a592257e3383213a5
4
+ data.tar.gz: 3a7272bc1b5e4a36974b859362d18209d3cbc36293e00c9722a2c6991bdcd0ee
5
5
  SHA512:
6
- metadata.gz: 1858acdfd4b8e7e32dd3c91765ca1e5e6921da70b8754842e1fa8284bb9b1ceb23b5abb7b22320218d6326167f821d032071915458155545e19ace88dfadd996
7
- data.tar.gz: a9f29bd6e263acb43c2a692ed848a71b99c61dce3fb3b64a9ebd7597bceae0280f8b93879b2e5b89a6a8a2244f9cfd5644fdfdc20444353fc962211def091591
6
+ metadata.gz: 7efbc5b6168046cac8b716864636ac9f16acd32b229e017ff876d93cde3aecf3ff4e94572e09407ecf2e3de6462592efd46e4bcacf49285c765aceba4e32787b
7
+ data.tar.gz: f7b2493f8dcca6913ab2167f9cb529610d0e9c91240d43ea324503b24352fa5b0cb774b8ffee4f1cf76791e38ef9440c90ad0bfdbe8390b3b62fa1d4d09d38f5
data/README.md CHANGED
@@ -1,20 +1,50 @@
1
1
  [![Ruby Tests](https://github.com/ATIX-AG/foreman_opentofu/actions/workflows/ruby.yml/badge.svg)](https://github.com/ATIX-AG/foreman_opentofu/actions/workflows/ruby.yml)
2
2
 
3
- # ForemanOpenTOFU
3
+ # Foreman OpenTofu
4
4
 
5
- [Foreman](http://theforeman.org/) plugin that adds that adds a generic openTOFU-based compute resource, enabling host provisioning through openTOFU scripts instead of provider-specific SDK integrations such as fog-vsphere.
5
+ [Foreman](http://theforeman.org/) plugin that adds that adds a generic OpenTofu-based compute resource, enabling host provisioning through OpenTofu scripts instead of provider-specific SDK integrations such as fog-vsphere.
6
6
 
7
- This plugin introduces a new provisioning model where Foreman remains responsible for host lifecycle and orchestration, while openTOFU handles infrastructure creation using its provider ecosystem.
7
+ This plugin introduces a new provisioning model where Foreman remains responsible for host lifecycle and orchestration, while OpenTofu handles infrastructure creation using its provider ecosystem.
8
8
 
9
9
  The plugin is designed to be easily extendable and can support multiple infrastructure platforms (for example Nutanix, Hetzner) without requiring a dedicated Foreman compute resource plugin per provider.
10
10
 
11
11
  ## Installation
12
12
 
13
+ Install the rubygem as usual or use the RPM/Deb package for your distribution.
14
+
15
+ If you use the rubygem, make sure to create the folllowing directory and set the correct permissions:
16
+
17
+ ```shell
18
+
19
+ mkdir -p /var/lib/foreman-opentofu
20
+ mkdir -p /var/lib/foreman-opentofu/plugin-cache
21
+ mkdir -p /var/lib/foreman-opentofu/tmp
22
+
23
+ chown -R foreman:foreman /var/lib/foreman-opentofu
24
+ chmod 755 /var/lib/foreman-opentofu
25
+ chmod 755 /var/lib/foreman-opentofu/plugin-cache
26
+ chmod 700 /var/lib/foreman-opentofu/tmp
27
+ ```
28
+
29
+ ## SELinux
30
+
31
+ If you have installed the rubygem manually, you need to set the correct SELinux context for the plugin to work properly.
32
+ Re-use the selinux directives defined in the selinux directory of the plugin.
33
+
34
+ ```shell
35
+ cd selinux
36
+ make clean && make all
37
+ mkdir -p /usr/share/selinux/targeted/
38
+ install -m 0600 foreman_opentofu.pp /usr/share/selinux/targeted/
39
+ /usr/sbin/semodule -i /usr/share/selinux/targeted/foreman_opentofu.pp
40
+ /sbin/restorecon -ri /var/lib/foreman-opentofu/
41
+ ```
42
+
13
43
 
14
44
  ## Usage
15
- Create a openTofu compute resource and set:
16
- * Provider: openTofu
17
- * Opentofu Provider: Select desired hypervisor supported by openTofu plugin
45
+ Create a OpenTofu compute resource and set:
46
+ * Provider: OpenTofu
47
+ * OpenTofu Provider: Select desired hypervisor supported by OpenTofu plugin
18
48
  * URL: Hypervisor specific URL
19
49
 
20
50
 
@@ -22,17 +52,17 @@ Then add all necessary information to the form.
22
52
 
23
53
  Provisioning workflow:
24
54
 
25
- * Create a host in Foreman using the openTOFU based compute resource
55
+ * Create a host in Foreman using the OpenTofu based compute resource
26
56
 
27
57
  * Foreman passes host parameters to the plugin
28
58
 
29
- * The plugin renders and executes openTOFU plans
59
+ * The plugin renders and executes OpenTofu plans
30
60
 
31
- * openTOFU provisions the infrastructure
61
+ * OpenTofu provisions the infrastructure
32
62
 
33
63
  * Foreman continues with OS provisioning and configuration
34
64
 
35
- Provider-specific details (for example Nutanix, Hetzner) are handled entirely through openTOFU scripts.
65
+ Provider-specific details (for example Nutanix, Hetzner) are handled entirely through OpenTofu scripts.
36
66
 
37
67
  ### Create new ProviderType
38
68
 
@@ -94,7 +124,7 @@ The name of the file must be the same as the provider-type name we set in the ne
94
124
 
95
125
  Sometimes it is necessary to provide a list of possible values that are defined by the backend-service.
96
126
  Curating the 'options'-Array is tedious at best or not possible if multiple instances of the backend service are in use.
97
- This can be addressed by specifiying an OpenTofu-Provider's [DataSource](https://opentofu.org/docs/language/data-sources/) in the following way:
127
+ This can be addressed by specifiying an OpenTofu provider's [DataSource](https://opentofu.org/docs/language/data-sources/) in the following way:
98
128
 
99
129
  ```json
100
130
  {
@@ -192,7 +222,7 @@ end
192
222
 
193
223
  > See [Foreman dev setup](https://github.com/theforeman/foreman/blob/develop/developer_docs/foreman_dev_setup.asciidoc)
194
224
 
195
- * You need a openTOFU installed on your machine.
225
+ * You need a OpenTofu installed on your machine.
196
226
  * You need ruby 2.7. You can install it with [asdf-vm](https://asdf-vm.com).
197
227
 
198
228
  ### Platform
@@ -226,7 +256,7 @@ bundle install
226
256
  RAILS_ENV=development bundle exec bin/rake permissions:reset password=changeme
227
257
  ```
228
258
 
229
- * In foreman_openTofu source directory, check code syntax with rubocop and foreman rules:
259
+ * In the `foreman_opentofu` directory, check code syntax with rubocop and foreman rules:
230
260
 
231
261
  ```shell
232
262
  bundle exec rubocop
@@ -92,6 +92,41 @@ module ForemanOpentofu
92
92
  ''
93
93
  end
94
94
  end
95
+
96
+ def build_disks
97
+ disks = @cr_attrs['volumes'].presence || @cr_attrs['volumes_attributes'].presence || @compute_resource.default_volumes
98
+ disks = [disks] if disks.is_a?(Hash)
99
+ disks.each_with_index.map do |disk, index|
100
+ data = @compute_resource.render_disk(disk, self, index)
101
+ render_provider_data(data)
102
+ end.join("\n")
103
+ end
104
+
105
+ def build_nics
106
+ nics = @cr_attrs['interfaces'].presence || @cr_attrs['interfaces_attributes'].presence || @compute_resource.default_interfaces
107
+ nics = normalize_interfaces(nics).map do |nic|
108
+ nic.respond_to?(:with_indifferent_access) ? nic.with_indifferent_access[:compute_attributes].presence || nic : nic
109
+ end
110
+
111
+ nics.each_with_index.map do |nic, index|
112
+ data = @compute_resource.render_nic(nic, self, index)
113
+ render_provider_data(data)
114
+ end.join("\n")
115
+ end
116
+
117
+ private
118
+
119
+ def render_provider_data(data)
120
+ if data.is_a?(Hash) && data[:resource].present?
121
+ resource = data[:resource]
122
+ block_to_hcl(['resource', resource[:type], resource[:name]], resource[:content], depth: 0)
123
+ elsif data.is_a?(Hash) && data.size == 1 && data.values.first.is_a?(Hash)
124
+ block_name, block_content = data.first
125
+ block_to_hcl([block_name.to_s], block_content, depth: 1)
126
+ else
127
+ to_hcl(data, snippet: true)
128
+ end
129
+ end
95
130
  end
96
131
  end
97
132
  end
@@ -0,0 +1,22 @@
1
+ module ForemanOpentofu
2
+ module VMCommandCollectionNormalization
3
+ private
4
+
5
+ def normalize_vm_args_collections!(args)
6
+ [:volumes, :interfaces].each do |collection|
7
+ raw = args.delete(:"#{collection}_attributes") || args[collection]
8
+ next if raw.nil?
9
+
10
+ args[collection] = normalize_collection_input(collection, raw)
11
+ end
12
+ end
13
+
14
+ def normalize_collection_input(collection, value)
15
+ return nested_attributes_for(collection, value) if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
16
+
17
+ Array(value).map do |entry|
18
+ entry.respond_to?(:deep_symbolize_keys) ? entry.deep_symbolize_keys : entry
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,7 @@
1
1
  module ForemanOpentofu
2
2
  module OpentofuVMCommands
3
+ include ForemanOpentofu::VMCommandCollectionNormalization
4
+
3
5
  def find_vm_by_uuid(uuid)
4
6
  vm_command_errors('find vm') do
5
7
  tf_state = ForemanOpentofu::TfState.find_by(uuid: uuid)
@@ -10,16 +12,19 @@ module ForemanOpentofu
10
12
 
11
13
  def new_vm(args = {})
12
14
  vm_command_errors('new vm') do
13
- args = default_attributes.merge(args)
15
+ args = default_attributes.merge(args).to_h.symbolize_keys
16
+ normalize_vm_args_collections!(args)
14
17
  executor = client(args)
15
18
  data = executor.run_new
16
- OpenStruct.new(data['resource_changes'].first['change']['after'])
19
+ attrs = data['resource_changes'].first['change']['after'] || {}
20
+ OpenStruct.new(attrs)
17
21
  end
18
22
  end
19
23
 
20
24
  def create_vm(args = {})
21
25
  vm_command_errors('create vm') do
22
- args = default_attributes.merge(args)
26
+ args = default_attributes.merge(args).to_h.symbolize_keys
27
+ normalize_vm_args_collections!(args)
23
28
  executor = client(args)
24
29
  output = executor.run_create
25
30
  ComputeVM.new(self, output)
@@ -50,6 +55,8 @@ module ForemanOpentofu
50
55
  raise StandardError, "VM with UUID #{uuid} does not exist" unless tf_state
51
56
  vm_command_errors('update vm') do
52
57
  attrs = attrs.empty? ? {} : attrs.first
58
+ attrs = attrs.to_h.symbolize_keys
59
+ normalize_vm_args_collections!(attrs)
53
60
  data = client({ 'name' => tf_state.name }.merge(attrs)).run_create
54
61
  ComputeVM.new(self, data)
55
62
  end
@@ -27,7 +27,7 @@ module ForemanOpentofu
27
27
  # alias_attribute :username, :user
28
28
  # alias_attribute :endpoint, :url
29
29
 
30
- delegate :available_attributes, :capabilities, to: :tofu_provider
30
+ delegate :available_attributes, :capabilities, :render_disk, :render_nic, to: :tofu_provider
31
31
 
32
32
  def available_images
33
33
  # make sure available_images can use this CR, e.g. for requesting data_source
@@ -90,12 +90,20 @@ module ForemanOpentofu
90
90
  ProviderTypeManager.find(opentofu_provider)
91
91
  end
92
92
 
93
- def new_interface
94
- { compute_attributes: {} }
93
+ def new_interface(attr = {})
94
+ OpenStruct.new(attr)
95
95
  end
96
96
 
97
- def new_volume
98
- { compute_attributes: {} }
97
+ def new_volume(attr = {})
98
+ OpenStruct.new(attr.merge({}))
99
+ end
100
+
101
+ def default_volumes
102
+ tofu_provider&.default_volumes || {}
103
+ end
104
+
105
+ def default_interfaces
106
+ tofu_provider&.default_interfaces || {}
99
107
  end
100
108
 
101
109
  def editable_network_interfaces?
@@ -0,0 +1,7 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'compute_resources_vms/form/_networks',
3
+ name: 'tofu_indexed_networks_fields',
4
+ replace_contents: 'div.children_fields',
5
+ partial: 'foreman_opentofu/compute_resources_vms/indexed_networks_fields',
6
+ namespaced: true
7
+ )
@@ -0,0 +1,8 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'compute_resources_vms/form/_volumes',
3
+ name: 'tofu_indexed_volumes_fields',
4
+ replace_contents: 'div.children_fields',
5
+ partial: 'foreman_opentofu/compute_resources_vms/indexed_volumes_fields',
6
+ original: '7d0607bbae4c5123e89fff9968e2740a3d0eb323',
7
+ namespaced: true
8
+ )
@@ -100,10 +100,24 @@ module ForemanOpentofu
100
100
  cmd.map { |item| "'#{item}'" }.append('2>&1').join(' ')
101
101
  end
102
102
 
103
- def envvars
103
+ def common_envvars
104
+ {
105
+ 'TF_PLUGIN_CACHE_DIR' => ForemanOpentofu::OPENTOFU_PLUGIN_CACHE_PATH,
106
+ 'TEMPDIR' => ForemanOpentofu::OPENTOFU_TMP_PATH,
107
+ 'TMPDIR' => ForemanOpentofu::OPENTOFU_TMP_PATH,
108
+ 'TMP' => ForemanOpentofu::OPENTOFU_TMP_PATH,
109
+ 'TEMP' => ForemanOpentofu::OPENTOFU_TMP_PATH,
110
+ }
111
+ end
112
+
113
+ def terraform_envvars
104
114
  @variables.transform_keys { |variable| "TF_VAR_#{variable}" }
105
115
  end
106
116
 
117
+ def envvars
118
+ common_envvars.merge(terraform_envvars)
119
+ end
120
+
107
121
  def execute(cmd)
108
122
  output = nil
109
123
  # quote cmdline parameters and add stderr to stdout
@@ -6,13 +6,13 @@ module ForemanOpentofu
6
6
 
7
7
  def initialize(compute_resource, args = {})
8
8
  @compute_resource = compute_resource
9
- @cr_attrs = args.to_h
9
+ @cr_attrs = args.to_h.with_indifferent_access
10
10
  @resource = @cr_attrs['resource']
11
11
  @host_name = @cr_attrs['name'] || 'test'
12
12
  end
13
13
 
14
14
  def run(mode = '')
15
- Dir.mktmpdir('opentofu_') do |dir|
15
+ Dir.mktmpdir('opentofu_', ForemanOpentofu::OPENTOFU_TMP_PATH) do |dir|
16
16
  # FIXME: integrate the user_data-file into AppWrapper!
17
17
  if @cr_attrs['user_data']
18
18
  @user_data_filename = File.join(dir, 'userdata')
@@ -21,9 +21,9 @@ module ForemanOpentofu
21
21
  end
22
22
  end
23
23
  tofu = AppWrapper.new(dir, variables: {
24
- username: @compute_resource['user'],
25
- password: @compute_resource['password'],
26
- endpoint: @compute_resource['url'],
24
+ username: @compute_resource.user,
25
+ password: @compute_resource.password,
26
+ endpoint: @compute_resource.url,
27
27
  })
28
28
  @use_backend = %w[create destroy output].include?(mode)
29
29
  @token = create_token(@host_name) if @use_backend
@@ -1,7 +1,7 @@
1
1
  module ForemanOpentofu
2
2
  class ProviderType
3
3
  attr_reader :id, :name, :default_attributes
4
- attr_accessor :capabilities
4
+ attr_accessor :capabilities, :disk_renderer, :nic_renderer
5
5
 
6
6
  def initialize(id)
7
7
  @id = id.to_sym
@@ -70,5 +70,15 @@ module ForemanOpentofu
70
70
  # on image-based deployment we usually only get IPv4/6-address
71
71
  { mac: :mac }
72
72
  end
73
+
74
+ def render_disk(disk, context, *args)
75
+ return '' unless disk_renderer
76
+ context.instance_exec(disk, *args, &disk_renderer)
77
+ end
78
+
79
+ def render_nic(nic, context, *args)
80
+ return '' unless nic_renderer
81
+ context.instance_exec(nic, *args, &nic_renderer)
82
+ end
73
83
  end
74
84
  end
@@ -1,14 +1,9 @@
1
1
  <% provider = compute_resource.tofu_provider %>
2
2
  <% if provider.attributes.present? %>
3
3
  <% vm_attrs = provider.attributes('vm') %>
4
- <% disk_attrs = provider.attributes('disk') %>
5
4
  <%= field_set_tag _("VM Config") do %>
6
5
  <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: vm_attrs, compute_resource: compute_resource } %>
7
6
  <% end %>
8
-
9
- <%= field_set_tag _("Storage") do %>
10
- <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: disk_attrs, compute_resource: compute_resource } %>
11
- <% end %>
12
7
  <% end %>
13
8
 
14
9
  <%
@@ -0,0 +1,2 @@
1
+ <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: compute_resource.tofu_provider.attributes('disk'), compute_resource: compute_resource } %>
2
+
@@ -0,0 +1,47 @@
1
+ <% if compute_resource.is_a?(ForemanOpentofu::Tofu) %>
2
+ <div class="<%= compute_resource.interfaces_attrs_name %>_fields_template form_template" style="display: none;">
3
+ <%= fields_for "#{f.object_name}[#{compute_resource.interfaces_attrs_name}][new_#{compute_resource.interfaces_attrs_name}]", compute_resource.new_interface do |i| %>
4
+ <%= render :partial => provider_partial(compute_resource, 'network'),
5
+ :locals => { :f => i, :compute_resource => compute_resource, :new_host => new_host, :new_vm => new_vm, :remove_title => _('remove network interface'), :selected_cluster => selected_cluster },
6
+ :layout => 'compute_resources_vms/form/deletable_layout' %>
7
+ <% end %>
8
+ </div>
9
+
10
+ <%= field_set_tag _("Network interfaces"), :id => "network_interfaces" do %>
11
+ <% if compute_resource.editable_network_interfaces? %>
12
+ <%= render :partial => 'foreman_opentofu/compute_resources_vms/form/tofu/interfaces_fields',
13
+ :locals => { :f => f, :compute_resource => compute_resource, :new_host => new_host, :new_vm => new_vm, :item_layout => item_layout, :selected_cluster => selected_cluster } %>
14
+
15
+ <% if new_vm %>
16
+ <%= add_child_link '+ ' + _("Add Interface"), compute_resource.interfaces_attrs_name, { :class => "info", :title => _('add new network interface') } %>
17
+ <% end %>
18
+ <% else %>
19
+ <div class="col-md-12">
20
+ <%= _('No networks found.') %>
21
+ </div>
22
+ <% end %>
23
+ <% end %>
24
+ <% else %>
25
+ <%= new_child_fields_template(f, compute_resource.interfaces_attrs_name, {
26
+ :object => compute_resource.new_interface,
27
+ :partial => provider_partial(compute_resource, 'network'),
28
+ :form_builder_attrs => { :compute_resource => compute_resource, :new_host => new_host, :new_vm => new_vm, :remove_title => _('remove network interface') },
29
+ :layout => 'compute_resources_vms/form/deletable_layout' }) %>
30
+
31
+ <%= field_set_tag _("Network interfaces"), :id => "network_interfaces" do %>
32
+ <% if compute_resource.editable_network_interfaces? %>
33
+ <%= f.fields_for compute_resource.interfaces_attrs_name do |i| %>
34
+ <%= render :partial => provider_partial(compute_resource, 'network'),
35
+ :locals => { :f => i, :compute_resource => compute_resource, :new_host => new_host, :new_vm => new_vm, :remove_title => _('remove network interface'), :selected_cluster => selected_cluster },
36
+ :layout => 'compute_resources_vms/form/deletable_layout' %>
37
+ <% end %>
38
+ <% if new_vm %>
39
+ <%= add_child_link '+ ' + _("Add Interface"), compute_resource.interfaces_attrs_name, { :class => "info", :title => _('add new network interface') } %>
40
+ <% end %>
41
+ <% else %>
42
+ <div class="col-md-12">
43
+ <%= _('No networks found.') %>
44
+ </div>
45
+ <% end %>
46
+ <% end %>
47
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <% if compute_resource.is_a?(ForemanOpentofu::Tofu) %>
2
+ <div class="volumes_fields_template form_template" style="display: none;">
3
+ <%= fields_for "#{f.object_name}[volumes][new_volumes]", volume do |i| %>
4
+ <%= render :partial => provider_partial(compute_resource, 'volume'),
5
+ :locals => { :f => i, :compute_resource => compute_resource, :new_host => new_vm, :new_vm => new_vm, :remove_title => _('remove storage volume') },
6
+ :layout => "compute_resources_vms/form/#{item_layout}_layout" %>
7
+ <% end %>
8
+ </div>
9
+
10
+ <%= render :partial => 'foreman_opentofu/compute_resources_vms/form/tofu/volumes_fields',
11
+ :locals => { :f => f, :compute_resource => compute_resource, :new_host => new_vm, :new_vm => new_vm, :item_layout => item_layout } %>
12
+
13
+ <% if new_vm %>
14
+ <%= add_child_link '+ ' + _("Add Volume"), :volumes, { :class => "info", :title => _('add new storage volume') } %>
15
+ <% end %>
16
+ <% else %>
17
+ <%= new_child_fields_template(f, :volumes, {
18
+ :object => volume,
19
+ :partial => provider_partial(compute_resource, 'volume'),
20
+ :form_builder_attrs => { :compute_resource => compute_resource, :new_host => new_vm, :new_vm => new_vm, :remove_title => _('remove storage volume') },
21
+ :layout => "compute_resources_vms/form/#{item_layout}_layout" }) %>
22
+
23
+ <%= f.fields_for :volumes do |i| %>
24
+ <%= render :partial => provider_partial(compute_resource, 'volume'), :locals => { :f => i, :compute_resource => compute_resource, :new_host => new_vm, :new_vm => new_vm, :remove_title => _('remove storage volume') }, :layout => "compute_resources_vms/form/#{item_layout}_layout" %>
25
+ <% end %>
26
+
27
+ <% if new_vm %>
28
+ <%= add_child_link '+ ' + _("Add Volume"), :volumes, { :class => "info", :title => _('add new storage volume') } %>
29
+ <% end %>
30
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <%
2
+ association = compute_resource.interfaces_attrs_name
3
+ interfaces = Array.wrap(f.object.public_send(association))
4
+ interfaces = [compute_resource.new_interface].compact if interfaces.empty?
5
+ %>
6
+ <% interfaces.each_with_index do |interface_obj, idx| %>
7
+ <%= fields_for "#{f.object_name}[#{association}][#{idx}]", interface_obj do |i| %>
8
+ <%= render :partial => provider_partial(compute_resource, 'network'),
9
+ :locals => { :f => i, :compute_resource => compute_resource, :new_host => new_host, :new_vm => new_vm, :remove_title => _('remove network interface'), :selected_cluster => selected_cluster },
10
+ :layout => "compute_resources_vms/form/#{item_layout}_layout" %>
11
+ <% end %>
12
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <%
2
+ volumes = Array.wrap(f.object.volumes)
3
+ volumes = [compute_resource.new_volume].compact if volumes.empty?
4
+ %>
5
+ <% volumes.each_with_index do |volume_obj, idx| %>
6
+ <%= fields_for "#{f.object_name}[volumes][#{idx}]", volume_obj do |i| %>
7
+ <%= render :partial => provider_partial(compute_resource, 'volume'),
8
+ :locals => { :f => i, :compute_resource => compute_resource, :new_host => new_host, :new_vm => new_vm, :remove_title => _('remove storage volume') },
9
+ :layout => "compute_resources_vms/form/#{item_layout}_layout" %>
10
+ <% end %>
11
+ <% end %>
@@ -39,6 +39,8 @@ resource "hcloud_server" "node1" {
39
39
  }
40
40
  }
41
41
 
42
+ <%= build_disks %>
43
+
42
44
  output "vm_attrs" {
43
45
  value = {
44
46
  identity = hcloud_server.node1.id
@@ -32,10 +32,11 @@ resource "nutanix_virtual_machine" "vm" {
32
32
  name = "<%= @host_name %>"
33
33
 
34
34
  <%= vm_attributes(['name']) %>
35
-
35
+ <%= build_nics %>
36
+ <%= build_disks %>
36
37
  <%- if @cr_attrs['image_id'].present? %>
37
38
  <%- if @cr_attrs['user_data'].present? %>
38
- guest_customization_cloud_init_user_data = base64encode("<%= @cr_attrs['user_data']) %>")
39
+ guest_customization_cloud_init_user_data = base64encode("<%= @cr_attrs['user_data'] %>")
39
40
  <%- end %>
40
41
  disk_list {
41
42
  data_source_reference = {
@@ -5,6 +5,10 @@ require "#{ForemanOpentofu::Engine.root}/app/services/foreman_opentofu/provider_
5
5
  ForemanOpentofu::ProviderTypeManager.register('hetzner') do
6
6
  @capabilities = [:image]
7
7
 
8
+ def default_volumes
9
+ { name: 'volume1', size: 50, automount: true, format: 'ext4' }
10
+ end
11
+
8
12
  def provided_attributes
9
13
  {
10
14
  ip: :vm_ip_address,
@@ -51,6 +55,14 @@ ForemanOpentofu::ProviderTypeManager.register('hetzner') do
51
55
  },
52
56
  output_path_postfix: 'networks',
53
57
  } },
58
+ { name: 'name', type: 'string', group: 'disk', mandatory: true,
59
+ label: 'Volume Name' },
60
+ { name: 'size', type: 'number', group: 'disk', mandatory: true,
61
+ label: 'Size (GB)' },
62
+ { name: 'automount', type: 'bool', group: 'disk', mandatory: false,
63
+ label: 'Automount' },
64
+ { name: 'format', type: 'select', group: 'disk', mandatory: false,
65
+ label: 'Format', options: %w[ext4 xfs] },
54
66
  { name: 'available_images', type: 'select',
55
67
  label: 'Base-OS-Image', options: {
56
68
  data_source: {
@@ -63,4 +75,24 @@ ForemanOpentofu::ProviderTypeManager.register('hetzner') do
63
75
  output_path_postfix: 'images',
64
76
  } },
65
77
  ]
78
+
79
+ self.disk_renderer = proc do |disk, index = 0|
80
+ disk = disk.with_indifferent_access
81
+ volume_name = disk[:name].presence || "volume#{index + 1}"
82
+ volume_data = {
83
+ name: volume_name,
84
+ size: disk[:size].presence || 50,
85
+ server_id: :"hcloud_server.node1.id",
86
+ }
87
+ volume_data[:automount] = Foreman::Cast.to_bool(disk[:automount]) unless disk[:automount].nil?
88
+ volume_data[:format] = disk[:format] if disk[:format].present?
89
+
90
+ {
91
+ resource: {
92
+ type: 'hcloud_volume',
93
+ name: "volume#{index + 1}",
94
+ content: volume_data,
95
+ },
96
+ }
97
+ end
66
98
  end
@@ -51,4 +51,30 @@ ForemanOpentofu::ProviderTypeManager.register('nutanix') do
51
51
  { "name": 'subnet_uuid', "type": 'select', "group": 'nic', "mandatory": true,
52
52
  "label": 'Subnet', "options": { "data_source": { "name": 'nutanix_subnets' }, "output_path_postfix": 'entities', "entity": { "id": 'metadata.uuid' } } },
53
53
  ]
54
+
55
+ self.disk_renderer = proc do |disk|
56
+ {
57
+ disk_list: {
58
+ disk_size_mib: disk[:size_gb] * 1024,
59
+ storage_config: {
60
+ storage_container_reference: {
61
+ kind: 'storage_container',
62
+ uuid: disk[:container_uuid],
63
+ },
64
+ },
65
+ },
66
+ }
67
+ end
68
+
69
+ self.nic_renderer = proc do |nic|
70
+ {
71
+ nic_list: {
72
+ subnet_reference: {
73
+ kind: 'subnet',
74
+ uuid: nic[:subnet_uuid],
75
+ },
76
+ nic_type: nic[:type] || 'NORMAL_NIC',
77
+ },
78
+ }
79
+ end
54
80
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanOpentofu
2
- VERSION = '0.0.3'.freeze
2
+ VERSION = '0.0.4'.freeze
3
3
  end
@@ -3,4 +3,8 @@ require 'deface'
3
3
  module ForemanOpentofu
4
4
  require 'foreman_opentofu/version'
5
5
  require 'foreman_opentofu/engine'
6
+
7
+ OPENTOFU_MAIN_PATH = '/var/lib/foreman-opentofu'.freeze
8
+ OPENTOFU_TMP_PATH = File.join(OPENTOFU_MAIN_PATH, 'tmp').freeze
9
+ OPENTOFU_PLUGIN_CACHE_PATH = File.join(OPENTOFU_MAIN_PATH, 'plugin-cache').freeze
6
10
  end
data/selinux/Makefile ADDED
@@ -0,0 +1,22 @@
1
+ # installation paths
2
+ SHAREDIR := /usr/share/selinux
3
+
4
+ AWK ?= gawk
5
+ NAME ?= $(strip $(shell $(AWK) -F= '/^SELINUXTYPE/{ print $$2 }' /etc/selinux/config))
6
+
7
+ ifeq ($(MLSENABLED),)
8
+ MLSENABLED := 1
9
+ endif
10
+
11
+ ifeq ($(MLSENABLED),1)
12
+ NTYPE = mcs
13
+ endif
14
+
15
+ ifeq ($(NAME),mls)
16
+ NTYPE = mls
17
+ endif
18
+
19
+ TYPE ?= $(NTYPE)
20
+
21
+ HEADERDIR := $(SHAREDIR)/devel/include
22
+ include $(HEADERDIR)/Makefile
@@ -0,0 +1,3 @@
1
+ /var/lib/foreman-opentofu$ gen_context(system_u:object_r:foreman_opentofu_base_t,s0)
2
+ /var/lib/foreman-opentofu/plugin-cache(/.*)? gen_context(system_u:object_r:foreman_opentofu_cache_t,s0)
3
+ /var/lib/foreman-opentofu/tmp(/.*)? gen_context(system_u:object_r:foreman_opentofu_work_t,s0)
File without changes
@@ -0,0 +1,93 @@
1
+ policy_module(foreman_opentofu, 1.0)
2
+
3
+ ############################################################
4
+ # Custom file types used only for the
5
+ # dedicated OpenTofu workspace under:
6
+ # /var/lib/foreman-opentofu
7
+ ############################################################
8
+
9
+ # Base directory: /var/lib/foreman-opentofu
10
+ type foreman_opentofu_base_t;
11
+ files_type(foreman_opentofu_base_t)
12
+
13
+ # Provider/plugin cache: /var/lib/foreman-opentofu/plugin-cache
14
+ type foreman_opentofu_cache_t;
15
+ files_type(foreman_opentofu_cache_t)
16
+
17
+ # Temporary work tree: /var/lib/foreman-opentofu/tmp
18
+ type foreman_opentofu_work_t;
19
+ files_type(foreman_opentofu_work_t)
20
+
21
+ require {
22
+ type foreman_rails_t;
23
+ type cgroup_t;
24
+ type sysctl_net_t;
25
+
26
+ # Directory operations needed for creating/removing OpenTofu work/cache directories.
27
+ class dir { add_name create getattr open read remove_name rmdir search write };
28
+
29
+ # File operations needed
30
+ class file { append create execute execute_no_trans getattr ioctl lock map open read rename setattr unlink write };
31
+
32
+ # Symlink handling inside cache/work trees.
33
+ class lnk_file { create getattr read unlink };
34
+
35
+ # Unix socket files created in the controlled tmp tree for tofu <-> provider communication.
36
+ class sock_file { create getattr open read write unlink };
37
+
38
+ # Required so tofu can connect to the provider plugin over a local unix stream socket.
39
+ class unix_stream_socket connectto;
40
+ }
41
+
42
+ ############################################################
43
+ # Exact system lookups observed in AVCs
44
+ ############################################################
45
+
46
+ # tofu inspects /sys/fs/cgroup during init/plan
47
+ allow foreman_rails_t cgroup_t:dir search;
48
+
49
+ # provider inspects /proc/sys/net and reads sysctl files
50
+ allow foreman_rails_t sysctl_net_t:dir search;
51
+ allow foreman_rails_t sysctl_net_t:file { getattr open read };
52
+
53
+ # tofu connects to the provider through a local unix socket
54
+ allow foreman_rails_t self:unix_stream_socket connectto;
55
+
56
+ ############################################################
57
+ # Base directory: /var/lib/foreman-opentofu
58
+ ############################################################
59
+
60
+ # Allow tofu to traverse the base dir and create child
61
+ # entries directly below it, such as plugin-cache/tmp.
62
+ allow foreman_rails_t foreman_opentofu_base_t:dir { create search getattr open read write add_name };
63
+
64
+ ############################################################
65
+ # Plugin cache: /var/lib/foreman-opentofu/plugin-cache
66
+ ############################################################
67
+
68
+ # Allow creating and managing cache subdirectories.
69
+ allow foreman_rails_t foreman_opentofu_cache_t:dir { create search getattr open read write add_name remove_name };
70
+
71
+ # Allow downloading providers, adjusting permissions,
72
+ # locking .lock files, and executing provider binaries
73
+ # directly from the cache tree.
74
+ allow foreman_rails_t foreman_opentofu_cache_t:file { create getattr open read write append rename unlink execute execute_no_trans map ioctl lock setattr };
75
+
76
+ # Allow minimal symlink handling inside the cache tree.
77
+ allow foreman_rails_t foreman_opentofu_cache_t:lnk_file { create getattr read unlink };
78
+
79
+ ############################################################
80
+ # Work tree: /var/lib/foreman-opentofu/tmp
81
+ ############################################################
82
+
83
+ # Allow creating/removing per-run temporary directories.
84
+ allow foreman_rails_t foreman_opentofu_work_t:dir { create search getattr open read write add_name remove_name rmdir };
85
+
86
+ # Allow creating and managing files in the work tree.
87
+ allow foreman_rails_t foreman_opentofu_work_t:file { create getattr open read write append rename unlink execute execute_no_trans map ioctl lock setattr };
88
+
89
+ # Allow minimal symlink handling in the work tree.
90
+ allow foreman_rails_t foreman_opentofu_work_t:lnk_file { create getattr read unlink };
91
+
92
+ # Allow creating local unix socket files in the controlled tmp tree
93
+ allow foreman_rails_t foreman_opentofu_work_t:sock_file { create getattr open read write unlink };
@@ -106,5 +106,32 @@ module ForemanOpentofu
106
106
  assert_not_empty block
107
107
  assert_snapshot self, 'vm_attributes', block
108
108
  end
109
+
110
+ test 'build_disks renders provider-defined resource snippets' do
111
+ cr = FactoryBot.create(:opentofu_nutanix_cr)
112
+ cr.stubs(:default_volumes).returns([])
113
+ cr.stubs(:render_disk).with({ 'size' => 50 }, anything, 0).returns(
114
+ {
115
+ resource: {
116
+ type: 'hcloud_volume',
117
+ name: 'volume1',
118
+ content: { size: 50, server_id: :'hcloud_server.node1.id' },
119
+ },
120
+ }
121
+ )
122
+ source = ::Foreman::Renderer::Source::String.new(
123
+ name: 'Parameter',
124
+ content: '<%= build_disks %>'
125
+ )
126
+ scope = ::Foreman::Renderer.get_scope(variables: {
127
+ cr_attrs: { 'volumes' => [{ 'size' => 50 }] },
128
+ compute_resource: cr,
129
+ })
130
+ block = ::Foreman::Renderer.render(source, scope)
131
+
132
+ assert_includes block, 'resource "hcloud_volume" "volume1"'
133
+ assert_includes block, 'size = 50'
134
+ assert_includes block, 'server_id = hcloud_server.node1.id'
135
+ end
109
136
  end
110
137
  end
@@ -44,6 +44,33 @@ module ForemanOpentofu
44
44
  assert_instance_of ComputeVM, vm
45
45
  end
46
46
 
47
+ test '#create_vm normalizes indexed volume and interface hashes before client call' do
48
+ captured_args = nil
49
+ @nutanix_cr.stubs(:client).with do |args|
50
+ captured_args = args
51
+ true
52
+ end.returns(@executor)
53
+ @executor.stubs(:run_create).returns({ 'id' => 'vm1' })
54
+
55
+ @nutanix_cr.create_vm(
56
+ 'name' => 'vm1',
57
+ 'volumes' => {
58
+ '0' => { 'size' => '13', 'label' => 'disk0' },
59
+ },
60
+ 'interfaces_attributes' => {
61
+ '0' => { 'network_id' => 'net-1', 'adapter_type' => 'vmxnet3' },
62
+ }
63
+ )
64
+
65
+ assert_kind_of Array, captured_args[:volumes]
66
+ assert_equal '13', captured_args[:volumes][0][:size]
67
+ assert_equal 'disk0', captured_args[:volumes][0][:label]
68
+
69
+ assert_kind_of Array, captured_args[:interfaces]
70
+ assert_equal 'net-1', captured_args[:interfaces][0][:network_id]
71
+ assert_equal 'vmxnet3', captured_args[:interfaces][0][:adapter_type]
72
+ end
73
+
47
74
  test '#create_vm wraps exceptions' do
48
75
  @executor.stubs(:run_create).raises(StandardError.new('boom'))
49
76
 
@@ -49,10 +49,22 @@ module ForemanOpentofu
49
49
 
50
50
  test 'variables specified as envvars' do
51
51
  envvars = app_wrapper.send(:envvars)
52
- assert_empty(envvars.keys.reject { |var| var.starts_with?('TF_VAR_') })
53
52
  assert_equal 'secret', envvars['TF_VAR_password']
54
53
  end
55
54
 
55
+ test 'terraform variables all start with TF_VAR_' do
56
+ terraform_envvars = app_wrapper.send(:terraform_envvars)
57
+ assert_empty(terraform_envvars.keys.reject { |var| var.starts_with?('TF_VAR_') })
58
+ end
59
+
60
+ test 'common variables as envvars' do
61
+ envvars = app_wrapper.send(:envvars)
62
+ assert_equal ForemanOpentofu::OPENTOFU_PLUGIN_CACHE_PATH, envvars['TF_PLUGIN_CACHE_DIR']
63
+ %w[TEMPDIR TMPDIR TMP TEMP].each do |var|
64
+ assert_equal ForemanOpentofu::OPENTOFU_TMP_PATH, envvars[var]
65
+ end
66
+ end
67
+
56
68
  test 'create_variables_file()' do
57
69
  app_wrapper.create_variables_file
58
70
  expected = "variable \"user\" {\n type = string\n sensitive = false\n}"
@@ -156,5 +156,18 @@ module ForemanOpentofu
156
156
  test 'provided_attributes()' do
157
157
  assert_instance_of Hash, provider_type.provided_attributes
158
158
  end
159
+
160
+ test 'hetzner renders disk as hcloud_volume resource data' do
161
+ hetzner = ProviderTypeManager.find('hetzner')
162
+
163
+ rendered = hetzner.render_disk({ size: 50, format: 'ext4', automount: true }, nil, 0)
164
+
165
+ assert_equal 'hcloud_volume', rendered.dig(:resource, :type)
166
+ assert_equal 'volume1', rendered.dig(:resource, :name)
167
+ assert_equal 50, rendered.dig(:resource, :content, :size)
168
+ assert_equal :'hcloud_server.node1.id', rendered.dig(:resource, :content, :server_id)
169
+ assert rendered.dig(:resource, :content, :automount)
170
+ assert_equal 'ext4', rendered.dig(:resource, :content, :format)
171
+ end
159
172
  end
160
173
  end
@@ -1,4 +1,5 @@
1
1
  require 'test_helper'
2
+ require 'minitest/stub_const'
2
3
 
3
4
  module ForemanOpentofu
4
5
  class OpentofuExecuterTest < ActiveSupport::TestCase
@@ -23,58 +24,80 @@ module ForemanOpentofu
23
24
  @app_mock.stubs(:output).with('vm_attrs').returns('identity' => 'uuid-1')
24
25
  end
25
26
 
27
+ # Could not add this stub in setup as it would then try to automatically run
28
+ # remove_const to remove the stub_const afterwards and this method does not exist
29
+ def stub_opentofu_tmp_dir(&block)
30
+ ForemanOpentofu.stub_const(:OPENTOFU_TMP_PATH, '/tmp/', &block)
31
+ end
32
+
26
33
  test '#run create updates tf_state and returns attrs' do
27
- tf_state = FactoryBot.create(:tf_state, name: 'vm-1')
28
- result = @executor.run_create
29
- tf_state.reload
30
- assert_equal tf_state.uuid, result['identity']
31
- assert_not_nil tf_state.uuid, 'tf_state UUID should be set'
34
+ stub_opentofu_tmp_dir do
35
+ tf_state = FactoryBot.create(:tf_state, name: 'vm-1')
36
+ result = @executor.run_create
37
+ tf_state.reload
38
+ assert_equal tf_state.uuid, result['identity']
39
+ assert_not_nil tf_state.uuid, 'tf_state UUID should be set'
40
+ end
32
41
  end
33
42
 
34
43
  test '#run output returns vm_attrs' do
35
- result = @executor.run_output
36
- assert_equal 'uuid-1', result['identity']
44
+ stub_opentofu_tmp_dir do
45
+ result = @executor.run_output
46
+ assert_equal 'uuid-1', result['identity']
47
+ end
37
48
  end
38
49
 
39
50
  test '#run destroy calls destroy' do
40
- @executor.run_destroy
41
- assert_nil ForemanOpentofu::TfState.find_by(uuid: 'uuid-1')
51
+ stub_opentofu_tmp_dir do
52
+ @executor.run_destroy
53
+ assert_nil ForemanOpentofu::TfState.find_by(uuid: 'uuid-1')
54
+ end
42
55
  end
43
56
 
44
57
  test '#run new calls plan and show_plan' do
45
- @app_mock.expects(:plan)
46
- @app_mock.expects(:show_plan)
47
- @executor.run_new
58
+ stub_opentofu_tmp_dir do
59
+ @app_mock.expects(:plan)
60
+ @app_mock.expects(:show_plan)
61
+ @executor.run_new
62
+ end
48
63
  end
49
64
 
50
65
  test '#run test_connection only plans' do
51
- @app_mock.expects(:plan)
52
- @executor.run_test_connection
66
+ stub_opentofu_tmp_dir do
67
+ @app_mock.expects(:plan)
68
+ @executor.run_test_connection
69
+ end
53
70
  end
54
71
 
55
72
  test '#run creates user_data file' do
56
- executor = OpentofuExecuter.new(@compute_resource, { 'user_data' => 'HelloWorld' })
57
- executor.expects(:render_template)
58
- file_mock = Minitest::Mock.new
59
- file_mock.expect(:write, nil, ['HelloWorld'])
60
- File.stub(:open, [], file_mock) do
61
- executor.run do |_tofu|
73
+ stub_opentofu_tmp_dir do
74
+ executor = OpentofuExecuter.new(@compute_resource, { 'user_data' => 'HelloWorld' })
75
+ executor.expects(:render_template)
76
+ file_mock = Minitest::Mock.new
77
+ file_mock.expect(:write, nil, ['HelloWorld'])
78
+ File.stub(:open, [], file_mock) do
79
+ executor.run do |_tofu|
80
+ end
62
81
  end
82
+ file_mock.verify
63
83
  end
64
- file_mock.verify
65
84
  end
66
85
 
67
86
  test '#render_template raises exception if nil returned' do
68
- Foreman::Renderer::UnsafeModeRenderer.stubs(:render).returns(nil)
69
- assert_raises(Foreman::Exception) { @executor.send(:render_template, 'create') }
87
+ stub_opentofu_tmp_dir do
88
+ Foreman::Renderer::UnsafeModeRenderer.stubs(:render).returns(nil)
89
+ assert_raises(Foreman::Exception) { @executor.send(:render_template, 'create') }
90
+ end
70
91
  end
71
92
 
72
93
  test 'render_template with resource' do
73
- executor = OpentofuExecuter.new(@compute_resource, { 'resource' => { name: 'datasource_name1', options: {} } })
74
- executor.stubs(:provision_template).returns(@template)
94
+ stub_opentofu_tmp_dir do
95
+ executor = OpentofuExecuter.new(@compute_resource, { 'resource' => { name: 'datasource_name1', options: {} } })
96
+ executor.stubs(:provision_template).returns(@template)
75
97
 
76
- @app_mock.expects(:output).with('resources')
77
- executor.run_fetch
98
+ @app_mock.expects(:output).with('resources')
99
+ executor.run_fetch
100
+ end
78
101
  end
79
102
  end
80
103
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_opentofu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - ATIX-AG
@@ -40,6 +40,7 @@ files:
40
40
  - app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb
41
41
  - app/lib/foreman_opentofu/hcl_format.rb
42
42
  - app/lib/foreman_opentofu/nic_helpers.rb
43
+ - app/models/concerns/foreman_opentofu/vm_command_collection_normalization.rb
43
44
  - app/models/concerns/orchestration/tofu/compute.rb
44
45
  - app/models/foreman_opentofu/compute_vm.rb
45
46
  - app/models/foreman_opentofu/opentofu_vm_commands.rb
@@ -47,6 +48,8 @@ files:
47
48
  - app/models/foreman_opentofu/tofu.rb
48
49
  - app/models/foreman_opentofu/token.rb
49
50
  - app/overrides/compute_resources/remove_virtual_machines_tab.rb
51
+ - app/overrides/compute_resources_vms/tofu_indexed_networks_fields.rb
52
+ - app/overrides/compute_resources_vms/tofu_indexed_volumes_fields.rb
50
53
  - app/services/foreman_opentofu/app_wrapper.rb
51
54
  - app/services/foreman_opentofu/opentofu_executer.rb
52
55
  - app/services/foreman_opentofu/provider_type.rb
@@ -57,7 +60,12 @@ files:
57
60
  - app/views/compute_resources_vms/form/tofu/_base.html.erb
58
61
  - app/views/compute_resources_vms/form/tofu/_dynamic_attrs.html.erb
59
62
  - app/views/compute_resources_vms/form/tofu/_network.html.erb
63
+ - app/views/compute_resources_vms/form/tofu/_volume.html.erb
60
64
  - app/views/compute_resources_vms/index/_tofu.html.erb
65
+ - app/views/foreman_opentofu/compute_resources_vms/_indexed_networks_fields.html.erb
66
+ - app/views/foreman_opentofu/compute_resources_vms/_indexed_volumes_fields.html.erb
67
+ - app/views/foreman_opentofu/compute_resources_vms/form/tofu/_interfaces_fields.html.erb
68
+ - app/views/foreman_opentofu/compute_resources_vms/form/tofu/_volumes_fields.html.erb
61
69
  - app/views/images/form/_tofu.html.erb
62
70
  - app/views/templates/provisioning/hetzner_provision_host.erb
63
71
  - app/views/templates/provisioning/nutanix_provision_default.erb
@@ -79,6 +87,10 @@ files:
79
87
  - locale/en/foreman_opentofu.po
80
88
  - locale/foreman_opentofu.pot
81
89
  - locale/gemspec.rb
90
+ - selinux/Makefile
91
+ - selinux/foreman_opentofu.fc
92
+ - selinux/foreman_opentofu.if
93
+ - selinux/foreman_opentofu.te
82
94
  - test/controllers/api/v2/tf_states_controller_test.rb
83
95
  - test/factories/compute_resources.rb
84
96
  - test/factories/foreman_opentofu_factories.rb