foreman_opentofu 0.0.2 → 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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +109 -12
  3. data/app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb +87 -44
  4. data/app/lib/foreman_opentofu/hcl_format.rb +95 -0
  5. data/app/lib/foreman_opentofu/nic_helpers.rb +47 -0
  6. data/app/models/concerns/foreman_opentofu/vm_command_collection_normalization.rb +22 -0
  7. data/app/models/concerns/orchestration/tofu/compute.rb +25 -6
  8. data/app/models/foreman_opentofu/compute_vm.rb +10 -0
  9. data/app/models/foreman_opentofu/opentofu_vm_commands.rb +22 -11
  10. data/app/models/foreman_opentofu/tofu.rb +43 -10
  11. data/app/overrides/compute_resources_vms/tofu_indexed_networks_fields.rb +7 -0
  12. data/app/overrides/compute_resources_vms/tofu_indexed_volumes_fields.rb +8 -0
  13. data/app/services/foreman_opentofu/app_wrapper.rb +51 -14
  14. data/app/services/foreman_opentofu/opentofu_executer.rb +67 -29
  15. data/app/services/foreman_opentofu/provider_type.rb +61 -7
  16. data/app/views/compute_resources/form/_tofu.html.erb +3 -0
  17. data/app/views/compute_resources/show/_tofu.html.erb +4 -0
  18. data/app/views/compute_resources/tofu.json.rabl +1 -0
  19. data/app/views/compute_resources_vms/form/tofu/_base.html.erb +12 -6
  20. data/app/views/compute_resources_vms/form/tofu/_dynamic_attrs.html.erb +17 -7
  21. data/app/views/compute_resources_vms/form/tofu/_network.html.erb +1 -5
  22. data/app/views/compute_resources_vms/form/tofu/_volume.html.erb +2 -0
  23. data/app/views/foreman_opentofu/compute_resources_vms/_indexed_networks_fields.html.erb +47 -0
  24. data/app/views/foreman_opentofu/compute_resources_vms/_indexed_volumes_fields.html.erb +30 -0
  25. data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_interfaces_fields.html.erb +12 -0
  26. data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_volumes_fields.html.erb +11 -0
  27. data/app/views/images/form/_tofu.html.erb +4 -0
  28. data/app/views/templates/provisioning/hetzner_provision_host.erb +64 -0
  29. data/app/views/templates/provisioning/nutanix_provision_default.erb +31 -22
  30. data/app/views/templates/provisioning/ovirt_provision_default.erb +7 -29
  31. data/lib/foreman_opentofu/provider_types/hetzner.rb +98 -0
  32. data/lib/foreman_opentofu/provider_types/nutanix.rb +74 -0
  33. data/lib/foreman_opentofu/provider_types/ovirt.rb +19 -0
  34. data/lib/foreman_opentofu/version.rb +1 -1
  35. data/lib/foreman_opentofu.rb +4 -0
  36. data/selinux/Makefile +22 -0
  37. data/selinux/foreman_opentofu.fc +3 -0
  38. data/selinux/foreman_opentofu.if +0 -0
  39. data/selinux/foreman_opentofu.te +93 -0
  40. data/test/fixtures/snapshots/foreman_opentofu/base_template_scope_extensions_test/backend_block.txt +6 -0
  41. data/test/fixtures/snapshots/foreman_opentofu/base_template_scope_extensions_test/terraform_block.txt +8 -0
  42. data/test/fixtures/snapshots/foreman_opentofu/base_template_scope_extensions_test/terraform_block_with_token.txt +14 -0
  43. data/test/fixtures/snapshots/foreman_opentofu/base_template_scope_extensions_test/vm_attributes.txt +3 -0
  44. data/test/fixtures/snapshots/foreman_opentofu/hcl_format_test/to_hcl.txt +19 -0
  45. data/test/fixtures/snapshots/foreman_opentofu/nic_helpers_test/nic_attributes.txt +8 -0
  46. data/test/fixtures/snapshots/foreman_opentofu/nic_helpers_test/nic_attributes_block.txt +6 -0
  47. data/test/lib/foreman_opentofu/concerns/base_template_scope_extensions_test.rb +137 -0
  48. data/test/lib/foreman_opentofu/concerns/nic_helpers_test.rb +36 -0
  49. data/test/lib/foreman_opentofu/hcl_format_test.rb +72 -0
  50. data/test/models/foreman_opentofu/opentofu_vm_commands_test.rb +41 -14
  51. data/test/models/foreman_opentofu/tofu_test.rb +22 -0
  52. data/test/services/app_wrapper_test.rb +51 -1
  53. data/test/services/foreman_opentofu/provider_type_test.rb +115 -10
  54. data/test/services/opentofu_executer_test.rb +60 -19
  55. data/test/test_plugin_helper.rb +6 -0
  56. metadata +40 -6
  57. data/app/services/foreman_opentofu/compute_fetcher.rb +0 -51
  58. data/config/initializers/compute_attrs.rb +0 -19
  59. data/config/nutanix.json +0 -27
  60. data/config/ovirt.json +0 -18
