foreman_opentofu 0.0.4 → 0.0.5

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +90 -55
  3. data/app/assets/javascripts/foreman_opentofu/locale/de_DE/foreman_opentofu.js +136 -0
  4. data/app/assets/javascripts/foreman_opentofu/locale/en/foreman_opentofu.js +80 -2
  5. data/app/controllers/concerns/foreman_opentofu/api/v2/hosts_controller_power_override.rb +22 -0
  6. data/app/controllers/concerns/foreman_opentofu/hosts_controller_power_override.rb +14 -0
  7. data/app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb +39 -15
  8. data/app/models/concerns/foreman_opentofu/vm_command_collection_normalization.rb +42 -0
  9. data/app/models/foreman_opentofu/compute_vm.rb +47 -6
  10. data/app/models/foreman_opentofu/opentofu_vm_commands.rb +6 -4
  11. data/app/models/foreman_opentofu/tf_state.rb +3 -0
  12. data/app/models/foreman_opentofu/tofu.rb +57 -1
  13. data/app/overrides/compute_resources_vms/tofu_remove_new_vm_from_removable_layout.rb +7 -0
  14. data/app/services/foreman_opentofu/app_wrapper.rb +11 -1
  15. data/app/services/foreman_opentofu/authorizer_power_override.rb +15 -0
  16. data/app/services/foreman_opentofu/key_pairs.rb +26 -0
  17. data/app/services/foreman_opentofu/opentofu_executer.rb +54 -4
  18. data/app/services/foreman_opentofu/power_capability.rb +14 -0
  19. data/app/services/foreman_opentofu/provider_type.rb +41 -2
  20. data/app/services/foreman_opentofu/tofu_key_pair.rb +24 -0
  21. data/app/views/compute_resources/form/_tofu.html.erb +3 -3
  22. data/app/views/compute_resources_vms/form/tofu/_base.html.erb +11 -10
  23. data/app/views/foreman_opentofu/compute_resources_vms/_indexed_networks_fields.html.erb +3 -4
  24. data/app/views/foreman_opentofu/compute_resources_vms/_indexed_volumes_fields.html.erb +1 -3
  25. data/app/views/foreman_opentofu/compute_resources_vms/form/_removable_layout.html.erb +5 -0
  26. data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_interfaces_fields.html.erb +2 -1
  27. data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_volumes_fields.html.erb +11 -4
  28. data/app/views/templates/provisioning/hetzner_provision_host.erb +36 -6
  29. data/lib/foreman_opentofu/engine.rb +12 -2
  30. data/lib/foreman_opentofu/provider_types/hetzner.rb +77 -24
  31. data/lib/foreman_opentofu/version.rb +1 -1
  32. data/locale/de_DE/LC_MESSAGES/foreman_opentofu.mo +0 -0
  33. data/locale/de_DE/foreman_opentofu.po +137 -0
  34. data/locale/en/LC_MESSAGES/foreman_opentofu.mo +0 -0
  35. data/locale/en/foreman_opentofu.po +80 -3
  36. data/locale/foreman_opentofu.pot +149 -11
  37. data/test/factories/compute_resources.rb +8 -0
  38. data/test/fixtures/snapshots/foreman_opentofu/base_template_scope_extensions_test/resource_block.txt +8 -0
  39. data/test/lib/foreman_opentofu/concerns/base_template_scope_extensions_test.rb +58 -1
  40. data/test/models/foreman_opentofu/opentofu_vm_commands_test.rb +69 -50
  41. data/test/models/foreman_opentofu/tf_state_test.rb +19 -0
  42. data/test/models/foreman_opentofu/tofu_test.rb +70 -0
  43. data/test/services/app_wrapper_test.rb +29 -0
  44. data/test/services/foreman_opentofu/provider_type_test.rb +96 -6
  45. data/test/services/key_pairs_test.rb +32 -0
  46. data/test/services/opentofu_executer_test.rb +113 -2
  47. data/test/services/tofu_key_pair_test.rb +31 -0
  48. metadata +24 -6
  49. data/locale/gemspec.rb +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3f7d22aa8036489bd3356eff61fb54608d15c1ced729e2a592257e3383213a5
