foreman_opentofu 0.0.1 → 0.0.2
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 +85 -0
- data/app/assets/javascripts/foreman_opentofu/locale/en/foreman_opentofu.js +58 -0
- data/app/controllers/api/v2/tf_states_controller.rb +43 -5
- data/app/controllers/concerns/foreman_opentofu/controller/parameters/compute_resource.rb +1 -19
- data/app/models/foreman_opentofu/tofu.rb +15 -1
- data/app/models/foreman_opentofu/token.rb +16 -0
- data/app/services/foreman_opentofu/opentofu_executer.rb +26 -11
- data/app/services/foreman_opentofu/provider_type.rb +1 -1
- data/app/views/compute_resources/form/_tofu.html.erb +2 -1
- data/app/views/compute_resources/show/_tofu.html.erb +4 -0
- data/app/views/templates/provisioning/{nutanix_provision_host.erb → nutanix_provision_default.erb} +8 -11
- data/app/views/templates/provisioning/{ovirt_provision_host.erb → ovirt_provision_default.erb} +9 -12
- data/db/migrate/20260127160904_add_table_token.foreman_opentofu.rb +10 -0
- data/db/seeds.d/71_provisioning_templates.rb +9 -1
- data/lib/foreman_opentofu/engine.rb +8 -24
- data/lib/foreman_opentofu/version.rb +1 -1
- data/lib/tasks/foreman_opentofu_tasks.rake +2 -2
- data/locale/en/LC_MESSAGES/foreman_opentofu.mo +0 -0
- data/locale/en/foreman_opentofu.po +44 -9
- data/locale/foreman_opentofu.pot +56 -8
- data/test/controllers/api/v2/tf_states_controller_test.rb +33 -8
- data/test/factories/token.rb +10 -0
- data/test/models/foreman_opentofu/tofu_test.rb +46 -0
- data/test/models/foreman_opentofu/token_test.rb +36 -0
- data/test/services/foreman_opentofu/provider_type_test.rb +68 -0
- data/test/services/opentofu_executer_test.rb +1 -1
- metadata +17 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6bff811eebb374eb3983de89c83faf991c8021e416c677bbfda90837cbeeb5a
|
|
4
|
+
data.tar.gz: 8741e8b977ba0a078c13ac4642a790e11646f719358f22b69c299368d698a8f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67e87113d04444061daa977fca5721d26122035fd821f86284667eb5ad247c7a2002d5bd1d451ec0d4cea84ade9f530eb1dcffa3afb6b931b7a0b78f1f57c5ca
|
|
7
|
+
data.tar.gz: d2627a46149453ae41fa6f5463bd792501ee51bc0760693a6bafdd3f0da7357ab9e8b8093c98149b5c4b4c7b2e8e03698c8d97996bb1ec3d1ac0990bc719a455
|
data/README.md
CHANGED
|
@@ -34,6 +34,91 @@ Provisioning workflow:
|
|
|
34
34
|
|
|
35
35
|
Provider-specific details (for example Nutanix, Hetzner) are handled entirely through openTOFU scripts.
|
|
36
36
|
|
|
37
|
+
### Create new ProviderType
|
|
38
|
+
|
|
39
|
+
This Plugin empowers you to add support of a new backend VM- or Cloud-Platform yourself.
|
|
40
|
+
Follow these simple steps to do so:
|
|
41
|
+
|
|
42
|
+
#### Find OpenTofu Provider
|
|
43
|
+
|
|
44
|
+
Visit the OpenTofu Registry to find a suitable [provider supported by OpenTofu](https://search.opentofu.org/providers).
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
#### Create Template
|
|
48
|
+
|
|
49
|
+
You may use the UI-Editor in Hosts -> Templates -> Provisioning Templates to create a new Template.
|
|
50
|
+
Either clone a pre-installed template or create one from scratch.
|
|
51
|
+
In the latter case be sure to select the correct Template Type: OpenTofu Script template.
|
|
52
|
+
|
|
53
|
+
#### Create Parameter Config
|
|
54
|
+
|
|
55
|
+
To define which Virtual Machine parameters can be set for a new Host a new config file under `/config` must be added.
|
|
56
|
+
Feel free to use either YAML or JSON (be sure to end the filename with `.json` or `.yaml`).
|
|
57
|
+
The config file defines an array of dicts, where each dict represents a configuration-parameter.
|
|
58
|
+
|
|
59
|
+
A config-parameter has the following values:
|
|
60
|
+
|
|
61
|
+
* `name`: the OpenTofu Provider Resource Arguments as stated on the OpenTofu Registry
|
|
62
|
+
* `label`: the label shown in the Foreman UI
|
|
63
|
+
* `type`: data-type of the value, supported values:
|
|
64
|
+
* `string`
|
|
65
|
+
* `number`
|
|
66
|
+
* `bool`
|
|
67
|
+
* `select`: requires setting `options`
|
|
68
|
+
* `help`: Tooltip describing what that value does and what values are allowed
|
|
69
|
+
* `mandatory`: `true`/`false` defines if omitting the value triggers an error
|
|
70
|
+
* `options`: array of strings representing the possible values
|
|
71
|
+
* `group`: define where the value should be configured
|
|
72
|
+
* `vm`: ones per Host in the 'Virtual Machine' tab,
|
|
73
|
+
* `disk`: for each defined disk/volume in the 'Virtual Machine' tab
|
|
74
|
+
* `nic`: for each defined network-interface on the 'Interfaces' tab
|
|
75
|
+
|
|
76
|
+
A short config file might look like this:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
[
|
|
80
|
+
{ "name": "memory_size_mib", "type": "number", "group": "vm", "mandatory": false,
|
|
81
|
+
"label": "Memory (MB)" },
|
|
82
|
+
{ "name": "boot_type", "type": "select", "group": "vm", "mandatory": false,
|
|
83
|
+
"label": "Firmware", "options": [ "UEFI", "LEGACY", "SECURE_BOOT" ] },
|
|
84
|
+
{ "name": "disk_size_mib", "type": "number", "group": "disk", "mandatory": true,
|
|
85
|
+
"label": "Size (MB)" },
|
|
86
|
+
{ "name": "model", "type": "select", "group": "nic", "mandatory": true,
|
|
87
|
+
"options": [ "VIRTIO", "E1000" ] }
|
|
88
|
+
]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
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`).
|
|
92
|
+
|
|
93
|
+
#### Create Provider Type
|
|
94
|
+
|
|
95
|
+
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/`.
|
|
96
|
+
|
|
97
|
+
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:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
ForemanOpentofu::ProviderTypeManager.register('nutanix') do
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Additional informations about the ProviderType can be set within the `register`-block:
|
|
105
|
+
|
|
106
|
+
##### `default_attributes`
|
|
107
|
+
|
|
108
|
+
Define values that should be set as default for attributes.
|
|
109
|
+
The values do not have to be defined in the config-file.
|
|
110
|
+
If attributes are also defined in the config-file and therefore set during Host creation, the default\_attribute values will be overwritten.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
ForemanOpentofu::ProviderTypeManager.register('nutanix') do
|
|
114
|
+
@default_attributes = {
|
|
115
|
+
'enable_cpu_passthrough' => true,
|
|
116
|
+
'num_threads_per_core' => 2,
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
|
|
37
122
|
## Development
|
|
38
123
|
|
|
39
124
|
### Dev prerequisites
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
locales['foreman_opentofu'] = locales['foreman_opentofu'] || {}; locales['foreman_opentofu']['en'] = {
|
|
2
|
+
"domain": "foreman_opentofu",
|
|
3
|
+
"locale_data": {
|
|
4
|
+
"foreman_opentofu": {
|
|
5
|
+
"": {
|
|
6
|
+
"Project-Id-Version": "foreman_opentofu 1.0.0",
|
|
7
|
+
"Report-Msgid-Bugs-To": "",
|
|
8
|
+
"PO-Revision-Date": "2026-02-16 18:46+0000",
|
|
9
|
+
"Last-Translator": "FULL NAME <EMAIL@ADDRESS>",
|
|
10
|
+
"Language-Team": "LANGUAGE <LL@li.org>",
|
|
11
|
+
"Language": "",
|
|
12
|
+
"MIME-Version": "1.0",
|
|
13
|
+
"Content-Type": "text/plain; charset=UTF-8",
|
|
14
|
+
"Content-Transfer-Encoding": "8bit",
|
|
15
|
+
"Plural-Forms": "nplurals=INTEGER; plural=EXPRESSION;",
|
|
16
|
+
"lang": "en",
|
|
17
|
+
"domain": "foreman_opentofu",
|
|
18
|
+
"plural_forms": "nplurals=INTEGER; plural=EXPRESSION;"
|
|
19
|
+
},
|
|
20
|
+
"127.0.0.1": [
|
|
21
|
+
""
|
|
22
|
+
],
|
|
23
|
+
"Common fields": [
|
|
24
|
+
""
|
|
25
|
+
],
|
|
26
|
+
"OpenTofu Provider": [
|
|
27
|
+
""
|
|
28
|
+
],
|
|
29
|
+
"OpenTofu Script template": [
|
|
30
|
+
""
|
|
31
|
+
],
|
|
32
|
+
"OpenTofu Template": [
|
|
33
|
+
""
|
|
34
|
+
],
|
|
35
|
+
"Storage": [
|
|
36
|
+
""
|
|
37
|
+
],
|
|
38
|
+
"TODO: Description of ForemanPluginTemplate.": [
|
|
39
|
+
""
|
|
40
|
+
],
|
|
41
|
+
"URL": [
|
|
42
|
+
""
|
|
43
|
+
],
|
|
44
|
+
"Unable to find template specified by %s setting": [
|
|
45
|
+
""
|
|
46
|
+
],
|
|
47
|
+
"Unable to render provisioning template": [
|
|
48
|
+
""
|
|
49
|
+
],
|
|
50
|
+
"VM Config": [
|
|
51
|
+
""
|
|
52
|
+
],
|
|
53
|
+
"e.g. admin": [
|
|
54
|
+
""
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -3,14 +3,25 @@ module Api
|
|
|
3
3
|
class TfStatesController < ::Api::V2::BaseController
|
|
4
4
|
include ::Api::Version2
|
|
5
5
|
|
|
6
|
+
# TODO: verify this
|
|
7
|
+
# We don't require any of these methods for provisioning
|
|
8
|
+
# skip_before_action :require_login, :check_user_enabled, :session_expiry, :update_activity_time, :set_taxonomy, :authorize, unless: -> { preview? }
|
|
9
|
+
skip_before_action :set_taxonomy
|
|
10
|
+
|
|
11
|
+
# Allow HTTP POST methods without CSRF
|
|
12
|
+
skip_before_action :verify_authenticity_token
|
|
13
|
+
|
|
14
|
+
# overwrite authorize with the local token-based authorization
|
|
15
|
+
before_action :authorize, except: [:destroy]
|
|
16
|
+
|
|
6
17
|
resource_description do
|
|
7
18
|
api_version 'v2'
|
|
8
19
|
api_base_url '/foreman_opentofu/api'
|
|
9
20
|
end
|
|
10
21
|
|
|
11
|
-
skip_before_action :verify_authenticity_token
|
|
12
22
|
def show
|
|
13
23
|
state = ForemanOpentofu::TfState.find_by(name: params[:name])
|
|
24
|
+
|
|
14
25
|
if state
|
|
15
26
|
render plain: state.state, content_type: 'application/json'
|
|
16
27
|
else
|
|
@@ -19,8 +30,6 @@ module Api
|
|
|
19
30
|
end
|
|
20
31
|
|
|
21
32
|
def create
|
|
22
|
-
state = ForemanOpentofu::TfState.find_or_create_by(name: params[:name])
|
|
23
|
-
|
|
24
33
|
raw_state = request.body.read
|
|
25
34
|
if raw_state.blank?
|
|
26
35
|
render plain: 'Missing state body', status: :unprocessable_entity
|
|
@@ -29,6 +38,7 @@ module Api
|
|
|
29
38
|
begin
|
|
30
39
|
JSON.parse(raw_state)
|
|
31
40
|
|
|
41
|
+
state = ForemanOpentofu::TfState.find_or_initialize_by(name: params[:name])
|
|
32
42
|
state.state = raw_state
|
|
33
43
|
state.save!
|
|
34
44
|
render plain: '', status: :ok
|
|
@@ -39,14 +49,42 @@ module Api
|
|
|
39
49
|
end
|
|
40
50
|
|
|
41
51
|
def destroy
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
# TODO: at the moment we want to get 200 OK, if the TfState does not exist.
|
|
53
|
+
# normally, this would fail with 401, because of no valid token.
|
|
54
|
+
# Needs re-evaluation, if this is a security risk.
|
|
55
|
+
state = ForemanOpentofu::TfState.where(name: params[:name])
|
|
56
|
+
if state.any?
|
|
57
|
+
authorize
|
|
58
|
+
state.first.destroy
|
|
59
|
+
end
|
|
60
|
+
|
|
44
61
|
render plain: '', status: :ok
|
|
45
62
|
end
|
|
46
63
|
|
|
47
64
|
def resource_class
|
|
48
65
|
@resource_class ||= ForemanOpentofu::TfState
|
|
49
66
|
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def authorize
|
|
71
|
+
authenticate_or_request_with_http_token do |token, _options|
|
|
72
|
+
token = ForemanOpentofu::Token.find_by(name: params[:name], token: token)
|
|
73
|
+
unless token
|
|
74
|
+
Rails.logger.warn('TfState-Auth-Token not found')
|
|
75
|
+
render_error('unauthorized', status: :unauthorized)
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if token.expired?
|
|
80
|
+
Rails.logger.warn 'TfState token expired, if this keeps happening increase the validity of the token'
|
|
81
|
+
render_error('unauthorized', status: :unauthorized)
|
|
82
|
+
return false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
50
88
|
end
|
|
51
89
|
end
|
|
52
90
|
end
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Copyright 2018 Tristan Robert
|
|
4
|
-
|
|
5
|
-
# This file is part of ForemanFogProxmox.
|
|
6
|
-
|
|
7
|
-
# ForemanFogProxmox is free software: you can redistribute it and/or modify
|
|
8
|
-
# it under the terms of the GNU General Public License as published by
|
|
9
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
-
# (at your option) any later version.
|
|
11
|
-
|
|
12
|
-
# ForemanFogProxmox is distributed in the hope that it will be useful,
|
|
13
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
-
# GNU General Public License for more details.
|
|
16
|
-
|
|
17
|
-
# You should have received a copy of the GNU General Public License
|
|
18
|
-
# along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
-
|
|
20
1
|
module ForemanOpentofu
|
|
21
2
|
module Controller
|
|
22
3
|
module Parameters
|
|
@@ -28,6 +9,7 @@ module ForemanOpentofu
|
|
|
28
9
|
super.tap do |filter|
|
|
29
10
|
filter.permit :endpoint,
|
|
30
11
|
:opentofu_provider,
|
|
12
|
+
:opentofu_template_id,
|
|
31
13
|
:username
|
|
32
14
|
end
|
|
33
15
|
end
|
|
@@ -42,6 +42,20 @@ module ForemanOpentofu
|
|
|
42
42
|
attrs[:opentofu_provider] = value
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
def opentofu_template
|
|
46
|
+
return ProvisioningTemplate.find(attrs[:opentofu_template_id]) if attrs.key? :opentofu_template_id
|
|
47
|
+
# or default-template for this opentofu_provider
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def opentofu_template_id
|
|
52
|
+
attrs[:opentofu_template_id]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def opentofu_template_id=(value)
|
|
56
|
+
attrs[:opentofu_template_id] = value
|
|
57
|
+
end
|
|
58
|
+
|
|
45
59
|
def self.provider_friendly_name
|
|
46
60
|
'OpenTofu'
|
|
47
61
|
end
|
|
@@ -55,7 +69,7 @@ module ForemanOpentofu
|
|
|
55
69
|
end
|
|
56
70
|
|
|
57
71
|
def default_attributes
|
|
58
|
-
{}
|
|
72
|
+
tofu_provider&.default_attributes || {}
|
|
59
73
|
end
|
|
60
74
|
|
|
61
75
|
def supports_update?
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module ForemanOpentofu
|
|
2
|
+
class Token < ApplicationRecord
|
|
3
|
+
validates :name, presence: true, uniqueness: true
|
|
4
|
+
|
|
5
|
+
def expired?
|
|
6
|
+
!expire.respond_to?(:<=) || (expire <= Time.current)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def generate
|
|
10
|
+
self.expire = Time.current + Setting[:tfstate_token_timeout]
|
|
11
|
+
self.token = SecureRandom.alphanumeric(255)
|
|
12
|
+
save!
|
|
13
|
+
token
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -5,20 +5,42 @@ module ForemanOpentofu
|
|
|
5
5
|
def initialize(*args)
|
|
6
6
|
@compute_resource = args[0]
|
|
7
7
|
@cr_attrs = args[1] || {}
|
|
8
|
+
@host_name = @cr_attrs['name'] || 'test'
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def run(mode = 'create')
|
|
11
12
|
Dir.mktmpdir('opentofu_') do |dir|
|
|
12
13
|
tofu = AppWrapper.new(dir)
|
|
13
14
|
@use_backend = %w[create destroy output].include?(mode)
|
|
15
|
+
@token = create_token(@host_name) if @use_backend
|
|
14
16
|
tofu.main_configuration = render_template
|
|
15
17
|
tofu.init
|
|
16
18
|
run_mode(tofu, mode)
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
22
|
+
# creates a new authentication token for the TfState API-controller
|
|
23
|
+
# needed for tofu command to send it's state-file to the database.
|
|
24
|
+
# returns the created token
|
|
25
|
+
def create_token(host_name)
|
|
26
|
+
new_token = nil
|
|
27
|
+
# This construct makes sure the token is created outside of the current transaction
|
|
28
|
+
# which is necessary for the API-controller to check the token, while the current transaction still runs
|
|
29
|
+
# see https://stackoverflow.com/a/11675647
|
|
30
|
+
Thread.new do
|
|
31
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
|
32
|
+
token = ForemanOpentofu::Token.find_or_create_by(name: host_name)
|
|
33
|
+
new_token = if token.expired?
|
|
34
|
+
token.generate
|
|
35
|
+
else
|
|
36
|
+
token.token
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end.join
|
|
40
|
+
new_token
|
|
41
|
+
end
|
|
42
|
+
|
|
20
43
|
def run_mode(tofu, mode = 'new')
|
|
21
|
-
@use_backend = true
|
|
22
44
|
case mode
|
|
23
45
|
when 'new'
|
|
24
46
|
tofu.plan
|
|
@@ -49,6 +71,8 @@ module ForemanOpentofu
|
|
|
49
71
|
scope.instance_variable_set(:@compute_resource, @compute_resource)
|
|
50
72
|
scope.instance_variable_set(:@cr_attrs, @cr_attrs) if @cr_attrs
|
|
51
73
|
scope.instance_variable_set(:@use_backend, @use_backend)
|
|
74
|
+
scope.instance_variable_set(:@token, @token) if @use_backend
|
|
75
|
+
scope.instance_variable_set(:@host_name, @host_name)
|
|
52
76
|
rendered_template = Foreman::Renderer::UnsafeModeRenderer.render(source, scope)
|
|
53
77
|
raise ::Foreman::Exception, N_('Unable to render provisioning template') unless rendered_template
|
|
54
78
|
|
|
@@ -56,16 +80,7 @@ module ForemanOpentofu
|
|
|
56
80
|
end
|
|
57
81
|
|
|
58
82
|
def provision_template
|
|
59
|
-
name =
|
|
60
|
-
provider = @compute_resource.opentofu_provider
|
|
61
|
-
case provider
|
|
62
|
-
when 'nutanix'
|
|
63
|
-
name = Setting[:provision_nutanix_host_template]
|
|
64
|
-
when 'ovirt'
|
|
65
|
-
name = Setting[:provision_ovirt_host_template]
|
|
66
|
-
when 'vsphere'
|
|
67
|
-
name = Setting[:provision_vsphere_host_template]
|
|
68
|
-
end
|
|
83
|
+
name = @compute_resource.opentofu_template.name
|
|
69
84
|
template = ProvisioningTemplate.unscoped.find_by(name: name)
|
|
70
85
|
unless template
|
|
71
86
|
raise ::Foreman::Exception.new(N_('Unable to find template specified by %s setting'),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<%= field_set_tag _("Common fields"), :id => "compute_ressource_common_field_set" do %>
|
|
2
2
|
<%= select_f f, :opentofu_provider, ForemanOpentofu::ProviderTypeManager.enabled_provider_types, :id, :name %>
|
|
3
|
+
<%= select_f f, :opentofu_template_id, ProvisioningTemplate.of_kind('opentofu_script'), :id, :name %>
|
|
3
4
|
<%= text_f f, :url, :help_block => _("127.0.0.1") %>
|
|
4
5
|
<%= text_f f, :user , :help_block => _("e.g. admin") %>
|
|
5
|
-
<%= password_f f, :password
|
|
6
|
+
<%= password_f f, :password, :unset => unset_password? %>
|
|
6
7
|
<% end %>
|
|
7
8
|
<div class="col-md-offset-2">
|
|
8
9
|
<%= test_connection_button_f(f, (true)) %>
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<td><%= _('OpenTofu Provider') %></td>
|
|
3
3
|
<td><%= @compute_resource.opentofu_provider %></td>
|
|
4
4
|
</tr>
|
|
5
|
+
<tr>
|
|
6
|
+
<td><%= _('OpenTofu Template') %></td>
|
|
7
|
+
<td><%= link_to @compute_resource.opentofu_template, edit_provisioning_template_path(@compute_resource.opentofu_template) %></td>
|
|
8
|
+
</tr>
|
|
5
9
|
<tr>
|
|
6
10
|
<td><%= _('URL') %></td>
|
|
7
11
|
<td><%= @compute_resource.url %></td>
|
data/app/views/templates/provisioning/{nutanix_provision_host.erb → nutanix_provision_default.erb}
RENAMED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
1
|
<%#
|
|
3
|
-
kind:
|
|
4
|
-
name: Nutanix provision
|
|
2
|
+
kind: opentofu_script
|
|
3
|
+
name: Nutanix provision default
|
|
5
4
|
model: ProvisioningTemplate
|
|
6
5
|
description: |
|
|
7
|
-
|
|
6
|
+
Nutanix OpenTofu script to create vm resource
|
|
8
7
|
-%>
|
|
9
|
-
<%-
|
|
10
|
-
host_name = @cr_attrs['name'] || 'test'
|
|
11
|
-
%>
|
|
12
8
|
|
|
13
9
|
terraform {
|
|
14
10
|
required_providers {
|
|
@@ -18,9 +14,10 @@ terraform {
|
|
|
18
14
|
}
|
|
19
15
|
<% if @use_backend %>
|
|
20
16
|
backend "http" {
|
|
21
|
-
address = "<%= Setting[:foreman_url] %>/api/v2/tf_states/<%= host_name %>"
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
address = "<%= Setting[:foreman_url] %>/api/v2/tf_states/<%= @host_name %>"
|
|
18
|
+
headers = {
|
|
19
|
+
Authorization = "Token <%= @token %>"
|
|
20
|
+
}
|
|
24
21
|
}
|
|
25
22
|
<% end %>
|
|
26
23
|
}
|
|
@@ -36,7 +33,7 @@ data "nutanix_clusters" "clusters" {}
|
|
|
36
33
|
|
|
37
34
|
resource "nutanix_virtual_machine" "vm" {
|
|
38
35
|
cluster_uuid = data.nutanix_clusters.clusters.entities.0.metadata.uuid
|
|
39
|
-
name = "<%= host_name %>"
|
|
36
|
+
name = "<%= @host_name %>"
|
|
40
37
|
|
|
41
38
|
<%= vm_attributes(['name', 'cluster_uuid']) %>
|
|
42
39
|
|
data/app/views/templates/provisioning/{ovirt_provision_host.erb → ovirt_provision_default.erb}
RENAMED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
1
|
<%#
|
|
3
|
-
kind:
|
|
4
|
-
name:
|
|
2
|
+
kind: opentofu_script
|
|
3
|
+
name: oVirt provision default
|
|
5
4
|
model: ProvisioningTemplate
|
|
6
5
|
description: |
|
|
7
|
-
|
|
6
|
+
oVirt OpenTofu script to create vm resource
|
|
8
7
|
-%>
|
|
9
|
-
<%-
|
|
10
|
-
host_name = @cr_attrs['name'] || 'test'
|
|
11
|
-
%>
|
|
12
8
|
|
|
13
9
|
terraform {
|
|
14
10
|
required_providers {
|
|
@@ -18,9 +14,10 @@ terraform {
|
|
|
18
14
|
}
|
|
19
15
|
<% if @use_backend %>
|
|
20
16
|
backend "http" {
|
|
21
|
-
address = "<%= Setting[:foreman_url] %>/api/v2/tf_states/<%= host_name %>"
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
address = "<%= Setting[:foreman_url] %>/api/v2/tf_states/<%= @host_name %>"
|
|
18
|
+
headers = {
|
|
19
|
+
Authorization = "Token <%= @token %>"
|
|
20
|
+
}
|
|
24
21
|
}
|
|
25
22
|
<% end %>
|
|
26
23
|
}
|
|
@@ -36,7 +33,7 @@ data "ovirt_blank_template" "blank" {
|
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
resource "ovirt_vm" "vm" {
|
|
39
|
-
name = "<%= host_name %>"
|
|
36
|
+
name = "<%= @host_name %>"
|
|
40
37
|
cluster_id = "2ad616ea-4b66-11f0-964d-901b0ecbd584"
|
|
41
38
|
template_id = data.ovirt_blank_template.blank.id
|
|
42
39
|
cpu_cores = <%= @cr_attrs['cpu_cores'] || 1 %>
|
|
@@ -47,7 +44,7 @@ resource "ovirt_vm" "vm" {
|
|
|
47
44
|
memory_ballooning = <%= @cr_attrs['memory_ballooning'] || false %>
|
|
48
45
|
}
|
|
49
46
|
resource "ovirt_vm" "vm" {
|
|
50
|
-
name = "<%= host_name %>"
|
|
47
|
+
name = "<%= @host_name %>"
|
|
51
48
|
cluster_id = "2ad616ea-4b66-11f0-964d-901b0ecbd584"
|
|
52
49
|
template_id = data.ovirt_blank_template.blank.id
|
|
53
50
|
<%= "cpu_cores = #{@cr_attrs['cpu_cores'] || 1}" %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class AddTableToken < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :foreman_opentofu_tokens do |t|
|
|
4
|
+
t.column :name, :string, limit: 255, index: true
|
|
5
|
+
t.column :token, :string, limit: 512, index: true
|
|
6
|
+
t.column :expire, :datetime
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
User.as_anonymous_admin do
|
|
4
4
|
ProvisioningTemplate.without_auditing do
|
|
5
|
+
# Create TemplateKind
|
|
6
|
+
Foreman::Plugin.find(:foreman_opentofu).get_template_labels.keys.map(&:to_sym).each do |type|
|
|
7
|
+
kind ||= TemplateKind.unscoped.find_or_create_by(name: type)
|
|
8
|
+
# kind.description = TemplateKind.default_template_descriptions[kind.name]
|
|
9
|
+
kind.save!
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
SeedHelper.import_templates(
|
|
6
|
-
Dir[File.join("#{ForemanOpentofu::Engine.root}/app/views/templates/provisioning/**/*.erb")]
|
|
13
|
+
Dir[File.join("#{ForemanOpentofu::Engine.root}/app/views/templates/provisioning/**/*.erb")],
|
|
14
|
+
'ForemanOpentofu'
|
|
7
15
|
)
|
|
8
16
|
end
|
|
9
17
|
end
|
|
@@ -22,32 +22,16 @@ module ForemanOpentofu
|
|
|
22
22
|
# Add Global files for extending foreman-core components and routes
|
|
23
23
|
# Register Nutanix compute resource in foreman
|
|
24
24
|
compute_resource ForemanOpentofu::Tofu
|
|
25
|
+
|
|
26
|
+
template_labels 'opentofu_script' => N_('OpenTofu Script template')
|
|
27
|
+
|
|
25
28
|
settings do
|
|
26
29
|
category :opentofu, N_('Opentofu') do
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
setting 'provision_nutanix_host_template',
|
|
34
|
-
type: :string,
|
|
35
|
-
collection: templates,
|
|
36
|
-
default: 'Nutanix provision - host',
|
|
37
|
-
full_name: N_('Nutanix Host provision template'),
|
|
38
|
-
description: N_('Opentofu script template to use for Nutanix based host provisioning')
|
|
39
|
-
setting 'provision_ovirt_host_template',
|
|
40
|
-
type: :string,
|
|
41
|
-
collection: templates,
|
|
42
|
-
default: 'Ovirt provision - host',
|
|
43
|
-
full_name: N_('Ovirt Host provision template'),
|
|
44
|
-
description: N_('Opentofu script template to use for Ovirt based host provisioning')
|
|
45
|
-
setting 'provision_vsphere_host_template',
|
|
46
|
-
type: :string,
|
|
47
|
-
collection: templates,
|
|
48
|
-
default: 'Vsphere provision - host',
|
|
49
|
-
full_name: N_('Vsphere Host provision template'),
|
|
50
|
-
description: N_('Opentofu script template to use for Vsphere based host provisioning')
|
|
30
|
+
setting 'tfstate_token_timeout',
|
|
31
|
+
type: :integer,
|
|
32
|
+
default: 600,
|
|
33
|
+
full_name: N_('TfState Token Timeout'),
|
|
34
|
+
description: N_('Number of seconds a run of Opentofu command is allowed to report tf-state back to the plugin.')
|
|
51
35
|
end
|
|
52
36
|
end
|
|
53
37
|
end
|
|
Binary file
|
|
@@ -1,19 +1,54 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# This file is distributed under the same license as foreman_opentofu.
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the foreman_opentofu package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
4
5
|
#
|
|
5
6
|
#, fuzzy
|
|
6
7
|
msgid ""
|
|
7
8
|
msgstr ""
|
|
8
|
-
"Project-Id-Version:
|
|
9
|
+
"Project-Id-Version: foreman_opentofu 1.0.0\n"
|
|
9
10
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"Language-Team: Foreman Team <foreman-dev@googlegroups.com>\n"
|
|
11
|
+
"PO-Revision-Date: 2026-02-16 18:46+0000\n"
|
|
12
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
13
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
14
14
|
"Language: \n"
|
|
15
15
|
"MIME-Version: 1.0\n"
|
|
16
16
|
"Content-Type: text/plain; charset=UTF-8\n"
|
|
17
17
|
"Content-Transfer-Encoding: 8bit\n"
|
|
18
|
-
"Plural-Forms: nplurals=
|
|
18
|
+
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
|
19
|
+
|
|
20
|
+
msgid "127.0.0.1"
|
|
21
|
+
msgstr ""
|
|
22
|
+
|
|
23
|
+
msgid "Common fields"
|
|
24
|
+
msgstr ""
|
|
25
|
+
|
|
26
|
+
msgid "OpenTofu Provider"
|
|
27
|
+
msgstr ""
|
|
28
|
+
|
|
29
|
+
msgid "OpenTofu Script template"
|
|
30
|
+
msgstr ""
|
|
31
|
+
|
|
32
|
+
msgid "OpenTofu Template"
|
|
33
|
+
msgstr ""
|
|
34
|
+
|
|
35
|
+
msgid "Storage"
|
|
36
|
+
msgstr ""
|
|
37
|
+
|
|
38
|
+
msgid "TODO: Description of ForemanPluginTemplate."
|
|
39
|
+
msgstr ""
|
|
40
|
+
|
|
41
|
+
msgid "URL"
|
|
42
|
+
msgstr ""
|
|
43
|
+
|
|
44
|
+
msgid "Unable to find template specified by %s setting"
|
|
45
|
+
msgstr ""
|
|
46
|
+
|
|
47
|
+
msgid "Unable to render provisioning template"
|
|
48
|
+
msgstr ""
|
|
19
49
|
|
|
50
|
+
msgid "VM Config"
|
|
51
|
+
msgstr ""
|
|
52
|
+
|
|
53
|
+
msgid "e.g. admin"
|
|
54
|
+
msgstr ""
|
data/locale/foreman_opentofu.pot
CHANGED
|
@@ -1,19 +1,67 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# This file is distributed under the same license as foreman_opentofu.
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the foreman_opentofu package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
4
5
|
#
|
|
5
6
|
#, fuzzy
|
|
6
7
|
msgid ""
|
|
7
8
|
msgstr ""
|
|
8
|
-
"Project-Id-Version:
|
|
9
|
+
"Project-Id-Version: foreman_opentofu 1.0.0\n"
|
|
9
10
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date:
|
|
11
|
-
"PO-Revision-Date:
|
|
12
|
-
"Last-Translator:
|
|
13
|
-
"Language-Team:
|
|
11
|
+
"POT-Creation-Date: 2026-02-16 18:46+0000\n"
|
|
12
|
+
"PO-Revision-Date: 2026-02-16 18:46+0000\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
14
15
|
"Language: \n"
|
|
15
16
|
"MIME-Version: 1.0\n"
|
|
16
17
|
"Content-Type: text/plain; charset=UTF-8\n"
|
|
17
18
|
"Content-Transfer-Encoding: 8bit\n"
|
|
18
19
|
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
|
19
20
|
|
|
21
|
+
#: ../app/services/foreman_opentofu/opentofu_executer.rb:53
|
|
22
|
+
msgid "Unable to render provisioning template"
|
|
23
|
+
msgstr ""
|
|
24
|
+
|
|
25
|
+
#: ../app/services/foreman_opentofu/opentofu_executer.rb:62
|
|
26
|
+
msgid "Unable to find template specified by %s setting"
|
|
27
|
+
msgstr ""
|
|
28
|
+
|
|
29
|
+
#: ../app/views/compute_resources/form/_tofu.html.erb:1
|
|
30
|
+
msgid "Common fields"
|
|
31
|
+
msgstr ""
|
|
32
|
+
|
|
33
|
+
#: ../app/views/compute_resources/form/_tofu.html.erb:4
|
|
34
|
+
msgid "127.0.0.1"
|
|
35
|
+
msgstr ""
|
|
36
|
+
|
|
37
|
+
#: ../app/views/compute_resources/form/_tofu.html.erb:5
|
|
38
|
+
msgid "e.g. admin"
|
|
39
|
+
msgstr ""
|
|
40
|
+
|
|
41
|
+
#: ../app/views/compute_resources/show/_tofu.html.erb:2
|
|
42
|
+
msgid "OpenTofu Provider"
|
|
43
|
+
msgstr ""
|
|
44
|
+
|
|
45
|
+
#: ../app/views/compute_resources/show/_tofu.html.erb:6
|
|
46
|
+
msgid "OpenTofu Template"
|
|
47
|
+
msgstr ""
|
|
48
|
+
|
|
49
|
+
#: ../app/views/compute_resources/show/_tofu.html.erb:10
|
|
50
|
+
msgid "URL"
|
|
51
|
+
msgstr ""
|
|
52
|
+
|
|
53
|
+
#: ../app/views/compute_resources_vms/form/tofu/_base.html.erb:5
|
|
54
|
+
msgid "VM Config"
|
|
55
|
+
msgstr ""
|
|
56
|
+
|
|
57
|
+
#: ../app/views/compute_resources_vms/form/tofu/_base.html.erb:9
|
|
58
|
+
msgid "Storage"
|
|
59
|
+
msgstr ""
|
|
60
|
+
|
|
61
|
+
#: ../lib/foreman_opentofu/engine.rb:26
|
|
62
|
+
msgid "OpenTofu Script template"
|
|
63
|
+
msgstr ""
|
|
64
|
+
|
|
65
|
+
#: gemspec.rb:2
|
|
66
|
+
msgid "TODO: Description of ForemanPluginTemplate."
|
|
67
|
+
msgstr ""
|
|
@@ -6,9 +6,14 @@ module Api
|
|
|
6
6
|
setup do
|
|
7
7
|
@tf_one = FactoryBot.create(:tf_state)
|
|
8
8
|
@tf_two = FactoryBot.create(:tf_state)
|
|
9
|
+
@token_one = FactoryBot.create(:foreman_opentofu_token, name: @tf_one.name)
|
|
10
|
+
@token_two = FactoryBot.create(:foreman_opentofu_token, name: @tf_two.name)
|
|
11
|
+
@authorization_one = ActionController::HttpAuthentication::Token.encode_credentials(@token_one.token)
|
|
12
|
+
@authorization_two = ActionController::HttpAuthentication::Token.encode_credentials(@token_two.token)
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
test 'should show tf_state when present' do
|
|
16
|
+
request.headers['HTTP_AUTHORIZATION'] = @authorization_one
|
|
12
17
|
get :show, params: { name: @tf_one.name }
|
|
13
18
|
|
|
14
19
|
assert_response :success
|
|
@@ -18,19 +23,34 @@ module Api
|
|
|
18
23
|
assert_equal 'bar', body['foo']
|
|
19
24
|
end
|
|
20
25
|
|
|
21
|
-
test 'should return
|
|
26
|
+
test 'should return 401 when tf_state is missing' do
|
|
27
|
+
request.headers['HTTP_AUTHORIZATION'] = @authorization_one
|
|
22
28
|
get :show, params: { name: 'missing-vm' }
|
|
23
29
|
|
|
24
|
-
assert_response :
|
|
25
|
-
|
|
30
|
+
assert_response :unauthorized
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
test 'should return 401 when token expired' do
|
|
34
|
+
@token_one.expire = Time.current - 3600
|
|
35
|
+
@token_one.save!
|
|
36
|
+
|
|
37
|
+
assert @token_one.expired?
|
|
38
|
+
|
|
39
|
+
get :show, params: { name: @tf_one.name }
|
|
40
|
+
|
|
41
|
+
assert_response :unauthorized
|
|
42
|
+
assert_not_equal @tf_one.state, @response.body
|
|
26
43
|
end
|
|
27
44
|
|
|
28
45
|
test 'should create tf_state with valid json body' do
|
|
46
|
+
token = FactoryBot.create(:foreman_opentofu_token, name: 'new-vm')
|
|
47
|
+
|
|
29
48
|
attrs = { hello: 'world' }
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
request.headers['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Token.encode_credentials(token.token)
|
|
50
|
+
@request.env['CONTENT_TYPE'] = 'application/json'
|
|
51
|
+
post :create,
|
|
52
|
+
params: { name: 'new-vm' },
|
|
53
|
+
body: attrs.to_json
|
|
34
54
|
|
|
35
55
|
assert_response :success
|
|
36
56
|
|
|
@@ -40,7 +60,10 @@ module Api
|
|
|
40
60
|
end
|
|
41
61
|
|
|
42
62
|
test 'should update tf_state when already exists' do
|
|
43
|
-
|
|
63
|
+
request.headers['HTTP_AUTHORIZATION'] = @authorization_one
|
|
64
|
+
post :create,
|
|
65
|
+
params: { name: @tf_one.name },
|
|
66
|
+
body: { updated: true }.to_json
|
|
44
67
|
|
|
45
68
|
assert_response :success
|
|
46
69
|
|
|
@@ -49,6 +72,7 @@ module Api
|
|
|
49
72
|
|
|
50
73
|
test 'should destroy tf_state when present' do
|
|
51
74
|
assert_difference('ForemanOpentofu::TfState.count', -1) do
|
|
75
|
+
request.headers['HTTP_AUTHORIZATION'] = @authorization_two
|
|
52
76
|
delete :destroy, params: { name: @tf_two.name }
|
|
53
77
|
end
|
|
54
78
|
|
|
@@ -57,6 +81,7 @@ module Api
|
|
|
57
81
|
|
|
58
82
|
test 'should return ok when destroying missing tf_state' do
|
|
59
83
|
assert_no_difference('ForemanOpentofu::TfState.count') do
|
|
84
|
+
request.headers['HTTP_AUTHORIZATION'] = @authorization_two
|
|
60
85
|
delete :destroy, params: { name: 'missing-vm' }
|
|
61
86
|
end
|
|
62
87
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
FactoryBot.define do
|
|
2
|
+
factory :foreman_opentofu_token, class: 'ForemanOpentofu::Token' do
|
|
3
|
+
sequence(:name) { |n| "vm-#{n}" }
|
|
4
|
+
sequence(:token) { |n| "secret#{n}" }
|
|
5
|
+
expire { Time.current + 3600 }
|
|
6
|
+
trait :expired do
|
|
7
|
+
expire { Time.current - 3600 }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class TofuTest < ActiveSupport::TestCase
|
|
4
|
+
# FIXME: be more generic!
|
|
5
|
+
let(:subject) { FactoryBot.create :opentofu_nutanix_cr }
|
|
6
|
+
|
|
7
|
+
should validate_presence_of :provider
|
|
8
|
+
should validate_presence_of :url
|
|
9
|
+
should validate_presence_of :user
|
|
10
|
+
should validate_presence_of :password
|
|
11
|
+
should delegate_method(:available_attributes).to(:tofu_provider)
|
|
12
|
+
|
|
13
|
+
test 'validates provider is Tofu' do
|
|
14
|
+
subject.provider = 'Unknown'
|
|
15
|
+
assert_not subject.valid?
|
|
16
|
+
|
|
17
|
+
subject.provider = 'Tofu'
|
|
18
|
+
assert subject.valid?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
test 'responds to opentofu_template' do
|
|
22
|
+
assert_respond_to subject, :opentofu_template
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test 'not assigned template returns nil' do
|
|
26
|
+
assert_not_include subject.attrs, :opentofu_template_id
|
|
27
|
+
|
|
28
|
+
assert_nil subject.opentofu_template
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
test 'has tofu-template' do
|
|
32
|
+
template = FactoryBot.create(:provisioning_template) # , template_kind: FactoryBot.create(:template_kind, name: 'opentofu_script'))
|
|
33
|
+
subject.opentofu_template_id = template.id
|
|
34
|
+
|
|
35
|
+
assert_equal template, subject.opentofu_template
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
test 'has tofu provider' do
|
|
39
|
+
assert_instance_of Symbol, subject.opentofu_provider
|
|
40
|
+
assert_instance_of ForemanOpentofu::ProviderType, subject.tofu_provider
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
test 'delegates available_attributes to opentofu-provider' do
|
|
44
|
+
assert_equal subject.tofu_provider.available_attributes, subject.available_attributes
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class TokenTest < ActiveSupport::TestCase
|
|
4
|
+
let(:subject) { FactoryBot.create(:foreman_opentofu_token, :expired) }
|
|
5
|
+
|
|
6
|
+
should validate_presence_of :name
|
|
7
|
+
|
|
8
|
+
test 'expires' do
|
|
9
|
+
assert subject.expired?
|
|
10
|
+
|
|
11
|
+
subject.expire = nil
|
|
12
|
+
assert subject.expired?
|
|
13
|
+
|
|
14
|
+
subject.expire = ''
|
|
15
|
+
assert subject.expired?
|
|
16
|
+
|
|
17
|
+
subject.expire = Time.zone.now
|
|
18
|
+
assert subject.expired?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
test 'generates new token' do
|
|
22
|
+
old_token = subject.token
|
|
23
|
+
|
|
24
|
+
subject.generate
|
|
25
|
+
assert_not_equal old_token, subject.token
|
|
26
|
+
assert_not_empty subject.token
|
|
27
|
+
|
|
28
|
+
subject.save!
|
|
29
|
+
assert_not_equal old_token, subject.reload.token
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test 'generates valid token' do
|
|
33
|
+
subject.generate
|
|
34
|
+
assert_not subject.expired?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module ForemanOpentofu
|
|
2
|
+
class ProviderTypeTest < ActiveSupport::TestCase
|
|
3
|
+
# FIXME: use a non-existing ProviderType and stub the CR_ATTRS instead
|
|
4
|
+
let(:provider_type) { ProviderType.new('nutanix') }
|
|
5
|
+
|
|
6
|
+
test 'has name' do
|
|
7
|
+
assert_not_empty provider_type.name
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
test 'has attributes' do
|
|
11
|
+
assert provider_type.attributes?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
test 'finds all attributes' do
|
|
15
|
+
attributes = provider_type.attributes
|
|
16
|
+
|
|
17
|
+
assert_not_empty attributes
|
|
18
|
+
assert_not_empty(attributes.select { |a| a['group'] == 'vm' })
|
|
19
|
+
assert_not_empty(attributes.select { |a| a['group'] == 'nic' })
|
|
20
|
+
assert_not_empty(attributes.select { |a| a['group'] == 'disk' })
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
test 'finds group attributes' do
|
|
24
|
+
attributes = provider_type.attributes('vm')
|
|
25
|
+
|
|
26
|
+
assert_not_empty attributes
|
|
27
|
+
assert_not_empty(attributes.select { |a| a['group'] == 'vm' })
|
|
28
|
+
assert_empty(attributes.select { |a| a['group'] == 'nic' })
|
|
29
|
+
assert_empty(attributes.select { |a| a['group'] == 'disk' })
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test 'has available_attributes' do
|
|
33
|
+
attr_hash = provider_type.available_attributes
|
|
34
|
+
|
|
35
|
+
assert_instance_of Hash, attr_hash
|
|
36
|
+
assert_include attr_hash.keys, 'num_sockets'
|
|
37
|
+
assert_equal 'num_sockets', attr_hash['num_sockets']['name']
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test 'no available_attributes raises' do
|
|
41
|
+
provider_type.expects(:attributes?).returns(false)
|
|
42
|
+
|
|
43
|
+
assert_raises(RuntimeError) do
|
|
44
|
+
provider_type.available_attributes
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
test 'no default_attributes returns nil' do
|
|
49
|
+
assert_nil provider_type.default_attributes
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
test 'returns default_attributes, if any' do
|
|
53
|
+
def_attr = {
|
|
54
|
+
'server_type' => 'cx23',
|
|
55
|
+
'image' => 'debian-13',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
provider_type.instance_eval do
|
|
59
|
+
@default_attributes = def_attr
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
assert_not_nil provider_type.default_attributes
|
|
63
|
+
assert_instance_of Hash, provider_type.default_attributes
|
|
64
|
+
assert_not_empty provider_type.default_attributes
|
|
65
|
+
assert_equal def_attr, provider_type.default_attributes
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -7,7 +7,7 @@ module ForemanOpentofu
|
|
|
7
7
|
@cr_attrs = { 'name' => 'vm-1' }
|
|
8
8
|
@executor = OpentofuExecuter.new(@compute_resource, @cr_attrs)
|
|
9
9
|
|
|
10
|
-
@template = FactoryBot.create(:provisioning_template, name:
|
|
10
|
+
@template = FactoryBot.create(:provisioning_template, name: 'Nutanix test script')
|
|
11
11
|
@executor.stubs(:provision_template).returns(@template)
|
|
12
12
|
|
|
13
13
|
@app_mock = mock('AppWrapper')
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ATIX-AG
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: deface
|
|
@@ -33,6 +33,7 @@ files:
|
|
|
33
33
|
- LICENSE
|
|
34
34
|
- README.md
|
|
35
35
|
- Rakefile
|
|
36
|
+
- app/assets/javascripts/foreman_opentofu/locale/en/foreman_opentofu.js
|
|
36
37
|
- app/controllers/api/v2/tf_states_controller.rb
|
|
37
38
|
- app/controllers/concerns/foreman_opentofu/compute_resources_vms_controller.rb
|
|
38
39
|
- app/controllers/concerns/foreman_opentofu/controller/parameters/compute_resource.rb
|
|
@@ -42,6 +43,7 @@ files:
|
|
|
42
43
|
- app/models/foreman_opentofu/opentofu_vm_commands.rb
|
|
43
44
|
- app/models/foreman_opentofu/tf_state.rb
|
|
44
45
|
- app/models/foreman_opentofu/tofu.rb
|
|
46
|
+
- app/models/foreman_opentofu/token.rb
|
|
45
47
|
- app/overrides/compute_resources/remove_virtual_machines_tab.rb
|
|
46
48
|
- app/services/foreman_opentofu/app_wrapper.rb
|
|
47
49
|
- app/services/foreman_opentofu/compute_fetcher.rb
|
|
@@ -54,13 +56,14 @@ files:
|
|
|
54
56
|
- app/views/compute_resources_vms/form/tofu/_dynamic_attrs.html.erb
|
|
55
57
|
- app/views/compute_resources_vms/form/tofu/_network.html.erb
|
|
56
58
|
- app/views/compute_resources_vms/index/_tofu.html.erb
|
|
57
|
-
- app/views/templates/provisioning/
|
|
58
|
-
- app/views/templates/provisioning/
|
|
59
|
+
- app/views/templates/provisioning/nutanix_provision_default.erb
|
|
60
|
+
- app/views/templates/provisioning/ovirt_provision_default.erb
|
|
59
61
|
- config/initializers/compute_attrs.rb
|
|
60
62
|
- config/nutanix.json
|
|
61
63
|
- config/ovirt.json
|
|
62
64
|
- config/routes.rb
|
|
63
65
|
- db/migrate/20250625192757_create_tf_state.foreman_opentofu.rb
|
|
66
|
+
- db/migrate/20260127160904_add_table_token.foreman_opentofu.rb
|
|
64
67
|
- db/seeds.d/71_provisioning_templates.rb
|
|
65
68
|
- lib/foreman_opentofu.rb
|
|
66
69
|
- lib/foreman_opentofu/engine.rb
|
|
@@ -70,6 +73,7 @@ files:
|
|
|
70
73
|
- lib/foreman_opentofu/version.rb
|
|
71
74
|
- lib/tasks/foreman_opentofu_tasks.rake
|
|
72
75
|
- locale/Makefile
|
|
76
|
+
- locale/en/LC_MESSAGES/foreman_opentofu.mo
|
|
73
77
|
- locale/en/foreman_opentofu.po
|
|
74
78
|
- locale/foreman_opentofu.pot
|
|
75
79
|
- locale/gemspec.rb
|
|
@@ -77,9 +81,13 @@ files:
|
|
|
77
81
|
- test/factories/compute_resources.rb
|
|
78
82
|
- test/factories/foreman_opentofu_factories.rb
|
|
79
83
|
- test/factories/tf_state_factories.rb
|
|
84
|
+
- test/factories/token.rb
|
|
80
85
|
- test/models/foreman_opentofu/opentofu_vm_commands_test.rb
|
|
86
|
+
- test/models/foreman_opentofu/tofu_test.rb
|
|
87
|
+
- test/models/foreman_opentofu/token_test.rb
|
|
81
88
|
- test/models/foreman_opentofu_test.rb
|
|
82
89
|
- test/services/app_wrapper_test.rb
|
|
90
|
+
- test/services/foreman_opentofu/provider_type_test.rb
|
|
83
91
|
- test/services/opentofu_executer_test.rb
|
|
84
92
|
- test/test_plugin_helper.rb
|
|
85
93
|
homepage: https://github.com/ATIX-AG/foreman_opentofu
|
|
@@ -104,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
104
112
|
- !ruby/object:Gem::Version
|
|
105
113
|
version: '0'
|
|
106
114
|
requirements: []
|
|
107
|
-
rubygems_version:
|
|
115
|
+
rubygems_version: 4.0.3
|
|
108
116
|
specification_version: 4
|
|
109
117
|
summary: Plugin to provision host using opentofu
|
|
110
118
|
test_files:
|
|
@@ -112,8 +120,12 @@ test_files:
|
|
|
112
120
|
- test/factories/compute_resources.rb
|
|
113
121
|
- test/factories/foreman_opentofu_factories.rb
|
|
114
122
|
- test/factories/tf_state_factories.rb
|
|
123
|
+
- test/factories/token.rb
|
|
115
124
|
- test/models/foreman_opentofu/opentofu_vm_commands_test.rb
|
|
125
|
+
- test/models/foreman_opentofu/tofu_test.rb
|
|
126
|
+
- test/models/foreman_opentofu/token_test.rb
|
|
116
127
|
- test/models/foreman_opentofu_test.rb
|
|
117
128
|
- test/services/app_wrapper_test.rb
|
|
129
|
+
- test/services/foreman_opentofu/provider_type_test.rb
|
|
118
130
|
- test/services/opentofu_executer_test.rb
|
|
119
131
|
- test/test_plugin_helper.rb
|