class GovukConnect::CLI

Constants

APP_TARGET_DESCRIPTION
CONNECTION_TYPE_DESCRIPTIONS
EXAMPLES
JUMPBOXES
MACHINE_TARGET_DESCRIPTION
RABBITMQ_PORT
USAGE_BANNER

Public Class Methods

bold(string) click to toggle source
# File lib/govuk_connect/cli.rb, line 10
def self.bold(string)
  "\e[1m#{string}\e[0m"
end

Public Instance Methods

application_names_from_node_class_data(environment, hosting) click to toggle source
# File lib/govuk_connect/cli.rb, line 340
def application_names_from_node_class_data(environment, hosting)
  node_class_data = govuk_puppet_node_class_data(
    environment,
    hosting,
  )

  all_names = node_class_data.flat_map do |_node_class, data|
    data["apps"]
  end

  all_names.sort.uniq
end
bold(string) click to toggle source
# File lib/govuk_connect/cli.rb, line 14
def bold(string)
  self.class.bold(string)
end
check_for_additional_arguments(command, args) click to toggle source
# File lib/govuk_connect/cli.rb, line 850
def check_for_additional_arguments(command, args)
  unless args.empty?
    error "error: #{command} doesn't support arguments: #{args}"
    exit 1
  end
end
check_for_target(target) click to toggle source
# File lib/govuk_connect/cli.rb, line 840
def check_for_target(target)
  unless target
    error "error: you must specify the target\n"
    warn USAGE_BANNER
    print_empty_line
    warn EXAMPLES
    exit 1
  end
end
check_ruby_version_greater_than(required_major:, required_minor:) click to toggle source
# File lib/govuk_connect/cli.rb, line 142
def check_ruby_version_greater_than(required_major:, required_minor:)
  major, minor = RUBY_VERSION.split "."

  insufficient_version = (
    major.to_i < required_major || (
      major.to_i == required_major &&
      minor.to_i < required_minor
    )
  )

  if insufficient_version
    error "insufficient Ruby version: #{RUBY_VERSION}"
    error "must be at least #{required_major}.#{required_minor}"

    exit 1
  end
end
config_file() click to toggle source
# File lib/govuk_connect/cli.rb, line 219
def config_file
  @config_file ||= begin
    directory = ENV.fetch("XDG_CONFIG_HOME", "#{Dir.home}/.config")

    File.join(directory, "config.yaml")
  end
end
error(message) click to toggle source
# File lib/govuk_connect/cli.rb, line 107
def error(message)
  warn "\e[1m\e[31m#{message}\e[0m"
end
get_domains_for_node_class(target, environment, hosting) click to toggle source
# File lib/govuk_connect/cli.rb, line 265
def get_domains_for_node_class(target, environment, hosting)
  domains = ssh_capture(environment, hosting, "govuk_node_list -c #{target}")
  if hosting == :aws
    domains
  else
    domains.sort
  end
end
govuk_app_command(target, environment, command) click to toggle source
# File lib/govuk_connect/cli.rb, line 461
def govuk_app_command(target, environment, command)
  node_class, app_name, number = parse_node_class_app_name_and_number(target)

  info "Connecting to the app #{command} for #{bold(app_name)},\
 in the #{bold(environment)} environment"

  hosting = hosting_for_app(app_name, environment)

  info "The relevant hosting provider is #{bold(hosting)}"

  node_class ||= node_class_for_app(
    app_name,
    environment,
    hosting,
  )

  unless node_class
    error "error: application '#{app_name}' not found."
    print_empty_line

    application_names = application_names_from_node_class_data(
      environment,
      hosting,
    )

    similar_application_names = strings_similar_to(app_name, application_names)
    if similar_application_names.any?
      info "did you mean:"
      similar_application_names.each { |s| info " - #{s}" }
    else
      info "all applications:"
      print_empty_line
      info "  #{application_names.join(', ')}"
      print_empty_line
    end

    exit 1
  end

  info "The relevant node class is #{bold(node_class)}"

  ssh(
    {
      hosting: hosting,
      name: node_class,
      number: number,
    },
    environment,
    command: "govuk_app_#{command} #{app_name}",
  )
end
govuk_directory() click to toggle source
# File lib/govuk_connect/cli.rb, line 300
def govuk_directory
  File.join(ENV["HOME"], "govuk")
