pbox 1.17.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYRIGHT +1 -0
- data/LICENSE +11 -0
- data/README.md +40 -0
- data/Rakefile +6 -0
- data/autocomplete/pbox_bash +1639 -0
- data/bin/pbox +37 -0
- data/conf/protonbox.conf +8 -0
- data/features/assets/deploy.tar.gz +0 -0
- data/features/core_feature.rb +178 -0
- data/features/deployments_feature.rb +127 -0
- data/features/domains_feature.rb +49 -0
- data/features/keys_feature.rb +37 -0
- data/features/members_feature.rb +166 -0
- data/lib/rhc/auth/basic.rb +64 -0
- data/lib/rhc/auth/token.rb +102 -0
- data/lib/rhc/auth/token_store.rb +53 -0
- data/lib/rhc/auth.rb +5 -0
- data/lib/rhc/autocomplete.rb +66 -0
- data/lib/rhc/autocomplete_templates/bash.erb +39 -0
- data/lib/rhc/cartridge_helpers.rb +118 -0
- data/lib/rhc/cli.rb +40 -0
- data/lib/rhc/command_runner.rb +186 -0
- data/lib/rhc/commands/account.rb +25 -0
- data/lib/rhc/commands/alias.rb +124 -0
- data/lib/rhc/commands/app.rb +701 -0
- data/lib/rhc/commands/apps.rb +20 -0
- data/lib/rhc/commands/authorization.rb +96 -0
- data/lib/rhc/commands/base.rb +174 -0
- data/lib/rhc/commands/cartridge.rb +326 -0
- data/lib/rhc/commands/deployment.rb +82 -0
- data/lib/rhc/commands/domain.rb +167 -0
- data/lib/rhc/commands/env.rb +142 -0
- data/lib/rhc/commands/git_clone.rb +29 -0
- data/lib/rhc/commands/logout.rb +51 -0
- data/lib/rhc/commands/member.rb +148 -0
- data/lib/rhc/commands/port_forward.rb +197 -0
- data/lib/rhc/commands/server.rb +40 -0
- data/lib/rhc/commands/setup.rb +60 -0
- data/lib/rhc/commands/snapshot.rb +137 -0
- data/lib/rhc/commands/ssh.rb +51 -0
- data/lib/rhc/commands/sshkey.rb +97 -0
- data/lib/rhc/commands/tail.rb +47 -0
- data/lib/rhc/commands/threaddump.rb +14 -0
- data/lib/rhc/commands.rb +396 -0
- data/lib/rhc/config.rb +320 -0
- data/lib/rhc/context_helper.rb +121 -0
- data/lib/rhc/core_ext.rb +202 -0
- data/lib/rhc/coverage_helper.rb +33 -0
- data/lib/rhc/deployment_helpers.rb +88 -0
- data/lib/rhc/exceptions.rb +232 -0
- data/lib/rhc/git_helpers.rb +91 -0
- data/lib/rhc/help_formatter.rb +55 -0
- data/lib/rhc/helpers.rb +477 -0
- data/lib/rhc/highline_extensions.rb +479 -0
- data/lib/rhc/json.rb +51 -0
- data/lib/rhc/output_helpers.rb +260 -0
- data/lib/rhc/rest/activation.rb +11 -0
- data/lib/rhc/rest/alias.rb +42 -0
- data/lib/rhc/rest/api.rb +87 -0
- data/lib/rhc/rest/application.rb +332 -0
- data/lib/rhc/rest/attributes.rb +36 -0
- data/lib/rhc/rest/authorization.rb +8 -0
- data/lib/rhc/rest/base.rb +79 -0
- data/lib/rhc/rest/cartridge.rb +154 -0
- data/lib/rhc/rest/client.rb +650 -0
- data/lib/rhc/rest/deployment.rb +18 -0
- data/lib/rhc/rest/domain.rb +98 -0
- data/lib/rhc/rest/environment_variable.rb +15 -0
- data/lib/rhc/rest/gear_group.rb +16 -0
- data/lib/rhc/rest/httpclient.rb +145 -0
- data/lib/rhc/rest/key.rb +44 -0
- data/lib/rhc/rest/membership.rb +105 -0
- data/lib/rhc/rest/mock.rb +1024 -0
- data/lib/rhc/rest/user.rb +32 -0
- data/lib/rhc/rest.rb +148 -0
- data/lib/rhc/ssh_helpers.rb +378 -0
- data/lib/rhc/tar_gz.rb +51 -0
- data/lib/rhc/usage_templates/command_help.erb +51 -0
- data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
- data/lib/rhc/usage_templates/help.erb +35 -0
- data/lib/rhc/usage_templates/missing_help.erb +1 -0
- data/lib/rhc/usage_templates/options_help.erb +12 -0
- data/lib/rhc/vendor/okjson.rb +600 -0
- data/lib/rhc/vendor/parseconfig.rb +178 -0
- data/lib/rhc/vendor/sshkey.rb +253 -0
- data/lib/rhc/vendor/zliby.rb +628 -0
- data/lib/rhc/version.rb +5 -0
- data/lib/rhc/wizard.rb +633 -0
- data/lib/rhc.rb +34 -0
- data/spec/coverage_helper.rb +89 -0
- data/spec/direct_execution_helper.rb +338 -0
- data/spec/keys/example.pem +23 -0
- data/spec/keys/example_private.pem +27 -0
- data/spec/keys/server.pem +19 -0
- data/spec/rest_spec_helper.rb +31 -0
- data/spec/rhc/assets/cert.crt +22 -0
- data/spec/rhc/assets/cert_key_rsa +27 -0
- data/spec/rhc/assets/empty.txt +0 -0
- data/spec/rhc/assets/env_vars.txt +7 -0
- data/spec/rhc/assets/env_vars_2.txt +1 -0
- data/spec/rhc/assets/foo.txt +1 -0
- data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
- data/spec/rhc/assets/targz_sample.tar.gz +0 -0
- data/spec/rhc/auth_spec.rb +442 -0
- data/spec/rhc/cli_spec.rb +188 -0
- data/spec/rhc/command_spec.rb +435 -0
- data/spec/rhc/commands/account_spec.rb +42 -0
- data/spec/rhc/commands/alias_spec.rb +333 -0
- data/spec/rhc/commands/app_spec.rb +754 -0
- data/spec/rhc/commands/apps_spec.rb +39 -0
- data/spec/rhc/commands/authorization_spec.rb +145 -0
- data/spec/rhc/commands/cartridge_spec.rb +641 -0
- data/spec/rhc/commands/deployment_spec.rb +286 -0
- data/spec/rhc/commands/domain_spec.rb +383 -0
- data/spec/rhc/commands/env_spec.rb +493 -0
- data/spec/rhc/commands/git_clone_spec.rb +80 -0
- data/spec/rhc/commands/logout_spec.rb +86 -0
- data/spec/rhc/commands/member_spec.rb +228 -0
- data/spec/rhc/commands/port_forward_spec.rb +217 -0
- data/spec/rhc/commands/server_spec.rb +69 -0
- data/spec/rhc/commands/setup_spec.rb +118 -0
- data/spec/rhc/commands/snapshot_spec.rb +179 -0
- data/spec/rhc/commands/ssh_spec.rb +163 -0
- data/spec/rhc/commands/sshkey_spec.rb +188 -0
- data/spec/rhc/commands/tail_spec.rb +81 -0
- data/spec/rhc/commands/threaddump_spec.rb +84 -0
- data/spec/rhc/config_spec.rb +407 -0
- data/spec/rhc/helpers_spec.rb +524 -0
- data/spec/rhc/highline_extensions_spec.rb +314 -0
- data/spec/rhc/json_spec.rb +30 -0
- data/spec/rhc/rest_application_spec.rb +248 -0
- data/spec/rhc/rest_client_spec.rb +752 -0
- data/spec/rhc/rest_spec.rb +740 -0
- data/spec/rhc/targz_spec.rb +55 -0
- data/spec/rhc/wizard_spec.rb +756 -0
- data/spec/spec_helper.rb +575 -0
- data/spec/wizard_spec_helper.rb +330 -0
- metadata +435 -0
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module RHC::Commands
|
4
|
+
class ForwardingSpec
|
5
|
+
include RHC::Helpers
|
6
|
+
include Enumerable
|
7
|
+
# class to represent how SSH port forwarding should be performed
|
8
|
+
attr_accessor :port_from
|
9
|
+
attr_reader :remote_host, :port_to, :host_from, :service
|
10
|
+
attr_writer :bound
|
11
|
+
|
12
|
+
def initialize(service, remote_host, port_to, port_from = nil)
|
13
|
+
@service = service
|
14
|
+
@remote_host = remote_host
|
15
|
+
@port_to = port_to
|
16
|
+
@host_from = '127.0.0.1'
|
17
|
+
@port_from = port_from || port_to # match ports if possible
|
18
|
+
@bound = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_cmd_arg
|
22
|
+
# string to be used in a direct SSH command
|
23
|
+
"-L #{port_from}:#{remote_host}:#{port_to}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_fwd_args
|
27
|
+
# array of arguments to be passed to Net::SSH::Service::Forward#local
|
28
|
+
[port_from.to_i, remote_host, port_to.to_i]
|
29
|
+
end
|
30
|
+
|
31
|
+
def bound?
|
32
|
+
@bound
|
33
|
+
end
|
34
|
+
|
35
|
+
# :nocov: These are for sorting. No need to test for coverage.
|
36
|
+
def <=>(other)
|
37
|
+
if bound? && !other.bound?
|
38
|
+
-1
|
39
|
+
elsif !bound? && other.bound?
|
40
|
+
1
|
41
|
+
else
|
42
|
+
order_by_attrs(other, :service, :remote_host, :port_from)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def order_by_attrs(other, *attrs)
|
47
|
+
# compare self and "other" by examining their "attrs" in order
|
48
|
+
# attrs should be an array of symbols to which self and "other"
|
49
|
+
# respond when sent.
|
50
|
+
while attribute = attrs.shift do
|
51
|
+
if self.send(attribute) != other.send(attribute)
|
52
|
+
return self.send(attribute) <=> other.send(attribute)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
0
|
56
|
+
end
|
57
|
+
# :nocov:
|
58
|
+
|
59
|
+
private :order_by_attrs
|
60
|
+
end
|
61
|
+
|
62
|
+
class PortForward < Base
|
63
|
+
|
64
|
+
UP_TO_256 = /25[0-5]|2[0-4][0-9]|[01]?(?:[0-9][0-9]?)/
|
65
|
+
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})/
|
66
|
+
# 'host' part is a bit lax; we rely on 'pbox-list-ports' to hand us a reasonable output
|
67
|
+
# about the host information, be it numeric or FQDN in IPv4 or IPv6.
|
68
|
+
HOST_AND_PORT = /(.+):(#{UP_TO_65535})\b/
|
69
|
+
|
70
|
+
summary "Forward remote ports to the workstation"
|
71
|
+
syntax "<application>"
|
72
|
+
takes_application :argument => true
|
73
|
+
option ["-g", "--gear ID"], "Gear ID you are port forwarding to (optional)"
|
74
|
+
def run(app)
|
75
|
+
rest_app = find_app
|
76
|
+
ssh_uri = URI.parse(options.gear ? rest_app.gear_ssh_url(options.gear) : rest_app.ssh_url)
|
77
|
+
|
78
|
+
say "Using #{ssh_uri}..." if options.debug
|
79
|
+
|
80
|
+
forwarding_specs = []
|
81
|
+
|
82
|
+
begin
|
83
|
+
say "Checking available ports ... "
|
84
|
+
|
85
|
+
Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
|
86
|
+
# If a specific gear is targeted, do not include remote (e.g. database) ports
|
87
|
+
list_ports_cmd = "pbox-list-ports#{options.gear ? ' --exclude-remote' : ''}"
|
88
|
+
ssh.exec! list_ports_cmd do |channel, stream, data|
|
89
|
+
if stream == :stderr
|
90
|
+
data.each_line do |line|
|
91
|
+
line.chomp!
|
92
|
+
# FIXME: This is really brittle; there must be a better way
|
93
|
+
# for the server to tell us that permission (what permission?)
|
94
|
+
# is denied.
|
95
|
+
raise RHC::PermissionDeniedException.new "Permission denied." if line =~ /permission denied/i
|
96
|
+
# ...and also which services are available for the application
|
97
|
+
# for us to forward ports for.
|
98
|
+
if line =~ /\A\s*(\S+) -> #{HOST_AND_PORT}\z/
|
99
|
+
debug fs = ForwardingSpec.new($1, $2, $3.to_i)
|
100
|
+
forwarding_specs << fs
|
101
|
+
else
|
102
|
+
debug line
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if forwarding_specs.length == 0
|
110
|
+
# check if the gears have been stopped
|
111
|
+
if rest_app.gear_groups.all?{ |gg| gg.gears.all?{ |g| g["state"] == "stopped" } }
|
112
|
+
warn "none"
|
113
|
+
error "The application is stopped. Please restart the application and try again."
|
114
|
+
return 1
|
115
|
+
else
|
116
|
+
warn "none"
|
117
|
+
raise RHC::NoPortsToForwardException.new "There are no available ports to forward for this application. Your application may be stopped or idled."
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
success "done"
|
122
|
+
|
123
|
+
begin
|
124
|
+
Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
|
125
|
+
say "Forwarding ports ..."
|
126
|
+
forwarding_specs.each do |fs|
|
127
|
+
given_up = nil
|
128
|
+
while !fs.bound? && !given_up
|
129
|
+
begin
|
130
|
+
args = fs.to_fwd_args
|
131
|
+
debug args.inspect
|
132
|
+
ssh.forward.local(*args)
|
133
|
+
fs.bound = true
|
134
|
+
rescue Errno::EADDRINUSE, Errno::EACCES => e
|
135
|
+
warn "#{e} while forwarding port #{fs.port_from}. Trying local port #{fs.port_from+1}"
|
136
|
+
fs.port_from += 1
|
137
|
+
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
138
|
+
given_up = true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
bound_ports = forwarding_specs.select(&:bound?)
|
144
|
+
if bound_ports.length > 0
|
145
|
+
paragraph{ say "To connect to a service running on ProtonBox, use the Local address" }
|
146
|
+
paragraph do
|
147
|
+
say table(
|
148
|
+
bound_ports.map do |fs|
|
149
|
+
[fs.service, "#{fs.host_from}:#{fs.port_from}", " => ", "#{fs.remote_host}:#{fs.port_to.to_s}"]
|
150
|
+
end,
|
151
|
+
:header => ["Service", "Local", " ", "ProtonBox"]
|
152
|
+
)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# for failed port forwarding attempts
|
157
|
+
failed_port_forwards = forwarding_specs.select { |fs| !fs.bound? }
|
158
|
+
if failed_port_forwards.length > 0
|
159
|
+
ssh_cmd_arg = failed_port_forwards.map { |fs| fs.to_cmd_arg }.join(" ")
|
160
|
+
ssh_cmd = "ssh -N #{ssh_cmd_arg} #{ssh_uri.user}@#{ssh_uri.host}"
|
161
|
+
warn "Error forwarding some port(s). You can try to forward manually by running:\n#{ssh_cmd}"
|
162
|
+
else
|
163
|
+
say "Press CTRL-C to terminate port forwarding"
|
164
|
+
end
|
165
|
+
|
166
|
+
unless forwarding_specs.any?(&:bound?)
|
167
|
+
warn "No ports have been bound"
|
168
|
+
return
|
169
|
+
end
|
170
|
+
ssh.loop { true }
|
171
|
+
end
|
172
|
+
rescue Interrupt
|
173
|
+
say " Ending port forward"
|
174
|
+
return 0
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
180
|
+
ssh_cmd = ["ssh","-N"]
|
181
|
+
unbound_fs = forwarding_specs.select { |fs| !fs.bound? }
|
182
|
+
ssh_cmd += unbound_fs.map { |fs| fs.to_cmd_arg }
|
183
|
+
ssh_cmd += ["#{ssh_uri.user}@#{ssh_uri.host}"]
|
184
|
+
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(" "))
|
185
|
+
end
|
186
|
+
|
187
|
+
0
|
188
|
+
rescue RHC::Rest::ConnectionException => e
|
189
|
+
error "Connection to #{protonbox_server} failed: #{e.message}"
|
190
|
+
1
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# mock for windows
|
196
|
+
if defined?(UNIXServer) != 'constant' or UNIXServer.class != Class then class UNIXServer; end; end
|
197
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RHC::Commands
|
2
|
+
class Server < Base
|
3
|
+
suppress_wizard
|
4
|
+
|
5
|
+
summary "Display information about the status of the ProtonBox service"
|
6
|
+
description <<-DESC
|
7
|
+
Retrieves any open issues or notices about the operation of the
|
8
|
+
ProtonBox service and displays them in the order they were opened.
|
9
|
+
|
10
|
+
When connected to an ProtonBox Enterprise server, will only display
|
11
|
+
the version of the API that it is connecting to.
|
12
|
+
DESC
|
13
|
+
def run
|
14
|
+
say "Connected to #{protonbox_server}"
|
15
|
+
|
16
|
+
if protonbox_online_server?
|
17
|
+
#status = decode_json(get("#{protonbox_url}/app/status/status.json").body)
|
18
|
+
status = rest_client.request(:method => :get, :url => "#{protonbox_url}/app/status/status.json", :lazy_auth => true){ |res| decode_json(res.content) }
|
19
|
+
open = status['open']
|
20
|
+
|
21
|
+
(success 'All systems running fine' and return 0) if open.blank?
|
22
|
+
|
23
|
+
open.each do |i|
|
24
|
+
i = i['issue']
|
25
|
+
say color("%-3s %s" % ["##{i['id']}", i['title']], :bold)
|
26
|
+
items = i['updates'].map{ |u| [u['description'], date(u['created_at'])] }
|
27
|
+
items.unshift ['Opened', date(i['created_at'])]
|
28
|
+
table(items, :align => [nil,:right], :join => ' ').each{ |s| say " #{s}" }
|
29
|
+
end
|
30
|
+
say "\n"
|
31
|
+
warn pluralize(open.length, "open issue")
|
32
|
+
|
33
|
+
open.length #exit with the count of open items
|
34
|
+
else
|
35
|
+
success "Using API version #{rest_client.api_version_negotiated}"
|
36
|
+
0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rhc/commands/base'
|
2
|
+
require 'rhc/wizard'
|
3
|
+
require 'rhc/config'
|
4
|
+
|
5
|
+
module RHC::Commands
|
6
|
+
class Setup < Base
|
7
|
+
suppress_wizard
|
8
|
+
|
9
|
+
summary "Connects to ProtonBox and sets up your keys and domain"
|
10
|
+
description <<-DESC
|
11
|
+
Connects to an ProtonBox server to get you started. Will help you
|
12
|
+
configure your SSH keys, set up a domain, and check for any potential
|
13
|
+
problems with Git or SSH.
|
14
|
+
|
15
|
+
Any options you pass to the setup command will be stored in a
|
16
|
+
.protonbox/protonbox.conf file in your home directory. If you run
|
17
|
+
setup at a later time, any previous configuration will be reused.
|
18
|
+
|
19
|
+
Pass the --clean option to ignore your saved configuration and only
|
20
|
+
use options you pass on the command line. Pass --config FILE to use
|
21
|
+
default values from another config (the values will still be written
|
22
|
+
to .protonbox/protonbox.conf).
|
23
|
+
|
24
|
+
If the server supports authorization tokens, you may pass the
|
25
|
+
--create-token option to instruct the wizard to generate a key for you.
|
26
|
+
|
27
|
+
If you would like to enable tab-completion in Bash shells, pass
|
28
|
+
--autocomplete for more information.
|
29
|
+
DESC
|
30
|
+
option ["--server NAME"], "Hostname of the ProtonBox server", :default => :server_context, :required => true
|
31
|
+
option ['--clean'], "Ignore any saved configuration options"
|
32
|
+
option ['--[no-]create-token'], "Create an authorization token for this server"
|
33
|
+
option ['--autocomplete'], "Instructions for enabling tab-completion"
|
34
|
+
def run
|
35
|
+
if options.autocomplete
|
36
|
+
src = File.join(File.join(Gem.loaded_specs['pbox'].full_gem_path, "autocomplete"), "pbox_bash")
|
37
|
+
dest = File.join(RHC::Config.home_conf_dir, "bash_autocomplete")
|
38
|
+
|
39
|
+
FileUtils.mkdir_p(RHC::Config.home_conf_dir)
|
40
|
+
FileUtils.cp(src, dest)
|
41
|
+
|
42
|
+
say <<-LINE.strip_heredoc
|
43
|
+
To enable tab-completion for PBOX under Bash shells, add the following command to
|
44
|
+
your .bashrc or .bash_profile file:
|
45
|
+
|
46
|
+
. #{dest}
|
47
|
+
|
48
|
+
Save your shell and then restart. Type "pbox" and then hit the TAB key twice to
|
49
|
+
trigger completion of your command.
|
50
|
+
|
51
|
+
Tab-completion is not available in the Windows terminal.
|
52
|
+
LINE
|
53
|
+
return 0
|
54
|
+
end
|
55
|
+
|
56
|
+
raise OptionParser::InvalidOption, "Setup can not be run with the --noprompt option" if options.noprompt
|
57
|
+
RHC::RerunWizard.new(config, options).run ? 0 : 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'rhc/commands/base'
|
2
|
+
|
3
|
+
module RHC::Commands
|
4
|
+
class Snapshot < Base
|
5
|
+
summary "Save the current state of your application locally"
|
6
|
+
syntax "<action>"
|
7
|
+
description <<-DESC
|
8
|
+
Snapshots allow you to export the current state of your ProtonBox application
|
9
|
+
into an archive on your local system, and then to restore it later.
|
10
|
+
|
11
|
+
The snapshot archive contains the Git repository, dumps of any attached databases,
|
12
|
+
and any other information that the cartridges decide to export.
|
13
|
+
|
14
|
+
WARNING: Both 'save' and 'restore' will stop the application and then restart
|
15
|
+
after the operation completes.
|
16
|
+
DESC
|
17
|
+
alias_action :"app snapshot", :root_command => true
|
18
|
+
default_action :help
|
19
|
+
|
20
|
+
summary "Save a snapshot of your app to disk"
|
21
|
+
syntax "<application> [--filepath FILE] [--ssh path_to_ssh_executable]"
|
22
|
+
takes_application :argument => true
|
23
|
+
option ["-f", "--filepath FILE"], "Local path to save tarball (default: ./$APPNAME.tar.gz)"
|
24
|
+
option ["--deployment"], "Snapshot as a deployable file which can be deployed with 'pbox deploy'"
|
25
|
+
option ["--ssh PATH"], "Full path to your SSH executable with additional options"
|
26
|
+
alias_action :"app snapshot save", :root_command => true, :deprecated => true
|
27
|
+
def save(app)
|
28
|
+
ssh = check_ssh_executable! options.ssh
|
29
|
+
rest_app = find_app
|
30
|
+
|
31
|
+
raise RHC::DeploymentsNotSupportedException.new if options.deployment && !rest_app.supports?("DEPLOY")
|
32
|
+
|
33
|
+
ssh_uri = URI.parse(rest_app.ssh_url)
|
34
|
+
filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz"
|
35
|
+
|
36
|
+
snapshot_cmd = options.deployment ? 'gear archive-deployment' : 'snapshot'
|
37
|
+
ssh_cmd = "#{ssh} #{ssh_uri.user}@#{ssh_uri.host} '#{snapshot_cmd}' > #{filename}"
|
38
|
+
debug ssh_cmd
|
39
|
+
|
40
|
+
say "Pulling down a snapshot to #{filename}..."
|
41
|
+
|
42
|
+
begin
|
43
|
+
if !RHC::Helpers.windows?
|
44
|
+
status, output = exec(ssh_cmd)
|
45
|
+
if status != 0
|
46
|
+
debug output
|
47
|
+
raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
|
48
|
+
end
|
49
|
+
else
|
50
|
+
Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
|
51
|
+
File.open(filename, 'wb') do |file|
|
52
|
+
ssh.exec! "snapshot" do |channel, stream, data|
|
53
|
+
if stream == :stdout
|
54
|
+
file.write(data)
|
55
|
+
else
|
56
|
+
debug data
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
63
|
+
debug e.backtrace
|
64
|
+
raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
|
65
|
+
end
|
66
|
+
results { say "Success" }
|
67
|
+
0
|
68
|
+
end
|
69
|
+
|
70
|
+
summary "Restores a previously saved snapshot"
|
71
|
+
syntax "<application> [--filepath FILE] [--ssh path_to_ssh_executable]"
|
72
|
+
takes_application :argument => true
|
73
|
+
option ["-f", "--filepath FILE"], "Local path to restore tarball"
|
74
|
+
option ["--ssh PATH"], "Full path to your SSH executable with additional options"
|
75
|
+
alias_action :"app snapshot restore", :root_command => true, :deprecated => true
|
76
|
+
def restore(app)
|
77
|
+
ssh = check_ssh_executable! options.ssh
|
78
|
+
rest_app = find_app
|
79
|
+
filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz"
|
80
|
+
|
81
|
+
if File.exists? filename
|
82
|
+
|
83
|
+
include_git = RHC::Helpers.windows? ? true : RHC::TarGz.contains(filename, './*/git')
|
84
|
+
ssh_uri = URI.parse(rest_app.ssh_url)
|
85
|
+
|
86
|
+
ssh_cmd = "cat '#{filename}' | #{ssh} #{ssh_uri.user}@#{ssh_uri.host} 'restore#{include_git ? ' INCLUDE_GIT' : ''}'"
|
87
|
+
|
88
|
+
say "Restoring from snapshot #{filename}..."
|
89
|
+
debug ssh_cmd
|
90
|
+
|
91
|
+
begin
|
92
|
+
if !RHC::Helpers.windows?
|
93
|
+
status, output = exec(ssh_cmd)
|
94
|
+
if status != 0
|
95
|
+
debug output
|
96
|
+
raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
|
97
|
+
end
|
98
|
+
else
|
99
|
+
ssh = Net::SSH.start(ssh_uri.host, ssh_uri.user)
|
100
|
+
ssh.open_channel do |channel|
|
101
|
+
channel.exec("restore#{include_git ? ' INCLUDE_GIT' : ''}") do |ch, success|
|
102
|
+
channel.on_data do |ch, data|
|
103
|
+
say data
|
104
|
+
end
|
105
|
+
channel.on_extended_data do |ch, type, data|
|
106
|
+
say data
|
107
|
+
end
|
108
|
+
channel.on_close do |ch|
|
109
|
+
say "Terminating..."
|
110
|
+
end
|
111
|
+
File.open(filename, 'rb') do |file|
|
112
|
+
file.chunk(1024) do |chunk|
|
113
|
+
channel.send_data chunk
|
114
|
+
end
|
115
|
+
end
|
116
|
+
channel.eof!
|
117
|
+
end
|
118
|
+
end
|
119
|
+
ssh.loop
|
120
|
+
end
|
121
|
+
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
122
|
+
debug e.backtrace
|
123
|
+
raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
|
124
|
+
end
|
125
|
+
|
126
|
+
else
|
127
|
+
raise RHC::SnapshotRestoreException.new "Archive not found: #{filename}"
|
128
|
+
end
|
129
|
+
results { say "Success" }
|
130
|
+
0
|
131
|
+
end
|
132
|
+
|
133
|
+
protected
|
134
|
+
include RHC::SSHHelpers
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rhc/commands/base'
|
2
|
+
require 'resolv'
|
3
|
+
require 'rhc/git_helpers'
|
4
|
+
require 'rhc/cartridge_helpers'
|
5
|
+
|
6
|
+
module RHC::Commands
|
7
|
+
class Ssh < Base
|
8
|
+
suppress_wizard
|
9
|
+
|
10
|
+
summary "SSH into the specified application"
|
11
|
+
description <<-DESC
|
12
|
+
Connect to your application using SSH. This will connect to your primary gear
|
13
|
+
(the one with your Git repository and web cartridge) by default. To SSH to
|
14
|
+
other gears run 'pbox show-app --gears' to get a list of their SSH hosts.
|
15
|
+
|
16
|
+
You may run a specific SSH command by passing one or more arguments, or use a
|
17
|
+
different SSH executable or pass options to SSH with the '--ssh' option.
|
18
|
+
DESC
|
19
|
+
syntax "[--ssh path_to_ssh_executable] [--gears] [<app> --] <command>"
|
20
|
+
takes_application :argument => true
|
21
|
+
argument :command, "Command to run in the application's SSH session", ['--command COMMAND'], :type => :list, :optional => true
|
22
|
+
option ["--ssh PATH"], "Path to your SSH executable or additional options"
|
23
|
+
option ["--gears"], "Execute this command on all gears in the app. Requires a command."
|
24
|
+
option ["--limit INTEGER"], "Limit the number of simultaneous SSH connections opened with --gears (default: 5).", :type => Integer, :default => 5
|
25
|
+
option ["--raw"], "Output only the data returned by each host, no hostname prefix."
|
26
|
+
alias_action 'app ssh', :root_command => true
|
27
|
+
def run(_, command)
|
28
|
+
raise ArgumentError, "--gears requires a command" if options.gears && command.blank?
|
29
|
+
raise ArgumentError, "--limit must be an integer greater than zero" if options.limit && options.limit < 1
|
30
|
+
|
31
|
+
ssh = check_ssh_executable! options.ssh
|
32
|
+
|
33
|
+
if options.gears
|
34
|
+
run_on_gears(command.join(' '), find_app(:with_gear_groups => true))
|
35
|
+
0
|
36
|
+
else
|
37
|
+
rest_app = find_app
|
38
|
+
$stderr.puts "Connecting to #{rest_app.ssh_string.to_s} ..." unless command.present?
|
39
|
+
|
40
|
+
debug "Using user specified SSH: #{options.ssh}" if options.ssh
|
41
|
+
|
42
|
+
command_line = [ ssh.split, rest_app.ssh_string.to_s, command].flatten.compact
|
43
|
+
debug "Invoking Kernel.exec with #{command_line.inspect}"
|
44
|
+
Kernel.send(:exec, *command_line)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
include RHC::SSHHelpers
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'rhc/commands/base'
|
2
|
+
|
3
|
+
module RHC::Commands
|
4
|
+
class Sshkey < Base
|
5
|
+
include RHC::SSHHelpers
|
6
|
+
|
7
|
+
summary 'Add and remove keys for Git and SSH'
|
8
|
+
syntax '<action>'
|
9
|
+
description <<-DESC
|
10
|
+
ProtonBox uses public keys to securely access your application source
|
11
|
+
code and to control access to your application gears via SSH. Your
|
12
|
+
account may have one or more public SSH keys associated with it, and
|
13
|
+
any computer with the private SSH key will be able to download code
|
14
|
+
from Git or SSH to the application.
|
15
|
+
|
16
|
+
Depending on your operating system, you may have to ensure that both
|
17
|
+
Git and the local SSH installation have access to your keys. Running
|
18
|
+
the 'setup' command is any easy way to get your first key created and
|
19
|
+
uploaded.
|
20
|
+
DESC
|
21
|
+
default_action :list
|
22
|
+
|
23
|
+
summary 'Display all the SSH keys for your account'
|
24
|
+
syntax ''
|
25
|
+
def list
|
26
|
+
keys = rest_client.sshkeys.each{ |key| paragraph{ display_key(key) } }
|
27
|
+
|
28
|
+
success "You have #{keys.length} SSH keys associated with your account."
|
29
|
+
|
30
|
+
0
|
31
|
+
end
|
32
|
+
|
33
|
+
summary 'Show the SSH key with the given name'
|
34
|
+
syntax '<name>'
|
35
|
+
argument :name, 'SSH key to display', []
|
36
|
+
def show(name)
|
37
|
+
key = rest_client.find_key(name)
|
38
|
+
display_key(key)
|
39
|
+
|
40
|
+
0
|
41
|
+
end
|
42
|
+
|
43
|
+
summary 'Add SSH key to your account'
|
44
|
+
syntax '<name> <path to SSH key file>'
|
45
|
+
argument :name, 'Name for this key', []
|
46
|
+
argument :key, 'SSH public key filepath', [], :optional => true
|
47
|
+
option ['--confirm'], 'Bypass key validation'
|
48
|
+
option ['--type TYPE'], 'Provide the key type directly if no key file is given'
|
49
|
+
option ['--content CONTENT'], 'Provide the key content directly if no key file is given'
|
50
|
+
def add(name, key_path=nil)
|
51
|
+
|
52
|
+
if key_path
|
53
|
+
type, content, comment = ssh_key_triple_for(key_path)
|
54
|
+
elsif options[:type].present? and options[:content].present?
|
55
|
+
type = options[:type]
|
56
|
+
content = options[:content]
|
57
|
+
else
|
58
|
+
raise ArgumentError, "You must either provide a key file, or the key type and content"
|
59
|
+
end
|
60
|
+
|
61
|
+
if type == 'krb5-principal'
|
62
|
+
# TODO: validate krb5?
|
63
|
+
else
|
64
|
+
# validate the user input before sending it to the server
|
65
|
+
begin
|
66
|
+
Net::SSH::KeyFactory.load_data_public_key "#{type} #{content}"
|
67
|
+
rescue NotImplementedError, OpenSSL::PKey::PKeyError, Net::SSH::Exception => e
|
68
|
+
debug e.inspect
|
69
|
+
if options.confirm
|
70
|
+
warn 'The key you are uploading is not recognized. You may not be able to authenticate to your application through Git or SSH.'
|
71
|
+
else
|
72
|
+
raise ::RHC::KeyDataInvalidException.new("File '#{key_path}' does not appear to be a recognizable key file (#{e}). You may specify the '--confirm' flag to add the key anyway.") if key_path
|
73
|
+
raise ::RHC::KeyDataInvalidException.new("The provided type and content does not appear to be a recognizable key (#{e}). You may specify the '--confirm' flag to add the key anyway.")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
rest_client.add_key(name, content, type)
|
79
|
+
results { say key_path ? "SSH key #{key_path} has been added as '#{name}'" : "SSH key '#{name}' has been added" }
|
80
|
+
|
81
|
+
0
|
82
|
+
end
|
83
|
+
|
84
|
+
summary 'Remove SSH key from your account'
|
85
|
+
syntax '<name>'
|
86
|
+
alias_action :delete, :deprecated => true
|
87
|
+
argument :name, 'Name of SSH key to remove'
|
88
|
+
def remove(name)
|
89
|
+
say "Removing the key '#{name} ... "
|
90
|
+
rest_client.delete_key(name)
|
91
|
+
|
92
|
+
success "removed"
|
93
|
+
|
94
|
+
0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rhc/commands/base'
|
2
|
+
require 'rhc/config'
|
3
|
+
require 'rhc/ssh_helpers'
|
4
|
+
|
5
|
+
module RHC::Commands
|
6
|
+
class Tail < Base
|
7
|
+
include RHC::SSHHelpers
|
8
|
+
|
9
|
+
summary "Tail the logs of an application"
|
10
|
+
syntax "<application>"
|
11
|
+
takes_application :argument => true
|
12
|
+
option ["-o", "--opts options"], "Options to pass to the server-side (linux based) tail command (applicable to tail command only) (-f is implicit. See the linux tail man page full list of options.) (Ex: --opts '-n 100')"
|
13
|
+
option ["-f", "--files files"], "File glob relative to app (default <application_name>/logs/*) (optional)"
|
14
|
+
option ["-g", "--gear ID"], "Tail only a specific gear"
|
15
|
+
#option ["-c", "--cartridge name"], "Tail only a specific cartridge"
|
16
|
+
alias_action :"app tail", :root_command => true, :deprecated => true
|
17
|
+
def run(app_name)
|
18
|
+
rest_app = find_app(:include => :cartridges)
|
19
|
+
ssh_url = options.gear ? rest_app.gear_ssh_url(options.gear) : rest_app.ssh_url
|
20
|
+
|
21
|
+
tail('*', URI(ssh_url), options)
|
22
|
+
|
23
|
+
0
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
#Application log file tailing
|
28
|
+
def tail(cartridge_name, ssh_url, options)
|
29
|
+
debug "Tail in progress for cartridge #{cartridge_name}"
|
30
|
+
|
31
|
+
host = ssh_url.host
|
32
|
+
uuid = ssh_url.user
|
33
|
+
|
34
|
+
file_glob = options.files ? options.files : "#{cartridge_name}/log*/*"
|
35
|
+
remote_cmd = "tail#{options.opts ? ' --opts ' + Base64::encode64(options.opts).chomp : ''} #{file_glob}"
|
36
|
+
ssh_cmd = "ssh -t #{uuid}@#{host} '#{remote_cmd}'"
|
37
|
+
begin
|
38
|
+
#Use ssh -t to tail the logs
|
39
|
+
debug ssh_cmd
|
40
|
+
ssh_ruby(host, uuid, remote_cmd, false, true)
|
41
|
+
rescue
|
42
|
+
warn "You can tail this application directly with:\n#{ssh_cmd}"
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rhc/commands/base'
|
2
|
+
module RHC::Commands
|
3
|
+
class Threaddump < Base
|
4
|
+
summary "Trigger a thread dump for JBoss and Ruby apps"
|
5
|
+
syntax "<application>"
|
6
|
+
takes_application :argument => true
|
7
|
+
def run(app)
|
8
|
+
rest_app = find_app
|
9
|
+
rest_app.threaddump.messages.each { |m| say m }
|
10
|
+
|
11
|
+
0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|