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.
- checksums.yaml +4 -4
- data/README.md +90 -55
- data/app/assets/javascripts/foreman_opentofu/locale/de_DE/foreman_opentofu.js +136 -0
- data/app/assets/javascripts/foreman_opentofu/locale/en/foreman_opentofu.js +80 -2
- data/app/controllers/concerns/foreman_opentofu/api/v2/hosts_controller_power_override.rb +22 -0
- data/app/controllers/concerns/foreman_opentofu/hosts_controller_power_override.rb +14 -0
- data/app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb +39 -15
- data/app/models/concerns/foreman_opentofu/vm_command_collection_normalization.rb +42 -0
- data/app/models/foreman_opentofu/compute_vm.rb +47 -6
- data/app/models/foreman_opentofu/opentofu_vm_commands.rb +6 -4
- data/app/models/foreman_opentofu/tf_state.rb +3 -0
- data/app/models/foreman_opentofu/tofu.rb +57 -1
- data/app/overrides/compute_resources_vms/tofu_remove_new_vm_from_removable_layout.rb +7 -0
- data/app/services/foreman_opentofu/app_wrapper.rb +11 -1
- data/app/services/foreman_opentofu/authorizer_power_override.rb +15 -0
- data/app/services/foreman_opentofu/key_pairs.rb +26 -0
- data/app/services/foreman_opentofu/opentofu_executer.rb +54 -4
- data/app/services/foreman_opentofu/power_capability.rb +14 -0
- data/app/services/foreman_opentofu/provider_type.rb +41 -2
- data/app/services/foreman_opentofu/tofu_key_pair.rb +24 -0
- data/app/views/compute_resources/form/_tofu.html.erb +3 -3
- data/app/views/compute_resources_vms/form/tofu/_base.html.erb +11 -10
- data/app/views/foreman_opentofu/compute_resources_vms/_indexed_networks_fields.html.erb +3 -4
- data/app/views/foreman_opentofu/compute_resources_vms/_indexed_volumes_fields.html.erb +1 -3
- data/app/views/foreman_opentofu/compute_resources_vms/form/_removable_layout.html.erb +5 -0
- data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_interfaces_fields.html.erb +2 -1
- data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_volumes_fields.html.erb +11 -4
- data/app/views/templates/provisioning/hetzner_provision_host.erb +36 -6
- data/lib/foreman_opentofu/engine.rb +12 -2
- data/lib/foreman_opentofu/provider_types/hetzner.rb +77 -24
- data/lib/foreman_opentofu/version.rb +1 -1
- data/locale/de_DE/LC_MESSAGES/foreman_opentofu.mo +0 -0
- data/locale/de_DE/foreman_opentofu.po +137 -0
- data/locale/en/LC_MESSAGES/foreman_opentofu.mo +0 -0
- data/locale/en/foreman_opentofu.po +80 -3
- data/locale/foreman_opentofu.pot +149 -11
- data/test/factories/compute_resources.rb +8 -0
- data/test/fixtures/snapshots/foreman_opentofu/base_template_scope_extensions_test/resource_block.txt +8 -0
- data/test/lib/foreman_opentofu/concerns/base_template_scope_extensions_test.rb +58 -1
- data/test/models/foreman_opentofu/opentofu_vm_commands_test.rb +69 -50
- data/test/models/foreman_opentofu/tf_state_test.rb +19 -0
- data/test/models/foreman_opentofu/tofu_test.rb +70 -0
- data/test/services/app_wrapper_test.rb +29 -0
- data/test/services/foreman_opentofu/provider_type_test.rb +96 -6
- data/test/services/key_pairs_test.rb +32 -0
- data/test/services/opentofu_executer_test.rb +113 -2
- data/test/services/tofu_key_pair_test.rb +31 -0
- metadata +24 -6
- data/locale/gemspec.rb +0 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 13aa21537aa49784d6b945e5e0169c20509c46e361e951b6db79a17d330559ef
|
|
4
|
+
data.tar.gz: e0f23b3286a236bd161421d8464518a9b47878d61e7ebd03ab41b361cb0b513f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
###
|
|
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
|
|
86
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
[
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
{
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
|
|
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
|
-
```
|
|
156
|
+
```ruby
|
|
130
157
|
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
166
|
+
entity: {
|
|
167
|
+
id: 'metadata.uuid'
|
|
141
168
|
}
|
|
142
169
|
},
|
|
143
|
-
|
|
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
|
|
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
|
-
```
|
|
206
|
+
```ruby
|
|
177
207
|
{
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
208
|
+
name: 'available_images', type: 'select',
|
|
209
|
+
options: {
|
|
210
|
+
data_source: {
|
|
211
|
+
name: 'hcloud_images',
|
|
212
|
+
arguments: { with_architecture: ['x86'] }
|
|
183
213
|
},
|
|
184
|
-
|
|
214
|
+
output_path_postfix: 'images'
|
|
185
215
|
}
|
|
186
216
|
}
|
|
187
217
|
```
|
|
188
218
|
|
|
219
|
+
###### available\_ssh\_keys
|
|
189
220
|
|
|
190
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
239
|
+
#### Protection against Accidental Resource Recreation
|
|
202
240
|
|
|
203
|
-
|
|
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
|
-
|
|
206
|
-
The
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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?(
|
|
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
|