end
govuk_node_list_classes(environment, hosting) click to toggle source
# File lib/govuk_connect/cli.rb, line 255
def govuk_node_list_classes(environment, hosting)
  log "debug: looking up classes in #{hosting}/#{environment}"
  classes = ssh_capture(environment, hosting, "govuk_node_list --classes").sort

  log "debug: classes:"
  classes.each { |c| log " - #{c}" }

  classes
end
govuk_puppet_node_class_data(environment, hosting) click to toggle source
# File lib/govuk_connect/cli.rb, line 304
def govuk_puppet_node_class_data(environment, hosting)
  log "debug: fetching govuk-puppet node class data for #{hosting} #{environment}"

  local_hieradata_root = File.join(
    govuk_directory,
    "govuk-puppet",
    {
      aws: "hieradata_aws",
    }[hosting],
  )

  hieradata_file = File.join(local_hieradata_root, "#{environment}.yaml")
  log "debug: reading #{hieradata_file}"

  environment_specific_hieradata = YAML.load_file(hieradata_file)

  if environment_specific_hieradata["node_class"]
    environment_specific_hieradata["node_class"]
  else
    common_hieradata = YAML.load_file(
      File.join(local_hieradata_root, "common.yaml"),
    )

    common_hieradata["node_class"]
  end
end
hosting_and_environment_from_url(url) click to toggle source
# File lib/govuk_connect/cli.rb, line 614
def hosting_and_environment_from_url(url)
  uri = URI(url)

  host_to_hosting_and_environment = {
    "alert.integration.publishing.service.gov.uk" => %i[aws integration],
    "alert.staging.govuk.digital" => %i[aws staging],
    "alert.blue.staging.govuk.digital" => %i[aws staging],
    "alert.production.govuk.digital" => %i[aws production],
    "alert.blue.production.govuk.digital" => %i[aws production],
  }

  unless host_to_hosting_and_environment.key? uri.host
    error "error: unknown hosting and environment for: #{uri.host}"
    exit 1
  end

  host_to_hosting_and_environment[uri.host]
end
hosting_for_app(app_name, environment) click to toggle source
# File lib/govuk_connect/cli.rb, line 436
def hosting_for_app(app_name, environment)
  log "debug: finding hosting for #{app_name} in #{environment}"

  hosting = single_hosting_provider_for_environment(environment)

  if hosting
    log "debug: this environment has a single hosting provider: #{hosting}"
    return hosting
  end

  aws_app_names = application_names_from_node_class_data(
    environment,
    :aws,
  )

  if aws_app_names.include? app_name
    log "debug: #{app_name} is hosted in AWS"

    return :aws
  end

  error "error: unknown hosting value '#{hosting}' for #{app_name}"
  exit 1
end
hosting_for_node_type(node_type, environment) click to toggle source
# File lib/govuk_connect/cli.rb, line 409
def hosting_for_node_type(node_type, environment)
  log "debug: Looking up hosting for node_type: #{node_type}"
  hosting = single_hosting_provider_for_environment(environment)

  return hosting if hosting

  aws_node_types = govuk_node_list_classes(environment, :aws)

  if aws_node_types.include?(node_type)
    :aws
  else
    error "error: couldn't find #{node_type} in #{environment}"

    similar_node_types = strings_similar_to(node_type, aws_node_types)

    if similar_node_types.any?
      info "\ndid you mean:"
      similar_node_types.each { |s| info " - #{s}" }
    else
      info "\nall node types:"
      all_node_types.each { |s| info " - #{s}" }
    end

    exit 1
  end
end
hosting_for_target_and_environment(target, environment) click to toggle source
# File lib/govuk_connect/cli.rb, line 395
def hosting_for_target_and_environment(target, environment)
  hosting = single_hosting_provider_for_environment(
    environment,
  )

  unless hosting
    hosting, name, _number = parse_hosting_name_and_number(target)

    hosting ||= hosting_for_node_type(name, environment)
  end

  hosting
end
hosting_providers() click to toggle source
# File lib/govuk_connect/cli.rb, line 188
def hosting_providers
  JUMPBOXES
    .map { |_env, jumpboxes| jumpboxes.keys }
    .flatten
    .uniq
end
info(message) click to toggle source
# File lib/govuk_connect/cli.rb, line 103
def info(message)
  warn message
end
jumpbox_for_environment_and_hosting(environment, hosting) click to toggle source
# File lib/govuk_connect/cli.rb, line 195
def jumpbox_for_environment_and_hosting(environment, hosting)
  raise "missing environment" unless environment
  raise "missing hosting" unless hosting

  jumpbox = JUMPBOXES[environment][hosting]

  unless jumpbox
    error "error: couldn't determine jumpbox for #{hosting}/#{environment}"
    exit 1
  end

  jumpbox
