Class: RHC::Commands::PortForward

Inherits:
Base show all
Defined in:
lib/rhc/commands/port_forward.rb

Constant Summary collapse

UP_TO_256 =
/25[0-5]|2[0-4][0-9]|[01]?(?:[0-9][0-9]?)/
UP_TO_65535 =
/6553[0-5]|655[0-2][0-9]|65[0-4][0-9][0-9]|6[0-4][0-9][0-9][0-9]|[0-5]?(?:[0-9][0-9]{0,3})/
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_65535})\b/

Constants included from Helpers

Helpers::BOUND_WARNING, Helpers::PREFIX, Helpers::ROLES

Instance Method Summary collapse

Methods inherited from Base

#initialize

Methods included from RHC::ContextHelpers

#find_app, #find_domain, #find_membership_container, #find_team, #from_local_git, included, #namespace_context, #server_context

Methods included from GitHelpers

#git_clone_application, #git_clone_deploy_hooks, #git_clone_repo, #git_cmd, #git_config_get, #git_config_set, #git_remote_add, #git_version, #has_git?

Methods included from Helpers

#agree, #certificate_file, #client_from_options, #collect_env_vars, #color, #confirm_action, #date, #datetime_rfc3339, #debug, #debug?, #debug_error, #decode_json, #deprecated, #deprecated_command, #disable_deprecated?, #distance_of_time_in_words, #env_var_regex_pattern, #error, #exec, #host_exists?, #hosts_file_contains?, #human_size, #info, #interactive?, #jruby?, #mac?, #openshift_online_server?, #openshift_rest_endpoint, #openshift_server, #openshift_url, #pluralize, #results, #role_name, #run_with_tee, #ssh_string, #ssh_string_parts, #ssl_options, #success, #system_path, #table_heading, #to_host, #to_uri, #token_for_user, #unix?, #user_agent, #warn, #windows?, #with_tolerant_encoding

Methods included from OutputHelpers

#default_display_env_var, #display_app, #display_app_configurations, #display_authorization, #display_cart, #display_cart_storage_info, #display_cart_storage_list, #display_deployment, #display_deployment_list, #display_domain, #display_env_var_list, #display_key, #display_team, #format_cart_gears, #format_cart_header, #format_gear_info, #format_key_header, #format_scaling_info, #format_usage_message

Constructor Details

This class inherits a constructor from RHC::Commands::Base

Instance Method Details

#run(app) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/rhc/commands/port_forward.rb', line 74

def run(app)
  rest_app = find_app
  ssh_uri = URI.parse(options.gear ? rest_app.gear_ssh_url(options.gear) : rest_app.ssh_url)

  say "Using #{ssh_uri}..." if options.debug

  forwarding_specs = []

  begin
    say "Checking available ports ... "

    Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
      # If a specific gear is targeted, do not include remote (e.g. database) ports
      list_ports_cmd = "rhc-list-ports#{options.gear ? ' --exclude-remote' : ''}"
      ssh.exec! list_ports_cmd 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 =~ /permission denied/i
            # ...and also which services are available for the application
            # for us to forward ports for.
            if line =~ /\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
        if rest_app.gear_groups.all?{ |gg| gg.gears.all?{ |g| g["state"] == "stopped" } }
          warn "none"
          error "The application is stopped. Please restart the application and try again."
          return 1
        else
          warn "none"
          raise RHC::NoPortsToForwardException.new "There are no available ports to forward for this application. Your application may be stopped or idled."
        end
      end

      success "done"

      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" }
            paragraph do
              say table(
                    bound_ports.map do |fs|
                      [fs.service, "#{fs.host_from}:#{fs.port_from}", " => ", "#{fs.remote_host}:#{fs.port_to.to_s}"]
                    end,
                    :header => ["Service", "Local", "    ", "OpenShift"]
                  )
            end
          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
        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