pbox 1.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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