end
levenshtein_distance(string1, string2) click to toggle source

From Rosetta Code: rosettacode.org/wiki/Levenshtein_distance#Ruby

# File lib/govuk_connect/cli.rb, line 117
def levenshtein_distance(string1, string2)
  string1 = string1.downcase
  string2 = string2.downcase
  costs = Array(0..string2.length) # i == 0
  (1..string1.length).each do |i|
    costs[0] = i
    nw = i - 1 # j == 0; nw is lev(i-1, j)
    (1..string2.length).each do |j|
      costs[j] = [
        costs[j] + 1,
        costs[j - 1] + 1,
        string1[i - 1] == string2[j - 1] ? nw : nw + 1,
      ].min
      nw = costs[j]
    end
  end
  costs[string2.length]
end
log(message) click to toggle source
# File lib/govuk_connect/cli.rb, line 95
def log(message)
  warn message if @verbose
end
main(argv) click to toggle source
# File lib/govuk_connect/cli.rb, line 945
def main(argv)
  check_ruby_version_greater_than(required_major: 2, required_minor: 0)

  extra_arguments_after_double_dash = []

  double_dash_index = argv.index "--"
  if double_dash_index
    # This is used in the case of passing extra options to ssh and
    # scp, the -- acts as a separator, so to avoid optparse
    # interpreting those as options, split argv around -- before
    # parsing the options
    extra_arguments_after_double_dash = argv[double_dash_index + 1, argv.length]
    argv = argv[0, double_dash_index]
  end

  govuk_connect_options = parse_options(argv)
  type, target, *extra_arguments_before_double_dash = argv

  unless type
    error "error: you must specify the connection type\n"

    warn @option_parser.help

    warn "\nValid connection types are:\n"
    types.keys.each do |x|
      warn " - #{x}"
    end
    print_empty_line
    warn "Example commands:"
    warn EXAMPLES

    exit 1
  end

  handler = types[type]

  unless handler
    error "error: unknown connection type: #{type}\n"

    warn "Valid connection types are:\n"
    types.keys.each do |x|
      warn " - #{x}"
    end
    print_empty_line
    warn "Example commands:"
    warn EXAMPLES

    exit 1
  end

  environment = govuk_connect_options[:environment]&.to_sym

  unless environment
    error "error: you must specify the environment\n"
    warn @option_parser.help
    exit 1
  end

  unless JUMPBOXES.key? environment
    error "error: unknown environment '#{environment}'"
    print_empty_line
    info "Valid environments are:"
    JUMPBOXES.keys.each { |e| info " - #{e}" }
    exit 1
  end

  handler.call(
    target,
    environment,
    extra_arguments_before_double_dash,
    extra_arguments_after_double_dash,
    govuk_connect_options,
  )
rescue Interrupt
  # Handle SIGTERM without printing a stacktrace
  exit 1
end
node_class_for_app(app_name, environment, hosting) click to toggle source
# File lib/govuk_connect/cli.rb, line 353
def node_class_for_app(app_name, environment, hosting)
  log "debug: finding node class for #{app_name} in #{hosting} #{environment}"

  node_class_data = govuk_puppet_node_class_data(
    environment,
    hosting,
  )

  app_lookup_hash = {}
  node_class_data.each do |node_class, data|
    data["apps"].each do |app|
      if app_lookup_hash.key? app
        app_lookup_hash[app] += [node_class]
      else
        app_lookup_hash[app] = [node_class]
      end
    end
  end

  node_classes = app_lookup_hash[app_name]

  return if node_classes.nil?

  if node_classes.length > 1
    error "error: ambiguous node class for #{app_name} in #{environment}"
    print_empty_line
    info "specify the node class and application mame, for example: "
    node_classes.each do |node_class|
      info "\n  gds govuk connect app-console -e #{environment} #{node_class}/#{app_name}"
    end
    print_empty_line

    exit 1
  else
    node_class = node_classes.first
  end

  log "debug: node class: #{node_class}"

  node_class
end
node_classes_for_environment_and_hosting(environment, hosting) click to toggle source
# File lib/govuk_connect/cli.rb, line 331
def node_classes_for_environment_and_hosting(environment, hosting)
  govuk_puppet_node_class_data(
    environment,
    hosting,
  ).map do |node_class, _data|
    node_class
  end
