pbox 1.17.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/COPYRIGHT +1 -0
  3. data/LICENSE +11 -0
  4. data/README.md +40 -0
  5. data/Rakefile +6 -0
  6. data/autocomplete/pbox_bash +1639 -0
  7. data/bin/pbox +37 -0
  8. data/conf/protonbox.conf +8 -0
  9. data/features/assets/deploy.tar.gz +0 -0
  10. data/features/core_feature.rb +178 -0
  11. data/features/deployments_feature.rb +127 -0
  12. data/features/domains_feature.rb +49 -0
  13. data/features/keys_feature.rb +37 -0
  14. data/features/members_feature.rb +166 -0
  15. data/lib/rhc/auth/basic.rb +64 -0
  16. data/lib/rhc/auth/token.rb +102 -0
  17. data/lib/rhc/auth/token_store.rb +53 -0
  18. data/lib/rhc/auth.rb +5 -0
  19. data/lib/rhc/autocomplete.rb +66 -0
  20. data/lib/rhc/autocomplete_templates/bash.erb +39 -0
  21. data/lib/rhc/cartridge_helpers.rb +118 -0
  22. data/lib/rhc/cli.rb +40 -0
  23. data/lib/rhc/command_runner.rb +186 -0
  24. data/lib/rhc/commands/account.rb +25 -0
  25. data/lib/rhc/commands/alias.rb +124 -0
  26. data/lib/rhc/commands/app.rb +701 -0
  27. data/lib/rhc/commands/apps.rb +20 -0
  28. data/lib/rhc/commands/authorization.rb +96 -0
  29. data/lib/rhc/commands/base.rb +174 -0
  30. data/lib/rhc/commands/cartridge.rb +326 -0
  31. data/lib/rhc/commands/deployment.rb +82 -0
  32. data/lib/rhc/commands/domain.rb +167 -0
  33. data/lib/rhc/commands/env.rb +142 -0
  34. data/lib/rhc/commands/git_clone.rb +29 -0
  35. data/lib/rhc/commands/logout.rb +51 -0
  36. data/lib/rhc/commands/member.rb +148 -0
  37. data/lib/rhc/commands/port_forward.rb +197 -0
  38. data/lib/rhc/commands/server.rb +40 -0
  39. data/lib/rhc/commands/setup.rb +60 -0
  40. data/lib/rhc/commands/snapshot.rb +137 -0
  41. data/lib/rhc/commands/ssh.rb +51 -0
  42. data/lib/rhc/commands/sshkey.rb +97 -0
  43. data/lib/rhc/commands/tail.rb +47 -0
  44. data/lib/rhc/commands/threaddump.rb +14 -0
  45. data/lib/rhc/commands.rb +396 -0
  46. data/lib/rhc/config.rb +320 -0
  47. data/lib/rhc/context_helper.rb +121 -0
  48. data/lib/rhc/core_ext.rb +202 -0
  49. data/lib/rhc/coverage_helper.rb +33 -0
  50. data/lib/rhc/deployment_helpers.rb +88 -0
  51. data/lib/rhc/exceptions.rb +232 -0
  52. data/lib/rhc/git_helpers.rb +91 -0
  53. data/lib/rhc/help_formatter.rb +55 -0
  54. data/lib/rhc/helpers.rb +477 -0
  55. data/lib/rhc/highline_extensions.rb +479 -0
  56. data/lib/rhc/json.rb +51 -0
  57. data/lib/rhc/output_helpers.rb +260 -0
  58. data/lib/rhc/rest/activation.rb +11 -0
  59. data/lib/rhc/rest/alias.rb +42 -0
  60. data/lib/rhc/rest/api.rb +87 -0
  61. data/lib/rhc/rest/application.rb +332 -0
  62. data/lib/rhc/rest/attributes.rb +36 -0
  63. data/lib/rhc/rest/authorization.rb +8 -0
  64. data/lib/rhc/rest/base.rb +79 -0
  65. data/lib/rhc/rest/cartridge.rb +154 -0
  66. data/lib/rhc/rest/client.rb +650 -0
  67. data/lib/rhc/rest/deployment.rb +18 -0
  68. data/lib/rhc/rest/domain.rb +98 -0
  69. data/lib/rhc/rest/environment_variable.rb +15 -0
  70. data/lib/rhc/rest/gear_group.rb +16 -0
  71. data/lib/rhc/rest/httpclient.rb +145 -0
  72. data/lib/rhc/rest/key.rb +44 -0
  73. data/lib/rhc/rest/membership.rb +105 -0
  74. data/lib/rhc/rest/mock.rb +1024 -0
  75. data/lib/rhc/rest/user.rb +32 -0
  76. data/lib/rhc/rest.rb +148 -0
  77. data/lib/rhc/ssh_helpers.rb +378 -0
  78. data/lib/rhc/tar_gz.rb +51 -0
  79. data/lib/rhc/usage_templates/command_help.erb +51 -0
  80. data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
  81. data/lib/rhc/usage_templates/help.erb +35 -0
  82. data/lib/rhc/usage_templates/missing_help.erb +1 -0
  83. data/lib/rhc/usage_templates/options_help.erb +12 -0
  84. data/lib/rhc/vendor/okjson.rb +600 -0
  85. data/lib/rhc/vendor/parseconfig.rb +178 -0
  86. data/lib/rhc/vendor/sshkey.rb +253 -0
  87. data/lib/rhc/vendor/zliby.rb +628 -0
  88. data/lib/rhc/version.rb +5 -0
  89. data/lib/rhc/wizard.rb +633 -0
  90. data/lib/rhc.rb +34 -0
  91. data/spec/coverage_helper.rb +89 -0
  92. data/spec/direct_execution_helper.rb +338 -0
  93. data/spec/keys/example.pem +23 -0
  94. data/spec/keys/example_private.pem +27 -0
  95. data/spec/keys/server.pem +19 -0
  96. data/spec/rest_spec_helper.rb +31 -0
  97. data/spec/rhc/assets/cert.crt +22 -0
  98. data/spec/rhc/assets/cert_key_rsa +27 -0
  99. data/spec/rhc/assets/empty.txt +0 -0
  100. data/spec/rhc/assets/env_vars.txt +7 -0
  101. data/spec/rhc/assets/env_vars_2.txt +1 -0
  102. data/spec/rhc/assets/foo.txt +1 -0
  103. data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
  104. data/spec/rhc/assets/targz_sample.tar.gz +0 -0
  105. data/spec/rhc/auth_spec.rb +442 -0
  106. data/spec/rhc/cli_spec.rb +188 -0
  107. data/spec/rhc/command_spec.rb +435 -0
  108. data/spec/rhc/commands/account_spec.rb +42 -0
  109. data/spec/rhc/commands/alias_spec.rb +333 -0
  110. data/spec/rhc/commands/app_spec.rb +754 -0
  111. data/spec/rhc/commands/apps_spec.rb +39 -0
  112. data/spec/rhc/commands/authorization_spec.rb +145 -0
  113. data/spec/rhc/commands/cartridge_spec.rb +641 -0
  114. data/spec/rhc/commands/deployment_spec.rb +286 -0
  115. data/spec/rhc/commands/domain_spec.rb +383 -0
  116. data/spec/rhc/commands/env_spec.rb +493 -0
  117. data/spec/rhc/commands/git_clone_spec.rb +80 -0
  118. data/spec/rhc/commands/logout_spec.rb +86 -0
  119. data/spec/rhc/commands/member_spec.rb +228 -0
  120. data/spec/rhc/commands/port_forward_spec.rb +217 -0
  121. data/spec/rhc/commands/server_spec.rb +69 -0
  122. data/spec/rhc/commands/setup_spec.rb +118 -0
  123. data/spec/rhc/commands/snapshot_spec.rb +179 -0
  124. data/spec/rhc/commands/ssh_spec.rb +163 -0
  125. data/spec/rhc/commands/sshkey_spec.rb +188 -0
  126. data/spec/rhc/commands/tail_spec.rb +81 -0
  127. data/spec/rhc/commands/threaddump_spec.rb +84 -0
  128. data/spec/rhc/config_spec.rb +407 -0
  129. data/spec/rhc/helpers_spec.rb +524 -0
  130. data/spec/rhc/highline_extensions_spec.rb +314 -0
  131. data/spec/rhc/json_spec.rb +30 -0
  132. data/spec/rhc/rest_application_spec.rb +248 -0
  133. data/spec/rhc/rest_client_spec.rb +752 -0
  134. data/spec/rhc/rest_spec.rb +740 -0
  135. data/spec/rhc/targz_spec.rb +55 -0
  136. data/spec/rhc/wizard_spec.rb +756 -0
  137. data/spec/spec_helper.rb +575 -0
  138. data/spec/wizard_spec_helper.rb +330 -0
  139. 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