4
- data.tar.gz: 3a7272bc1b5e4a36974b859362d18209d3cbc36293e00c9722a2c6991bdcd0ee
3
+ metadata.gz: 13aa21537aa49784d6b945e5e0169c20509c46e361e951b6db79a17d330559ef
4
+ data.tar.gz: e0f23b3286a236bd161421d8464518a9b47878d61e7ebd03ab41b361cb0b513f
5
5
  SHA512:
6
- metadata.gz: 7efbc5b6168046cac8b716864636ac9f16acd32b229e017ff876d93cde3aecf3ff4e94572e09407ecf2e3de6462592efd46e4bcacf49285c765aceba4e32787b
7
- data.tar.gz: f7b2493f8dcca6913ab2167f9cb529610d0e9c91240d43ea324503b24352fa5b0cb774b8ffee4f1cf76791e38ef9440c90ad0bfdbe8390b3b62fa1d4d09d38f5
6
+ metadata.gz: f1459d8aa7ad4e99b3d43b2725093f7cdc06d7473073432ca8386f46b1d2db4a5ac3acad3ec7c3580830e2d13a11e25354713c9a702a1f3ac669a527a504137f
7
+ data.tar.gz: 4724e84ebc240ef60bdd95c65710fc3156f718628d38ba65570d8305df762be8883e00df13783002927589cdb2e7eed0304b98b414e5ac4e1edf9a48c5ca3d8c
data/README.md CHANGED
@@ -64,7 +64,7 @@ Provisioning workflow:
64
64
 
65
65
  Provider-specific details (for example Nutanix, Hetzner) are handled entirely through OpenTofu scripts.
66
66
 
67
- ### Create new ProviderType
67
+ ### Add support for a new Tofu-Provider
68
68
 
69
69
  This Plugin empowers you to add support of a new backend VM- or Cloud-Platform yourself.
70
70
  Follow these simple steps to do so:
@@ -80,11 +80,39 @@ You may use the UI-Editor in Hosts -> Templates -> Provisioning Templates to cre
80
80
  Either clone a pre-installed template or create one from scratch.
81
81
  In the latter case be sure to select the correct Template Type: OpenTofu Script template.
82
82
 
83
+
84
+ #### Create Provider Type
85
+
86
+ To let the Foreman OpenTofu Plugin know about your new Provider Type, one additional file has to be created in `/lib/foreman_opentofu/provider_types/`.
87
+
88
+ A very simple ProviderType file to add a new Provider named `nutanix` has to be located in `lib/foreman_opentofu/provider_types/nutanix.rb` and might look like this:
89
+
90
+ ```ruby
91
+ ForemanOpentofu::ProviderTypeManager.register('nutanix') do
92
+ end
93
+ ```
94
+
95
+ Additional informations about the ProviderType can be set within the `register`-block:
96
+
97
+ ##### `default_attributes`
98
+
99
+ Define values that should be set as default for attributes.
100
+ The values do not have to be defined in the config-file.
101
+ If attributes are also defined in the config-file and therefore set during Host creation, the default\_attribute values will be overwritten.
102
+
103
+ ```ruby
104
+ ForemanOpentofu::ProviderTypeManager.register('nutanix') do
105
+ @default_attributes = {
106
+ 'enable_cpu_passthrough' => true,
107
+ 'num_threads_per_core' => 2,
108
+ }
109
+ end
110
+ ```
111
+
83
112
  #### Create Parameter Config
84
113
 
85
- To define which Virtual Machine parameters can be set for a new Host a new config file under `/config` must be added.
86
- Feel free to use either YAML or JSON (be sure to end the filename with `.json` or `.yaml`).
87
- The config file defines an array of dicts, where each dict represents a configuration-parameter.
114
+ To define which Virtual Machine parameters can be set for a new Host the `self.provider_attrs` variable must be defined within the `register`-block.
115
+ The `provider_attrs`-variable defines an Array of Dicts/Hashes, where each Dict/Hash represents a configuration-parameter.
88
116
 
89
117
  A config-parameter has the following values:
90
118
 