end
parse_hosting_name_and_number(target) click to toggle source
# File lib/govuk_connect/cli.rb, line 691
def parse_hosting_name_and_number(target)
  log "debug: parsing target: #{target}"
  if target.is_a? Hash
    return %i[hosting name number].map do |key|
      target[key]
    end
  end

  if target.include? "/"
    hosting, name_and_number = target.split "/"

    hosting = hosting.to_sym

    unless %i[aws].include? hosting
      error "error: unknown hosting provider: #{hosting}"
      print_empty_line
      info "available hosting providers are:"
      hosting_providers.each { |x| info " - #{x}" }

      exit 1
    end
  else
    name_and_number = target
  end

  if name_and_number.include? ":"
    name, number = name_and_number.split ":"

    number = number.to_i
  else
    name = name_and_number
  end

  log "debug: hosting: #{hosting.inspect}, name: #{name.inspect}, number: #{number.inspect}"

  [hosting, name, number]
end
parse_node_class_app_name_and_number(target) click to toggle source
# File lib/govuk_connect/cli.rb, line 729
def parse_node_class_app_name_and_number(target)
  log "debug: parsing target: #{target}"
  if target.is_a? Hash
    return %i[node_class app_name number].map do |key|
      target[key]
    end
  end

  if target.include? "/"
    node_class, app_name_and_number = target.split "/"
  else
    app_name_and_number = target
  end

  if app_name_and_number.include? ":"
    app_name, number = app_name_and_number.split ":"

    number = number.to_i
  else
    app_name = app_name_and_number
  end

  log "debug: node_class: #{node_class.inspect}, app_name: #{app_name.inspect}, number: #{number.inspect}"

  [node_class, app_name, number]
end
parse_options(argv) click to toggle source
# File lib/govuk_connect/cli.rb, line 633
def parse_options(argv)
  options = {}
  @option_parser = OptionParser.new do |opts|
    opts.banner = USAGE_BANNER

    opts.on(
      "-e",
      "--environment ENVIRONMENT",
      "Select which environment to connect to",
    ) do |o|
      options[:environment] = o.to_sym
    end
    opts.on(
      "--hosting-and-environment-from-alert-url URL",
      "Select which environment to connect to based on the URL provided.",
    ) do |o|
      hosting, environment = hosting_and_environment_from_url(o)
      options[:hosting] = hosting
      options[:environment] = environment
    end
    opts.on("-p", "--port-forward SERVICE", "Connect to a remote port") do |o|
      options[:port_forward] = o
    end
    opts.on("-v", "--verbose", "Enable more detailed logging") do
      @verbose = true
    end

    opts.on("-h", "--help", "Prints usage examples and information") do
      info opts
      print_empty_line
      info bold("EXAMPLES")
      info EXAMPLES
      print_empty_line
      info bold("CONNECTION TYPES")
      types.keys.each do |x|
        info "  #{x}"
        description = CONNECTION_TYPE_DESCRIPTIONS[x]
        info "    #{description}" if description
      end
      print_empty_line
      info bold("MACHINE TARGET")
      info MACHINE_TARGET_DESCRIPTION
      print_empty_line
      info bold("APPLICATION TARGET")
      info APP_TARGET_DESCRIPTION
      exit
    end
    opts.on("-V", "--version", "Prints version information") do
      info GovukConnect::VERSION.to_s
      exit
    end
  end

  @option_parser.parse!(argv)

  options
end
port_free?(port) click to toggle source
# File lib/govuk_connect/cli.rb, line 160
def port_free?(port)
  # No idea how well this works, but it's hopefully better than nothing

  log "debug: checking if port #{port} is free"
  Socket.tcp("127.0.0.1", port, connect_timeout: 0.1) {}
  false
rescue Errno::ETIMEDOUT
  log "debug: port #{port} doesn't seem to be free"
  false
rescue Errno::ECONNREFUSED
  log "debug: port #{port} is free"
  true
end
print_empty_line() click to toggle source
print_ssh_username_configuration_help() click to toggle source
rabbitmq_root_password_command(hosting, environment) click to toggle source
# File lib/govuk_connect/cli.rb, line 600
def rabbitmq_root_password_command(hosting, environment)
  hieradata_directory = {
    aws: "puppet_aws",
  }[hosting]

  directory = File.join(
    govuk_directory,
    "govuk-secrets",
    hieradata_directory,
  )

  "cd #{directory} && rake eyaml:decrypt_value[#{environment},govuk_rabbitmq::root_password]"