@@ -18,6 +18,7 @@
18
18
  module ForemanOpentofu
19
19
  class Tofu < ComputeResource
20
20
  include OpentofuVMCommands
21
+ include ComputeResourceCaching
21
22
  validates :provider, presence: true, inclusion: { in: %w[Tofu] }
22
23
  validates :url, presence: true
23
24
  validates :user, presence: true
@@ -26,12 +27,19 @@ module ForemanOpentofu
26
27
  # alias_attribute :username, :user
27
28
  # alias_attribute :endpoint, :url
28
29
 
29
- delegate :available_attributes, to: :tofu_provider
30
+ delegate :available_attributes, :capabilities, :render_disk, :render_nic, to: :tofu_provider
31
+
32
+ def available_images
33
+ # make sure available_images can use this CR, e.g. for requesting data_source
34
+ tofu_provider.available_images(self)
35
+ end
30
36
 
31
37
  def provided_attributes
32
- super.merge(
33
- mac: :mac
34
- )
38
+ super.merge(tofu_provider.provided_attributes || {})
39
+ end
40
+
41
+ def user_data_supported?
42
+ true
35
43
  end
36
44
 
37
45
  def opentofu_provider
@@ -60,10 +68,6 @@ module ForemanOpentofu
60
68
  'OpenTofu'
61
69
  end
62
70
 
63
- def capabilities
64
- [:build]
65
- end
66
-
67
71
  def self.model_name
68
72
  ComputeResource.model_name
69
73
  end
@@ -76,16 +80,45 @@ module ForemanOpentofu
76
80
  true
77
81
  end
78
82
 
83
+ def vm_ready(vm)
84
+ return tofu_provider.vm_ready(vm) if tofu_provider.respond_to? :vm_ready
85
+
86
+ vm.wait_for { ready? }
87
+ end
88
+
79
89
  def tofu_provider
80
90
  ProviderTypeManager.find(opentofu_provider)
81
91
  end
82
92
 
83
- def new_interface
84
- { compute_attributes: {} }
93
+ def new_interface(attr = {})
94
+ OpenStruct.new(attr)
95
+ end
96
+
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 || {}
85
107
  end
86
108
 
87
109
  def editable_network_interfaces?
88
110
  true
89
111
  end
112
+
113
+ def available_resource(resource_name, options = {})
114
+ cache.cache("#{name}_#{resource_name}") do
115
+ resource = fetch_resource(resource_name, options)
116
+ resource.map { |h| OpenStruct.new(h) }
117
+ end
118
+ end
119
+
120
+ def available_resource_ui_select(resource_name, options = {})
121
+ available_resource(resource_name, options)&.map { |obj| [obj['name'], obj['id']] }
122
+ end
90
123
  end
91
124
  end
@@ -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
+ )
@@ -1,6 +1,7 @@
1
1
  module ForemanOpentofu
2
2
  class AppWrapper
3
- attr_reader :workdir, :planfile, :conffile
3
+ include HclFormat
4
+ attr_reader :workdir
4
5
 
