govuk-connect 0.0.2 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 155ea20492095add3ae9e4680886c07012bcd01f41bdebfbe6cb43ad4b4b9cdc
4
- data.tar.gz: 902527a0b134cfebb2d7e85c0c49426f3834d2dd8df7c5e1899ba97249947f03
3
+ metadata.gz: 3ab00b53cb2051839c1904260cab6b947201e9a14ef150ccc68bed765ab5faed
4
+ data.tar.gz: aaf2f9d137b13b5fe11157a1dea809f6ec0ac8614c6139a7a1526fedf7d62c49
5
5
  SHA512:
6
- metadata.gz: 0cd41a07139275d3f1482863a23dad9f86a3cfe9526eb1291841117da08bb7d5491a5242a39bc2b7b8bf88b6f4b7dfc5daab0a0d1c99d2450abb864c0d554173
7
- data.tar.gz: 263cbc97edcabb670d577cf9a8dd53a70908b44066ca717395a4b55a15f730232121f8cccb1403e09db2e8bc410a2efd167bf473f9d5e83b5428f101ba3b1ed9
6
+ metadata.gz: 8e25fbc7b08900e9ffac04cc1a8491422c73179946e273bfe16e7754dfefa97d71741e3931bd4eb90d0c65fdf351ffb297aff83a604fadfeb95a76f33b35c36a
7
+ data.tar.gz: cb727b8cfa99fb47935358e7de62457d3ce65c78db64411b8a9d8f32dc21bae988b0a561723a87f4fb5e348d4871c554c934d54feb74954f2442cd65856c6e59
@@ -5,4 +5,4 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  require 'govuk_connect'
7
7
 
8
- main
8
+ GovukConnect.main(ARGV)
@@ -1,1060 +1,7 @@
1
- #!/usr/bin/env ruby
1
+ require "govuk_connect/cli"
2
2
 
