class RHC::Commands::PortForward

Constants

HOST_AND_PORT

'host' part is a bit lax; we rely on 'rhc-list-ports' to hand us a reasonable output about the host information, be it numeric or FQDN in IPv4 or IPv6.

UP_TO_256
UP_TO_65535

Public Instance Methods

run(app) click to toggle source
# File lib/rhc/commands/port_forward.rb, line 75
def run(app)
  rest_app = rest_client.find_application(options.namespace, app)

  ssh_uri = URI.parse(rest_app.ssh_url)
  say "Using #{rest_app.ssh_url}..." if options.debug

  forwarding_specs = []

  begin
    say "Checking available ports..."

    Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
      ssh.exec! "rhc-list-ports" do |channel, stream, data|
        if stream == :stderr
          data.each_line do |line|
            line.chomp!
            # FIXME: This is really brittle; there must be a better way
            # for the server to tell us that permission (what permission?)
            # is denied.
            raise RHC::PermissionDeniedException.new "Permission denied." if line =~ %rpermission denied/
            # ...and also which services are available for the application
            # for us to forward ports for.
            if line =~ %r\A\s*(\S+) -> #{HOST_AND_PORT}\z/
              debug fs = ForwardingSpec.new($1, $2, $3.to_i)
              forwarding_specs << fs
            else
              debug line
            end

          end
        end
      end
      
      if forwarding_specs.length == 0
        # check if the gears have been stopped
        ggs = rest_app.gear_groups
        if ggs.any? { |gg|
          gears = gg.gears
          true if gears.any? { |g| g["state"] == "stopped" }
        }
          warn "Application #{rest_app.name} is stopped. Please restart the application and try again."
          return 1
        else
          raise RHC::NoPortsToForwardException.new "There are no available ports to forward for this application. Your application may be stopped." 
        end
      end

      begin
        Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
          say "Forwarding ports"
          forwarding_specs.each do |fs|
            given_up = nil
            while !fs.bound? && !given_up
              begin
                args = fs.to_fwd_args
                debug args.inspect
                ssh.forward.local(*args)
                fs.bound = true
              rescue Errno::EADDRINUSE, Errno::EACCES => e
                warn "#{e} while forwarding port #{fs.port_from}. Trying local port #{fs.port_from+1}"
                fs.port_from += 1
              rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
                given_up = true
              end
            end
          end

          bound_ports = forwarding_specs.select(&:bound?)
          if bound_ports.length > 0
            paragraph {
              say "To connect to a service running on OpenShift, use the Local address"
            }
            items = bound_ports.map do |fs|
              [fs.service, "#{fs.host_from}:#{fs.port_from}", " => ", "#{fs.remote_host}:#{fs.port_to.to_s}"]
            end
            table(items, :header => ["Service", "Local", "    ", "OpenShift"]).each { |s| success "  #{s}" }
          end

          # for failed port forwarding attempts
          failed_port_forwards = forwarding_specs.select { |fs| !fs.bound? }
          if failed_port_forwards.length > 0
            ssh_cmd_arg = failed_port_forwards.map { |fs| fs.to_cmd_arg }.join(" ")
            ssh_cmd = "ssh -N #{ssh_cmd_arg} #{ssh_uri.user}@#{ssh_uri.host}"
            warn "Error forwarding some port(s). You can try to forward manually by running:\n#{ssh_cmd}"
          else
            say "Press CTRL-C to terminate port forwarding"
          end

          unless forwarding_specs.any?(&:bound?)
            warn "No ports have been bound"
            return
          end
          ssh.loop { true }
        end
      rescue Interrupt
        results { say "Ending port forward" }
        return 0
      end

    end

  rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
    ssh_cmd = ["ssh","-N"]
    unbound_fs = forwarding_specs.select { |fs| !fs.bound? }
    ssh_cmd += unbound_fs.map { |fs| fs.to_cmd_arg }
    ssh_cmd += ["#{ssh_uri.user}@#{ssh_uri.host}"]
    raise RHC::PortForwardFailedException.new("#{e.message + "\n" if options.debug}Error trying to forward ports. You can try to forward manually by running:\n" + ssh_cmd.join(" "))
  end

  0
rescue RHC::Rest::ConnectionException => e
  error "Connection to #{openshift_server} failed: #{e.message}"
  1
end