5
6
  # TODO: for future versions
6
7
  # - manage temp-work-dir; problem: no auto-remove after finished :-(
@@ -9,14 +10,33 @@ module ForemanOpentofu
9
10
  # - use JSON-output for easier parsing
10
11
  # - do we need locking or has the object be atomic
11
12
 
12
- def initialize(workdir)
13
+ def initialize(workdir, opts = {})
13
14
  @workdir = workdir
14
- @planfile = File.join(workdir, 'plan.bin')
15
- @conffile = File.join(workdir, 'main.tf')
15
+ @variables = opts[:variables]
16
16
  end
17
17
 
18
- def base_command
19
- 'tofu'
18
+ # rubocop:disable Style/SingleLineMethods
19
+ def planfile() File.join(workdir, 'plan.bin') end
20
+ def conffile() File.join(workdir, 'main.tf') end
21
+ def vardeffile() File.join(workdir, 'variables.tf') end
22
+
23
+ def base_command() 'tofu' end
24
+ # rubocop:enable Style/SingleLineMethods
25
+
26
+ # write variables definition file based on @variables into 'variables.tf'
27
+ def create_variables_file
28
+ File.open(vardeffile, 'w') do |f|
29
+ @variables.each_key do |var|
30
+ data = { 'type' => :string }
31
+ data['sensitive'] = var.to_s == 'password'
32
+
33
+ f << block_to_hcl(['variable', var], data)
34
+ end
35
+ end
36
+ end
37
+
38
+ def variable_params
39
+ @variables.map { |var, val| ['-var', "#{var}=#{val}"] }.flatten
20
40
  end
21
41
 
22
42
  def default_params
@@ -32,6 +52,8 @@ module ForemanOpentofu
32
52
  # end
33
53
  # end
34
54
  def init(params = [], &block)
55
+ create_variables_file if @variables
56
+
35
57
  tofu_execute('init', ['-input=false'].concat(parse_params(params)), &block)
36
58
  end
37
59
 
@@ -78,18 +100,33 @@ module ForemanOpentofu
78
100
  cmd.map { |item| "'#{item}'" }.append('2>&1').join(' ')
79
101
  end
80
102
 
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
114
+ @variables.transform_keys { |variable| "TF_VAR_#{variable}" }
115
+ end
116
+
117
+ def envvars
118
+ common_envvars.merge(terraform_envvars)
119
+ end
120
+
81
121
  def execute(cmd)
82
122
  output = nil
83
123
  # quote cmdline parameters and add stderr to stdout
84
124
  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
125
+ IO.popen(envvars, commandline, 'r+', chdir: workdir) do |pipe|
126
+ if block_given?
127
+ yield pipe
128
+ else
129
+ output = pipe.read
93
130
  end
94
131
  end
95
132
  ret = $CHILD_STATUS
@@ -1,21 +1,35 @@
1
- require 'json'
2
-
3
1
  module ForemanOpentofu
4
2
  class OpentofuExecuter
5
- def initialize(*args)
6
- @compute_resource = args[0]
7
- @cr_attrs = args[1] || {}
3
+ # 'destroy' works with TfState rather than the actual tf-file
4
+ # also it might not receive all the necessary data to create a valid tf-file
5
+ DRY_RUN_MODES = %w[destroy test].freeze
6
+
7
+ def initialize(compute_resource, args = {})
8
+ @compute_resource = compute_resource
9
+ @cr_attrs = args.to_h.with_indifferent_access
10
+ @resource = @cr_attrs['resource']
8
11
  @host_name = @cr_attrs['name'] || 'test'
9
12
  end
10
13
 
