razorrisk-cassini-utilities-cassis 0.7.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.
@@ -0,0 +1,743 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # ######################################################################## #
5
+ #
6
+ # The Cassini Installation driver Script
7
+ #
8
+ # Copyright (c) 2018, Razor Risk Technologies Pty Ltd All rights reserved.
9
+ #
10
+ # ######################################################################## #
11
+
12
+
13
+ # ##########################################################
14
+ # requires
15
+
16
+ require 'razor_risk/cassini/utilities/cassis/configuration_generator'
17
+ require 'razor_risk/cassini/utilities/cassis/program_utils'
18
+ require 'razor_risk/cassini/utilities/cassis/version'
19
+ require 'razor_risk/cassini/utilities/configuration'
20
+ require 'razor_risk/cassini/utilities/extensions/hash'
21
+
22
+ require 'highline/import'
23
+ require 'nokogiri'
24
+ require 'recls'
25
+
26
+ require 'pathname'
27
+ require 'securerandom'
28
+ require 'yaml'
29
+
30
+
31
+ # ##########################################################################
32
+ # constants
33
+
34
+ PROGRAM_VERSION = ::RazorRisk::Cassini::Utilities::CassIS::VERSION
35
+ DIAGNOSTIC_SEVERITIES = %w{ trace debug informational notice warning failure }
36
+
37
+ module Constants
38
+ CassiniSpecYml = 'cassini.spec.yml'
39
+ ConfigDir = 'config'
40
+ CassidYamlFile = 'cassid.yml'
41
+ SecretsFile = 'secrets.yml'
42
+ end
43
+
44
+
45
+ # ##########################################################
46
+ # includes
47
+
48
+ include ::RazorRisk::Cassini::Utilities
49
+ include ::RazorRisk::Cassini::Utilities::CassIS::ProgramUtils
50
+
51
+
52
+ # ##########################################################
53
+ # classes
54
+
55
+ Microservice = CassIS::ConfigurationGenerator::Microservice
56
+ Route = CassIS::ConfigurationGenerator::Microservice::Route
57
+
58
+ class RESTfulMicroservice < Microservice
59
+
60
+ def initialize name, external_route, service_path, routes, **options
61
+
62
+ super name, external_route, service_path, routes, options.merge({ restful: true })
63
+ end
64
+ end # class RESTfulMicroservice
65
+
66
+
67
+ # ##########################################################################
68
+ # traps
69
+
70
+ trap 'SIGINT' do
71
+ if $writing_init_config
72
+ $stderr.puts "\n#{basename}: processing cancelled without writing initial configuration file ..."
73
+ elsif $writing_secrets
74
+ $stderr.puts "\n#{basename}: processing cancelled without generating secrets ..."
75
+ elsif $configuring
76
+ $stderr.puts "\n#{basename}: processing cancelled without writing changes ..."
77
+ end
78
+ exit!
79
+ end
80
+
81
+
82
+ # ##########################################################
83
+ # functions
84
+
85
+ def parse_cassini_spec cassini_spec
86
+
87
+ microservices_stock = []
88
+ microservices_restful = []
89
+
90
+ cassini_spec['zips'].each do |microservice, zip|
91
+
92
+ if zip['is_cassini_microservice'] and zip['routes']
93
+ routes = zip['routes'].map do |h|
94
+ Route.new(
95
+ h['route'],
96
+ h['secure'] ? true : false,
97
+ h['verb'].to_sym
98
+ ) if h['include']
99
+ end.compact
100
+
101
+ if zip['is_restful']
102
+ microservices_restful << RESTfulMicroservice.new(
103
+ zip['name'],
104
+ zip['ex_route'],
105
+ zip['rel_path'],
106
+ routes,
107
+ )
108
+ else
109
+ microservices_stock << Microservice.new(
110
+ zip['name'],
111
+ zip['ex_route'],
112
+ zip['rel_path'],
113
+ routes,
114
+ )
115
+ end
116
+ end
117
+ end
118
+
119
+ CassIS::ConfigurationGenerator
120
+ .new(stock: microservices_stock, restful: microservices_restful)
121
+ .generate
122
+ .to_yaml(line_width: -1)
123
+ end
124
+
125
+ def generate_secrets
126
+
127
+ sf = {}
128
+ sf['secrets'] = {}
129
+ sf['secrets']['all'] = {}
130
+ sf['secrets']['all']['SHA256'] = SecureRandom.hex(16)
131
+ sf['secrets']['all']['HS256'] = SecureRandom.hex(16)
132
+ sf['secrets']['all']['AES-256-CBC'] = SecureRandom.hex(16)
133
+
134
+ sf.to_yaml(line_width: -1)
135
+ end
136
+
137
+ def current_or_nil h, *keys
138
+ keys.each do |key|
139
+ return nil unless h.has_key?(key)
140
+ h = h[key]
141
+ end
142
+ return nil if h.to_s =~ /^\*\*\*/
143
+ h
144
+ end
145
+
146
+ def read_environment_from_clarite_config(clar_conf)
147
+
148
+ begin
149
+ if xml_doc = ::Nokogiri.XML(File.read(clar_conf)) { |c| c.strict }
150
+ if xml_root = xml_doc.children.first
151
+ if rzr_svr = xml_root.at_xpath('razor_server')
152
+ env = rzr_svr['environment'] and return env
153
+ end
154
+ end
155
+ end
156
+ $stderr.puts "WARNING: The ClarITe configuration file '#{clar_conf}' does not have the element/attribute '/clarite_config/razor_server@environment'"
157
+ rescue ::Nokogiri::XML::SyntaxError => x
158
+ $stderr.puts "WARNING: The ClarITe configuration file '#{clar_conf}' does not contain a valid XML document : #{x.message}"
159
+ end
160
+
161
+ nil
162
+ end
163
+
164
+ def make_target_relative target_dir, rel_path
165
+ abs_path = Recls.combine_paths target_dir, rel_path
166
+ abs_path = Recls.canonicalise_path abs_path
167
+ abs_path
168
+ end
169
+
170
+ def update_edit_history properties
171
+ properties['edit-history'] ||= []
172
+ properties['edit-history'].unshift "This file reconfigured by '#{basename}' on #{Time.now.strftime('%Y/%m/%d %H:%M:%S.%L')}"
173
+ end
174
+
175
+ def configure_web_services conf, aborter, **options
176
+
177
+ begin
178
+
179
+
180
+ aborter.abort("configuration file - '#{cassid_yaml_path}' - does not contain a valid Cassini configuration") unless ::Hash === conf && conf.keys.size >= 4
181
+
182
+ properties = conf['properties']
183
+ cassini = conf['cassini']
184
+ control = conf['control']
185
+ diagnostics = conf['diagnostics']
186
+ razor = conf['razor']
187
+
188
+ unless properties && cassini && control && diagnostics && razor
189
+
190
+ aborter.abort("configuration file - '#{cassid_yaml_path}' - does not contain a valid Cassini configuration : it does not contain the required top-level elements")
191
+ end
192
+
193
+ # properties
194
+ update_edit_history properties
195
+
196
+ # general
197
+
198
+ say('General settings:')
199
+
200
+ abs_paths = options[:absolute_paths] || agree("\tDo you want to make all given paths absolute? ") { |q| q.default = false }
201
+
202
+ # cassini
203
+
204
+ say('Cassini settings:')
205
+
206
+ # root directory
207
+ #
208
+ current = current_or_nil(cassini, 'root_dir')
209
+ target_dir = current ? current : Dir.pwd
210
+ cassini['root_dir'] = target_dir
211
+
212
+ # - auth_mode
213
+
214
+ choices = %w{ authorisation_only basic jwt }
215
+ current = current_or_nil(cassini, 'auth_mode')
216
+ auth_mode = ask("\tAuthorisation mode (one of '#{choices.join('\', \'')}'): ", choices) { |q| q.default = current }
217
+
218
+ cassini['auth_mode'] = auth_mode
219
+
220
+ # - first_port
221
+
222
+ port_range = 1024..65000
223
+ current = current_or_nil(cassini, 'first_port')
224
+ current &&= current.to_s
225
+ first_port = ask("\tSpecify the first port in the range, which is used for the Cassini end-point (in the range #{port_range}) ", Integer) { |q| q.in = port_range; q.default = current }
226
+
227
+ cassini['first_port'] = first_port
228
+
229
+ # - host
230
+
231
+ current = current_or_nil(cassini, 'host') || 'localhost'
232
+ host = ask("\tSpecify the host name ") { |q| q.default = current }
233
+
234
+ cassini['host'] = host
235
+
236
+ # - web_server
237
+
238
+ choices = %w{ thin webrick }
239
+ current = current_or_nil(cassini, 'web_server') || 'thin'
240
+ web_server = ask("\tSpecify the web-server (one of '#{choices.join('\', \'')}'): ", choices) { |q| q.default = current }
241
+
242
+ cassini['web_server'] = web_server
243
+
244
+ # - web_ui_server
245
+
246
+ current = current_or_nil(cassini, 'web_ui_server')
247
+ web_ui_server = ask("\tSpecify the web-ui-server: ") { |q| q.default = current }
248
+
249
+ cassini['web_ui_server'] = web_ui_server
250
+
251
+ # - us_multiplier
252
+
253
+ usm_range = 1..50
254
+ current = current_or_nil(cassini, 'us_multiplier') || 5
255
+ current &&= current.to_s
256
+ us_multiplier = ask("\tSpecify how many instances of each internal microservice to be started ", Integer) { |q| q.in = usm_range; q.default = current }
257
+
258
+ cassini['us_multiplier'] = us_multiplier
259
+
260
+ # - JWT
261
+ #
262
+ #
263
+
264
+ want_jwt = cassini['auth_mode'] == 'jwt'
265
+
266
+ unless want_jwt
267
+
268
+ cassini['secret_server'] = nil
269
+ else
270
+
271
+ say("\tJWT/Secret Server settings:")
272
+
273
+ jwt_host = nil
274
+ jwt_port = nil
275
+ jwt_service = nil
276
+ jwt_secret = nil
277
+
278
+ cur_jwt_host = nil
279
+ cur_jwt_port = nil
280
+ cur_jwt_service = nil
281
+ cur_jwt_secret = nil
282
+
283
+ # Get the current settings if any
284
+ case ss = cassini['secret_server']
285
+ when nil
286
+
287
+ ;
288
+ when ::Hash
289
+
290
+ cur_jwt_host = current_or_nil(cassini, 'secret_server', 'host')
291
+ cur_jwt_port = current_or_nil(cassini, 'secret_server', 'port')
292
+ cur_jwt_secret = current_or_nil(cassini, 'secret_server', 'secrets_path')
293
+ else
294
+
295
+ fail("configuration file - '#{cassid_yaml_path}' - does not contain a valid Cassini configuration : the 'cassini/secret_server' element is not a hash")
296
+ end
297
+
298
+ # Use top level host if not already defined
299
+ if cur_jwt_host.nil?
300
+ cur_jwt_host = cassini['host']
301
+ end
302
+
303
+ jwt_host = ask("\t\tSpecify the jwt/secret server host name ") { |q| q.default = cur_jwt_host }
304
+
305
+ if cur_jwt_port.nil?
306
+
307
+ change_jwt_port = true
308
+ else
309
+
310
+ change_jwt_port = agree("\t\tWould you like to change the jwt/secret port from '#{cur_jwt_port}' (Yes|No) ") { |q| q.default = 'no' }
311
+ end
312
+
313
+ if change_jwt_port
314
+
315
+ jwt_port = ask("\t\tSpecify the jwt/secret server port (leave blank to be assigned automatically) ")
316
+ else
317
+
318
+ jwt_port = cur_jwt_port
319
+ end
320
+
321
+ # secret/seed file (for test/uat jwt config)
322
+ if cur_jwt_secret.nil?
323
+ cur_jwt_secret = 'config/secrets.yml'
324
+ end
325
+
326
+ ask("\t\tSpecify the jwt/secret server secret(s) configuration path (either absolute, or relative to '#{target_dir}'): ") do |q|
327
+
328
+ q.default = cur_jwt_secret
329
+ q.validate = lambda do |v|
330
+
331
+ result = nil
332
+
333
+ unless v.strip.empty?
334
+
335
+ result ||= Recls.stat(v)
336
+ result ||= Recls.stat(Recls.combine_paths(target_dir, v))
337
+
338
+ result = nil if result && !result.file?
339
+ end
340
+
341
+ jwt_secret = result.to_s
342
+
343
+ result
344
+ end
345
+ q.responses[:not_valid] = "The given secret configuration path does not specify an existing file or is not relative to '#{target_dir}'"
346
+ end
347
+
348
+ # Cant think of a way around this one yet - maybe should actually check it exists under these conditions
349
+ jwt_service = 'bin/razorrisk-microservice-secretserver'
350
+
351
+ cassini['secret_server'] = {}
352
+ cassini['secret_server']['host'] = jwt_host
353
+ cassini['secret_server']['port'] = jwt_port.to_i unless jwt_port.nil? || jwt_port.empty?
354
+ cassini['secret_server']['service_path'] = jwt_service
355
+ cassini['secret_server']['secrets_path'] = jwt_secret
356
+ end
357
+
358
+ # - TLS
359
+ #
360
+ # This one is a little more complex, because we want to look whether
361
+ # there's currently any TLS
362
+
363
+ has_tls = !cassini['tls'].nil?
364
+ want_tls = agree("\tDo you wish to use TLS (https://)? (Yes|No) ") { |q| q.default = 'yes' if has_tls }
365
+
366
+ unless want_tls
367
+
368
+ cassini['tls'] = nil
369
+ else
370
+
371
+ tls_cert = nil
372
+ tls_pkey = nil
373
+
374
+ cur_cert = nil
375
+ cur_pkey = nil
376
+
377
+ case tls = cassini['tls']
378
+ when nil
379
+
380
+ ;
381
+ when ::String
382
+
383
+ cur_cert = current_or_nil(cassini, 'tls')
384
+ when ::Hash
385
+
386
+ cur_cert = current_or_nil(cassini, 'tls', 'cert')
387
+ cur_pkey = current_or_nil(cassini, 'tls', 'key')
388
+ else
389
+
390
+ aborter.abort("configuration file - '#{cassid_yaml_path}' - does not contain a valid Cassini configuration : the 'cassini/tls' element is neither a hash nor a string")
391
+ end
392
+
393
+
394
+ tls_cert = nil
395
+ ask("\tSpecify the TLS certificate path: ") do |q|
396
+
397
+ q.default = cur_cert
398
+ q.validate = lambda do |v|
399
+
400
+ result = nil
401
+
402
+ unless v.strip.empty?
403
+
404
+ result ||= Recls.stat(v)
405
+ result ||= Recls.stat(Recls.combine_paths(target_dir, v))
406
+
407
+ result = nil if result && !result.file?
408
+ end
409
+
410
+ tls_cert = result.to_s
411
+
412
+ result
413
+ end
414
+ q.responses[:not_valid] = "The given certificate file path does not specify an existing file or is not relative to '#{target_dir}'"
415
+ end
416
+
417
+ unless cur_pkey
418
+
419
+ fe_cert = Recls.stat(tls_cert)
420
+
421
+ %w{ .key .pkey }.each do |ext|
422
+
423
+ path_pkey = "#{fe_cert.directory_path}#{fe_cert.stem}#{ext}"
424
+
425
+ if cur_pkey = Recls.stat(path_pkey)
426
+
427
+ cur_pkey = cur_pkey.to_s
428
+
429
+ break
430
+ end
431
+ end
432
+ end
433
+
434
+ tls_pkey = ask("\tSpecify the TLS certificate public key path: ") do |q|
435
+
436
+ q.default = cur_pkey
437
+ q.validate = lambda do |v|
438
+
439
+ result = nil
440
+
441
+ unless v.strip.empty?
442
+
443
+ result ||= Recls.stat(v)
444
+ result ||= Recls.stat(Recls.combine_paths(target_dir, v))
445
+
446
+ result = nil if result && !result.file?
447
+ end
448
+
449
+ result
450
+ end
451
+ q.responses[:not_valid] = "The given certificate public key file path does not specify an existing file or is not relative to '#{target_dir}'"
452
+ end
453
+
454
+ cassini['tls'] = {}
455
+
456
+ if abs_paths
457
+
458
+ tls_cert = make_target_relative(target_dir, tls_cert)
459
+ tls_pkey = make_target_relative(target_dir, tls_pkey)
460
+ end
461
+
462
+ cassini['tls']['cert'] = tls_cert
463
+ cassini['tls']['key'] = tls_pkey
464
+ end
465
+
466
+
467
+ # control
468
+
469
+ say('Control settings:')
470
+
471
+ # - detach
472
+
473
+ current = current_or_nil(control, 'detach')
474
+ current = current ? 'yes' : 'no' unless current.nil?
475
+ detach = agree("\tDo you wish to run in detached mode? (Yes|No) ") { |q| q.default = current }
476
+
477
+ control['detach'] = detach
478
+
479
+
480
+ # diagnostics
481
+
482
+ say('Diagnostic settings:')
483
+
484
+ # - benchmark
485
+
486
+ current = current_or_nil(diagnostics, 'benchmark')
487
+ current = current ? 'yes' : 'no' unless current.nil?
488
+ benchmark = agree("\tDo you wish to conduct benchmark logging? (Yes|No) ") { |q| q.default = current }
489
+
490
+ diagnostics['benchmark'] = benchmark
491
+
492
+ # - console/threshold
493
+
494
+ choices = DIAGNOSTIC_SEVERITIES
495
+ current = current_or_nil(diagnostics, 'console', 'threshold')
496
+ thr_console = ask("\tDiagnostic logging threshold for console logging (one of '#{choices.join('\', \'')}'): ", choices) { |q| q.default = current }
497
+
498
+ diagnostics['console'] ||= {}
499
+ diagnostics['console']['threshold'] = thr_console
500
+
501
+ # - main/threshold
502
+
503
+ choices = DIAGNOSTIC_SEVERITIES
504
+ current = current_or_nil(diagnostics, 'main', 'threshold')
505
+ thr_main = ask("\tDiagnostic logging threshold for file logging (one of '#{choices.join('\', \'')}'): ", choices) { |q| q.default = current }
506
+
507
+ diagnostics['main'] ||= {}
508
+ diagnostics['main']['threshold'] = thr_main
509
+
510
+ # - directory
511
+
512
+ current = current_or_nil(diagnostics, 'directory')
513
+ diag_dir = nil
514
+ ask("\tDiagnostic logging directory (either absolute, or relative to '#{target_dir}'): ") do |d|
515
+
516
+ d.default = current || 'logs'
517
+ d.validate = lambda do |v|
518
+
519
+ result = nil
520
+ v = v.strip
521
+
522
+ unless v.empty?
523
+
524
+ result ||= Recls.stat(v)
525
+ result ||= Recls.stat(Recls.combine_paths(target_dir, v))
526
+
527
+ if result.nil?
528
+ result = v
529
+ elsif result.file?
530
+ result = nil
531
+ end
532
+ end
533
+
534
+ diag_dir = result.to_s
535
+ result
536
+ end
537
+ d.responses[:not_valid] = "The given logging directory does not specify a valid directory path"
538
+ end
539
+ diag_dir = make_target_relative(target_dir, diag_dir) if abs_paths
540
+ diagnostics['directory'] = diag_dir
541
+
542
+ # razor
543
+
544
+ say('Razor settings:')
545
+
546
+ # - clarite_config
547
+
548
+ current = current_or_nil(razor, 'clarite_config')
549
+ clar_conf = nil
550
+ ask("\tClarITe configuration path (either absolute, or relative to '#{target_dir}'): ") do |q|
551
+
552
+ q.default = current
553
+ q.validate = lambda do |v|
554
+
555
+ result = nil
556
+
557
+ unless v.strip.empty?
558
+
559
+ begin
560
+ result ||= Recls.stat(v)
561
+ result ||= Recls.stat(Recls.combine_paths(target_dir, v))
562
+
563
+ if !result.nil? and result.directory?
564
+ result = Recls.stat(
565
+ Recls.combine_paths(
566
+ result.path,
567
+ 'clarite_config.xml'
568
+ )
569
+ )
570
+ end
571
+
572
+ result = nil if result && !result.file?
573
+ rescue Errno::EINVAL
574
+ result = nil
575
+ end
576
+ end
577
+
578
+ clar_conf = result.to_s
579
+
580
+ result
581
+ end
582
+ q.responses[:not_valid] = "The given ClarITe configuration path does not specify an existing file or is not relative to '#{target_dir}'"
583
+ end
584
+
585
+ clar_conf = make_target_relative(target_dir, clar_conf) if abs_paths
586
+
587
+ razor['clarite_config'] = clar_conf
588
+
589
+ # - environment
590
+
591
+ current = current_or_nil(razor, 'environment')
592
+ current ||= read_environment_from_clarite_config(Recls.stat(clar_conf).to_s)
593
+ environment = ask("\tRazor environment name: ") { |q| q.default = current; q.validate = /^[A-Z][-A-Z0-9_$#.]*$/i; q.responses[:not_valid] = "Must supply a valid name" }
594
+
595
+ razor['environment'] = environment
596
+
597
+ # - executable
598
+
599
+ current = current_or_nil(razor, 'executable')
600
+ executable = nil
601
+ ask("\tRazor Connectivity Adaptor Executable path (either absolute, or relative to '#{target_dir}'): ") do |q|
602
+
603
+ q.default = current
604
+ q.validate = lambda do |v|
605
+
606
+ result = nil
607
+
608
+ unless v.strip.empty?
609
+
610
+ begin
611
+ result ||= Recls.stat(v)
612
+ result ||= Recls.stat(Recls.combine_paths(target_dir, v))
613
+
614
+ if !result.nil? and !result.file?
615
+ dir = result.path
616
+ result = Recls.stat(
617
+ Recls.combine_paths(
618
+ dir,
619
+ 'RazorRequest.exe'
620
+ )
621
+ )
622
+ result ||= Recls.stat(
623
+ Recls.combine_paths(
624
+ dir,
625
+ 'RazorRisk.Cassini.ConnectivityAdapter.exe'
626
+ )
627
+ )
628
+ end
629
+
630
+ result = nil if result && !result.file?
631
+ rescue Errno::EINVAL
632
+ result = nil
633
+ end
634
+ end
635
+
636
+ executable = result.to_s
637
+
638
+ result
639
+ end
640
+ q.responses[:not_valid] = "The given Razor Connectivity Adaptor Executable path does not specify an existing file or is not relative to '#{target_dir}'"
641
+ end
642
+
643
+ executable = make_target_relative(target_dir, executable) if abs_paths
644
+ razor['executable'] = executable
645
+
646
+ conf.to_yaml(line_width: -1)
647
+ rescue ::ArgumentError, ::NameError, ::NoMethodError, ::TypeError => x
648
+
649
+ $stderr.puts "unexpected exception (#{x.class}): '#{x.message}': #{x.backtrace}"
650
+
651
+ raise
652
+ rescue ::StandardError => x
653
+
654
+ $stderr.puts "#{basename}: failed with exception of type #{x.class}: #{x.message}"
655
+
656
+ exit 1
657
+ end
658
+ end
659
+
660
+
661
+ # ##########################################################
662
+ # main
663
+
664
+ options = Configuration::CLI.parse_args ARGV.dup
665
+
666
+ # Generate initial config file
667
+ if options[:gen_init_conf]
668
+
669
+ $writing_init_config = true
670
+
671
+ # Cassini Spec is required for initial generation
672
+ unless File.file? Constants::CassiniSpecYml
673
+ options[:aborter].abort "Cassini Spec file is required to generate initial cassini config"
674
+ end
675
+
676
+ # Do not overwrite existing file
677
+ path = File.join(Constants::ConfigDir, Constants::CassidYamlFile)
678
+ options[:aborter].abort "Cassini Config file already exists" if File.file? path
679
+
680
+ Dir.mkdir Constants::ConfigDir unless File.directory? Constants::ConfigDir
681
+ cassini_spec = YAML.load_file(Constants::CassiniSpecYml)
682
+ initial_config = parse_cassini_spec cassini_spec
683
+ File.open(path, 'w') { |f| f.write initial_config }
684
+
685
+ $writing_init_config = false
686
+ end
687
+
688
+ # Generate Secrets File
689
+ if options[:gen_secrets]
690
+
691
+ $writing_secrets = true
692
+
693
+ path = File.join(Constants::ConfigDir, Constants::SecretsFile)
694
+
695
+ # Do not overwrite existing file
696
+ options[:aborter].abort "Secrets file already exists" if File.file? path
697
+
698
+ Dir.mkdir Constants::ConfigDir unless File.directory? Constants::ConfigDir
699
+ secrets = generate_secrets
700
+ File.open(path, 'w') { |f| f.write secrets }
701
+
702
+ $writing_secrets = false
703
+ end
704
+
705
+ # Configure cassini
706
+ unless options[:no_configuration]
707
+
708
+ $configuring = true
709
+
710
+ cassid_yaml_path = File.join(Constants::ConfigDir, Constants::CassidYamlFile)
711
+ cassid_yaml = YAML.load_file(cassid_yaml_path)
712
+ cassid_yaml = configure_web_services cassid_yaml, options[:aborter], options
713
+ if agree("\nYou have completed (re)configuration of '#{cassid_yaml_path}'. Do you wish to save your changes (which will overwrite the existing configuration)? (Yes|No) ")
714
+ File.open(cassid_yaml_path, 'w') { |f| f.write cassid_yaml }
715
+ end
716
+
717
+ $configuring = false
718
+ end
719
+
720
+ if options[:input_config]
721
+
722
+ $configuring = true
723
+
724
+ cassid_yaml_path = File.join(Constants::ConfigDir, Constants::CassidYamlFile)
725
+ cassid_yaml = YAML.load_file(cassid_yaml_path)
726
+
727
+ input_yaml_path = File.expand_path(options[:input_config])
728
+ input_yaml = YAML.load_file(input_yaml_path)
729
+
730
+ cassid_yaml = cassid_yaml.deep_merge(input_yaml)
731
+
732
+ cassid_yaml['properties'] ||= {}
733
+ update_edit_history cassid_yaml['properties']
734
+
735
+ cassid_yaml = cassid_yaml.to_yaml(line_width: -1)
736
+ File.open(cassid_yaml_path, 'w') { |f| f.write cassid_yaml }
737
+
738
+ $configuring = false
739
+ end
740
+
741
+ # ############################## end of file ############################# #
742
+
743
+