3
- #
4
- # This script aims to make it easy to connect to various parts of the GOV.UK
5
- # infrastructure. It aims to provide a consistent way to do this, even across
6
- # different hosting providers.
7
- #
8
- #
9
- # Usage: govuk-connect TYPE TARGET [options]
10
- # -e, --environment ENVIRONMENT Select which environment to connect to
11
- # --hosting-and-environment-from-alert-url URL
12
- # Select which environment to connect to based on the URL provided.
13
- # -p, --port-forward SERVICE Connect to a remote port
14
- # -v, --verbose Enable more detailed logging
15
- # -h, --help Prints usage information and examples
16
- #
17
- # CONNECTION TYPES
18
- # app-console
19
- # Launch a console for an application. For example, a rails console when connecting to a Rails application.
20
- # app-dbconsole
21
- # Launch a console for the database for an application.
22
- # rabbitmq
23
- # Setup port forwarding to the RabbitMQ admin interface.
24
- # sidekiq-monitoring
25
- # Setup port forwarding to the Sidekiq Monitoring application.
26
- # ssh
27
- # Connect to a machine through SSH.
28
- #
29
- # MACHINE TARGET
30
- # The ssh, rabbitmq and sidekiq-monitoring connection types target
31
- # machines.
32
- #
33
- # The machine can be specified by name, for example:
34
- #
35
- # govuk-connect ssh -e integration backend
36
- #
37
- # If the hosting provider is ambiguous, you'll need to specify it prior
38
- # to the name, for example:
39
- #
40
- # govuk-connect ssh -e staging aws/backend
41
- #
42
- # If you want to connect to a specific machine, you can specify a number
43
- # after the name, for example:
44
- #
45
- # govuk-connect ssh -e integration backend:2
46
- #
47
- # APPLICATION TARGET
48
- # The app-console and app-dbconsole connection types target
49
- # applications.
50
- #
51
- # The application is specified by name, for example:
52
- #
53
- # govuk-connect app-console -e integration publishing-api
54
- #
55
- # If the node class is ambiguous, you'll need to specify it prior to
56
- # the name, for example:
57
- #
58
- # govuk-connect app-console -e integration whitehall_backend/whitehall
59
- #
60
- # If you want to connect to a specific machine, you can specify a
61
- # number after the name, for example:
62
- #
63
- # govuk-connect app-console -e integration publishing-api:2
64
- #
65
- # EXAMPLES
66
- # govuk-connect ssh --environment integration backend
67
- #
68
- # govuk-connect app-console --environment staging publishing-api
69
- #
70
- # govuk-connect app-dbconsole -e integration whitehall_backend/whitehall
71
- #
72
- # govuk-connect rabbitmq -e staging aws/rabbitmq
73
- #
74
- # govuk-connect sidekiq-monitoring -e integration
75
-
76
-
77
- require 'uri'
78
- require 'yaml'
79
- require 'open3'
80
- require 'socket'
81
- require 'timeout'
82
- require 'optparse'
83
- require 'govuk_connect/version'
84
-
85
- def bold(string)
86
- "\e[1m#{string}\e[0m"
87
- end
88
-
89
- USAGE_BANNER = 'Usage: govuk-connect TYPE TARGET [options]'
90
-
91
- EXAMPLES = <<EXAMPLES
92
- govuk-connect ssh --environment integration backend
93
-
94
- govuk-connect app-console --environment staging publishing-api
95
-
96
- govuk-connect app-dbconsole -e integration whitehall_backend/whitehall
97
-
98
- govuk-connect rabbitmq -e staging aws/rabbitmq
99
-
100
- govuk-connect sidekiq-monitoring -e integration
101
- EXAMPLES
102
-
103
- MACHINE_TARGET_DESCRIPTION = <<DOCS
104
- The ssh, rabbitmq and sidekiq-monitoring connection types target
105
- machines.
106
-
107
- The machine can be specified by name, for example:
108
-
109
- govuk-connect ssh -e integration #{bold('backend')}
110
-
111
- If the hosting provider is ambiguous, you'll need to specify it prior
112
- to the name, for example:
113
-
114
- govuk-connect ssh -e staging #{bold('aws/')}backend
115
-
116
- If you want to connect to a specific machine, you can specify a number
117
- after the name, for example:
118
-
119
- govuk-connect ssh -e integration backend#{bold(':2')}
120
- DOCS
121
-
122
- APP_TARGET_DESCRIPTION = <<DOCS
123
- The app-console and app-dbconsole connection types target
124
- applications.
125
-
126
- The application is specified by name, for example:
127
-
128
- govuk-connect app-console -e integration #{bold('publishing-api')}
129
-
130
- If the node class is ambiguous, you'll need to specify it prior to
131
- the name, for example:
132
-
133
- govuk-connect app-console -e integration #{bold('whitehall_backend/')}whitehall
134
-
135
- If you want to connect to a specific machine, you can specify a
136
- number after the name, for example:
137
-
138
- govuk-connect app-console -e integration publishing-api#{bold(':2')}
139
- DOCS
140
-
141
- CONNECTION_TYPE_DESCRIPTIONS = {
142
- 'ssh' => 'Connect to a machine through SSH.',
143
- 'app-console' => 'Launch a console for an application. For example, a rails console when connecting to a Rails application.',
144
- 'app-dbconsole' => 'Launch a console for the database for an application.',
145
- 'rabbitmq' => 'Setup port forwarding to the RabbitMQ admin interface.',
146
- 'sidekiq-monitoring' => 'Setup port forwarding to the Sidekiq Monitoring application.'
147
- }
148
-
149
- RABBITMQ_PORT = 15672
150
- SIDEKIQ_MONITORING_PORT = 3211
151
-
152
- JUMPBOXES = {
153
- ci: {
154
- carrenza: 'ci-jumpbox.integration.publishing.service.gov.uk',
155
- },
156
- integration: {
157
- aws: 'jumpbox.integration.publishing.service.gov.uk',
158
- },
159
- staging: {
160
- carrenza: 'jumpbox.staging.publishing.service.gov.uk',
161
- aws: 'jumpbox.staging.govuk.digital',
162
- },
163
- production: {
164
- carrenza: 'jumpbox.publishing.service.gov.uk',
165
- aws: 'jumpbox.production.govuk.digital',
166
- }
167
- }
168
-
169
- def log(message)
170
- STDERR.puts message if $verbose
171
- end
172
-
173
- def newline
174
- STDERR.puts
175
- end
176
-
177
- def info(message)
178
- STDERR.puts message
179
- end
180
-
181
- def error(message)
182
- STDERR.puts "\e[1m\e[31m#{message}\e[0m"
183
- end
184
-
185
- def print_ssh_username_configuration_help
186
- info "The SSH username used was: #{bold(ssh_username)}"
187
- info "Check this is correct, and if it isn't, set the `USER` environment variable to the correct username."
188
- end
189
-
190
- # From Rosetta Code: https://rosettacode.org/wiki/Levenshtein_distance#Ruby
191
- def levenshtein_distance(a, b)
192
- a, b = a.downcase, b.downcase
193
- costs = Array(0..b.length) # i == 0
194
- (1..a.length).each do |i|
195
- costs[0], nw = i, i - 1 # j == 0; nw is lev(i-1, j)
196
- (1..b.length).each do |j|
197
- costs[j], nw = [
198
- costs[j] + 1, costs[j-1] + 1,
199
- a[i-1] == b[j-1] ? nw : nw + 1
200
- ].min, costs[j]
201
- end
202
- end
203
- costs[b.length]
204
- end
205
-
206
- def strings_similar_to(target, strings)
207
- strings.select do |s|
208
- levenshtein_distance(s, target) <= 3 # No specific reasoning for this value
209
- end
210
- end
211
-
212
- def check_ruby_version_greater_than(required_major:, required_minor:)
213
- major, minor = RUBY_VERSION.split '.'
214
-
215
- insufficient_version = (
216
- major.to_i < required_major || (
217
- major.to_i == required_major &&
218
- minor.to_i < required_minor
219
- )
220
- )
221
-
222
- if insufficient_version
223
- error "insufficient Ruby version: #{RUBY_VERSION}"
224
- error "must be at least #{required_major}.#{required_minor}"
225
-
226
- exit 1
227
- end
228
- end
229
-
230
- def port_free?(port)
231
- # No idea how well this works, but it's hopefully better than nothing
232
-
233
- log "debug: checking if port #{port} is free"
234
- Socket.tcp('127.0.0.1', port, connect_timeout: 0.1) {}
235
- false
236
- rescue Errno::ETIMEDOUT
237
- log "debug: port #{port} doesn't seem to be free"
238
- false
239
- rescue Errno::ECONNREFUSED
240
- log "debug: port #{port} is free"
241
- true
242
- end
243
-
244
- def random_free_port
245
- tries = 0
246
-
247
- while tries <= 10
248
- port = rand(32768...61000)
249
-
250
- return port if port_free? port
251
-
252
- tries += 1
253
- end
254
-
255
- raise "couldn't find open port"
256
- end
257
-
258
- def hosting_providers
259
- JUMPBOXES
260
- .map { |env, jumpboxes| jumpboxes.keys }
261
- .flatten
262
- .uniq
263
- end
264
-
265
- def jumpbox_for_environment_and_hosting(environment, hosting)
266
- raise 'missing environment' unless environment
267
- raise 'missing hosting' unless hosting
268
-
269
- jumpbox = JUMPBOXES[environment][hosting]
270
-
271
- unless jumpbox
272
- error "error: couldn't determine jumpbox for #{hosting}/#{environment}"
273
- exit 1
274
- end
275
-
276
- jumpbox
277
- end
278
-
279
- def single_hosting_provider_for_environment(environment)
280
- jumpboxes = JUMPBOXES[environment]
281
-
282
- if jumpboxes.size == 1
283
- jumpboxes.keys[0]
284
- else
285
- false
286
- end
287
- end
288
-
289
- def config_file
290
- @config_file ||= begin
291
- directory = ENV.fetch('XDG_CONFIG_HOME', "#{Dir.home}/.config")
292
-
293
- File.join(directory, 'config.yaml')
294
- end
295
- end
296
-
297
- def ssh_username
298
- @ssh_username ||= begin
299
- if File.exist? config_file
300
- config_ssh_username = YAML::load_file(config_file)['ssh_username']
301
- end
302
-
303
- config_ssh_username or ENV['USER']
304
- end
305
- end
306
-
307
- def ssh_identity_file
308
- @ssh_identity_file ||= begin
309
- YAML::load_file(config_file)['ssh_identity_file'] if File.exist? config_file
310
- end
311
- end
312
-
313
- def ssh_identity_arguments
314
- if ssh_identity_file
315
- ['-i', ssh_identity_file]
316
- else
317
- []
318
- end
319
- end
320
-
321
- def user_at_host(user, host)
322
- "#{user}@#{host}"
323
- end
324
-
325
- def govuk_node_list_classes(environment, hosting)
326
- log "debug: looking up classes in #{hosting}/#{environment}"
327
- command = [
328
- 'ssh',
329
- '-o', 'ConnectTimeout=2', # Show a failure quickly
330
- *ssh_identity_arguments,
331
- user_at_host(
332
- ssh_username,
333
- jumpbox_for_environment_and_hosting(environment, hosting)
334
- ),
335
- "govuk_node_list --classes",
336
- ].join(' ')
337
-
338
- log "debug: running command: #{command}"
339
- output, status = Open3.capture2(command)
340
-
341
- unless status.success?
342
- error "\nerror: command failed: #{command}"
343
- newline
344
- print_ssh_username_configuration_help
345
- exit 1
346
- end
347
-
348
- classes = output.split("\n").sort
349
-
350
- log "debug: classes:"
351
- classes.each { |c| log " - #{c}" }
352
-
353
- classes
354
- end
355
-
356
- def get_domains_for_node_class(target, environment, hosting, ssh_username)
357
- command = [
358
- 'ssh',
359
- '-o', 'ConnectTimeout=2', # Show a failure quickly
360
- *ssh_identity_arguments,
361
- user_at_host(
362
- ssh_username,
363
- jumpbox_for_environment_and_hosting(environment, hosting)
364
- ),
365
- "govuk_node_list -c #{target}",
366
- ].join(' ')
367
-
368
- output, status = Open3.capture2(command)
369
-
370
- unless status.success?
371
- error "error: command failed: #{command}"
372
- newline
373
- print_ssh_username_configuration_help
374
- exit 1
375
- end
376
-
377
- output.split("\n").sort
378
- end
379
-
380
- def govuk_directory
381
- File.join(ENV['HOME'], 'govuk')
382
- end
383
-
384
- def govuk_puppet_node_class_data(environment, hosting)
385
- log "debug: fetching govuk-puppet node class data for #{hosting} #{environment}"
386
-
387
- local_hieradata_root = File.join(
388
- govuk_directory,
389
- 'govuk-puppet',
390
- {
391
- carrenza: 'hieradata',
392
- aws: 'hieradata_aws'
393
- }[hosting]
394
- )
395
-
396
- hieradata_file = File.join(local_hieradata_root, "#{environment}.yaml")
397
- log "debug: reading #{hieradata_file}"
398
-
399
- environment_specific_hieradata = YAML::load_file(hieradata_file)
400
-
401
- if environment_specific_hieradata['node_class']
402
- environment_specific_hieradata['node_class']
403
- else
404
- common_hieradata = YAML::load_file(
405
- File.join(local_hieradata_root, 'common.yaml')
406
- )
407
-
408
- common_hieradata['node_class']
409
- end
410
- end
411
-
412
- def node_classes_for_environment_and_hosting(environment, hosting)
413
- govuk_puppet_node_class_data(
414
- environment,
415
- hosting
416
- ).map do |node_class, _data|
417
- node_class
418
- end
419
- end
420
-
421
- def application_names_from_node_class_data(environment, hosting)
422
- node_class_data = govuk_puppet_node_class_data(
423
- environment,
424
- hosting
425
- )
426
-
427
- all_names = node_class_data.flat_map do |node_class, data|
428
- data['apps']
429
- end
430
-
431
- all_names.sort.uniq
432
- end
433
-
434
- def node_class_for_app(app_name, environment, hosting)
435
- log "debug: finding node class for #{app_name} in #{hosting} #{environment}"
436
-
437
- node_class_data = govuk_puppet_node_class_data(
438
- environment,
439
- hosting
440
- )
441
-
442
- app_lookup_hash = {}
443
- node_class_data.each do |node_class, data|
444
- data['apps'].each do |app|
445
- if app_lookup_hash.key? app
446
- app_lookup_hash[app] += [node_class]
447
- else
448
- app_lookup_hash[app] = [node_class]
449
- end
450
- end
451
- end
452
-
453
- node_classes = app_lookup_hash[app_name]
454
-
455
- return if node_classes.nil?
456
-
457
- if node_classes.length > 1
458
- error "error: ambiguous node class for #{app_name} in #{environment}"
459
- newline
460
- info "specify the node class and application mame, for example: "
461
- node_classes.each do |node_class|
462
- info "\n govuk-connect app-console -e #{environment} #{node_class}/#{app_name}"
463
- end
464
- newline
465
-
466
- exit 1
467
- else
468
- node_class = node_classes.first
469
- end
470
-
471
- log "debug: node class: #{node_class}"
472
-
473
- node_class
474
- end
475
-
476
- def hosting_for_target_and_environment(target, environment)
477
- hosting = single_hosting_provider_for_environment(
478
- environment
479
- )
480
-
481
- unless hosting
482
- hosting, name, _number = parse_hosting_name_and_number(target)
483
-
484
- unless hosting
485
- hosting = hosting_for_node_type(name, environment)
486
- end
487
- end
488
-
489
- hosting
490
- end
491
-
492
- def hosting_for_node_type(node_type, environment)
493
- log "debug: Looking up hosting for node_type: #{node_type}"
494
- hosting = single_hosting_provider_for_environment(environment)
495
-
496
- return hosting if hosting
497
-
498
- aws_node_types = govuk_node_list_classes(environment, :aws)
499
- carrenza_node_types = govuk_node_list_classes(environment, :carrenza)
500
-
501
- if (
502
- aws_node_types.include?(node_type) &&
503
- carrenza_node_types.include?(node_type)
504
- )
505
- error "error: ambiguous hosting for #{node_type} in #{environment}"
506
- newline
507
- info "specify the hosting provider and node type, for example: "
508
- hosting_providers.each do |hosting_provider|
509
- info "\n govuk-connect ssh #{bold(hosting_provider)}/#{node_type}"
510
- end
511
- info "\n"
512
-
513
- exit 1
514
- elsif aws_node_types.include?(node_type)
515
- :aws
516
- elsif carrenza_node_types.include?(node_type)
517
- :carrenza
518
- else
519
- error "error: couldn't find #{node_type} in #{environment}"
520
-
521
- all_node_types = (aws_node_types + carrenza_node_types).uniq.sort
522
- similar_node_types = strings_similar_to(node_type, all_node_types)
523
-
524
- if similar_node_types.any?
525
- info "\ndid you mean:"
526
- similar_node_types.each { |s| info " - #{s}" }
527
- else
528
- info "\nall node types:"
529
- all_node_types.each { |s| info " - #{s}" }
530
- end
531
-
532
- exit 1
533
- end
534
- end
535
-
536
- def hosting_for_app(app_name, environment)
537
- log "debug: finding hosting for #{app_name} in #{environment}"
538
-
539
- hosting = single_hosting_provider_for_environment(environment)
540
-
541
- if hosting
542
- log "debug: this environment has a single hosting provider: #{hosting}"
543
- return hosting
544
- end
545
-
546
- aws_app_names = application_names_from_node_class_data(
547
- environment,
548
- :aws,
549
- )
550
-
551
- if aws_app_names.include? app_name
552
- log "debug: #{app_name} is hosted in AWS"
553
-
554
- return :aws
555
- end
556
-
557
- carrenza_app_names = application_names_from_node_class_data(
558
- environment,
559
- :carrenza,
560
- )
561
-
562
- if carrenza_app_names.include? app_name
563
- log "debug: #{app_name} is hosted in Carrenza"
564
-
565
- return :carrenza
566
- end
567
-
568
- error "error: unknown hosting value '#{hosting}' for #{app_name}"
569
- exit 1
570
- end
571
-
572
- def govuk_app_command(target, environment, command)
573
- node_class, app_name, number = parse_node_class_app_name_and_number(target)
574
-
575
- info "Connecting to the app #{command} for #{bold(app_name)},\
576
- in the #{bold(environment)} environment"
577
-
578
- hosting = hosting_for_app(app_name, environment)
579
-
580
- info "The relevant hosting provider is #{bold(hosting)}"
581
-
582
- unless node_class
583
- node_class = node_class_for_app(
584
- app_name,
585
- environment,
586
- hosting
587
- )
588
- end
589
-
590
- unless node_class
591
- error "error: application '#{app_name}' not found."
592
- newline
593
-
594
- application_names = application_names_from_node_class_data(
595
- environment,
596
- hosting
597
- )
598
-
599
- similar_application_names = strings_similar_to(app_name, application_names)
600
- if similar_application_names.any?
601
- info "did you mean:"
602
- similar_application_names.each { |s| info " - #{s}" }
603
- else
604
- info "all applications:"
605
- newline
606
- info " #{application_names.join(', ')}"
607
- newline
608
- end
609
-
610
- exit 1
611
- end
612
-
613
- info "The relevant node class is #{bold(node_class)}"
614
-
615
- ssh(
616
- {
617
- hosting: hosting,
618
- name: node_class,
619
- number: number
620
- },
621
- environment,
622
- command: "govuk_app_#{command} #{app_name}"
623
- )
624
- end
625
-
626
- def ssh(
627
- target,
628
- environment,
629
- command: false,
630
- port_forward: false,
631
- additional_arguments: []
632
- )
633
- log "debug: ssh to #{target} in #{environment}"
634
-
635
- # Split something like aws/backend:2 in to :aws, 'backend', 2
636
- hosting, name, number = parse_hosting_name_and_number(target)
637
-
638
- if name.end_with? '.internal'
639
- ssh_target = name
640
- hosting = :aws
641
- elsif name.end_with? '.gov.uk'
642
- ssh_target = name
643
- hosting = :carrenza
644
- else
645
- # The hosting might not have been provided, so check if necessary
646
- hosting ||= hosting_for_target_and_environment(target, environment)
647
-
648
- domains = get_domains_for_node_class(
649
- name,
650
- environment,
651
- hosting,
652
- ssh_username
653
- )
654
-
655
- if domains.length.zero?
656
- error "error: couldn't find #{name} in #{hosting}/#{environment}"
657
-
658
- node_types = govuk_node_list_classes(environment, hosting)
659
-
660
- similar_node_types = strings_similar_to(name, node_types)
661
-
662
- if similar_node_types.any?
663
- info "\ndid you mean:"
664
- similar_node_types.each { |s| info " - #{s}" }
665
- else
666
- info "\nall node types:"
667
- node_types.each { |s| info " - #{s}" }
668
- end
669
-
670
- exit 1
671
- elsif domains.length == 1
672
- ssh_target = domains.first
673
-
674
- info "There is #{bold('one machine')} to connect to"
675
- else
676
- n_machines = bold("#{domains.length} machines")
677
- info "There are #{n_machines} of this class"
678
-
679
- if number
680
- unless number > 0
681
- newline
682
- error "error: invalid machine number '#{number}', it must be > 0"
683
- exit 1
684
- end
685
-
686
- unless number <= domains.length
687
- newline
688
- error "error: cannot connect to machine number: #{number}"
689
- exit 1
690
- end
691
-
692
- ssh_target = domains[number - 1]
693
- info "Connecting to number #{number}"
694
- else
695
- ssh_target = domains.sample
696
- info "Connecting to a random machine (number #{domains.find_index(ssh_target) + 1})"
697
- end
698
- end
699
- end
700
-
701
- ssh_command = [
702
- 'ssh',
703
- *ssh_identity_arguments,
704
- '-J', user_at_host(
705
- ssh_username,
706
- jumpbox_for_environment_and_hosting(environment, hosting)
707
- ),
708
- user_at_host(
709
- ssh_username,
710
- ssh_target
711
- )
712
- ]
713
-
714
- if command
715
- ssh_command += [
716
- '-t', # Force tty allocation so that interactive commands work
717
- command
718
- ]
719
- elsif port_forward
720
- localhost_port = random_free_port
721
-
722
- ssh_command += [
723
- '-N',
724
- '-L', "#{localhost_port}:127.0.0.1:#{port_forward}"
725
- ]
726
-
727
- info "Port forwarding setup, access:\n\n http://127.0.0.1:#{localhost_port}/\n\n"
728
- end
729
-
730
- ssh_command += additional_arguments
731
-
732
- info "\n#{bold('Running command:')} #{ssh_command.join(' ')}\n\n"
733
-
734
- exec(*ssh_command)
735
- end
736
-
737
- def rabbitmq_root_password_command(hosting, environment)
738
- hieradata_directory = {
739
- aws: 'puppet_aws',
740
- carrenza: 'puppet'
741
- }[hosting]
742
-
743
- directory = File.join(
744
- govuk_directory,
745
- 'govuk-secrets',
746
- hieradata_directory
747
- )
748
-
749
- "cd #{directory} && rake eyaml:decrypt_value[#{environment},govuk_rabbitmq::root_password]"
750
- end
751
-
752
- def hosting_and_environment_from_url(url)
753
- uri = URI(url)
754
-
755
- host_to_hosting_and_environment = {
756
- 'ci-alert.integration.publishing.service.gov.uk' => %i[carrenza ci],
757
- 'alert.integration.publishing.service.gov.uk' => %i[aws integration],
758
- 'alert.staging.govuk.digital' => %i[aws staging],
759
- 'alert.blue.staging.govuk.digital' => %i[aws staging],
760
- 'alert.staging.publishing.service.gov.uk' => %i[carrenza staging],
761
- 'alert.production.govuk.digital' => %i[aws production],
762
- 'alert.blue.production.govuk.digital' => %i[aws production],
763
- 'alert.publishing.service.gov.uk' => %i[carrenza production],
764
- }
765
-
766
- unless host_to_hosting_and_environment.key? uri.host
767
- error "error: unknown hosting and environment for: #{uri.host}"
768
- exit 1
769
- end
770
-
771
- host_to_hosting_and_environment[uri.host]
772
- end
773
-
774
- def parse_options
775
- options = {}
776
-
777
- @option_parser = OptionParser.new do |opts|
778
- opts.banner = USAGE_BANNER
779
-
780
- opts.on(
781
- '-e',
782
- '--environment ENVIRONMENT',
783
- 'Select which environment to connect to'
784
- ) do |o|
785
- options[:environment] = o.to_sym
786
- end
787
- opts.on(
788
- '--hosting-and-environment-from-alert-url URL',
789
- 'Select which environment to connect to based on the URL provided.'
790
- ) do |o|
791
- hosting, environment = hosting_and_environment_from_url(o)
792
- options[:hosting] = hosting
793
- options[:environment] = environment
794
- end
795
- opts.on('-p', '--port-forward SERVICE', 'Connect to a remote port') do |o|
796
- options[:port_forward] = o
797
- end
798
- opts.on('-v', '--verbose', 'Enable more detailed logging') do
799
- $verbose = true
800
- end
801
-
802
- opts.on('-h', '--help', 'Prints usage information and examples') do
803
- info opts
804
- newline
805
- info bold('CONNECTION TYPES')
806
- TYPES.keys.each do |x|
807
- info " #{x}"
808
- description = CONNECTION_TYPE_DESCRIPTIONS[x]
809
- info " #{description}" if description
810
- end
811
- newline
812
- info bold('MACHINE TARGET')
813
- info MACHINE_TARGET_DESCRIPTION
814
- newline
815
- info bold('APPLICATION TARGET')
816
- info APP_TARGET_DESCRIPTION
817
- newline
818
- info bold('EXAMPLES')
819
- info EXAMPLES
820
- exit
821
- end
822
- opts.on('-V', '--version', 'Prints version information') do
823
- info "#{GovukConnect::VERSION}"
824
- exit
825
- end
826
- end
827
-
828
- @option_parser.parse!
829
-
830
- options
831
- end
832
-
833
- def parse_hosting_name_and_number(target)
834
- log "debug: parsing target: #{target}"
835
- if target.is_a? Hash
836
- return %i[hosting name number].map do |key|
837
- target[key]
838
- end
839
- end
840
-
841
- if target.include? '/'
842
- hosting, name_and_number = target.split '/'
843
-
844
- hosting = hosting.to_sym
845
-
846
- unless %i[carrenza aws].include? hosting
847
- error "error: unknown hosting provider: #{hosting}"
848
- newline
849
- info "available hosting providers are:"
850
- hosting_providers.each { |x| info " - #{x}" }
851
-
852
- exit 1
853
- end
854
- else
855
- name_and_number = target
856
- end
857
-
858
- if name_and_number.include? ':'
859
- name, number = name_and_number.split ':'
860
-
861
- number = number.to_i
862
- else
863
- name = name_and_number
864
- end
865
-
866
- log "debug: hosting: #{hosting.inspect}, name: #{name.inspect}, number: #{number.inspect}"
867
-
868
- return hosting, name, number
869
- end
870
-
871
- def parse_node_class_app_name_and_number(target)
872
- log "debug: parsing target: #{target}"
873
- if target.is_a? Hash
874
- return %i[node_class app_name number].map do |key|
875
- target[key]
876
- end
877
- end
878
-
879
- if target.include? '/'
880
- node_class, app_name_and_number = target.split '/'
881
- else
882
- app_name_and_number = target
883
- end
884
-
885
- if app_name_and_number.include? ':'
886
- app_name, number = name_and_number.split ':'
887
-
888
- number = number.to_i
889
- else
890
- app_name = app_name_and_number
891
- end
892
-
893
- log "debug: node_class: #{node_class.inspect}, app_name: #{app_name.inspect}, number: #{number.inspect}"
894
-
895
- return node_class, app_name, number
896
- end
897
-
898
- def check_for_target(target)
899
- unless target
900
- error "error: you must specify the target\n"
901
- STDERR.puts USAGE_BANNER
902
- STDERR.puts
903
- STDERR.puts EXAMPLES
904
- exit 1
905
- end
906
- end
907
-
908
- def check_for_additional_arguments(command, args)
909
- unless args.empty?
910
- error "error: #{command} doesn't support arguments: #{args}"
911
- exit 1
912
- end
913
- end
914
-
915
- TYPES = {
916
- 'app-console' => Proc.new do |target, environment, args, _options|
917
- check_for_target(target)
918
- check_for_additional_arguments('app-console', args)
919
- govuk_app_command(target, environment, 'console')
920
- end,
921
-
922
- 'app-dbconsole' => Proc.new do |target, environment, args, _options|
923
- check_for_target(target)
924
- check_for_additional_arguments('app-dbconsole', args)
925
- govuk_app_command(target, environment, 'dbconsole')
926
- end,
927
-
928
- 'rabbitmq' => Proc.new do |target, environment, args, options|
929
- check_for_additional_arguments('rabbitmq', args)
930
-
931
- target ||= 'rabbitmq'
932
-
933
- root_password_command = rabbitmq_root_password_command(
934
- hosting_for_target_and_environment(target, environment),
935
- environment
936
- )
937
-
938
- info "You'll need to login as the RabbitMQ #{bold('root')} user."
939
- info "Get the password from govuk-secrets, or example:\n\n"
940
- info " #{bold(root_password_command)}"
941
- newline
942
-
943
- ssh(
944
- target,
945
- environment,
946
- port_forward: RABBITMQ_PORT,
947
- )
948
- end,
949
-
950
- 'sidekiq-monitoring' => Proc.new do |target, environment, args, options|
951
- check_for_additional_arguments('sidekiq-monitoring', args)
952
- ssh(
953
- target || 'backend',
954
- environment,
955
- port_forward: SIDEKIQ_MONITORING_PORT,
956
- )
957
- end,
958
-
959
- 'ssh' => Proc.new do |target, environment, args, options|
960
- check_for_target(target)
961
-
962
- if options.key? :hosting
963
- hosting, name, number = parse_hosting_name_and_number(target)
964
- if hosting
965
- error "error: hosting specified twice"
966
- exit 1
967
- end
968
-
969
- target = {
970
- hosting: options[:hosting],
971
- name: name,
972
- number: number
973
- }
974
- end
975
-
976
- ssh(
977
- target,
978
- environment,
979
- port_forward: options[:port_forward],
980
- additional_arguments: args
981
- )
3
+ module GovukConnect
4
+ def self.main(argv)
5
+ CLI.new.main(argv)
982
6
  end
983
- }
984
-
985
- def main
986
- check_ruby_version_greater_than(required_major: 2, required_minor: 0)
987
-
988
- double_dash_index = ARGV.index '--'
989
- if double_dash_index
990
- # This is used in the case of passing extra options to ssh, the --
991
- # acts as a separator, so to avoid optparse interpreting those
992
- # options, split ARGV around -- before parsing the options
993
- rest = ARGV[double_dash_index + 1, ARGV.length]
994
- argv = ARGV[0, double_dash_index]
995
-
996
- ARGV.clear
997
- ARGV.concat argv
998
-
999
- options = parse_options
1000
-
1001
- type, target = ARGV
1002
- else
1003
- options = parse_options
1004
-
1005
- type, target, *rest = ARGV
1006
- end
1007
-
1008
- unless type
1009
- error "error: you must specify the connection type\n"
1010
-
1011
- STDERR.puts @option_parser.help
1012
-
1013
- STDERR.puts "\nValid connection types are:\n"
1014
- TYPES.keys.each do |x|
1015
- STDERR.puts " - #{x}"
1016
- end
1017
- STDERR.puts
1018
- STDERR.puts "Example commands:"
1019
- STDERR.puts EXAMPLES
1020
-
1021
- exit 1
1022
- end
1023
-
1024
- handler = TYPES[type]
1025
-
1026
- unless handler
1027
- error "error: unknown connection type: #{type}\n"
1028
-
1029
- STDERR.puts "Valid connection types are:\n"
1030
- TYPES.keys.each do |x|
1031
- STDERR.puts " - #{x}"
1032
- end
1033
- STDERR.puts
1034
- STDERR.puts "Example commands:"
1035
- STDERR.puts EXAMPLES
1036
-
1037
- exit 1
1038
- end
1039
-
1040
- environment = options[:environment]&.to_sym
1041
-
1042
- unless environment
1043
- error "error: you must specify the environment\n"
1044
- STDERR.puts @option_parser.help
1045
- exit 1
1046
- end
1047
-
1048
- unless JUMPBOXES.key? environment
1049
- error "error: unknown environment '#{environment}'"
1050
- newline
1051
- info "Valid environments are:"
1052
- JUMPBOXES.keys.each { |e| info " - #{e}" }
1053
- exit 1
1054
- end
1055
-
1056
- handler.call(target, environment, rest, options)
1057
- rescue Interrupt
1058
- # Handle SIGTERM without printing a stacktrace
1059
- exit 1
1060
7
  end