11
- def run(mode = 'create')
12
- Dir.mktmpdir('opentofu_') do |dir|
13
- tofu = AppWrapper.new(dir)
14
+ def run(mode = '')
15
+ Dir.mktmpdir('opentofu_', ForemanOpentofu::OPENTOFU_TMP_PATH) do |dir|
16
+ # FIXME: integrate the user_data-file into AppWrapper!
17
+ if @cr_attrs['user_data']
18
+ @user_data_filename = File.join(dir, 'userdata')
19
+ File.open(@user_data_filename, 'w') do |f|
20
+ f.write(@cr_attrs['user_data'])
21
+ end
22
+ end
23
+ tofu = AppWrapper.new(dir, variables: {
24
+ username: @compute_resource.user,
25
+ password: @compute_resource.password,
26
+ endpoint: @compute_resource.url,
27
+ })
14
28
  @use_backend = %w[create destroy output].include?(mode)
15
29
  @token = create_token(@host_name) if @use_backend
16
- tofu.main_configuration = render_template
30
+ tofu.main_configuration = render_template(mode)
17
31
  tofu.init
18
- run_mode(tofu, mode)
32
+ yield(tofu)
19
33
  end
20
34
  end
21
35
 
@@ -40,45 +54,69 @@ module ForemanOpentofu
40
54
  new_token
41
55
  end
42
56
 
43
- def run_mode(tofu, mode = 'new')
44
- case mode
45
- when 'new'
57
+ def run_new
58
+ run('new') do |tofu|
46
59
  tofu.plan
47
60
  tofu.show_plan
48
- when 'test_connection'
49
- tofu.plan
50
- when 'create'
51
- tofu.plan
61
+ end
62
+ end
63
+
64
+ def run_test_connection
65
+ run('test', &:plan)
66
+ end
67
+
68
+ def run_create
69
+ run('create') do |tofu|
52
70
  tofu.apply
53
71
  attrs = tofu.output('vm_attrs')
54
72
  ForemanOpentofu::TfState.find_by(name: @cr_attrs['name'])&.update(uuid: attrs['identity'])
55
73
  attrs
56
- when 'output'
74
+ end
75
+ end
76
+
77
+ def run_output
78
+ run('output') do |tofu|
57
79
  tofu.output('vm_attrs')
58
- when 'destroy'
59
- tofu.destroy
60
- else
61
- raise "Please select one of the modes: 'new', 'test_connection', 'create' or 'destroy'"
80
+ end
81
+ end
82
+
83
+ def run_destroy
84
+ run('destroy', &:destroy)
85
+ end
86
+
87
+ def run_fetch
88
+ run do |tofu|
89
+ tofu.apply
90
+ tofu.output('resources')
62
91
  end
63
92
  end
64
93
 
65
94
  private
66
95
 
67
- def render_template
96
+ def render_template(mode)
68
97
  template = provision_template
69
- scope = Foreman::Renderer.get_scope(source: template)
98
+ variables = {
99
+ compute_resource: @compute_resource,
100
+ cr_attrs: @cr_attrs,
101
+ use_backend: @use_backend,
102
+ token: @token,
103
+ host_name: @host_name,
104
+ resource: @resource,
105
+ dry_run: dry_run(mode),
106
+ user_data_filename: @user_data_filename,
107
+ }
108
+ scope = Foreman::Renderer.get_scope(source: template, variables: variables)
70
109
  source = Foreman::Renderer.get_source(template: template)
71
- scope.instance_variable_set(:@compute_resource, @compute_resource)
72
- scope.instance_variable_set(:@cr_attrs, @cr_attrs) if @cr_attrs
73
- scope.instance_variable_set(:@use_backend, @use_backend)
74
- scope.instance_variable_set(:@token, @token) if @use_backend
75
- scope.instance_variable_set(:@host_name, @host_name)
76
110
  rendered_template = Foreman::Renderer::UnsafeModeRenderer.render(source, scope)
77
111
  raise ::Foreman::Exception, N_('Unable to render provisioning template') unless rendered_template
78
112
 
79
113
  rendered_template
80
114
  end
81
115
 
116
+ def dry_run(mode)
117
+ mode.empty? || DRY_RUN_MODES.include?(mode)
118
+ end
119
+
82
120
  def provision_template
83
121
  name = @compute_resource.opentofu_template.name
84
122
  template = ProvisioningTemplate.unscoped.find_by(name: name)