@@ -98,54 +126,53 @@ A config-parameter has the following values:
98
126
  * `help`: Tooltip describing what that value does and what values are allowed
99
127
  * `mandatory`: `true`/`false` defines if omitting the value triggers an error
100
128
  * `options`: array of strings representing the possible values
129
+ * `default`: default-value should be specified if parameter is mandatory and options is empty
101
130
  * `group`: define where the value should be configured
102
131
  * `vm`: ones per Host in the 'Virtual Machine' tab,
103
132
  * `disk`: for each defined disk/volume in the 'Virtual Machine' tab
104
133
  * `nic`: for each defined network-interface on the 'Interfaces' tab
105
134
 
106
- A short config file might look like this:
107
-
108
- ```json
109
- [
110
- { "name": "memory_size_mib", "type": "number", "group": "vm", "mandatory": false,
111
- "label": "Memory (MB)" },
112
- { "name": "boot_type", "type": "select", "group": "vm", "mandatory": false,
113
- "label": "Firmware", "options": [ "UEFI", "LEGACY", "SECURE_BOOT" ] },
114
- { "name": "disk_size_mib", "type": "number", "group": "disk", "mandatory": true,
115
- "label": "Size (MB)" },
116
- { "name": "model", "type": "select", "group": "nic", "mandatory": true,
117
- "options": [ "VIRTIO", "E1000" ] }
135
+ A short definition might look like this:
136
+
137
+ ```ruby
138
+ self.provider_attrs = [
139
+ { name: 'memory_size_mib', type: 'number', group: 'vm', mandatory: false,
140
+ label: 'Memory (MB)' },
141
+ { name: 'boot_type', type: 'select', group: 'vm', mandatory: false,
142
+ label: 'Firmware', options: [ 'UEFI', 'LEGACY', 'SECURE_BOOT' ] },
143
+ { name: 'disk_size_mib', type: 'number', group: 'disk', mandatory: true,
144
+ label: 'Size (MB)' },
145
+ { name: 'model', type: 'select', group: 'nic', mandatory: true,
146
+ options: [ 'VIRTIO', 'E1000' ] }
118
147
  ]
119
148
  ```
120
149
 
121
- The name of the file must be the same as the provider-type name we set in the next step (e.g. `/config/nutanix.json`).
122
-
123
150
  ##### Dynamic Config Parameter
124
151
 
125
152
  Sometimes it is necessary to provide a list of possible values that are defined by the backend-service.
126
153
  Curating the 'options'-Array is tedious at best or not possible if multiple instances of the backend service are in use.
127
154
  This can be addressed by specifiying an OpenTofu provider's [DataSource](https://opentofu.org/docs/language/data-sources/) in the following way:
128
155
 
129
- ```json
156
+ ```ruby
130
157
  {
131
- "name": "volume_group", "type": "select", "group": "disk", "mandatory": true, "label": "Volume Group",
132
- "options": {
133
- "data_source": {
134
- "name": "nutanix_volume_groups_v2",
135
- "arguments": {
136
- "filter": "name eq 'volume_group_test'",
137
- "limit": 20
158
+ name: 'volume_group', type: 'select', group: 'disk', mandatory: true, label: 'Volume Group',
159
+ options: {
160
+ data_source: {
161
+ name: 'nutanix_volume_groups_v2',
162
+ arguments: {
163
+ filter: 'name eq 'volume_group_test'',
164
+ limit: 20
138
165
  },
139
- "entity": {
140
- "id": "metadata.uuid"
166
+ entity: {
167
+ id: 'metadata.uuid'
141
168
  }
142
169
  },
143
- "output_path_postfix": "volume_groups"
170
+ output_path_postfix: 'volume_groups'
144
171
  }
145
172
  }
146
173
 
147
174
  ```
148
- The GUI requires a list of objects that at least contain a name and an id for each select-option.
175
+ The GUI requires a list of objects that at least contains a name and an id for each select-option.
149
176
  The `entity` section can be used to define a specific value from an object within the list that the DataSource returns.
150
177
  If the object already has `name` and `id` entries, these will automatically used.
151
178
  In the above example `name` exists in the object and can be used.
