ForemanOpenTOFU
Foreman 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.
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.
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.
Installation
Usage
Create a openTofu compute resource and set:
- Provider: openTofu
- Opentofu Provider: Select desired hypervisor supported by openTofu plugin
- URL: Hypervisor specific URL
Then add all necessary information to the form.
Provisioning workflow:
-
Create a host in Foreman using the openTOFU based compute resource
-
Foreman passes host parameters to the plugin
-
The plugin renders and executes openTOFU plans
-
openTOFU provisions the infrastructure
-
Foreman continues with OS provisioning and configuration
Provider-specific details (for example Nutanix, Hetzner) are handled entirely through openTOFU scripts.
Create new ProviderType
This Plugin empowers you to add support of a new backend VM- or Cloud-Platform yourself. Follow these simple steps to do so:
Find OpenTofu Provider
Visit the OpenTofu Registry to find a suitable provider supported by OpenTofu. The Registry supplies the necessary Data Sources to read information from the Backend as well as Resources to create/update/destroy resources on the Backend.
Create Template
You may use the UI-Editor in Hosts -> Templates -> Provisioning Templates to create a new Template. Either clone a pre-installed template or create one from scratch. In the latter case be sure to select the correct Template Type: OpenTofu Script template.
Create Parameter Config
To define which Virtual Machine parameters can be set for a new Host a new config file under /config must be added.
Feel free to use either YAML or JSON (be sure to end the filename with .json or .yaml).
The config file defines an array of dicts, where each dict represents a configuration-parameter.
A config-parameter has the following values:
-
name: the OpenTofu Provider Resource Arguments as stated on the OpenTofu Registry -
label: the label shown in the Foreman UI -
type: data-type of the value, supported values:stringnumberbool-
select: requires settingoptions
-
help: Tooltip describing what that value does and what values are allowed -
mandatory:true/falsedefines if omitting the value triggers an error -
options: array of strings representing the possible values -
group: define where the value should be configured-
vm: ones per Host in the 'Virtual Machine' tab, -
disk: for each defined disk/volume in the 'Virtual Machine' tab -
nic: for each defined network-interface on the 'Interfaces' tab
-
A short config file might look like this:
[
{ "name": "memory_size_mib", "type": "number", "group": "vm", "mandatory": false,
"label": "Memory (MB)" },
{ "name": "boot_type", "type": "select", "group": "vm", "mandatory": false,
"label": "Firmware", "options": [ "UEFI", "LEGACY", "SECURE_BOOT" ] },
{ "name": "disk_size_mib", "type": "number", "group": "disk", "mandatory": true,
"label": "Size (MB)" },
{ "name": "model", "type": "select", "group": "nic", "mandatory": true,
"options": [ "VIRTIO", "E1000" ] }
]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).
Dynamic Config Parameter
Sometimes it is necessary to provide a list of possible values that are defined by the backend-service. Curating the 'options'-Array is tedious at best or not possible if multiple instances of the backend service are in use. This can be addressed by specifiying an OpenTofu-Provider's DataSource in the following way:
{
"name": "volume_group", "type": "select", "group": "disk", "mandatory": true, "label": "Volume Group",
"options": {
"data_source": {
"name": "nutanix_volume_groups_v2",
"arguments": {
"filter": "name eq 'volume_group_test'",
"limit": 20
},
"entity": {
"id": "metadata.uuid"
}
},
"output_path_postfix": "volume_groups"
}
}
The GUI requires a list of objects that at least contain a name and an id for each select-option.
The entity section can be used to define a specific value from an object within the list that the DataSource returns.
If the object already has name and id entries, these will automatically used.
In the above example name exists in the object and can be used.
For the id however, a different value must be selected from the object.
This requests the data via OpenTofu in the following construct:
data "nutanix_volume_groups_v2" "all" {
filter = "name eq 'volume_group_test'"
limit = 20
}
output "resources" {
value = [ for e in data.nutanix_volume_groups_v2.all.volume_groups: {
id = e.metadata.uuid
name = e.name
} ]
}Create Provider Type
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/.
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:
ForemanOpentofu::ProviderTypeManager.register('nutanix') do
endAdditional informations about the ProviderType can be set within the register-block:
default_attributes
Define values that should be set as default for attributes. The values do not have to be defined in the config-file. If attributes are also defined in the config-file and therefore set during Host creation, the default_attribute values will be overwritten.
ForemanOpentofu::ProviderTypeManager.register('nutanix') do
@default_attributes = {
'enable_cpu_passthrough' => true,
'num_threads_per_core' => 2,
}
endDevelopment
Dev prerequisites
- You need a openTOFU installed on your machine.
- You need ruby 2.7. You can install it with asdf-vm.
Platform
- Fork this github repo.
- Clone it on your local machine
- Install foreman v2.5+ on your machine:
git clone https://github.com/theforeman/foreman -b develop- Create a Gemfile.local.rb file in foreman/bundler.d/
- Add this line:
gem 'foreman_opentofu', :path => '../../theforeman/foreman_opentofu'- In foreman directory, install dependencies:
gem install bundler
# prerequisites libraries on Ubuntu OS:
bundle install- You can reset and change your admin password if needed:
RAILS_ENV=development bundle exec bin/rake permissions:reset password=changeme- In foreman_openTofu source directory, check code syntax with rubocop and foreman rules:
bundle exec rubocopsafe autocorrect:
bundle exec rubocop -aTemporary ignore offenses:
bundle exec rubocop --auto-gen-config- See deface overrides result:
bundle exec bin/rake deface:get_result['hosts/_compute_detail']- In foreman directory, after you modify foreman_opentofu translations (language, texts in new files, etc) you have to compile it:
Prerequisites: Transifex CLI
bundle exec bin/rake plugin:gettext\[foreman_opentofu\]- In foreman directory, run rails server:
bundle exec bin/rails server- Or you can launch all together:
bundle exec foreman startSee details in foreman plugin development
Contributing
Fork and send a Pull Request or create Issue. Thank you.
Copyright
Copyright (c) 2026 ATIX AG - http://www.atix.de
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.