@@ -1,30 +1,84 @@
1
1
  module ForemanOpentofu
2
2
  class ProviderType
3
3
  attr_reader :id, :name, :default_attributes
4
+ attr_accessor :capabilities, :disk_renderer, :nic_renderer
4
5
 
5
6
  def initialize(id)
6
7
  @id = id.to_sym
7
8
  @name = id.capitalize
9
+ @capabilities = [:build]
10
+ @provider_attrs = []
11
+ end
12
+
13
+ def provider_attrs=(input)
14
+ @provider_attrs = Array(input).map do |attr|
15
+ ActiveSupport::HashWithIndifferentAccess.new(attr)
16
+ end
17
+ end
18
+
19
+ # if necessary, select-parameter named 'available_images' must be specified!
20
+ def available_images(compute_resource)
21
+ attribute = find_attr_by('name', 'available_images')
22
+ raise NotImplementedError if attribute.nil?
23
+
24
+ query_opts = attribute['options']
25
+ case query_opts
26
+ when nil then raise NotImplementedError
27
+ # TODO: Check if Array really works!
28
+ when Array then query_opts
29
+ when Hash then compute_resource.available_resource(query_opts.dig('data_source', 'name'), query_opts)
30
+ else raise 'available_images in ProviderType config is of unknown type.'
31
+ end
8
32
  end
9
33
 
10
34
  # returns hash of available-attributes with attr-name as key
11
- def available_attributes
35
+ def available_attributes(group = nil)
12
36
  raise "No available-attributes found for #{name}" unless attributes?
13
37
 
14
- attributes&.index_by { |e| e['name'] }
38
+ attributes(group).index_by { |e| e['name'] }.with_indifferent_access
15
39
  end
16
40
 
17
41
  def attributes?
18
- CR_ATTRS.key? id.to_s
42
+ @provider_attrs.present?
19
43
  end
20
44
 
21
45
  def attributes(group = nil)
22
- return nil unless CR_ATTRS.key? id.to_s
46
+ return [] unless attributes?
47
+
48
+ return @provider_attrs if group.nil?
49
+
50
+ @provider_attrs.select { |e| e['group'] == group }
51
+ end
23
52
 
24
- a = CR_ATTRS[id.to_s]
25
- return a if group.nil?
53
+ # return Array of Hashes of all attributes that have `key` set to `value`.
54
+ # Optional: limited to `group`
55
+ def search_attr_by(key, value, group = nil)
56
+ attributes(group).select { |attr| attr[key] == value }
57
+ end
58
+
59
+ # return Hash of attribute that has `key` set to `value`.
60
+ # If multiple exists, first in list is returned
61
+ # Returns `nil` if none is found
62
+ # Optional: limited to `group`
63
+ def find_attr_by(key, value, group = nil)
64
+ search_attr_by(key, value, group).first
65
+ end
66
+
67
+ def provided_attributes
68
+ # TODO: maybe we need to do something more sophisticated, here.
69
+ # network-based deployment needs MAC to set-up DHCP, but
70
+ # on image-based deployment we usually only get IPv4/6-address
71
+ { mac: :mac }
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
26
78
 
27
- a.select { |e| e['group'] == group }
79
+ def render_nic(nic, context, *args)
80
+ return '' unless nic_renderer
81
+ context.instance_exec(nic, *args, &nic_renderer)
28
82
  end
29
83
  end
30
84
  end
@@ -4,6 +4,9 @@
4
4
  <%= text_f f, :url, :help_block => _("127.0.0.1") %>
5
5
  <%= text_f f, :user , :help_block => _("e.g. admin") %>
6
6
  <%= password_f f, :password, :unset => unset_password? %>
7
+ <%= checkbox_f f, :caching_enabled, :label => _("Enable caching"),
8
+ :help_inline => _("Cache slow calls to Opentofu Compute resources to speed up page rendering") %>
9
+
7
10
  <% end %>
8
11
  <div class="col-md-offset-2">
9
12
  <%= test_connection_button_f(f, (true)) %>
