vagrant-eryph 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +79 -0
- data/lib/vagrant-eryph/actions/connect_eryph.rb +40 -0
- data/lib/vagrant-eryph/actions/create_catlet.rb +88 -0
- data/lib/vagrant-eryph/actions/destroy_catlet.rb +33 -0
- data/lib/vagrant-eryph/actions/is_created.rb +34 -0
- data/lib/vagrant-eryph/actions/is_stopped.rb +20 -0
- data/lib/vagrant-eryph/actions/message_already_created.rb +18 -0
- data/lib/vagrant-eryph/actions/message_not_created.rb +18 -0
- data/lib/vagrant-eryph/actions/message_will_not_destroy.rb +18 -0
- data/lib/vagrant-eryph/actions/prepare_cloud_init.rb +52 -0
- data/lib/vagrant-eryph/actions/read_ssh_info.rb +20 -0
- data/lib/vagrant-eryph/actions/read_state.rb +52 -0
- data/lib/vagrant-eryph/actions/start_catlet.rb +40 -0
- data/lib/vagrant-eryph/actions/stop_catlet.rb +40 -0
- data/lib/vagrant-eryph/actions.rb +210 -0
- data/lib/vagrant-eryph/command.rb +573 -0
- data/lib/vagrant-eryph/config.rb +553 -0
- data/lib/vagrant-eryph/errors.rb +61 -0
- data/lib/vagrant-eryph/helpers/cloud_init.rb +113 -0
- data/lib/vagrant-eryph/helpers/eryph_client.rb +413 -0
- data/lib/vagrant-eryph/plugin.rb +28 -0
- data/lib/vagrant-eryph/provider.rb +142 -0
- data/lib/vagrant-eryph/version.rb +7 -0
- data/lib/vagrant-eryph.rb +8 -0
- data/vagrant-eryph.gemspec +27 -0
- metadata +165 -0
@@ -0,0 +1,573 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'yaml'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module VagrantPlugins
|
8
|
+
module Eryph
|
9
|
+
class Command < Vagrant.plugin('2', :command)
|
10
|
+
def self.synopsis
|
11
|
+
'manage Eryph projects and network configurations'
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(argv, env)
|
15
|
+
super
|
16
|
+
@main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
|
17
|
+
@subcommands = Vagrant::Registry.new
|
18
|
+
@subcommands.register(:project) { ProjectCommand }
|
19
|
+
@subcommands.register(:network) { NetworkCommand }
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute
|
23
|
+
if @main_args.include?('-h') || @main_args.include?('--help')
|
24
|
+
return help
|
25
|
+
end
|
26
|
+
|
27
|
+
command_class = @subcommands.get(@sub_command.to_sym) if @sub_command
|
28
|
+
return help if !command_class || !@sub_command
|
29
|
+
|
30
|
+
@logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}")
|
31
|
+
command_class.new(@sub_args, @env).execute
|
32
|
+
end
|
33
|
+
|
34
|
+
def help
|
35
|
+
opts = OptionParser.new do |o|
|
36
|
+
o.banner = 'Usage: vagrant eryph <subcommand> [<args>]'
|
37
|
+
o.separator ''
|
38
|
+
o.separator 'Available subcommands:'
|
39
|
+
o.separator ' project Manage Eryph projects'
|
40
|
+
o.separator ' network Manage project network configurations'
|
41
|
+
o.separator ''
|
42
|
+
o.separator 'For help on any individual subcommand run `vagrant eryph <subcommand> -h`'
|
43
|
+
end
|
44
|
+
|
45
|
+
@env.ui.info(opts.help, prefix: false)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ProjectCommand < Vagrant.plugin('2', :command)
|
50
|
+
def self.synopsis
|
51
|
+
'manage Eryph projects'
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(argv, env)
|
55
|
+
super
|
56
|
+
@options = {}
|
57
|
+
@parser = OptionParser.new do |o|
|
58
|
+
o.banner = 'Usage: vagrant eryph project <subcommand> [options]'
|
59
|
+
o.separator ''
|
60
|
+
o.separator 'Subcommands:'
|
61
|
+
o.separator ' list List available projects'
|
62
|
+
o.separator ' create Create a new project'
|
63
|
+
o.separator ' remove Remove a project'
|
64
|
+
o.separator ''
|
65
|
+
o.separator 'Global Options:'
|
66
|
+
|
67
|
+
o.on('--configuration-name NAME', String, 'Eryph configuration name (default: auto-detect)') do |name|
|
68
|
+
@options[:configuration_name] = name
|
69
|
+
end
|
70
|
+
|
71
|
+
o.on('--client-id ID', String, 'Eryph client ID') do |id|
|
72
|
+
@options[:client_id] = id
|
73
|
+
end
|
74
|
+
|
75
|
+
o.separator ''
|
76
|
+
end
|
77
|
+
|
78
|
+
@main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
|
79
|
+
end
|
80
|
+
|
81
|
+
def execute
|
82
|
+
case @sub_command
|
83
|
+
when 'list'
|
84
|
+
execute_list
|
85
|
+
when 'create'
|
86
|
+
execute_create
|
87
|
+
when 'remove'
|
88
|
+
execute_remove
|
89
|
+
else
|
90
|
+
@env.ui.info(@parser.help, prefix: false)
|
91
|
+
1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def execute_list
|
98
|
+
client = get_eryph_client
|
99
|
+
@env.ui.info('Available Eryph projects:', prefix: false)
|
100
|
+
|
101
|
+
projects = client.list_projects
|
102
|
+
if projects.empty?
|
103
|
+
@env.ui.warn('No projects found')
|
104
|
+
else
|
105
|
+
projects.each do |project|
|
106
|
+
@env.ui.info(" #{project.name} (ID: #{project.id})", prefix: false)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
0
|
110
|
+
end
|
111
|
+
|
112
|
+
def execute_create
|
113
|
+
parser = OptionParser.new do |o|
|
114
|
+
o.banner = 'Usage: vagrant eryph project create <project-name> [options]'
|
115
|
+
o.separator ''
|
116
|
+
o.separator 'Options:'
|
117
|
+
o.on('--no-wait', 'Do not wait for operation to complete') do
|
118
|
+
@options[:no_wait] = true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
begin
|
123
|
+
argv = parser.parse!(@sub_args.dup)
|
124
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument => e
|
125
|
+
@env.ui.error("#{e.message}")
|
126
|
+
@env.ui.info(parser.help, prefix: false)
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
|
130
|
+
if argv.empty?
|
131
|
+
@env.ui.error('Project name is required')
|
132
|
+
@env.ui.info(parser.help, prefix: false)
|
133
|
+
return 1
|
134
|
+
end
|
135
|
+
|
136
|
+
project_name = argv[0]
|
137
|
+
client = get_eryph_client
|
138
|
+
|
139
|
+
begin
|
140
|
+
@env.ui.info("Creating project: #{project_name}")
|
141
|
+
project = client.create_project(project_name)
|
142
|
+
@env.ui.info("Project '#{project.name}' created successfully (ID: #{project.id})")
|
143
|
+
0
|
144
|
+
rescue StandardError => e
|
145
|
+
@env.ui.error("Failed to create project: #{e.message}")
|
146
|
+
1
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def execute_remove
|
151
|
+
parser = OptionParser.new do |o|
|
152
|
+
o.banner = 'Usage: vagrant eryph project remove <project-name> [options]'
|
153
|
+
o.separator ''
|
154
|
+
o.separator 'Options:'
|
155
|
+
o.on('--force', 'Do not ask for confirmation') do
|
156
|
+
@options[:force] = true
|
157
|
+
end
|
158
|
+
o.on('--no-wait', 'Do not wait for operation to complete') do
|
159
|
+
@options[:no_wait] = true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
begin
|
164
|
+
argv = parser.parse!(@sub_args.dup)
|
165
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument => e
|
166
|
+
@env.ui.error("#{e.message}")
|
167
|
+
@env.ui.info(parser.help, prefix: false)
|
168
|
+
return nil
|
169
|
+
end
|
170
|
+
|
171
|
+
if argv.empty?
|
172
|
+
@env.ui.error('Project name is required')
|
173
|
+
@env.ui.info(parser.help, prefix: false)
|
174
|
+
return 1
|
175
|
+
end
|
176
|
+
|
177
|
+
project_name = argv[0]
|
178
|
+
client = get_eryph_client
|
179
|
+
|
180
|
+
begin
|
181
|
+
project = client.get_project(project_name)
|
182
|
+
unless project
|
183
|
+
@env.ui.error("Project '#{project_name}' not found")
|
184
|
+
return 1
|
185
|
+
end
|
186
|
+
|
187
|
+
unless @options[:force]
|
188
|
+
response = @env.ui.ask("Project '#{project.name}' (ID: #{project.id}) and all catlets will be deleted! Continue? (y/N)")
|
189
|
+
return 0 unless response.downcase.start_with?('y')
|
190
|
+
end
|
191
|
+
|
192
|
+
@env.ui.info("Removing project: #{project.name}")
|
193
|
+
delete_project(client, project.id)
|
194
|
+
@env.ui.info("Project '#{project.name}' removed successfully")
|
195
|
+
0
|
196
|
+
rescue StandardError => e
|
197
|
+
@env.ui.error("Failed to remove project: #{e.message}")
|
198
|
+
1
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def get_eryph_client
|
203
|
+
# Try to get client from existing machines
|
204
|
+
@env.machine_names.each do |name|
|
205
|
+
machine = @env.machine(name, :eryph)
|
206
|
+
if machine.provider_config.is_a?(VagrantPlugins::Eryph::Config)
|
207
|
+
client = Helpers::EryphClient.new(machine)
|
208
|
+
|
209
|
+
# Override client configuration if command line options are provided
|
210
|
+
if @options[:configuration_name] || @options[:client_id]
|
211
|
+
override_client_config(client)
|
212
|
+
end
|
213
|
+
|
214
|
+
return client
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# If no machines found, create a temporary config with command line options
|
219
|
+
create_standalone_client
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def override_client_config(client)
|
225
|
+
# Update the client's configuration with command line options
|
226
|
+
config = client.instance_variable_get(:@config)
|
227
|
+
config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
|
228
|
+
config.client_id = @options[:client_id] if @options[:client_id]
|
229
|
+
|
230
|
+
# Force recreation of the client with new config
|
231
|
+
client.instance_variable_set(:@client, nil)
|
232
|
+
end
|
233
|
+
|
234
|
+
def create_standalone_client
|
235
|
+
# Create a minimal machine-like object for standalone client
|
236
|
+
require 'ostruct'
|
237
|
+
|
238
|
+
config = VagrantPlugins::Eryph::Config.new
|
239
|
+
config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
|
240
|
+
config.client_id = @options[:client_id] if @options[:client_id]
|
241
|
+
config.finalize!
|
242
|
+
|
243
|
+
# Create a fake machine with just the provider config we need
|
244
|
+
fake_machine = OpenStruct.new(
|
245
|
+
provider_config: config,
|
246
|
+
ui: @env.ui
|
247
|
+
)
|
248
|
+
|
249
|
+
Helpers::EryphClient.new(fake_machine)
|
250
|
+
rescue StandardError => e
|
251
|
+
raise "Failed to create Eryph client: #{e.message}. Please configure at least one machine with the Eryph provider or ensure your Eryph configuration is set up."
|
252
|
+
end
|
253
|
+
|
254
|
+
def delete_project(client, project_id)
|
255
|
+
operation = client.client.projects.projects_delete(project_id)
|
256
|
+
raise 'Failed to delete project: No operation returned' unless operation&.id
|
257
|
+
|
258
|
+
result = client.wait_for_operation(operation.id)
|
259
|
+
unless result.completed?
|
260
|
+
error_msg = result.status_message || 'Operation failed'
|
261
|
+
raise "Project deletion failed: #{error_msg}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class NetworkCommand < Vagrant.plugin('2', :command)
|
267
|
+
def self.synopsis
|
268
|
+
'manage project network configurations'
|
269
|
+
end
|
270
|
+
|
271
|
+
def initialize(argv, env)
|
272
|
+
super
|
273
|
+
@options = {}
|
274
|
+
@parser = OptionParser.new do |o|
|
275
|
+
o.banner = 'Usage: vagrant eryph network <subcommand> [options]'
|
276
|
+
o.separator ''
|
277
|
+
o.separator 'Subcommands:'
|
278
|
+
o.separator ' get Get project network configuration (YAML)'
|
279
|
+
o.separator ' set Set project network configuration from YAML'
|
280
|
+
o.separator ''
|
281
|
+
o.separator 'Global Options:'
|
282
|
+
|
283
|
+
o.on('--configuration-name NAME', String, 'Eryph configuration name (default: auto-detect)') do |name|
|
284
|
+
@options[:configuration_name] = name
|
285
|
+
end
|
286
|
+
|
287
|
+
o.on('--client-id ID', String, 'Eryph client ID') do |id|
|
288
|
+
@options[:client_id] = id
|
289
|
+
end
|
290
|
+
|
291
|
+
o.separator ''
|
292
|
+
end
|
293
|
+
|
294
|
+
@main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
|
295
|
+
end
|
296
|
+
|
297
|
+
def execute
|
298
|
+
case @sub_command
|
299
|
+
when 'get'
|
300
|
+
execute_get
|
301
|
+
when 'set'
|
302
|
+
execute_set
|
303
|
+
else
|
304
|
+
@env.ui.info(@parser.help, prefix: false)
|
305
|
+
1
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
def execute_get
|
312
|
+
parser = OptionParser.new do |o|
|
313
|
+
o.banner = 'Usage: vagrant eryph network get <project-name> [options]'
|
314
|
+
o.separator ''
|
315
|
+
o.separator 'Options:'
|
316
|
+
o.on('-o', '--output FILE', 'Write configuration to file') do |file|
|
317
|
+
@options[:output] = file
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
begin
|
322
|
+
argv = parser.parse!(@sub_args.dup)
|
323
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument => e
|
324
|
+
@env.ui.error("#{e.message}")
|
325
|
+
@env.ui.info(parser.help, prefix: false)
|
326
|
+
return nil
|
327
|
+
end
|
328
|
+
|
329
|
+
if argv.empty?
|
330
|
+
@env.ui.error('Project name is required')
|
331
|
+
@env.ui.info(parser.help, prefix: false)
|
332
|
+
return 1
|
333
|
+
end
|
334
|
+
|
335
|
+
project_name = argv[0]
|
336
|
+
client = get_eryph_client
|
337
|
+
|
338
|
+
begin
|
339
|
+
project = client.get_project(project_name)
|
340
|
+
unless project
|
341
|
+
@env.ui.error("Project '#{project_name}' not found")
|
342
|
+
return 1
|
343
|
+
end
|
344
|
+
|
345
|
+
config_response = client.client.virtual_networks.virtual_networks_get_config(project.id)
|
346
|
+
|
347
|
+
if config_response&.configuration
|
348
|
+
# Configuration is already a Hash/Object, convert symbols to strings for clean YAML
|
349
|
+
clean_config = deep_stringify_keys(config_response.configuration)
|
350
|
+
yaml_config = clean_config.to_yaml
|
351
|
+
|
352
|
+
if @options[:output]
|
353
|
+
File.write(@options[:output], yaml_config)
|
354
|
+
@env.ui.info("Network configuration written to: #{@options[:output]}")
|
355
|
+
else
|
356
|
+
@env.ui.info("Network configuration for project '#{project.name}':", prefix: false)
|
357
|
+
@env.ui.info(yaml_config, prefix: false)
|
358
|
+
end
|
359
|
+
else
|
360
|
+
@env.ui.info("No network configuration found for project '#{project.name}'")
|
361
|
+
end
|
362
|
+
0
|
363
|
+
rescue StandardError => e
|
364
|
+
@env.ui.error("Failed to get network configuration: #{e.message}")
|
365
|
+
1
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def execute_set
|
370
|
+
parser = OptionParser.new do |o|
|
371
|
+
o.banner = 'Usage: vagrant eryph network set <project-name> [options]'
|
372
|
+
o.separator ''
|
373
|
+
o.separator 'Options:'
|
374
|
+
o.on('-f', '--file FILE', 'Read configuration from file') do |file|
|
375
|
+
@options[:file] = file
|
376
|
+
end
|
377
|
+
o.on('-c', '--config CONFIG', 'Configuration as string') do |config|
|
378
|
+
@options[:config] = config
|
379
|
+
end
|
380
|
+
o.on('--force', 'Force import even if project names differ') do
|
381
|
+
@options[:force] = true
|
382
|
+
end
|
383
|
+
o.on('--no-wait', 'Do not wait for operation to complete') do
|
384
|
+
@options[:no_wait] = true
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
begin
|
389
|
+
argv = parser.parse!(@sub_args.dup)
|
390
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument => e
|
391
|
+
@env.ui.error("#{e.message}")
|
392
|
+
@env.ui.info(parser.help, prefix: false)
|
393
|
+
return nil
|
394
|
+
end
|
395
|
+
|
396
|
+
if argv.empty?
|
397
|
+
@env.ui.error('Project name is required')
|
398
|
+
@env.ui.info(parser.help, prefix: false)
|
399
|
+
return 1
|
400
|
+
end
|
401
|
+
|
402
|
+
unless @options[:file] || @options[:config]
|
403
|
+
@env.ui.error('Either --file or --config is required')
|
404
|
+
@env.ui.info(parser.help, prefix: false)
|
405
|
+
return 1
|
406
|
+
end
|
407
|
+
|
408
|
+
project_name = argv[0]
|
409
|
+
client = get_eryph_client
|
410
|
+
|
411
|
+
begin
|
412
|
+
project = client.get_project(project_name)
|
413
|
+
unless project
|
414
|
+
@env.ui.error("Project '#{project_name}' not found")
|
415
|
+
return 1
|
416
|
+
end
|
417
|
+
|
418
|
+
# Read configuration
|
419
|
+
config_content = if @options[:file]
|
420
|
+
unless File.exist?(@options[:file])
|
421
|
+
@env.ui.error("File not found: #{@options[:file]}")
|
422
|
+
return 1
|
423
|
+
end
|
424
|
+
File.read(@options[:file])
|
425
|
+
else
|
426
|
+
@options[:config]
|
427
|
+
end
|
428
|
+
|
429
|
+
# Parse configuration
|
430
|
+
config_data = parse_network_config(config_content)
|
431
|
+
unless config_data
|
432
|
+
@env.ui.error('Invalid configuration format. Expected YAML or JSON.')
|
433
|
+
return 1
|
434
|
+
end
|
435
|
+
|
436
|
+
# Validate project name in config
|
437
|
+
if config_data['project'] && config_data['project'] != project_name
|
438
|
+
unless @options[:force]
|
439
|
+
response = @env.ui.ask("Configuration was exported from project '#{config_data['project']}' but will be imported to '#{project_name}'. Continue? (y/N)")
|
440
|
+
return 0 unless response.downcase.start_with?('y')
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# Set project name in config
|
445
|
+
config_data['project'] = project_name
|
446
|
+
|
447
|
+
@env.ui.info("Setting network configuration for project '#{project.name}'...")
|
448
|
+
|
449
|
+
# Create request body - API expects Hash object directly
|
450
|
+
request_body = ::Eryph::ComputeClient::UpdateProjectNetworksRequestBody.new(
|
451
|
+
configuration: config_data
|
452
|
+
)
|
453
|
+
|
454
|
+
operation = client.client.virtual_networks.virtual_networks_update_config(
|
455
|
+
project.id,
|
456
|
+
request_body
|
457
|
+
)
|
458
|
+
|
459
|
+
raise 'Failed to update network configuration: No operation returned' unless operation&.id
|
460
|
+
|
461
|
+
unless @options[:no_wait]
|
462
|
+
result = client.wait_for_operation(operation.id)
|
463
|
+
unless result.completed?
|
464
|
+
error_msg = result.status_message || 'Operation failed'
|
465
|
+
raise "Network configuration update failed: #{error_msg}"
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
@env.ui.info("Network configuration updated successfully for project '#{project.name}'")
|
470
|
+
0
|
471
|
+
rescue StandardError => e
|
472
|
+
@env.ui.error("Failed to set network configuration: #{e.message}")
|
473
|
+
1
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def get_eryph_client
|
478
|
+
# Try to get client from existing machines
|
479
|
+
@env.machine_names.each do |name|
|
480
|
+
machine = @env.machine(name, :eryph)
|
481
|
+
if machine.provider_config.is_a?(VagrantPlugins::Eryph::Config)
|
482
|
+
client = Helpers::EryphClient.new(machine)
|
483
|
+
|
484
|
+
# Override client configuration if command line options are provided
|
485
|
+
if @options[:configuration_name] || @options[:client_id]
|
486
|
+
override_client_config(client)
|
487
|
+
end
|
488
|
+
|
489
|
+
return client
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# If no machines found, create a temporary config with command line options
|
494
|
+
create_standalone_client
|
495
|
+
end
|
496
|
+
|
497
|
+
private
|
498
|
+
|
499
|
+
def override_client_config(client)
|
500
|
+
# Update the client's configuration with command line options
|
501
|
+
config = client.instance_variable_get(:@config)
|
502
|
+
config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
|
503
|
+
config.client_id = @options[:client_id] if @options[:client_id]
|
504
|
+
|
505
|
+
# Force recreation of the client with new config
|
506
|
+
client.instance_variable_set(:@client, nil)
|
507
|
+
end
|
508
|
+
|
509
|
+
def create_standalone_client
|
510
|
+
# Create a minimal machine-like object for standalone client
|
511
|
+
require 'ostruct'
|
512
|
+
|
513
|
+
config = VagrantPlugins::Eryph::Config.new
|
514
|
+
config.configuration_name = @options[:configuration_name] if @options[:configuration_name]
|
515
|
+
config.client_id = @options[:client_id] if @options[:client_id]
|
516
|
+
config.finalize!
|
517
|
+
|
518
|
+
# Create a fake machine with just the provider config we need
|
519
|
+
fake_machine = OpenStruct.new(
|
520
|
+
provider_config: config,
|
521
|
+
ui: @env.ui
|
522
|
+
)
|
523
|
+
|
524
|
+
Helpers::EryphClient.new(fake_machine)
|
525
|
+
rescue StandardError => e
|
526
|
+
raise "Failed to create Eryph client: #{e.message}. Please configure at least one machine with the Eryph provider or ensure your Eryph configuration is set up."
|
527
|
+
end
|
528
|
+
|
529
|
+
def parse_network_config(config_string)
|
530
|
+
# Handle encoding issues first
|
531
|
+
# Detect UTF-16LE content (null bytes between characters)
|
532
|
+
if config_string.include?("\u0000")
|
533
|
+
# This is UTF-16LE content read as UTF-8, convert it properly
|
534
|
+
clean_config = config_string.force_encoding('UTF-16LE').encode('UTF-8')
|
535
|
+
else
|
536
|
+
clean_config = config_string
|
537
|
+
end
|
538
|
+
|
539
|
+
clean_config = clean_config.strip
|
540
|
+
clean_config = clean_config.gsub(/\r\n/, "\n")
|
541
|
+
|
542
|
+
# Try JSON first
|
543
|
+
if clean_config.start_with?('{') && clean_config.end_with?('}')
|
544
|
+
begin
|
545
|
+
return JSON.parse(clean_config)
|
546
|
+
rescue JSON::ParserError
|
547
|
+
return nil
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
# Try YAML
|
552
|
+
begin
|
553
|
+
return YAML.safe_load(clean_config)
|
554
|
+
rescue Psych::SyntaxError
|
555
|
+
return nil
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def deep_stringify_keys(obj)
|
560
|
+
case obj
|
561
|
+
when Hash
|
562
|
+
obj.each_with_object({}) do |(key, value), result|
|
563
|
+
result[key.to_s] = deep_stringify_keys(value)
|
564
|
+
end
|
565
|
+
when Array
|
566
|
+
obj.map { |item| deep_stringify_keys(item) }
|
567
|
+
else
|
568
|
+
obj
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|