foreman_opentofu 0.0.5 → 0.0.6
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 +17 -0
- data/app/controllers/api/v2/tf_states_controller.rb +13 -13
- data/app/lib/foreman_opentofu/concerns/base_template_scope_extensions.rb +11 -0
- data/app/models/concerns/foreman_opentofu/vm_command_collection_normalization.rb +26 -1
- data/app/models/foreman_opentofu/compute_vm.rb +28 -2
- data/app/models/foreman_opentofu/opentofu_vm_commands.rb +2 -1
- data/app/services/foreman_opentofu/opentofu_executer.rb +25 -4
- data/app/services/foreman_opentofu/provider_type.rb +6 -0
- data/app/services/foreman_opentofu/renderer/source/snapshot.rb +25 -0
- data/app/services/foreman_opentofu/template_snapshot_service.rb +68 -0
- data/app/views/api/v2/compute_resources/tofu.json.rabl +1 -0
- data/app/views/compute_resources_vms/form/tofu/_network.html.erb +4 -1
- data/app/views/compute_resources_vms/form/tofu/_volume.html.erb +4 -2
- data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_interfaces_fields.html.erb +1 -0
- data/app/views/foreman_opentofu/compute_resources_vms/form/tofu/_volumes_fields.html.erb +8 -1
- data/app/views/templates/provisioning/hetzner_provision_host.erb +6 -21
- data/lib/foreman_opentofu/engine.rb +1 -0
- data/lib/foreman_opentofu/provider_types/hetzner.rb +5 -1
- data/lib/foreman_opentofu/provider_types/nutanix.rb +1 -1
- data/lib/foreman_opentofu/version.rb +1 -1
- data/lib/tasks/snapshots.rake +63 -0
- data/test/controllers/api/v2/tf_states_controller_test.rb +71 -2
- data/test/fixtures/renderer/snapshots/ProvisioningTemplate/opentofu_script/Hetzner_provision_default.empty.snap.txt +72 -0
- data/test/fixtures/renderer/snapshots/ProvisioningTemplate/opentofu_script/Hetzner_provision_default.hetzner_host.snap.txt +74 -0
- data/test/fixtures/renderer/snapshots/ProvisioningTemplate/opentofu_script/Nutanix_provision_default.empty.snap.txt +56 -0
- data/test/fixtures/renderer/snapshots/ProvisioningTemplate/opentofu_script/Nutanix_provision_default.nutanix_host.snap.txt +62 -0
- data/test/fixtures/renderer/snapshots.yaml +6 -0
- data/test/lib/foreman_opentofu/concerns/base_template_scope_extensions_test.rb +33 -17
- data/test/models/foreman_opentofu/compute_vm_test.rb +32 -0
- data/test/models/foreman_opentofu/opentofu_vm_commands_test.rb +45 -3
- data/test/renderer/renderer_test.rb +60 -0
- data/test/services/foreman_opentofu/provider_type_test.rb +10 -0
- data/test/services/opentofu_executer_test.rb +28 -1
- data/test/services/tofu_key_pair_test.rb +4 -1
- metadata +20 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aac98f7ab29438afb053493e3ef572e5374d5140eec49cc67e4f4d9787776a08
|
|
4
|
+
data.tar.gz: 85f7eb01a3b220b06b357c5ef9b0270bbf49fc19ea286f7e421964871f7bf726
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a8231fb351a2f809d768880ed32dd0be78b13333453f4d5fbb71fbcc4c801dc963c0b08beeed69ba916c307c669856d17e8513d1cdb4923f25e0c6b49487e73
|
|
7
|
+
data.tar.gz: c221f8852cfedf5d07bb40bb93832c2512b77b59b58c043dd8f23cfdf61ee102321be84825158e61981bcd1255b4bc2b6c18fa589d368c5c0d2df16dc2697994
|
data/README.md
CHANGED
|
@@ -337,6 +337,23 @@ bundle exec foreman start
|
|
|
337
337
|
|
|
338
338
|
See details in [foreman plugin development](https://projects.theforeman.org/projects/foreman/wiki/How_to_Create_a_Plugin)
|
|
339
339
|
|
|
340
|
+
|
|
341
|
+
### Testing
|
|
342
|
+
|
|
343
|
+
#### Template Snapshots
|
|
344
|
+
|
|
345
|
+
The OpenTofu templates are tested against recorded snapshots similar to the Provisioning Templates in Foreman.
|
|
346
|
+
The snapshots are generated by rendering the templates with predefined fixture-data using the following command in the foreman-directory:
|
|
347
|
+
|
|
348
|
+
```shell
|
|
349
|
+
bundle exec rake foreman_opentofu:snapshots:generate RAILS_ENV=test
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
At the moment the fixture-data is defined in `app/services/foreman_opentofu/template_snapshot_service.rb`.
|
|
353
|
+
|
|
354
|
+
Templates to test must be added to `test/fixtures/renderer/snapshots.yaml`.
|
|
355
|
+
|
|
356
|
+
|
|
340
357
|
## Contributing
|
|
341
358
|
|
|
342
359
|
Fork and send a Pull Request or create Issue. Thank you.
|
|
@@ -2,10 +2,8 @@ module Api
|
|
|
2
2
|
module V2
|
|
3
3
|
class TfStatesController < ::Api::V2::BaseController
|
|
4
4
|
include ::Api::Version2
|
|
5
|
+
include ::Foreman::Controller::FilterParameters
|
|
5
6
|
|
|
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
7
|
skip_before_action :set_taxonomy
|
|
10
8
|
|
|
11
9
|
# Allow HTTP POST methods without CSRF
|
|
@@ -14,6 +12,11 @@ module Api
|
|
|
14
12
|
# overwrite authorize with the local token-based authorization
|
|
15
13
|
before_action :authorize, except: [:destroy]
|
|
16
14
|
|
|
15
|
+
# Don't log tfstate response in logs as it can have sensitive information
|
|
16
|
+
skip_after_action :log_response_body
|
|
17
|
+
|
|
18
|
+
filter_parameters :version, :terraform_version, :serial, :lineage, :outputs, :resources, :tf_state, :check_results
|
|
19
|
+
|
|
17
20
|
resource_description do
|
|
18
21
|
api_version 'v2'
|
|
19
22
|
api_base_url '/foreman_opentofu/api'
|
|
@@ -22,15 +25,13 @@ module Api
|
|
|
22
25
|
def show
|
|
23
26
|
state = ForemanOpentofu::TfState.find_by(name: params[:name])
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
render plain: '', status: :not_found
|
|
29
|
-
end
|
|
28
|
+
return head :not_found unless state
|
|
29
|
+
|
|
30
|
+
render plain: state.state, content_type: 'application/json'
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
def create
|
|
33
|
-
raw_state = request.
|
|
34
|
+
raw_state = request.raw_post
|
|
34
35
|
if raw_state.blank?
|
|
35
36
|
render plain: 'Missing state body', status: :unprocessable_entity
|
|
36
37
|
return
|
|
@@ -41,10 +42,9 @@ module Api
|
|
|
41
42
|
state = ForemanOpentofu::TfState.find_or_initialize_by(name: params[:name])
|
|
42
43
|
state.state = raw_state
|
|
43
44
|
state.save!
|
|
44
|
-
|
|
45
|
-
rescue JSON::ParserError
|
|
46
|
-
|
|
47
|
-
render plain: 'Invalid state format', status: :unprocessable_entity
|
|
45
|
+
head :ok
|
|
46
|
+
rescue JSON::ParserError
|
|
47
|
+
render plain: 'Invalid state format', status: :bad_request
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -97,6 +97,8 @@ module ForemanOpentofu
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def build_disks
|
|
100
|
+
return rendered_disk(0, disk_render_source.first).to_s if disk_renderer_collection?
|
|
101
|
+
|
|
100
102
|
disk_render_source.each_with_index.filter_map do |disk, index|
|
|
101
103
|
# drop removed disks; tofu will drop them automatically, if they are no longer defined
|
|
102
104
|
next if disk.respond_to?(:[]) && disk['_delete'].to_i == 1
|
|
@@ -151,6 +153,15 @@ module ForemanOpentofu
|
|
|
151
153
|
to_hcl(data, snippet: true)
|
|
152
154
|
end
|
|
153
155
|
end
|
|
156
|
+
|
|
157
|
+
def disk_renderer_collection?
|
|
158
|
+
return false unless @compute_resource.respond_to?(:tofu_provider)
|
|
159
|
+
|
|
160
|
+
provider = @compute_resource.tofu_provider
|
|
161
|
+
provider.respond_to?(:disk_renderer_collection?) && provider.disk_renderer_collection?
|
|
162
|
+
rescue StandardError
|
|
163
|
+
false
|
|
164
|
+
end
|
|
154
165
|
end
|
|
155
166
|
end
|
|
156
167
|
end
|
|
@@ -29,13 +29,38 @@ module ForemanOpentofu
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def normalize_collection_input(collection, value)
|
|
32
|
-
return
|
|
32
|
+
return normalize_nested_hash_collection(collection, value) if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
|
|
33
33
|
|
|
34
34
|
Array(value).map do |entry|
|
|
35
35
|
entry.respond_to?(:deep_symbolize_keys) ? entry.deep_symbolize_keys : entry
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
def normalize_nested_hash_collection(collection, value)
|
|
40
|
+
values = (value.respond_to?(:to_unsafe_h) ? value.to_unsafe_h : value.to_h).deep_stringify_keys
|
|
41
|
+
values.delete("new_#{collection}")
|
|
42
|
+
|
|
43
|
+
values.sort_by { |key, _| key.to_s.sub('new_', '').to_i }.filter_map do |_, val|
|
|
44
|
+
normalized = normalize_nested_collection_value(val)
|
|
45
|
+
next if normalized.is_a?(Hash) && normalized[:_delete] == '1' && normalized[:id].blank?
|
|
46
|
+
|
|
47
|
+
normalized
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def normalize_nested_collection_value(value)
|
|
52
|
+
normalized =
|
|
53
|
+
if value.is_a?(ActionController::Parameters) && value.respond_to?(:to_unsafe_h)
|
|
54
|
+
value.to_unsafe_h
|
|
55
|
+
elsif value.respond_to?(:to_h)
|
|
56
|
+
value.to_h
|
|
57
|
+
else
|
|
58
|
+
value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
normalized.respond_to?(:deep_symbolize_keys) ? normalized.deep_symbolize_keys : normalized
|
|
62
|
+
end
|
|
63
|
+
|
|
39
64
|
def prefill_mandatory_attributes(args)
|
|
40
65
|
# FIXME: add volume/nic attributes
|
|
41
66
|
res = {}
|
|
@@ -14,7 +14,7 @@ module ForemanOpentofu
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def to_h
|
|
17
|
-
unwrap(@attributes.
|
|
17
|
+
unwrap(@attributes.deep_dup)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def power
|
|
@@ -30,6 +30,21 @@ module ForemanOpentofu
|
|
|
30
30
|
self['name']
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def vm_description
|
|
34
|
+
return nil unless @provider.respond_to?(:tofu_provider)
|
|
35
|
+
|
|
36
|
+
vm_attrs = @provider.tofu_provider.attributes('vm')
|
|
37
|
+
values = vm_attrs.filter_map do |attr|
|
|
38
|
+
key = attr['name'].to_s
|
|
39
|
+
value = attribute_value(key)
|
|
40
|
+
next if value.blank?
|
|
41
|
+
|
|
42
|
+
label = attr['label'].presence || key.humanize
|
|
43
|
+
"#{label}: #{value}"
|
|
44
|
+
end
|
|
45
|
+
values.join(', ') if values.present?
|
|
46
|
+
end
|
|
47
|
+
|
|
33
48
|
def persisted?
|
|
34
49
|
self['identity'].present? || self['id'].present?
|
|
35
50
|
end
|
|
@@ -119,6 +134,17 @@ module ForemanOpentofu
|
|
|
119
134
|
end
|
|
120
135
|
end
|
|
121
136
|
|
|
137
|
+
def unwrap(value)
|
|
138
|
+
case value
|
|
139
|
+
when Hash
|
|
140
|
+
value.transform_values { |v| unwrap(v) }
|
|
141
|
+
when Array
|
|
142
|
+
value.map { |v| unwrap(v) }
|
|
143
|
+
else
|
|
144
|
+
value
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
122
148
|
def flatten_attrs(attrs)
|
|
123
149
|
result = {}
|
|
124
150
|
|
|
@@ -144,7 +170,7 @@ module ForemanOpentofu
|
|
|
144
170
|
key = method_name.to_s
|
|
145
171
|
return attribute_value(key) if dynamic_attribute_keys.include?(key)
|
|
146
172
|
|
|
147
|
-
|
|
173
|
+
nil
|
|
148
174
|
end
|
|
149
175
|
end
|
|
150
176
|
end
|
|
@@ -18,7 +18,8 @@ module ForemanOpentofu
|
|
|
18
18
|
executor = client(args)
|
|
19
19
|
data = executor.run_new
|
|
20
20
|
attrs = data['resource_changes'].first['change']['after'] || {}
|
|
21
|
-
|
|
21
|
+
attrs = args.deep_stringify_keys.merge(attrs.to_h.deep_stringify_keys)
|
|
22
|
+
ComputeVM.new(self, attrs)
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
@@ -80,10 +80,14 @@ module ForemanOpentofu
|
|
|
80
80
|
tofu.plan
|
|
81
81
|
raise 'OpenTofu planned to re-create a resource; action aborted (check logs for details)!' if plan_wants_recreate? tofu.show_plan
|
|
82
82
|
end
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
begin
|
|
84
|
+
tofu.apply
|
|
85
|
+
attrs = tofu.output('vm_attrs')
|
|
86
|
+
ForemanOpentofu::TfState.find_by(name: @cr_attrs['name'])&.update(uuid: attrs['identity'])
|
|
87
|
+
attrs
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
handle_failed_create(tofu, e)
|
|
90
|
+
end
|
|
87
91
|
end
|
|
88
92
|
end
|
|
89
93
|
|
|
@@ -177,5 +181,22 @@ module ForemanOpentofu
|
|
|
177
181
|
end
|
|
178
182
|
false
|
|
179
183
|
end
|
|
184
|
+
|
|
185
|
+
def handle_failed_create(tofu, error)
|
|
186
|
+
tofu.destroy
|
|
187
|
+
ForemanOpentofu::TfState.find_by(name: @cr_attrs['name'])&.destroy
|
|
188
|
+
raise error
|
|
189
|
+
rescue StandardError => e
|
|
190
|
+
raise if e.equal?(error)
|
|
191
|
+
|
|
192
|
+
Rails.logger.error(
|
|
193
|
+
"Removing OpenTofu resource after host create for #{@cr_attrs['name']} failed: #{e.message}"
|
|
194
|
+
)
|
|
195
|
+
wrapped_error = error.exception(
|
|
196
|
+
"#{error.message}\n Removing resource after failed host creation also failed: #{e.message}"
|
|
197
|
+
)
|
|
198
|
+
wrapped_error.set_backtrace(error.backtrace)
|
|
199
|
+
raise wrapped_error
|
|
200
|
+
end
|
|
180
201
|
end
|
|
181
202
|
end
|
|
@@ -119,5 +119,11 @@ module ForemanOpentofu
|
|
|
119
119
|
|
|
120
120
|
result
|
|
121
121
|
end
|
|
122
|
+
|
|
123
|
+
# Whether disk renderer emits one collection resource for all disks (for_each)
|
|
124
|
+
# instead of one resource block per disk entry.
|
|
125
|
+
def disk_renderer_collection?
|
|
126
|
+
false
|
|
127
|
+
end
|
|
122
128
|
end
|
|
123
129
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module ForemanOpentofu
|
|
2
|
+
module Renderer
|
|
3
|
+
module Source
|
|
4
|
+
class Snapshot < Foreman::Renderer::Source::Snapshot
|
|
5
|
+
SNAPSHOTS_DIRECTORY = ForemanOpentofu::Engine.root.join('test', 'fixtures', 'renderer', 'snapshots')
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def snapshot_path(template, suffix = 'host4dhcp')
|
|
9
|
+
File.join(SNAPSHOTS_DIRECTORY, "#{template_path(template).tr(' ', '_')}.#{suffix}.snap.txt")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def hosts(template)
|
|
13
|
+
['empty', "#{template.name.split(' ').first.downcase}_host"]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def templates_directory
|
|
20
|
+
ForemanOpentofu::TemplateSnapshotService::TEMPLATES_DIRECTORY
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module ForemanOpentofu
|
|
2
|
+
# < Foreman::TemplateSnapshotService
|
|
3
|
+
class TemplateSnapshotService
|
|
4
|
+
TEMPLATES_DIRECTORY = ForemanOpentofu::Engine.root.join('app', 'views', 'templates', 'provisioning')
|
|
5
|
+
|
|
6
|
+
def self.templates
|
|
7
|
+
new.templates
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.render_template(template, host_name = :empty)
|
|
11
|
+
raise 'TemplateSnapshotService can only be used in test environment' unless Rails.env.test?
|
|
12
|
+
require 'factory_bot_rails'
|
|
13
|
+
|
|
14
|
+
cr_attrs = new.send(host_name.to_sym)
|
|
15
|
+
provider_type = template.name.split(' ').first.downcase
|
|
16
|
+
variables = {
|
|
17
|
+
compute_resource: FactoryBot.build("opentofu_#{provider_type}_cr".to_sym),
|
|
18
|
+
cr_attrs: cr_attrs || {},
|
|
19
|
+
use_backend: true,
|
|
20
|
+
token: 'very_secret_token',
|
|
21
|
+
host_name: host_name,
|
|
22
|
+
resource: nil,
|
|
23
|
+
}
|
|
24
|
+
variables[:ssh_key] = variables[:compute_resource].key_pair if variables[:compute_resource].key_pair
|
|
25
|
+
source = ForemanOpentofu::Renderer::Source::Snapshot.new(template)
|
|
26
|
+
scope = Foreman::Renderer.get_scope(source: source, variables: variables)
|
|
27
|
+
Foreman::Renderer::UnsafeModeRenderer.render(source, scope)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def templates
|
|
31
|
+
files.map { |path| ForemanOpentofu::Renderer::Source::Snapshot.load_file(path) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def hetzner_host
|
|
35
|
+
{
|
|
36
|
+
server_type: 'cx23',
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def nutanix_host
|
|
41
|
+
{
|
|
42
|
+
'cluster_uuid' => 'my_cluster_uuid',
|
|
43
|
+
'volumes_attributes' => [
|
|
44
|
+
{
|
|
45
|
+
size_gb: 40,
|
|
46
|
+
container_uuid: 'my_container_uuid',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
'interfaces_attributes' => [
|
|
50
|
+
{
|
|
51
|
+
model: 'E1000',
|
|
52
|
+
subnet_uuid: 'my_subnet_uuid',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def empty
|
|
59
|
+
{}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def files
|
|
65
|
+
@files ||= YAML.safe_load_file(ForemanOpentofu::Engine.root.join('test', 'fixtures', 'renderer', 'snapshots.yaml')).fetch('files', []).map { |path| File.join(TEMPLATES_DIRECTORY, path) }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
attributes :user, :opentofu_provider, :opentofu_template_id
|
|
@@ -1 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<% index = f.index.present? ? f.index : 0 %>
|
|
2
|
+
<%= field_set_tag _("Network"), :id => "network_#{index}" do %>
|
|
3
|
+
<%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: compute_resource.tofu_provider.attributes('nic'), compute_resource: compute_resource } %>
|
|
4
|
+
<% end %>
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<% index = f.index.present? ? f.index : 0 %>
|
|
2
|
+
<%= field_set_tag _("Volume"), :id => "volume_#{index}" do %>
|
|
3
|
+
<%= render :partial => provider_partial(compute_resource, "dynamic_attrs"), :locals => { f: f, attrs: compute_resource.tofu_provider.attributes('disk'), compute_resource: compute_resource } %>
|
|
4
|
+
<% end %>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
interfaces = [compute_resource.new_interface].compact if interfaces.empty?
|
|
6
6
|
%>
|
|
7
7
|
<% interfaces.each_with_index do |interface_obj, idx| %>
|
|
8
|
+
<% interface_obj = compute_resource.new_interface(interface_obj.to_h) if interface_obj.is_a?(Hash) %>
|
|
8
9
|
<%= fields_for "#{f.object_name}[#{association_attributes}][#{idx}]", interface_obj do |i| %>
|
|
9
10
|
<%= render :partial => provider_partial(compute_resource, 'network'),
|
|
10
11
|
:locals => { :f => i, :compute_resource => compute_resource, :new_host => new_host, :new_vm => new_vm, :remove_title => _('remove network interface'), :selected_cluster => selected_cluster },
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
<%
|
|
2
|
-
volumes =
|
|
2
|
+
volumes =
|
|
3
|
+
if f.object.respond_to?(:volumes_attributes)
|
|
4
|
+
f.object.volumes_attributes
|
|
5
|
+
elsif f.object.respond_to?(:volumes)
|
|
6
|
+
f.object.volumes
|
|
7
|
+
else
|
|
8
|
+
{}
|
|
9
|
+
end
|
|
3
10
|
volumes =
|
|
4
11
|
if volumes.is_a?(Hash)
|
|
5
12
|
volumes.sort_by { |key, _| key.to_s.sub('new_', '').to_i }
|
|
@@ -30,11 +30,9 @@ data "hcloud_ssh_key" "deploy_key" {
|
|
|
30
30
|
}
|
|
31
31
|
<%- end %>
|
|
32
32
|
|
|
33
|
-
# Create a new server
|
|
33
|
+
# Create a new server
|
|
34
34
|
resource "hcloud_server" "node1" {
|
|
35
35
|
name = "<%= @host_name %>"
|
|
36
|
-
# image = "debian-12"
|
|
37
|
-
# server_type = "cx23"
|
|
38
36
|
|
|
39
37
|
<%= vm_attributes(['name', 'image']) %>
|
|
40
38
|
|
|
@@ -68,27 +66,14 @@ output "vm_attrs" {
|
|
|
68
66
|
vm_ip6_address = hcloud_server.node1.ipv6_address
|
|
69
67
|
vm = hcloud_server.node1
|
|
70
68
|
volumes_attributes = try({
|
|
71
|
-
for v in
|
|
72
|
-
id = v.id
|
|
73
|
-
name = v.name
|
|
74
|
-
size = v.size
|
|
69
|
+
for k, v in try(local.disks, {}) : tostring(try(v.id, k)) => {
|
|
70
|
+
id = try(v.id, null)
|
|
71
|
+
name = try(v.name, "volume-${k}")
|
|
72
|
+
size = try(v.size, 20)
|
|
75
73
|
automount = try(v.automount, null)
|
|
76
74
|
format = try(v.format, null)
|
|
77
|
-
}
|
|
75
|
+
} if try(v["_delete"], "0") != "1"
|
|
78
76
|
}, {})
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
|
-
|
|
82
|
-
<% else %>
|
|
83
|
-
# # get available images
|
|
84
|
-
# data "hcloud_images" "all" {}
|
|
85
|
-
# output "images" {
|
|
86
|
-
# value = data.hcloud_images.all.images
|
|
87
|
-
# }
|
|
88
|
-
#
|
|
89
|
-
# # get available server types
|
|
90
|
-
# data "hcloud_server_types" "all" {}
|
|
91
|
-
# output "server_types" {
|
|
92
|
-
# value = data.hcloud_server_types.all.server_types
|
|
93
|
-
# }
|
|
94
79
|
<% end %>
|
|
@@ -47,6 +47,7 @@ module ForemanOpentofu
|
|
|
47
47
|
# Include concerns in this config.to_prepare block
|
|
48
48
|
config.to_prepare do
|
|
49
49
|
::ComputeResourcesController.include ForemanOpentofu::Controller::Parameters::ComputeResource
|
|
50
|
+
::Api::V2::ComputeResourcesController.include ForemanOpentofu::Controller::Parameters::ComputeResource
|
|
50
51
|
::ComputeResourcesVmsController.include ForemanOpentofu::ComputeResourcesVmsController
|
|
51
52
|
::Host::Managed.include Orchestration::Tofu::Compute
|
|
52
53
|
::Authorizer.prepend ForemanOpentofu::AuthorizerPowerOverride unless ::Authorizer < ForemanOpentofu::AuthorizerPowerOverride
|
|
@@ -5,6 +5,10 @@ require "#{ForemanOpentofu::Engine.root}/app/services/foreman_opentofu/provider_
|
|
|
5
5
|
ForemanOpentofu::ProviderTypeManager.register('hetzner') do
|
|
6
6
|
@capabilities = [:image, :key_pair, :power_status_only]
|
|
7
7
|
|
|
8
|
+
def disk_renderer_collection?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
def provided_attributes
|
|
9
13
|
{
|
|
10
14
|
ip: :vm_ip_address,
|
|
@@ -125,7 +129,7 @@ ForemanOpentofu::ProviderTypeManager.register('hetzner') do
|
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
resource "hcloud_volume" "volumes" {
|
|
128
|
-
for_each = { for k, d in local.disks : tostring(
|
|
132
|
+
for_each = { for k, d in local.disks : tostring(k) => d if try(d["_delete"], "0") != "1" }
|
|
129
133
|
name = try(each.value.name, "volume-${each.key}")
|
|
130
134
|
size = try(each.value.size, 20)
|
|
131
135
|
server_id = hcloud_server.node1.id
|
|
@@ -55,7 +55,7 @@ ForemanOpentofu::ProviderTypeManager.register('nutanix') do
|
|
|
55
55
|
self.disk_renderer = proc do |disk|
|
|
56
56
|
{
|
|
57
57
|
disk_list: {
|
|
58
|
-
disk_size_mib: disk[:size_gb] * 1024,
|
|
58
|
+
disk_size_mib: disk[:size_gb].to_i * 1024,
|
|
59
59
|
storage_config: {
|
|
60
60
|
storage_container_reference: {
|
|
61
61
|
kind: 'storage_container',
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
namespace :foreman_opentofu do
|
|
2
|
+
namespace :snapshots do
|
|
3
|
+
desc 'Generate snapshots'
|
|
4
|
+
task generate: :environment do
|
|
5
|
+
unless Rails.env.test?
|
|
6
|
+
puts 'This task can only be run in test environment'
|
|
7
|
+
exit
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require 'database_cleaner'
|
|
11
|
+
require 'factory_bot_rails'
|
|
12
|
+
|
|
13
|
+
# Add plugin to FactoryBot's paths
|
|
14
|
+
FactoryBot.definition_file_paths << ForemanOpentofu::Engine.root.join('test', 'factories')
|
|
15
|
+
FactoryBot.reload
|
|
16
|
+
|
|
17
|
+
module BaseMacrosStub
|
|
18
|
+
def dns_lookup(_name_or_ip)
|
|
19
|
+
'foreman.example.com'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# ::Foreman::Plugin.singleton_class.send :prepend, PluginSnapshotStub
|
|
24
|
+
::Foreman::Renderer::Scope::Base.prepend BaseMacrosStub
|
|
25
|
+
|
|
26
|
+
# clean the snapshot directory in order to delete renamed ones and keep it clean
|
|
27
|
+
FileUtils.rm_rf(Dir.glob(File.join(
|
|
28
|
+
::ForemanOpentofu::Renderer::Source::Snapshot::SNAPSHOTS_DIRECTORY, '*'
|
|
29
|
+
)))
|
|
30
|
+
|
|
31
|
+
DatabaseCleaner.cleaning do
|
|
32
|
+
Foreman.settings.load
|
|
33
|
+
Setting[:foreman_url] = 'http://foreman.example.com'
|
|
34
|
+
|
|
35
|
+
User.current = FactoryBot.build(:user, :admin)
|
|
36
|
+
admin = FactoryBot.create(:user, :admin, password: 'password123', auth_source: FactoryBot.create(:auth_source_ldap))
|
|
37
|
+
|
|
38
|
+
failed_snapshots = []
|
|
39
|
+
|
|
40
|
+
User.as(admin.login) do
|
|
41
|
+
ForemanOpentofu::TemplateSnapshotService.templates.each do |template|
|
|
42
|
+
ForemanOpentofu::Renderer::Source::Snapshot.hosts(template).each do |host|
|
|
43
|
+
snapshot_path = ForemanOpentofu::Renderer::Source::Snapshot.snapshot_path(template, host)
|
|
44
|
+
dir = File.dirname(snapshot_path)
|
|
45
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
snapshot = ForemanOpentofu::TemplateSnapshotService.render_template(template, host)
|
|
49
|
+
puts "Writing #{snapshot_path}"
|
|
50
|
+
File.write(snapshot_path, snapshot)
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
warn "Snapshot #{snapshot_path} failed with #{e}"
|
|
53
|
+
failed_snapshots << snapshot_path
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
raise "#{failed_snapshots.count} snapshots failed generation or validation. Please inspect the output to see the details." unless failed_snapshots.empty?
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|