@@ -170,49 +197,57 @@ output "resources" {
170
197
 
171
198
  Some config parameter names have special meanings.
172
199
  For instance, Image-based Deployment requires binding images available on the backend-service with Operating Systems configured in Foreman.
200
+
201
+ ###### available\_images
202
+
173
203
  To enable Foreman OpenTofu to display the available images, a `select`-parameter with the name `available_images` must be specified.
174
204
  It is recommended to tie this to a data-source available in the OpenTofu provider.
175
205
 
176
- ```json
206
+ ```ruby
177
207
  {
178
- "name": "available_images", "type": "select",
179
- "options": {
180
- "data_source": {
181
- "name": "hcloud_images",
182
- "arguments": { "with_architecture": ["x86"] }
208
+ name: 'available_images', type: 'select',
209
+ options: {
210
+ data_source: {
211
+ name: 'hcloud_images',
212
+ arguments: { with_architecture: ['x86'] }
183
213
  },
184
- "output_path_postfix": "images"
214
+ output_path_postfix: 'images'
185
215
  }
186
216
  }
187
217
  ```
188
218
 
219
+ ###### available\_ssh\_keys
189
220
 
190
- #### Create Provider Type
191
-
192
- To let the Foreman OpenTofu Plugin know about your new Provider Type, one additional file has to be created in `/lib/foreman_opentofu/provider_types/`.
193
-
194
- A very simple ProviderType file to add a new Provider named `nutanix` has to be located in `lib/foreman_opentofu/provider_types/nutanix.rb` and might look like this:
221
+ Cloud-based Providers usually require an SSH key pair to configure image-based hosts.
222
+ This dynamic data-source should provide a list of SSH keys known to the cloud-provider.
195
223
 
196
224
  ```ruby
197
- ForemanOpentofu::ProviderTypeManager.register('nutanix') do
198
- end
225
+ {
226
+ name: 'available_ssh_keys', type: 'select',
227
+ label: 'SSH-Deployment-Keys', options: {
228
+ data_source: {
229
+ name: 'hcloud_ssh_keys',
230
+ },
231
+ entity: {
232
+ fingerprint: 'fingerprint',
233
+ },
234
+ output_path_postfix: 'ssh_keys',
235
+ }
236
+ }
199
237
  ```
200
238
 
201
- Additional informations about the ProviderType can be set within the `register`-block:
239
+ #### Protection against Accidental Resource Recreation
202
240
 
203
- ##### `default_attributes`
241
+ Depending on the settings that are changed for an existing resource (e.g. a Host), OpenTofu may decide that it has to destroy the existing resource and create a new one.
242
+ This is almost never what we want for a managed Foreman Host.
243
+ After editing a host, this Plugin checks OpenTofu's plan for applying the changes.
244
+ If the plan includes a 'delete'-action, it will raise an exception and will not apply the changes.
204
245
 
205
- Define values that should be set as default for attributes.
206
- The values do not have to be defined in the config-file.
207
- If attributes are also defined in the config-file and therefore set during Host creation, the default\_attribute values will be overwritten.
246
+ In case the recreation of a specific resource type is expected behavior, the resource type can be added to an allow-list within the ProviderType definition file.
247
+ The following example allows OpenTofu to destroy and recreate resources of type `hcloud_volume`:
208
248
 
209
249
  ```ruby
210
- ForemanOpentofu::ProviderTypeManager.register('nutanix') do
211
- @default_attributes = {
212
- 'enable_cpu_passthrough' => true,
213
- 'num_threads_per_core' => 2,
214
- }
215
- end
250
+ self.recreate_type_allow_list = ['hcloud_volume']
216
251
  ```
217
252
 
218
253
 
@@ -0,0 +1,136 @@
1
+ locales['foreman_opentofu'] = locales['foreman_opentofu'] || {}; locales['foreman_opentofu']['de_DE'] = {
2
+ "domain": "foreman_opentofu",
3
+ "locale_data": {
4
+ "foreman_opentofu": {
5
+ "": {
6
+ "Project-Id-Version": "foreman_opentofu 0.0.4",
7
+ "Report-Msgid-Bugs-To": "",
8
+ "PO-Revision-Date": "2026-02-16 11:45+0000",
9
+ "Last-Translator": "Markus Bucher <bucher@atix.de>, 2026",
10
+ "Language-Team": "German (Germany) (https://app.transifex.com/foreman/teams/114/de_DE/)",
11
+ "MIME-Version": "1.0",
12
+ "Content-Type": "text/plain; charset=UTF-8",
13
+ "Content-Transfer-Encoding": "8bit",
14
+ "Language": "de_DE",
15
+ "Plural-Forms": "nplurals=2; plural=(n != 1);",
16
+ "lang": "de_DE",
17
+ "domain": "foreman_opentofu",
18
+ "plural_forms": "nplurals=2; plural=(n != 1);"
19
+ },
20
+ "127.0.0.1": [
21
+ "127.0.0.1"
22
+ ],
23
+ "Add Interface": [
24
+ "Interface hinzufügen"
25
+ ],
26
+ "Add Volume": [
27
+ "Volume hinzufügen"
28
+ ],
29
+ "Cache slow calls to OpenTofu Compute resources to speed up page rendering": [
30
+ "Ergebnisse langsamer Abfragen zu OpenTofu zwischenspeichern um das Laden der Seiten zu beschleunigen"
31
+ ],
32
+ "Caching": [
33
+ "Zwischenspeicher"
34
+ ],
35
+ "Common fields": [
36
+ "Allgemeine Werte"
37
+ ],
38
+ "Disabled": [
39
+ "Deaktiviert"
40
+ ],
41
+ "Does this image support user data input (e.g. via cloud-init)?": [
42
+ "Unterstützt dieses Image die Eingabe von Benutzerdaten (z.B. via cloud-init)?"
43
+ ],
44
+ "Enable caching": [
45
+ "Caching aktivieren"
46
+ ],
47
+ "Enabled": [
48
+ "Aktiviert"
49
+ ],
50
+ "ID of the Image on the Hypervisor": [
51
+ "ID des Abbilds auf dem Hypervisor"
52
+ ],
53
+ "Image": [
54
+ "Abbild"
55
+ ],
56
+ "Image \\\"%{image}\\\" needs user data, but \\\"%{os}\\\" is not associated to any provisioning template of the kind user_data. Please associate it with a suitable template or uncheck 'User data' from the image definition.": [
57
+ "Abbild \\\"%{image}\\\" benötigt Benutzerdaten. Jedoch ist \\\"%{os}\\\" keine Bereitstellungsvorlage der Art user_data zugeordnet. Bitte weisen Sie eine geeignete Vorlage zu, oder deaktivieren sie \\\"Benutzerdaten\\\" in der Abbilddefinition."
58
+ ],
59
+ "Image ID": [
60
+ "Abbild ID"
61
+ ],
62
+ "Network interfaces": [
63
+ "Netzwerkinterface"
64
+ ],
65
+ "No networks found.": [
66
+ "Keine Netzwerke gefunden."
67
+ ],
68
+ "Number of Bits for the SSH-Key required to provision hosts on image-based providers.": [
69
+ "Bitanzahl des SSH-Schlüssels der für die abbildbasierte Bereitstellung von Hosts benötigt wird."
70
+ ],
71
+ "Number of seconds a run of OpenTofu command is allowed to report TfState back to the plugin.": [
72
+ "Anzahl der Sekunden in denen ein OpenTofu Befehl einen TfState an das Plugin zurücksenden darf."
73
+ ],
74
+ "OpenTofu": [
75
+ "OpenTofu"
76
+ ],
77
+ "OpenTofu Provider": [
78
+ "OpenTofu Provider"
79
+ ],
80
+ "OpenTofu Script template": [
81
+ "OpenTofu Script Vorlage"
82
+ ],
83
+ "OpenTofu Template": [
84
+ "OpenTofu Vorlage"
85
+ ],
86
+ "Password to authenticate with - used for SSH finish step.": [
87
+ "Passwort mit dem sich authentifiziert wird - benötigt, um den letzten Schritt mit SSH abzuschließen."
88
+ ],
89
+ "Please select an image": [
90
+ "Bitte Abbild auswählen"
91
+ ],
92
+ "Power change operations are not enabled on this host.": [
93
+ "Power-Operationen werden auf diesem Host nicht unterstützt"
94
+ ],
95
+ "SSH-Key Length": [
96
+ "SSH-Schlüssellänge"
97
+ ],
98
+ "TODO: Description of ForemanPluginTemplate.": [
99
+ ""
100
+ ],
101
+ "TfState Token Timeout": [
102
+ "TfState Token Timeout"
103
+ ],
104
+ "The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc": [
105
+ "Der Benutzer für die SSH-Verbindung zur Instanz. Normalerweise cloud-user, ec2-user, ubuntu, root, etc."
106
+ ],
107
+ "URL": [
108
+ "URL"
109
+ ],
110
+ "Unable to find template specified by %s setting": [
111
+ "Konnte Vorlage mit der %s-Einstellung nicht finden"
112
+ ],
113
+ "Unable to render provisioning template": [
114
+ "Bereitstellungsvorlage konnte nicht verarbeitet werden"
115
+ ],
116
+ "VM Config": [
117
+ "VM Konfiguration"
118
+ ],
119
+ "add new network interface": [
120
+ "neues Netzwerkinterface hinzufügen"
121
+ ],
122
+ "add new storage volume": [
123
+ "neues Speichervolume hinzufügen"
124
+ ],
125
+ "e.g. admin": [
126
+ "z.B. admin"
127
+ ],
128
+ "remove network interface": [
129
+ "Netzwerkinterface entfernen"
130
+ ],
131
+ "remove storage volume": [
132
+ "entferne Speichervolume"
133
+ ]
134
+ }
135
+ }
136
+ };
@@ -3,7 +3,7 @@
3
3
  "locale_data": {
4
4
  "foreman_opentofu": {
5
5
  "": {
6
- "Project-Id-Version": "foreman_opentofu 1.0.0",
6
+ "Project-Id-Version": "foreman_opentofu 0.0.4",
7
7
  "Report-Msgid-Bugs-To": "",
8
8
  "PO-Revision-Date": "2026-02-16 18:46+0000",
9
9
  "Last-Translator": "FULL NAME <EMAIL@ADDRESS>",
@@ -20,9 +20,60 @@
20
20
  "127.0.0.1": [
21
21
  ""
22
22
  ],
23
+ "Add Interface": [
24
+ ""
25
+ ],
26
+ "Add Volume": [
27
+ ""
28
+ ],
29
+ "Cache slow calls to OpenTofu Compute resources to speed up page rendering": [
30
+ ""
31
+ ],
32
+ "Caching": [
33
+ ""
34
+ ],
23
35
  "Common fields": [
24
36
  ""
25
37
  ],
38
+ "Disabled": [
39
+ ""
40
+ ],
41
+ "Does this image support user data input (e.g. via cloud-init)?": [
42
+ ""
43
+ ],
44
+ "Enable caching": [
45
+ ""
46
+ ],
47
+ "Enabled": [
48
+ ""
49
+ ],
50
+ "ID of the Image on the Hypervisor": [
51
+ ""
52
+ ],
53
+ "Image": [
54
+ ""
55
+ ],
56
+ "Image \\\"%{image}\\\" needs user data, but \\\"%{os}\\\" is not associated to any provisioning template of the kind user_data. Please associate it with a suitable template or uncheck 'User data' from the image definition.": [
57
+ ""
58
+ ],
59
+ "Image ID": [
60
+ ""
61
+ ],
62
+ "Network interfaces": [
63
+ ""
64
+ ],
65
+ "No networks found.": [
66
+ ""
67
+ ],
68
+ "Number of Bits for the SSH-Key required to provision hosts on image-based providers.": [
69
+ ""
70
+ ],
71
+ "Number of seconds a run of OpenTofu command is allowed to report TfState back to the plugin.": [
72
+ ""
73
+ ],
74
+ "OpenTofu": [
75
+ ""
76
+ ],
26
77
  "OpenTofu Provider": [
27
78
  ""
28
79
  ],
@@ -32,12 +83,27 @@
32
83
  "OpenTofu Template": [
33
84
  ""
34
85
  ],
35
- "Storage": [
86
+ "Password to authenticate with - used for SSH finish step.": [
87
+ ""
88
+ ],
89
+ "Please select an image": [
90
+ ""
91
+ ],
92
+ "Power change operations are not enabled on this host.": [
93
+ ""
94
+ ],
95
+ "SSH-Key Length": [
36
96
  ""
37
97
  ],
38
98
  "TODO: Description of ForemanPluginTemplate.": [
39
99
  ""
40
100
  ],
101
+ "TfState Token Timeout": [
102
+ ""
103
+ ],
104
+ "The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc": [
105
+ ""
106
+ ],
41
107
  "URL": [
42
108
  ""
43
109
  ],
@@ -50,8 +116,20 @@
50
116
  "VM Config": [
51
117
  ""
52
118
  ],
119
+ "add new network interface": [
120
+ ""
121
+ ],
122
+ "add new storage volume": [
123
+ ""
124
+ ],
53
125
  "e.g. admin": [
54
126
  ""
127
+ ],
128
+ "remove network interface": [
129
+ ""
130
+ ],
131
+ "remove storage volume": [
132
+ ""
55
133
  ]
56
134
  }
57
135
  }
@@ -0,0 +1,22 @@
1
+ module ForemanOpentofu
2
+ module Api
3
+ module V2
4
+ module HostsControllerPowerOverride
5
+ def power
6
+ if ForemanOpentofu::PowerCapability.power_change_disabled_for_host?(@host)
7
+ return render_error :custom_error, status: :unprocessable_entity, locals: { message: _('Power change operations are not enabled on this host.') }
8
+ end
9
+
10
+ super
11
+ end
12
+
13
+ def power_status
14
+ result = PowerManager::PowerStatus.safe_power_state(@host, timeout: params[:timeout])
15
+ result[:statusText] = _('Power change operations are not enabled on this host.') if ForemanOpentofu::PowerCapability.power_change_disabled_for_host?(@host)
16
+
17
+ render json: result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module ForemanOpentofu
2
+ module HostsControllerPowerOverride
3
+ def power
4
+ if ForemanOpentofu::PowerCapability.power_change_disabled_for_host?(@host)
5
+ return process_error(
6
+ redirect: :back,
7
+ error_msg: _('Power change operations are not enabled on this host.')
8
+ )
9
+ end
10
+
11
+ super
12
+ end
13
+ end
14
+ end
@@ -48,9 +48,12 @@ module ForemanOpentofu
48
48
  block << block_to_hcl(%w[output resources])
49
49
  block << '{' << "\n"
50
50
  block << " value = [ for e in data.#{resource[:name]}.all.#{resource.dig(:options, 'output_path_postfix')}: {\n"
51
- block << " id = e.#{resource.dig(:options, 'entity', 'id') || 'id'}\n"
52
- block << " name = e.#{resource.dig(:options, 'entity', 'name') || 'name'}\n"
53
- # block << 'obj = e'
51
+ params = resource.dig(:options, 'entity') || {}
52
+ params[:id] = 'id' unless params.key? :id
53
+ params[:name] = 'name' unless params.key? :name
54
+ params.each do |key, value|
55
+ block << " #{key} = e.#{value}\n"
56
+ end
54
57
  block << ' } ]' << "\n"
55
58
  block << '}' << "\n"
56
59
  end
@@ -94,21 +97,16 @@ module ForemanOpentofu
94
97
  end
95
98
 
96
99
  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)
100
+ disk_render_source.each_with_index.filter_map do |disk, index|
101
+ # drop removed disks; tofu will drop them automatically, if they are no longer defined
102
+ next if disk.respond_to?(:[]) && disk['_delete'].to_i == 1
103
+
104
+ rendered_disk(index, disk)
102
105
  end.join("\n")
103
106
  end
104
107
 
105
108
  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|
109
+ nics_from_cr_attrs.each_with_index.map do |nic, index|
112
110
  data = @compute_resource.render_nic(nic, self, index)
113
111
  render_provider_data(data)
114
112
  end.join("\n")
@@ -116,8 +114,34 @@ module ForemanOpentofu
116
114
 
117
115
  private
118
116
 
117
+ def disk_render_source
118
+ disks = @cr_attrs['volumes'].presence || @cr_attrs['volumes_attributes'].presence || @compute_resource.default_volumes
119
+ disks = [disks] if disks.is_a?(Hash)
120
+ disks = [] if disks.blank?
121
+ disks.empty? ? [nil] : disks
122
+ end
123
+
124
+ def rendered_disk(index, disk)
125
+ data = @compute_resource.render_disk(disk, self, index)
126
+ return if data.blank?
127
+
128
+ render_provider_data(data)
129
+ end
130
+
131
+ def nics_from_cr_attrs
132
+ nics = @cr_attrs['interfaces'].presence || @cr_attrs['interfaces_attributes'].presence || @compute_resource.default_interfaces
133
+ normalize_interfaces(nics).map do |nic|
134
+ # drop removed nics; tofu will drop them automatically, if they are no longer defined
135
+ next if nic['_destroy'].to_i == 1
136
+
137
+ nic.respond_to?(:with_indifferent_access) ? nic.with_indifferent_access[:compute_attributes].presence || nic : nic
138
+ end.compact
139
+ end
140
+
119
141
  def render_provider_data(data)
120
- if data.is_a?(Hash) && data[:resource].present?
142
+ if data.is_a?(String)
143
+ data
144
+ elsif data.is_a?(Hash) && data[:resource].present?
121
145
  resource = data[:resource]
122
146
  block_to_hcl(['resource', resource[:type], resource[:name]], resource[:content], depth: 0)
123
147
  elsif data.is_a?(Hash) && data.size == 1 && data.values.first.is_a?(Hash)
@@ -3,6 +3,8 @@ module ForemanOpentofu
3
3
  private
4
4
 
5
5
  def normalize_vm_args_collections!(args)
6
+ args[:image_id] = args[:image] if args.key?(:image)
7
+ normalize_vm_boolean_args!(args)
6
8
  [:volumes, :interfaces].each do |collection|
7
9
  raw = args.delete(:"#{collection}_attributes") || args[collection]
8
10
  next if raw.nil?
@@ -11,6 +13,21 @@ module ForemanOpentofu
11
13
  end
12
14
  end
13
15
 
16
+ def normalize_vm_boolean_args!(args)
17
+ vm_boolean_keys.each do |key|
18
+ next unless args.key?(key)
19
+ args[key] = Foreman::Cast.to_bool(args[key])
20
+ end
21
+ end
22
+
23
+ def vm_boolean_keys
24
+ return [] unless respond_to?(:tofu_provider) && tofu_provider.respond_to?(:attributes)
25
+
26
+ tofu_provider.attributes('vm')
27
+ .select { |attr| attr['type'] == 'bool' }
28
+ .map { |attr| attr['name'].to_sym }
29
+ end
30
+
14
31
  def normalize_collection_input(collection, value)
15
32
  return nested_attributes_for(collection, value) if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
16
33
 
@@ -18,5 +35,30 @@ module ForemanOpentofu
18
35
  entry.respond_to?(:deep_symbolize_keys) ? entry.deep_symbolize_keys : entry
19
36
  end
20
37
  end
38
+
39
+ def prefill_mandatory_attributes(args)
40
+ # FIXME: add volume/nic attributes
41
+ res = {}
42
+ mandatory_attrs = tofu_provider.attributes('vm').select { |attr| attr['mandatory'] && !args.key?(attr['name']) }
43
+ mandatory_attrs.each do |attribute|
44
+ name = attribute['name'].to_sym
45
+ res[name] = determine_default(attribute) || ''
46
+ end
47
+ res
48
+ end
49
+
50
+ def determine_default(attribute)
51
+ return attribute['default'] if attribute.key? 'default'
52
+
53
+ options = attribute['options']
54
+ case options
55
+ when Array then options.first
56
+ when Hash
57
+ if options.dig('data_source', 'name')
58
+ dyn_res = fetch_resource(options.dig('data_source', 'name'), options)
59
+ dyn_res&.first&.fetch('id')
60
+ end
61
+ end
62
+ end
21
63
  end
22
64
  end