end
random_free_port() click to toggle source
# File lib/govuk_connect/cli.rb, line 174
def random_free_port
  tries = 0

  while tries <= 10
    port = rand(32_768...61_000)

    return port if port_free? port

    tries += 1
  end

  raise "couldn't find open port"
end
scp( target, environment, files, push: false, additional_arguments: [] ) click to toggle source
# File lib/govuk_connect/cli.rb, line 562
def scp(
  target,
  environment,
  files,
  push: false,
  additional_arguments: []
)
  log "debug: scp #{push ? 'push' : 'pull'} to #{target} in #{environment}"

  target, hosting = ssh_target(target, environment)

  sources = files[0, files.length - 1]
  destination = files[-1]

  if push
    destination = "#{target}:#{destination}"
  else
    sources = sources.map { |source| "#{target}:#{source}" }
  end

  scp_command = [
    "scp",
    *ssh_identity_arguments,
    "-o",
    "ProxyJump=#{user_at_host(ssh_username, jumpbox_for_environment_and_hosting(environment, hosting))}",
    "-o",
    "User=#{ssh_username}",
    *additional_arguments,
    "--",
    *sources,
    destination,
  ]

  info "\n#{bold('Running command:')} #{scp_command.join(' ')}\n\n"

  exec(*scp_command)
end
single_hosting_provider_for_environment(environment) click to toggle source
# File lib/govuk_connect/cli.rb, line 209
def single_hosting_provider_for_environment(environment)
  jumpboxes = JUMPBOXES[environment]

  if jumpboxes.size == 1
    jumpboxes.keys[0]
  else
    false
  end
end
ssh( target, environment, command: false, port_forward: false, additional_arguments: [] ) click to toggle source
# File lib/govuk_connect/cli.rb, line 513
def ssh(
  target,
  environment,
  command: false,
  port_forward: false,
  additional_arguments: []
)
  log "debug: ssh to #{target} in #{environment}"

  target, hosting = ssh_target(target, environment)

  ssh_command = [
    "ssh",
    *ssh_identity_arguments,
    "-J",
    user_at_host(
      ssh_username,
      jumpbox_for_environment_and_hosting(environment, hosting),
    ),
    user_at_host(
      ssh_username,
      target,
    ),
  ]

  if command
    ssh_command += [
      "-t", # Force tty allocation so that interactive commands work
      command,
    ]
  elsif port_forward
    localhost_port = random_free_port

    ssh_command += [
      "-N",
      "-L",
      "#{localhost_port}:127.0.0.1:#{port_forward}",
    ]

    info "Port forwarding setup, access:\n\n  http://127.0.0.1:#{localhost_port}/\n\n"
  end

  ssh_command += additional_arguments

  info "\n#{bold('Running command:')} #{ssh_command.join(' ')}\n\n"

  exec(*ssh_command)
end
ssh_capture(environment, hosting, remote_command) click to toggle source
# File lib/govuk_connect/cli.rb, line 274
def ssh_capture(environment, hosting, remote_command)
  command = [
    "ssh",
    "-o",
    "ConnectTimeout=2", # Show a failure quickly
    *ssh_identity_arguments,
    user_at_host(
      ssh_username,
      jumpbox_for_environment_and_hosting(environment, hosting),
    ),
    remote_command,
  ].join(" ")

  log "debug: running command: #{command}"
  output, status = Open3.capture2(command)

  unless status.success?
    error "\nerror: command failed: #{command}"
    print_empty_line
    print_ssh_username_configuration_help
    exit 1
  end

  output.split("\n")
end
ssh_identity_arguments() click to toggle source
# File lib/govuk_connect/cli.rb, line 243
def ssh_identity_arguments
  if ssh_identity_file
    ["-i", ssh_identity_file]
  else
    []
  end
end
ssh_identity_file() click to toggle source
# File lib/govuk_connect/cli.rb, line 237
def ssh_identity_file
  @ssh_identity_file ||= begin
    YAML.load_file(config_file)["ssh_identity_file"] if File.exist? config_file
  end
