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.
- checksums.yaml +4 -4
- data/README.md +43 -13
- data/app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb +35 -0
- data/app/models/concerns/foreman_opentofu/vm_command_collection_normalization.rb +22 -0
- data/app/models/foreman_opentofu/opentofu_vm_commands.rb +10 -3
- data/app/models/foreman_opentofu/tofu.rb +13 -5
- data/app/overrides/compute_resources_vms/tofu_indexed_networks_fields.rb +7 -0
- data/app/overrides/compute_resources_vms/tofu_indexed_volumes_fields.rb +8 -0
- data/app/services/foreman_opentofu/app_wrapper.rb +15 -1
- data/app/services/foreman_opentofu/opentofu_executer.rb +5 -5
- data/app/services/foreman_opentofu/provider_type.rb +11 -1
- data/app/views/compute_resources_vms/form/tofu/_base.html.erb +0 -5
- data/app/views/compute_resources_vms/form/tofu/_volume.html.erb +2 -0
- data/app/views/foreman_opentofu/compute_resources_vms/_indexed_networks_fields.html.erb +47 -0
- data/app/views/foreman_opentofu/compute_resources_vms/_indexed_volumes_fields.html.erb +30 -0
- data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_interfaces_fields.html.erb +12 -0
- data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_volumes_fields.html.erb +11 -0
- data/app/views/templates/provisioning/hetzner_provision_host.erb +2 -0
- data/app/views/templates/provisioning/nutanix_provision_default.erb +3 -2
- data/lib/foreman_opentofu/provider_types/hetzner.rb +32 -0
- data/lib/foreman_opentofu/provider_types/nutanix.rb +26 -0
- data/lib/foreman_opentofu/version.rb +1 -1
- data/lib/foreman_opentofu.rb +4 -0
- data/selinux/Makefile +22 -0
- data/selinux/foreman_opentofu.fc +3 -0
- data/selinux/foreman_opentofu.if +0 -0
- data/selinux/foreman_opentofu.te +93 -0
- data/test/lib/foreman_opentofu/concerns/base_template_scope_extensions_test.rb +27 -0
- data/test/models/foreman_opentofu/opentofu_vm_commands_test.rb +27 -0
- data/test/services/app_wrapper_test.rb +13 -1
- data/test/services/foreman_opentofu/provider_type_test.rb +13 -0
- data/test/services/opentofu_executer_test.rb +50 -27
- metadata +13 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3f7d22aa8036489bd3356eff61fb54608d15c1ced729e2a592257e3383213a5
|
|
4
|
+
data.tar.gz: 3a7272bc1b5e4a36974b859362d18209d3cbc36293e00c9722a2c6991bdcd0ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7efbc5b6168046cac8b716864636ac9f16acd32b229e017ff876d93cde3aecf3ff4e94572e09407ecf2e3de6462592efd46e4bcacf49285c765aceba4e32787b
|
|
7
|
+
data.tar.gz: f7b2493f8dcca6913ab2167f9cb529610d0e9c91240d43ea324503b24352fa5b0cb774b8ffee4f1cf76791e38ef9440c90ad0bfdbe8390b3b62fa1d4d09d38f5
|
data/README.md
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
1
|
[](https://github.com/ATIX-AG/foreman_opentofu/actions/workflows/ruby.yml)
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Foreman OpenTofu
|
|
4
4
|
|
|
5
|
-
[Foreman](http://theforeman.org/) plugin that adds that adds a generic
|
|
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
|
|
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
|
|
16
|
-
* Provider:
|
|
17
|
-
*
|
|
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
|
|
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
|
|
59
|
+
* The plugin renders and executes OpenTofu plans
|
|
30
60
|
|
|
31
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
93
|
+
def new_interface(attr = {})
|
|
94
|
+
OpenStruct.new(attr)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
def new_volume
|
|
98
|
-
{
|
|
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,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
|
|
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
|
|
25
|
-
password: @compute_resource
|
|
26
|
-
endpoint: @compute_resource
|
|
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,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 %>
|
|
@@ -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
|
data/lib/foreman_opentofu.rb
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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.
|
|
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
|