@@ -10,3 +10,7 @@
10
10
  <td><%= _('URL') %></td>
11
11
  <td><%= @compute_resource.url %></td>
12
12
  </tr>
13
+ <tr>
14
+ <td><%= _("Caching") %></td>
15
+ <td><%= @compute_resource.caching_enabled ? _("Enabled") : _("Disabled") %></td>
16
+ </tr>
@@ -0,0 +1 @@
1
+ attributes :caching_enabled
@@ -1,13 +1,19 @@
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
- <%= 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 } %>
5
+ <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: vm_attrs, compute_resource: compute_resource } %>
11
6
  <% end %>
12
7
  <% end %>
13
8
 
9
+ <%
10
+ arch ||= nil ; os ||= nil
11
+ images = possible_images(compute_resource, arch, os)
12
+ %>
13
+
14
+ <div id='image_selection'>
15
+ <%= select_f f, :image_id, images, :uuid, :name,
16
+ { :include_blank => (images.empty? || images.size == 1) ? false : _('Please select an image') },
17
+ { :disabled => images.empty?, :label => _('Image'), :label_size => "col-md-2" } %>
18
+ </div>
19
+
@@ -1,18 +1,28 @@
1
1
  <% attrs.each do |attr|
2
- label = attr.fetch('label', attr['name'].humanize) %>
2
+ label = attr.fetch('label', attr['name'].humanize)
3
+ common_options = {
4
+ label: _(label),
5
+ label_help: attr['help'],
6
+ required: attr['mandatory'],
7
+ } %>
3
8
 
4
- <% case attr["type"] %>
9
+ <% case attr['type'] %>
5
10
  <% when 'string' %>
6
- <%= text_f f, attr["name"].to_sym, label: _(label), label_help: attr['help'] %>
11
+ <%= text_f f, attr['name'].to_sym, common_options %>
7
12
  <% when 'bool' %>
8
- <%= checkbox_f f, attr["name"].to_sym, label: _(label), label_help: attr['help'] %>
13
+ <%= checkbox_f f, attr['name'].to_sym, common_options %>
9
14
  <% when 'number' %>
10
- <%= counter_f f, attr["name"].to_sym, label: _(label), label_help: attr['help'] %>
15
+ <%= counter_f f, attr['name'].to_sym, common_options %>
11
16
  <% when 'select' %>
12
17
  <% 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'] %>
18
+ <% if attr['options'].is_a?(Hash) && attr['options'].key?('data_source') -%>
19
+ <%= selectable_f f, attr['name'].to_sym, compute_resource.available_resource_ui_select(attr.dig('options', 'data_source', 'name'), attr.dig('options')), { :include_blank => !attr['mandatory'] }, common_options %>
20
+ <% else -%>
21
+ <%= select_f f, attr['name'].to_sym, attr.fetch('options', []), :to_s, :to_s, { :include_blank => !attr['mandatory'] }, common_options %>
22
+ <%end -%>
14
23
  <% else %>
15
- <%= select_f f, attr["name"].to_sym, (options || []), :id, :name, { :include_blank => true }, label: _(label), label_help: attr['help'] %>
24
+ <% #FIXME: what to do with this? %>
25
+ <%= select_f f, attr['name'].to_sym, (options || []), :id, :name, { :include_blank => true }, common_options %>
16
26
  <% end %>
17
27
  <% end %>
18
28
  <% end %>
@@ -1,5 +1 @@
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 %>
1
+ <%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: compute_resource.tofu_provider.attributes('nic'), compute_resource: compute_resource } %>
@@ -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 %>
@@ -0,0 +1,4 @@
1
+ <%= text_f f, :username, :value => @image.username || "root", :help_inline => _("The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc") %>
2
+ <%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input (e.g. via cloud-init)?") %>
3
+ <%= password_f f, :password, :help_inline => _("Password to authenticate with - used for SSH finish step.") %>
4
+ <%= image_field(f, :label => _("Image ID"), :help_inline => _("ID of the Image on the Hypervisor")) %>