end
ssh_target(target, environment) click to toggle source
# File lib/govuk_connect/cli.rb, line 774
def ssh_target(target, environment)
  # Split something like aws/backend:2 in to :aws, 'backend', 2
  hosting, name, number = parse_hosting_name_and_number(target)

  if name.end_with? ".internal"
    target = name
    hosting = :aws
  else
    # The hosting might not have been provided, so check if necessary
    hosting ||= hosting_for_target_and_environment(target, environment)

    domains = get_domains_for_node_class(
      name,
      environment,
      hosting,
    )

    if domains.length.zero?
      error "error: couldn't find #{name} in #{hosting}/#{environment}"

      node_types = govuk_node_list_classes(environment, hosting)

      similar_node_types = strings_similar_to(name, node_types)

      if similar_node_types.any?
        info "\ndid you mean:"
        similar_node_types.each { |s| info " - #{s}" }
      else
        info "\nall node types:"
        node_types.each { |s| info " - #{s}" }
      end

      exit 1
    elsif domains.length == 1
      target = domains.first

      info "There is #{bold('one machine')} to connect to"
    else
      n_machines = bold("#{domains.length} machines")
      info "There are #{n_machines} of this class"

      if number
        unless number.positive?
          print_empty_line
          error "error: invalid machine number '#{number}', it must be > 0"
          exit 1
        end

        unless number <= domains.length
          print_empty_line
          error "error: cannot connect to machine number: #{number}"
          exit 1
        end

        target = domains[number - 1]
        info "Connecting to number #{number}"
      else
        target = domains.sample
        info "Connecting to a random machine (number #{domains.find_index(target) + 1})"
      end
    end
  end

  [target, hosting]
end
ssh_username() click to toggle source
# File lib/govuk_connect/cli.rb, line 227
def ssh_username
  @ssh_username ||= begin
    if File.exist? config_file
      config_ssh_username = YAML.load_file(config_file)["ssh_username"]
    end

    config_ssh_username || ENV["USER"]
  end
end
strings_similar_to(target, strings) click to toggle source
# File lib/govuk_connect/cli.rb, line 136
def strings_similar_to(target, strings)
  strings.select do |s|
    levenshtein_distance(s, target) <= 3 # No specific reasoning for this value
  end
end
target_from_options(target, options) click to toggle source
# File lib/govuk_connect/cli.rb, line 756
def target_from_options(target, options)
  if options.key? :hosting
    hosting, name, number = parse_hosting_name_and_number(target)
    if hosting
      error "error: hosting specified twice"
      exit 1
    end

    {
      hosting: options[:hosting],
      name: name,
      number: number,
    }
  else
    target
  end
end
types() click to toggle source
# File lib/govuk_connect/cli.rb, line 857
def types
  @types ||= {
    "app-console" => proc do |target, environment, args, extra_args, _options|
      check_for_target(target)
      check_for_additional_arguments("app-console", args)
      check_for_additional_arguments("app-console", extra_args)
      govuk_app_command(target, environment, "console")
    end,

    "app-dbconsole" => proc do |target, environment, args, extra_args, _options|
      check_for_target(target)
      check_for_additional_arguments("app-dbconsole", args)
      check_for_additional_arguments("app-dbconsole", extra_args)
      govuk_app_command(target, environment, "dbconsole")
    end,

    "rabbitmq" => proc do |target, environment, args, extra_args, _options|
      check_for_additional_arguments("rabbitmq", args)
      check_for_additional_arguments("rabbitmq", extra_args)

      target ||= "rabbitmq"

      root_password_command = rabbitmq_root_password_command(
        hosting_for_target_and_environment(target, environment),
        environment,
      )

      info "You'll need to login as the RabbitMQ #{bold('root')} user."
      info "Get the password from govuk-secrets, or example:\n\n"
      info "  #{bold(root_password_command)}"
      print_empty_line

      ssh(
        target,
        environment,
        port_forward: RABBITMQ_PORT,
      )
    end,

    "ssh" => proc do |target, environment, args, extra_args, options|
      check_for_target(target)
      target = target_from_options(target, options)

      ssh(
        target,
        environment,
        port_forward: options[:port_forward],
        additional_arguments: [args, extra_args].flatten,
      )
    end,

    "scp-pull" => proc do |target, environment, args, extra_args, options|
      check_for_target(target)
      target = target_from_options(target, options)

      if args.length < 2
        error "error: need at least two filenames"
        exit 1
      end

      scp(
        target,
        environment,
        args,
        additional_arguments: extra_args,
      )
    end,

    "scp-push" => proc do |target, environment, args, extra_args, options|
      check_for_target(target)
      target = target_from_options(target, options)

      if args.length < 2
        error "error: need at least two filenames"
        exit 1
      end

      scp(
        target,
        environment,
        args,
        push: true,
        additional_arguments: extra_args,
      )
    end,
  }
end
user_at_host(user, host) click to toggle source
# File lib/govuk_connect/cli.rb, line 251
def user_at_host(user, host)
  "#{user}@#{host}"
end