turbot 0.0.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.
- checksums.yaml +15 -0
- data/README.md +36 -0
- data/bin/turbot +17 -0
- data/data/cacert.pem +3988 -0
- data/lib/turbot/auth.rb +315 -0
- data/lib/turbot/cli.rb +38 -0
- data/lib/turbot/client/cisaurus.rb +25 -0
- data/lib/turbot/client/pgbackups.rb +113 -0
- data/lib/turbot/client/rendezvous.rb +111 -0
- data/lib/turbot/client/ssl_endpoint.rb +25 -0
- data/lib/turbot/client/turbot_postgresql.rb +148 -0
- data/lib/turbot/client.rb +757 -0
- data/lib/turbot/command/auth.rb +85 -0
- data/lib/turbot/command/base.rb +192 -0
- data/lib/turbot/command/bots.rb +326 -0
- data/lib/turbot/command/config.rb +123 -0
- data/lib/turbot/command/help.rb +179 -0
- data/lib/turbot/command/keys.rb +115 -0
- data/lib/turbot/command/logs.rb +34 -0
- data/lib/turbot/command/ssl.rb +43 -0
- data/lib/turbot/command/status.rb +51 -0
- data/lib/turbot/command/update.rb +47 -0
- data/lib/turbot/command/version.rb +23 -0
- data/lib/turbot/command.rb +304 -0
- data/lib/turbot/deprecated/help.rb +38 -0
- data/lib/turbot/deprecated.rb +5 -0
- data/lib/turbot/distribution.rb +9 -0
- data/lib/turbot/errors.rb +28 -0
- data/lib/turbot/excon.rb +11 -0
- data/lib/turbot/helpers/log_displayer.rb +70 -0
- data/lib/turbot/helpers/pg_dump_restore.rb +115 -0
- data/lib/turbot/helpers/turbot_postgresql.rb +213 -0
- data/lib/turbot/helpers.rb +521 -0
- data/lib/turbot/plugin.rb +165 -0
- data/lib/turbot/updater.rb +171 -0
- data/lib/turbot/version.rb +3 -0
- data/lib/turbot.rb +19 -0
- data/lib/vendor/turbot/okjson.rb +598 -0
- data/spec/helper/legacy_help.rb +16 -0
- data/spec/helper/pg_dump_restore_spec.rb +67 -0
- data/spec/schemas/dummy_schema.json +12 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +220 -0
- data/spec/support/display_message_matcher.rb +49 -0
- data/spec/support/dummy_api.rb +120 -0
- data/spec/support/openssl_mock_helper.rb +8 -0
- data/spec/support/organizations_mock_helper.rb +11 -0
- data/spec/turbot/auth_spec.rb +214 -0
- data/spec/turbot/client/pgbackups_spec.rb +43 -0
- data/spec/turbot/client/rendezvous_spec.rb +62 -0
- data/spec/turbot/client/ssl_endpoint_spec.rb +48 -0
- data/spec/turbot/client/turbot_postgresql_spec.rb +71 -0
- data/spec/turbot/client_spec.rb +548 -0
- data/spec/turbot/command/auth_spec.rb +38 -0
- data/spec/turbot/command/base_spec.rb +66 -0
- data/spec/turbot/command/bots_spec.rb +54 -0
- data/spec/turbot/command/config_spec.rb +143 -0
- data/spec/turbot/command/help_spec.rb +90 -0
- data/spec/turbot/command/keys_spec.rb +117 -0
- data/spec/turbot/command/logs_spec.rb +60 -0
- data/spec/turbot/command/status_spec.rb +48 -0
- data/spec/turbot/command/version_spec.rb +16 -0
- data/spec/turbot/command_spec.rb +131 -0
- data/spec/turbot/helpers/turbot_postgresql_spec.rb +181 -0
- data/spec/turbot/helpers_spec.rb +48 -0
- data/spec/turbot/plugin_spec.rb +172 -0
- data/spec/turbot/updater_spec.rb +44 -0
- data/templates/manifest.json +7 -0
- data/templates/scraper.py +5 -0
- data/templates/scraper.rb +6 -0
- metadata +199 -0
data/lib/turbot/auth.rb
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "turbot"
|
3
|
+
require "turbot/client"
|
4
|
+
require "turbot/helpers"
|
5
|
+
|
6
|
+
require "netrc"
|
7
|
+
|
8
|
+
class Turbot::Auth
|
9
|
+
class << self
|
10
|
+
include Turbot::Helpers
|
11
|
+
|
12
|
+
attr_accessor :credentials
|
13
|
+
|
14
|
+
def api
|
15
|
+
@api ||= begin
|
16
|
+
Turbot::API.new(default_params.merge(:api_key => password))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def client
|
21
|
+
@client ||= begin
|
22
|
+
client = Turbot::Client.new(user, password, host)
|
23
|
+
client.on_warning { |msg| self.display("\n#{msg}\n\n") }
|
24
|
+
client
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def login
|
29
|
+
delete_credentials
|
30
|
+
get_credentials
|
31
|
+
end
|
32
|
+
|
33
|
+
def logout
|
34
|
+
delete_credentials
|
35
|
+
end
|
36
|
+
|
37
|
+
# will raise if not authenticated
|
38
|
+
def check
|
39
|
+
api.get_user
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_host
|
43
|
+
"http://turbot"
|
44
|
+
end
|
45
|
+
|
46
|
+
def git_host
|
47
|
+
ENV['TURBOT_GIT_HOST'] || host
|
48
|
+
end
|
49
|
+
|
50
|
+
def host
|
51
|
+
ENV['TURBOT_HOST'] || default_host
|
52
|
+
end
|
53
|
+
|
54
|
+
def reauthorize
|
55
|
+
@credentials = ask_for_and_save_credentials
|
56
|
+
end
|
57
|
+
|
58
|
+
def user # :nodoc:
|
59
|
+
get_credentials[0]
|
60
|
+
end
|
61
|
+
|
62
|
+
def password # :nodoc:
|
63
|
+
get_credentials[1]
|
64
|
+
end
|
65
|
+
|
66
|
+
def api_key
|
67
|
+
api.get_api_key
|
68
|
+
end
|
69
|
+
|
70
|
+
def api_key_for_credentials(user = get_credentials[0], password = get_credentials[1])
|
71
|
+
api = Turbot::API.new(default_params)
|
72
|
+
api.get_api_key_for_credentials(user, password)["api_key"]
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_credentials # :nodoc:
|
76
|
+
@credentials ||= (read_credentials || ask_for_and_save_credentials)
|
77
|
+
end
|
78
|
+
|
79
|
+
def delete_credentials
|
80
|
+
if netrc
|
81
|
+
netrc.delete("api.#{host}")
|
82
|
+
netrc.delete("code.#{host}")
|
83
|
+
netrc.save
|
84
|
+
end
|
85
|
+
@api, @client, @credentials = nil, nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def netrc_path
|
89
|
+
default = Netrc.default_path
|
90
|
+
encrypted = default + ".gpg"
|
91
|
+
if File.exists?(encrypted)
|
92
|
+
encrypted
|
93
|
+
else
|
94
|
+
default
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def netrc # :nodoc:
|
99
|
+
@netrc ||= begin
|
100
|
+
File.exists?(netrc_path) && Netrc.read(netrc_path)
|
101
|
+
rescue => error
|
102
|
+
if error.message =~ /^Permission bits for/
|
103
|
+
perm = File.stat(netrc_path).mode & 0777
|
104
|
+
abort("Permissions #{perm} for '#{netrc_path}' are too open. You should run `chmod 0600 #{netrc_path}` so that your credentials are NOT accessible by others.")
|
105
|
+
else
|
106
|
+
raise error
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def read_credentials
|
112
|
+
if ENV['TURBOT_API_KEY']
|
113
|
+
['', ENV['TURBOT_API_KEY']]
|
114
|
+
else
|
115
|
+
|
116
|
+
# read netrc credentials if they exist
|
117
|
+
if netrc
|
118
|
+
# force migration of long api tokens (80 chars) to short ones (40)
|
119
|
+
# #write_credentials rewrites both api.* and code.*
|
120
|
+
credentials = netrc["api.#{host}"]
|
121
|
+
if credentials && credentials[1].length > 40
|
122
|
+
@credentials = [ credentials[0], credentials[1][0,40] ]
|
123
|
+
write_credentials
|
124
|
+
end
|
125
|
+
|
126
|
+
netrc["api.#{host}"]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def write_credentials
|
132
|
+
FileUtils.mkdir_p(File.dirname(netrc_path))
|
133
|
+
FileUtils.touch(netrc_path)
|
134
|
+
unless running_on_windows?
|
135
|
+
FileUtils.chmod(0600, netrc_path)
|
136
|
+
end
|
137
|
+
netrc["api.#{host}"] = self.credentials
|
138
|
+
netrc["code.#{host}"] = self.credentials
|
139
|
+
netrc.save
|
140
|
+
end
|
141
|
+
|
142
|
+
def echo_off
|
143
|
+
with_tty do
|
144
|
+
system "stty -echo"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def echo_on
|
149
|
+
with_tty do
|
150
|
+
system "stty echo"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def ask_for_credentials
|
155
|
+
puts "Enter your Turbot credentials."
|
156
|
+
|
157
|
+
print "Email: "
|
158
|
+
user = ask
|
159
|
+
|
160
|
+
print "Password (typing will be hidden): "
|
161
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
162
|
+
|
163
|
+
[user, api_key_for_credentials(user, password)]
|
164
|
+
end
|
165
|
+
|
166
|
+
def ask_for_password_on_windows
|
167
|
+
require "Win32API"
|
168
|
+
char = nil
|
169
|
+
password = ''
|
170
|
+
|
171
|
+
while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
|
172
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
173
|
+
if char == 127 || char == 8 # backspace and delete
|
174
|
+
password.slice!(-1, 1)
|
175
|
+
else
|
176
|
+
# windows might throw a -1 at us so make sure to handle RangeError
|
177
|
+
(password << char.chr) rescue RangeError
|
178
|
+
end
|
179
|
+
end
|
180
|
+
puts
|
181
|
+
return password
|
182
|
+
end
|
183
|
+
|
184
|
+
def ask_for_password
|
185
|
+
echo_off
|
186
|
+
password = ask
|
187
|
+
puts
|
188
|
+
echo_on
|
189
|
+
return password
|
190
|
+
end
|
191
|
+
|
192
|
+
def ask_for_and_save_credentials
|
193
|
+
begin
|
194
|
+
# ask for username and password, look up API key against API given these
|
195
|
+
# In looking up the API key it also attempts to log the user in
|
196
|
+
@credentials = ask_for_credentials
|
197
|
+
# write these to a hidden file
|
198
|
+
write_credentials
|
199
|
+
check
|
200
|
+
rescue Turbot::API::Errors::NotFound, Turbot::API::Errors::Unauthorized => e
|
201
|
+
delete_credentials
|
202
|
+
display "Authentication failed."
|
203
|
+
retry if retry_login?
|
204
|
+
exit 1
|
205
|
+
rescue Exception => e
|
206
|
+
delete_credentials
|
207
|
+
raise e
|
208
|
+
end
|
209
|
+
check_for_associated_ssh_key unless Turbot::Command.current_command == "keys:add"
|
210
|
+
@credentials
|
211
|
+
end
|
212
|
+
|
213
|
+
def check_for_associated_ssh_key
|
214
|
+
if api.get_ssh_keys.empty?
|
215
|
+
associate_or_generate_ssh_key
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def associate_or_generate_ssh_key
|
220
|
+
public_keys = Dir.glob("#{home_directory}/.ssh/*.pub").sort
|
221
|
+
|
222
|
+
case public_keys.length
|
223
|
+
when 0 then
|
224
|
+
display "Could not find an existing public key."
|
225
|
+
display "Would you like to generate one? [Yn] ", false
|
226
|
+
unless ask.strip.downcase == "n"
|
227
|
+
display "Generating new SSH public key."
|
228
|
+
generate_ssh_key("id_rsa")
|
229
|
+
associate_key("#{home_directory}/.ssh/id_rsa.pub")
|
230
|
+
end
|
231
|
+
when 1 then
|
232
|
+
display "Found existing public key: #{public_keys.first}"
|
233
|
+
associate_key(public_keys.first)
|
234
|
+
else
|
235
|
+
display "Found the following SSH public keys:"
|
236
|
+
public_keys.each_with_index do |key, index|
|
237
|
+
display "#{index+1}) #{File.basename(key)}"
|
238
|
+
end
|
239
|
+
display "Which would you like to use with your Turbot account? ", false
|
240
|
+
choice = ask.to_i - 1
|
241
|
+
chosen = public_keys[choice]
|
242
|
+
if choice == -1 || chosen.nil?
|
243
|
+
error("Invalid choice")
|
244
|
+
end
|
245
|
+
associate_key(chosen)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def generate_ssh_key(keyfile)
|
250
|
+
ssh_dir = File.join(home_directory, ".ssh")
|
251
|
+
unless File.exists?(ssh_dir)
|
252
|
+
FileUtils.mkdir_p ssh_dir
|
253
|
+
unless running_on_windows?
|
254
|
+
File.chmod(0700, ssh_dir)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
output = `ssh-keygen -t rsa -N "" -f \"#{home_directory}/.ssh/#{keyfile}\" 2>&1`
|
258
|
+
if ! $?.success?
|
259
|
+
error("Could not generate key: #{output}")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def associate_key(key)
|
264
|
+
action("Uploading SSH public key #{key}") do
|
265
|
+
if File.exists?(key)
|
266
|
+
api.post_key(File.read(key))
|
267
|
+
else
|
268
|
+
error("Could not upload SSH public key: key file '" + key + "' does not exist")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def retry_login?
|
274
|
+
@login_attempts ||= 0
|
275
|
+
@login_attempts += 1
|
276
|
+
@login_attempts < 3
|
277
|
+
end
|
278
|
+
|
279
|
+
def verified_hosts
|
280
|
+
%w( turbot.com turbot-shadow.com )
|
281
|
+
end
|
282
|
+
|
283
|
+
def base_host(host)
|
284
|
+
parts = URI.parse(full_host(host)).host.split(".")
|
285
|
+
return parts.first if parts.size == 1
|
286
|
+
parts[-2..-1].join(".")
|
287
|
+
end
|
288
|
+
|
289
|
+
def full_host(host)
|
290
|
+
(host =~ /^http/) ? host : "https://api.#{host}"
|
291
|
+
end
|
292
|
+
|
293
|
+
def verify_host?(host)
|
294
|
+
hostname = base_host(host)
|
295
|
+
verified = verified_hosts.include?(hostname)
|
296
|
+
verified = false if ENV["TURBOT_SSL_VERIFY"] == "disable"
|
297
|
+
verified
|
298
|
+
end
|
299
|
+
|
300
|
+
protected
|
301
|
+
|
302
|
+
def default_params
|
303
|
+
uri = URI.parse(full_host(host))
|
304
|
+
{
|
305
|
+
:headers => {
|
306
|
+
'User-Agent' => Turbot.user_agent
|
307
|
+
},
|
308
|
+
:host => uri.host,
|
309
|
+
:port => uri.port,
|
310
|
+
:scheme => uri.scheme,
|
311
|
+
:ssl_verify_peer => verify_host?(host)
|
312
|
+
}
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
data/lib/turbot/cli.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
load('turbot/helpers.rb') # reload helpers after possible inject_loadpath
|
2
|
+
load('turbot/updater.rb') # reload updater after possible inject_loadpath
|
3
|
+
|
4
|
+
require "turbot"
|
5
|
+
require "turbot/command"
|
6
|
+
require "turbot/helpers"
|
7
|
+
|
8
|
+
# workaround for rescue/reraise to define errors in command.rb failing in 1.8.6
|
9
|
+
if RUBY_VERSION =~ /^1.8.6/
|
10
|
+
require('turbot-api')
|
11
|
+
require('rest_client')
|
12
|
+
end
|
13
|
+
|
14
|
+
class Turbot::CLI
|
15
|
+
|
16
|
+
extend Turbot::Helpers
|
17
|
+
|
18
|
+
def self.start(*args)
|
19
|
+
begin
|
20
|
+
if $stdin.isatty
|
21
|
+
$stdin.sync = true
|
22
|
+
end
|
23
|
+
if $stdout.isatty
|
24
|
+
$stdout.sync = true
|
25
|
+
end
|
26
|
+
command = args.shift.strip rescue "help"
|
27
|
+
Turbot::Command.load
|
28
|
+
Turbot::Command.run(command, args)
|
29
|
+
rescue Interrupt
|
30
|
+
`stty icanon echo`
|
31
|
+
error("Command cancelled.")
|
32
|
+
rescue => error
|
33
|
+
styled_error(error)
|
34
|
+
exit(1)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "turbot/client"
|
2
|
+
|
3
|
+
class Turbot::Client::Cisaurus
|
4
|
+
|
5
|
+
include Turbot::Helpers
|
6
|
+
|
7
|
+
def initialize(uri)
|
8
|
+
require 'rest_client'
|
9
|
+
@uri = URI.parse(uri)
|
10
|
+
end
|
11
|
+
|
12
|
+
def authenticated_resource(path)
|
13
|
+
host = "#{@uri.scheme}://#{@uri.host}"
|
14
|
+
host += ":#{@uri.port}" if @uri.port
|
15
|
+
RestClient::Resource.new("#{host}#{path}", "", Turbot::Auth.api_key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def copy_slug(from, to)
|
19
|
+
authenticated_resource("/v1/bots/#{from}/copy/#{to}").post(json_encode("description" => "Forked from #{from}"), :content_type => :json).headers[:location]
|
20
|
+
end
|
21
|
+
|
22
|
+
def job_done?(job_location)
|
23
|
+
202 != authenticated_resource(job_location).get.code
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require "turbot/client"
|
2
|
+
|
3
|
+
class Turbot::Client::Pgbackups
|
4
|
+
|
5
|
+
include Turbot::Helpers
|
6
|
+
|
7
|
+
def initialize(uri)
|
8
|
+
require 'rest_client'
|
9
|
+
@uri = URI.parse(uri)
|
10
|
+
end
|
11
|
+
|
12
|
+
def authenticated_resource(path)
|
13
|
+
host = "#{@uri.scheme}://#{@uri.host}"
|
14
|
+
host += ":#{@uri.port}" if @uri.port
|
15
|
+
RestClient::Resource.new("#{host}#{path}",
|
16
|
+
:user => @uri.user,
|
17
|
+
:password => @uri.password,
|
18
|
+
:headers => {:x_turbot_gem_version => Turbot::Client.version}
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_transfer(from_url, from_name, to_url, to_name, opts={})
|
23
|
+
# opts[:expire] => true will delete the oldest backup if at the plan limit
|
24
|
+
resource = authenticated_resource("/client/transfers")
|
25
|
+
params = {:from_url => from_url, :from_name => from_name, :to_url => to_url, :to_name => to_name}.merge opts
|
26
|
+
json_decode post(resource, params).body
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_transfers
|
30
|
+
resource = authenticated_resource("/client/transfers")
|
31
|
+
json_decode get(resource).body
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_transfer(id)
|
35
|
+
resource = authenticated_resource("/client/transfers/#{id}")
|
36
|
+
json_decode get(resource).body
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_backups(opts={})
|
40
|
+
resource = authenticated_resource("/client/backups")
|
41
|
+
json_decode get(resource).body
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_backup(name, opts={})
|
45
|
+
name = URI.escape(name)
|
46
|
+
resource = authenticated_resource("/client/backups/#{name}")
|
47
|
+
json_decode get(resource).body
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_latest_backup
|
51
|
+
resource = authenticated_resource("/client/latest_backup")
|
52
|
+
json_decode get(resource).body
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete_backup(name)
|
56
|
+
name = URI.escape(name)
|
57
|
+
begin
|
58
|
+
resource = authenticated_resource("/client/backups/#{name}")
|
59
|
+
delete(resource).body
|
60
|
+
true
|
61
|
+
rescue RestClient::ResourceNotFound => e
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def get(resource)
|
69
|
+
check_errors do
|
70
|
+
response = resource.get
|
71
|
+
display_turbot_warning response
|
72
|
+
response
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def post(resource, params)
|
77
|
+
check_errors do
|
78
|
+
response = resource.post(params)
|
79
|
+
display_turbot_warning response
|
80
|
+
response
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete(resource)
|
85
|
+
check_errors do
|
86
|
+
response = resource.delete
|
87
|
+
display_turbot_warning response
|
88
|
+
response
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def check_errors
|
93
|
+
yield
|
94
|
+
rescue RestClient::Unauthorized
|
95
|
+
error "Invalid PGBACKUPS_URL"
|
96
|
+
end
|
97
|
+
|
98
|
+
def display_turbot_warning(response)
|
99
|
+
warning = response.headers[:x_turbot_warning]
|
100
|
+
display warning if warning
|
101
|
+
response
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
module Pgbackups
|
107
|
+
class Client < Turbot::Client::Pgbackups
|
108
|
+
def initialize(*args)
|
109
|
+
Turbot::Helpers.deprecate "Pgbackups::Client has been deprecated. Please use Turbot::Client::Pgbackups instead."
|
110
|
+
super
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "timeout"
|
2
|
+
require "socket"
|
3
|
+
require "uri"
|
4
|
+
require "turbot/auth"
|
5
|
+
require "turbot/client"
|
6
|
+
require "turbot/helpers"
|
7
|
+
|
8
|
+
class Turbot::Client::Rendezvous
|
9
|
+
|
10
|
+
include Turbot::Helpers
|
11
|
+
|
12
|
+
attr_reader :rendezvous_url, :connect_timeout, :activity_timeout, :input, :output, :on_connect
|
13
|
+
|
14
|
+
def initialize(opts)
|
15
|
+
@rendezvous_url = opts[:rendezvous_url]
|
16
|
+
@connect_timeout = opts[:connect_timeout]
|
17
|
+
@activity_timeout = opts[:activity_timeout]
|
18
|
+
@input = opts[:input]
|
19
|
+
@output = opts[:output]
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_connect(&blk)
|
23
|
+
@on_connect = blk if block_given?
|
24
|
+
@on_connect
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
uri = URI.parse(rendezvous_url)
|
29
|
+
host, port, secret = uri.host, uri.port, uri.path[1..-1]
|
30
|
+
|
31
|
+
ssl_socket = Timeout.timeout(connect_timeout) do
|
32
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
33
|
+
ssl_context.ssl_version = :TLSv1
|
34
|
+
|
35
|
+
if Turbot::Auth.verify_host?(host)
|
36
|
+
ssl_context.ca_file = File.expand_path("../../../../data/cacert.pem", __FILE__)
|
37
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
38
|
+
end
|
39
|
+
|
40
|
+
tcp_socket = TCPSocket.open(host, port)
|
41
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
42
|
+
ssl_socket.connect
|
43
|
+
ssl_socket.puts(secret)
|
44
|
+
ssl_socket.readline
|
45
|
+
ssl_socket
|
46
|
+
end
|
47
|
+
|
48
|
+
on_connect.call if on_connect
|
49
|
+
|
50
|
+
readables = [input, ssl_socket].compact
|
51
|
+
|
52
|
+
begin
|
53
|
+
loop do
|
54
|
+
if o = IO.select(readables, nil, nil, activity_timeout)
|
55
|
+
if (input && (o.first.first == input))
|
56
|
+
begin
|
57
|
+
data = input.readpartial(10000)
|
58
|
+
rescue EOFError
|
59
|
+
ssl_socket.write(4.chr)
|
60
|
+
ssl_socket.flush
|
61
|
+
readables.delete(input)
|
62
|
+
next
|
63
|
+
end
|
64
|
+
if running_on_windows?
|
65
|
+
data.gsub!("\r\n", "\n") # prevent double CRs
|
66
|
+
end
|
67
|
+
ssl_socket.write(data)
|
68
|
+
ssl_socket.flush
|
69
|
+
elsif (o.first.first == ssl_socket)
|
70
|
+
begin
|
71
|
+
data = ssl_socket.readpartial(10000)
|
72
|
+
rescue EOFError
|
73
|
+
break
|
74
|
+
end
|
75
|
+
output.write(fixup(data))
|
76
|
+
end
|
77
|
+
else
|
78
|
+
raise(Timeout::Error.new)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
rescue Interrupt
|
82
|
+
ssl_socket.write(3.chr)
|
83
|
+
ssl_socket.flush
|
84
|
+
retry
|
85
|
+
rescue SignalException => e
|
86
|
+
if Signal.list["QUIT"] == e.signo
|
87
|
+
ssl_socket.write(28.chr)
|
88
|
+
ssl_socket.flush
|
89
|
+
retry
|
90
|
+
end
|
91
|
+
raise
|
92
|
+
rescue Errno::EIO
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def fixup(data)
|
99
|
+
return nil if ! data
|
100
|
+
if data.respond_to?(:force_encoding)
|
101
|
+
data.force_encoding('utf-8') if data.respond_to?(:force_encoding)
|
102
|
+
end
|
103
|
+
if running_on_windows?
|
104
|
+
begin
|
105
|
+
data.gsub!(/\e\[[\d;]+m/, '')
|
106
|
+
rescue # ignore failed gsub, for instance when non-utf8
|
107
|
+
end
|
108
|
+
end
|
109
|
+
output.isatty ? data : data.gsub(/\cM/,"")
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Turbot::Client
|
2
|
+
def ssl_endpoint_add(bot, pem, key)
|
3
|
+
json_decode(post("bots/#{bot}/ssl-endpoints", :accept => :json, :pem => pem, :key => key).to_s)
|
4
|
+
end
|
5
|
+
|
6
|
+
def ssl_endpoint_info(bot, cname)
|
7
|
+
json_decode(get("bots/#{bot}/ssl-endpoints/#{escape(cname)}", :accept => :json).to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
def ssl_endpoint_list(bot)
|
11
|
+
json_decode(get("bots/#{bot}/ssl-endpoints", :accept => :json).to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ssl_endpoint_remove(bot, cname)
|
15
|
+
json_decode(delete("bots/#{bot}/ssl-endpoints/#{escape(cname)}", :accept => :json).to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ssl_endpoint_rollback(bot, cname)
|
19
|
+
json_decode(post("bots/#{bot}/ssl-endpoints/#{escape(cname)}/rollback", :accept => :json).to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ssl_endpoint_update(bot, cname, pem, key)
|
23
|
+
json_decode(put("bots/#{bot}/ssl-endpoints/#{escape(cname)}", :accept => :json, :pem => pem, :key => key).to_s)
|
24
|
+
end
|
25
|
+
end
|