cloulu 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -29,13 +29,24 @@ class Mothership
29
29
  input :command, :argument => :optional
30
30
  input :all, :type => :boolean
31
31
  def help
32
+ platform_is_cross = (Object::RUBY_PLATFORM =~ /darwin/i) ? true : false
33
+ platform_is_windows = !platform_is_cross
34
+
35
+ if platform_is_windows
36
+ prefix = ""
37
+ suffix = ""
38
+ else
39
+ prefix = "\x1B[1m\x1B[33m"
40
+ suffix = "\x1B[00m"
41
+ end
42
+
32
43
  # custom header
33
- puts "\x1B[1m\x1B[33m===========================================================\x1B[00m"
34
- puts "\x1B[1m\x1B[33m* Welcome Cloulu Platform\x1B[00m"
35
- puts "\x1B[1m\x1B[33m*\x1B[00m"
36
- puts "\x1B[1m\x1B[33m* @author clouluteam <cloulu@sk.com>\x1B[00m"
37
- puts "\x1B[1m\x1B[33m* @homepage https://cloulu.com, http://blog.cloulu.com\x1B[00m"
38
- puts "\x1B[1m\x1B[33m============================================================\x1B[00m"
44
+ puts "#{prefix}===========================================================#{suffix}"
45
+ puts "#{prefix}* Welcome Cloulu PaaS Platform#{suffix}"
46
+ puts "#{prefix}*#{suffix}"
47
+ puts "#{prefix}* @author clouluteam <cloulu@sk.com>#{suffix}"
48
+ puts "#{prefix}* @homepage https://cloulu.com, https://github.com/cloulu#{suffix}"
49
+ puts "#{prefix}============================================================#{suffix}"
39
50
  puts
40
51
 
41
52
  if name = input[:command]
@@ -23,16 +23,24 @@ module Mothership::Help
23
23
 
24
24
  i = " " * indent
25
25
 
26
+ platform_is_cross = (Object::RUBY_PLATFORM =~ /darwin/i) ? true : false
27
+ platform_is_windows = !platform_is_cross
28
+
26
29
  # set prefix
27
30
  prefix = "*"
28
31
  prefix = ">>" if indent == 1
29
32
 
30
33
  # set color
31
- color = "31"
32
- color = "32" if indent == 1
33
-
34
34
  print i
35
- puts "\x1B[1m\x1B[#{color}m" + prefix + " " + group[:description] + "\x1B[00m"
35
+ if platform_is_windows
36
+ puts prefix + " " + group[:description]
37
+ else
38
+ color = "31"
39
+ color = "32" if indent == 1
40
+
41
+ puts "\x1B[1m\x1B[#{color}m" + prefix + " " + group[:description] + "\x1B[00m"
42
+ end
43
+
36
44
 
37
45
  commands = unique_commands(commands)
38
46
 
@@ -60,78 +60,5 @@ module VMC::App
60
60
  rescue CFoundry::FileError => e
61
61
  fail e.description
62
62
  end
63
-
64
- desc "Stream an app's file contents"
65
- group :apps, :info
66
- input :app, :desc => "Application to inspect the files of",
67
- :argument => true, :from_given => by_name(:app)
68
- input :path, :desc => "Path of file to stream", :argument => :optional
69
- def tail
70
- app = input[:app]
71
-
72
- lines = Queue.new
73
- max_len = 0
74
-
75
- if path = input[:path]
76
- max_len = path.size
77
- app.instances.each do |i|
78
- Thread.new do
79
- stream_path(lines, i, path.split("/"))
80
- end
81
- end
82
- else
83
- app.instances.each do |i|
84
- i.files("logs").each do |path|
85
- len = path.join("/").size
86
- max_len = len if len > max_len
87
-
88
- Thread.new do
89
- stream_path(lines, i, path)
90
- end
91
- end
92
- end
93
- end
94
-
95
- while line = lines.pop
96
- instance, path, log = line
97
-
98
- unless log.end_with?("\n")
99
- log += i("%") if color?
100
- log += "\n"
101
- end
102
-
103
- print "\##{c(instance.id, :instance)} "
104
- print "#{c(path.join("/").ljust(max_len), :name)} "
105
- print log
106
- end
107
- rescue CFoundry::NotFound
108
- fail "Invalid path #{b(path)} for app #{b(app.name)}"
109
- rescue CFoundry::FileError => e
110
- fail e.description
111
- end
112
-
113
- def stream_path(lines, instance, path)
114
- if verbose?
115
- lines << [instance, path, c("streaming...", :good) + "\n"]
116
- end
117
-
118
- instance.stream_file(*path) do |contents|
119
- contents.each_line do |line|
120
- lines << [instance, path, line]
121
- end
122
- end
123
-
124
- lines << [instance, path, c("end of file", :bad) + "\n"]
125
- rescue Timeout::Error
126
- if verbose?
127
- lines << [
128
- instance,
129
- path,
130
- c("timed out; reconnecting...", :bad) + "\n"
131
- ]
132
- end
133
-
134
- retry
135
- end
136
63
  end
137
64
  end
@@ -10,71 +10,20 @@ module VMC::Route
10
10
  group :apps, :info
11
11
  input :app, :desc => "Application to add the URL to",
12
12
  :argument => :optional, :from_given => by_name(:app)
13
- input :host, :desc => "Host name for the route",
14
- :argument => :optional, :default => ""
15
13
  input :domain, :desc => "Domain to add the route to",
16
- :argument => true,
17
- :from_given => proc { |name, space|
18
- if v2?
19
- space.domain_by_name(name) ||
20
- fail_unknown("domain", name)
21
- else
22
- name
23
- end
24
- }
14
+ :argument => true
25
15
  def map
26
16
  app = input[:app]
27
- space = app.space if v2?
17
+ domain = input[:domain]
28
18
 
29
- host = input[:host]
30
- domain = input[:domain, space]
31
-
32
- if v2?
33
- route = find_or_create_route(domain, host, space)
34
- bind_route(route, app) if app
35
- else
36
- with_progress("Updating #{c(app.name, :name)}") do
37
- app.urls << domain
38
- app.update!
39
- end
19
+ with_progress("Updating #{c(app.name, :name)}") do
20
+ app.urls << domain
21
+ app.update!
40
22
  end
41
23
  end
42
24
 
43
25
  private
44
26
 
45
- def bind_route(route, app)
46
- with_progress("Binding #{c(route.name, :name)} to #{c(app.name, :name)}") do
47
- app.add_route(route)
48
- end
49
- end
50
-
51
- def find_or_create_route(domain, host, space)
52
- find_route(domain, host) || create_route(domain, host, space)
53
- end
54
-
55
- def find_route(domain, host)
56
- client.routes_by_host(host, :depth => 0).find { |r| r.domain == domain }
57
- end
58
-
59
- def create_route(domain, host, space)
60
- route = client.route
61
- route.host = host
62
- route.domain = domain
63
- route.space = space
64
-
65
- with_progress("Creating route #{c(route.name, :name)}") do
66
- route.create!
67
- end
68
-
69
- route
70
- end
71
-
72
- def find_domain(space, name)
73
- domain = space.domain_by_name(name, :depth => 0)
74
- fail "Invalid domain '#{name}'" unless domain
75
- domain
76
- end
77
-
78
27
  def ask_app
79
28
  ask("Which application?", :choices => client.apps, :display => proc(&:name))
80
29
  end
data/lib/vmc/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module VMC
2
- VERSION = "0.2.1".freeze
2
+ VERSION = "0.2.3".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloulu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -108,23 +108,7 @@ dependencies:
108
108
  - - ~>
109
109
  - !ruby/object:Gem::Version
110
110
  version: 0.2.2
111
- - !ruby/object:Gem::Dependency
112
- name: rr
113
- requirement: !ruby/object:Gem::Requirement
114
- none: false
115
- requirements:
116
- - - ~>
117
- - !ruby/object:Gem::Version
118
- version: '1.0'
119
- type: :development
120
- prerelease: false
121
- version_requirements: !ruby/object:Gem::Requirement
122
- none: false
123
- requirements:
124
- - - ~>
125
- - !ruby/object:Gem::Version
126
- version: '1.0'
127
- description: test
111
+ description: cloulu client command line tool
128
112
  email:
129
113
  - cloulu@sk.com
130
114
  executables:
@@ -209,9 +193,6 @@ files:
209
193
  - lib/mothership/parser.rb
210
194
  - lib/mothership/version.rb
211
195
  - lib/mothership.rb
212
- - lib/tunnel-vmc-plugin/plugin.rb
213
- - lib/tunnel-vmc-plugin/tunnel.rb
214
- - lib/tunnel-vmc-plugin/version.rb
215
196
  - lib/uaa/http.rb
216
197
  - lib/uaa/misc.rb
217
198
  - lib/uaa/scim.rb
@@ -1,178 +0,0 @@
1
- require "vmc/cli"
2
- require "tunnel-vmc-plugin/tunnel"
3
-
4
- module VMCTunnel
5
- class Tunnel < VMC::CLI
6
- CLIENTS_FILE = "#{VMC::CONFIG_DIR}/tunnel-clients.yml"
7
- STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)
8
-
9
- desc "Create a local tunnel to a service."
10
- group :services, :manage
11
- input(:instance, :argument => :optional,
12
- :from_given => find_by_name("service instance"),
13
- :desc => "Service instance to tunnel to") { |instances|
14
- ask("Which service instance?", :choices => instances,
15
- :display => proc(&:name))
16
- }
17
- input(:client, :argument => :optional,
18
- :desc => "Client to automatically launch") { |clients|
19
- if clients.empty?
20
- "none"
21
- else
22
- ask("Which client would you like to start?",
23
- :choices => clients.keys.unshift("none"))
24
- end
25
- }
26
- input(:port, :default => 10000, :desc => "Port to bind the tunnel to")
27
- def tunnel
28
- instances = client.service_instances
29
- fail "No services available for tunneling." if instances.empty?
30
-
31
- instance = input[:instance, instances.sort_by(&:name)]
32
- vendor = v2? ? instance.service_plan.service.label : instance.vendor
33
- clients = tunnel_clients[vendor] || {}
34
- client_name = input[:client, clients]
35
-
36
- tunnel = CFTunnel.new(client, instance)
37
- port = tunnel.pick_port!(input[:port])
38
-
39
- conn_info =
40
- with_progress("Opening tunnel on port #{c(port, :name)}") do
41
- tunnel.open!
42
- end
43
-
44
- if client_name == "none"
45
- unless quiet?
46
- line
47
- display_tunnel_connection_info(conn_info)
48
-
49
- line
50
- line "Open another shell to run command-line clients or"
51
- line "use a UI tool to connect using the displayed information."
52
- line "Press Ctrl-C to exit..."
53
- end
54
-
55
- tunnel.wait_for_end
56
- else
57
- with_progress("Waiting for local tunnel to become available") do
58
- tunnel.wait_for_start
59
- end
60
-
61
- unless start_local_prog(clients, client_name, conn_info, port)
62
- fail "'#{client_name}' execution failed; is it in your $PATH?"
63
- end
64
- end
65
- end
66
-
67
- def tunnel_clients
68
- return @tunnel_clients if @tunnel_clients
69
- stock_config = YAML.load_file(STOCK_CLIENTS)
70
- custom_config_file = File.expand_path(CLIENTS_FILE)
71
- if File.exists?(custom_config_file)
72
- custom_config = YAML.load_file(custom_config_file)
73
- @tunnel_clients = deep_merge(stock_config, custom_config)
74
- else
75
- @tunnel_clients = stock_config
76
- end
77
- end
78
-
79
- private
80
-
81
- def display_tunnel_connection_info(info)
82
- line "Service connection info:"
83
-
84
- to_show = [nil, nil, nil] # reserved for user, pass, db name
85
- info.keys.each do |k|
86
- case k
87
- when "host", "hostname", "port", "node_id"
88
- # skip
89
- when "user", "username"
90
- # prefer "username" over "user"
91
- to_show[0] = k unless to_show[0] == "username"
92
- when "password"
93
- to_show[1] = k
94
- when "name"
95
- to_show[2] = k
96
- else
97
- to_show << k
98
- end
99
- end
100
- to_show.compact!
101
-
102
- align_len = to_show.collect(&:size).max + 1
103
-
104
- indented do
105
- to_show.each do |k|
106
- # TODO: modify the server services rest call to have explicit knowledge
107
- # about the items to return. It should return all of them if
108
- # the service is unknown so that we don't have to do this weird
109
- # filtering.
110
- line "#{k.ljust align_len}: #{b(info[k])}"
111
- end
112
- end
113
-
114
- line
115
- end
116
-
117
- def start_local_prog(clients, command, info, port)
118
- client = clients[File.basename(command)]
119
-
120
- cmdline = "#{command} "
121
-
122
- case client
123
- when Hash
124
- cmdline << resolve_symbols(client["command"], info, port)
125
- client["environment"].each do |e|
126
- if e =~ /([^=]+)=(["']?)([^"']*)\2/
127
- ENV[$1] = resolve_symbols($3, info, port)
128
- else
129
- fail "Invalid environment variable: #{e}"
130
- end
131
- end
132
- when String
133
- cmdline << resolve_symbols(client, info, port)
134
- else
135
- raise "Unknown client info: #{client.inspect}."
136
- end
137
-
138
- if verbose?
139
- line
140
- line "Launching '#{cmdline}'"
141
- end
142
-
143
- system(cmdline)
144
- end
145
-
146
- def resolve_symbols(str, info, local_port)
147
- str.gsub(/\$\{\s*([^\}]+)\s*\}/) do
148
- sym = $1
149
-
150
- case sym
151
- when "host"
152
- # TODO: determine proper host
153
- "localhost"
154
- when "port"
155
- local_port
156
- when "user", "username"
157
- info["username"]
158
- when /^ask (.+)/
159
- ask($1)
160
- else
161
- info[sym] || raise("Unknown symbol in config: #{sym}")
162
- end
163
- end
164
- end
165
-
166
- def deep_merge(a, b)
167
- merge = proc { |_, old, new|
168
- if old.is_a?(Hash) && new.is_a?(Hash)
169
- old.merge(new, &merge)
170
- else
171
- new
172
- end
173
- }
174
-
175
- a.merge(b, &merge)
176
- end
177
- end
178
- end
@@ -1,308 +0,0 @@
1
- require "addressable/uri"
2
- require "restclient"
3
- require "uuidtools"
4
-
5
- require "caldecott-client"
6
-
7
-
8
- class CFTunnel
9
- HELPER_NAME = "caldecott"
10
- HELPER_APP = File.expand_path("../../../helper-app", __FILE__)
11
-
12
- # bump this AND the version info reported by HELPER_APP/server.rb
13
- # this is to keep the helper in sync with any updates here
14
- HELPER_VERSION = "0.0.4"
15
-
16
- def initialize(client, service, port = 10000)
17
- @client = client
18
- @service = service
19
- @port = port
20
- end
21
-
22
- def open!
23
- if helper
24
- auth = helper_auth
25
-
26
- unless helper_healthy?(auth)
27
- delete_helper
28
- auth = create_helper
29
- end
30
- else
31
- auth = create_helper
32
- end
33
-
34
- bind_to_helper if @service && !helper_already_binds?
35
-
36
- info = get_connection_info(auth)
37
-
38
- start_tunnel(info, auth)
39
-
40
- info
41
- end
42
-
43
- def wait_for_start
44
- 10.times do |n|
45
- begin
46
- TCPSocket.open("localhost", @port).close
47
- return true
48
- rescue => e
49
- sleep 1
50
- end
51
- end
52
-
53
- raise "Could not connect to local tunnel."
54
- end
55
-
56
- def wait_for_end
57
- if @local_tunnel_thread
58
- @local_tunnel_thread.join
59
- else
60
- raise "Tunnel wasn't started!"
61
- end
62
- end
63
-
64
- PORT_RANGE = 10
65
- def pick_port!(port = @port)
66
- original = port
67
-
68
- PORT_RANGE.times do |n|
69
- begin
70
- TCPSocket.open("localhost", port)
71
- port += 1
72
- rescue
73
- return @port = port
74
- end
75
- end
76
-
77
- @port = grab_ephemeral_port
78
- end
79
-
80
- private
81
-
82
- def helper
83
- @helper ||= @client.app_by_name(HELPER_NAME)
84
- end
85
-
86
- def create_helper
87
- auth = UUIDTools::UUID.random_create.to_s
88
- push_helper(auth)
89
- start_helper
90
- auth
91
- end
92
-
93
- def helper_auth
94
- helper.env["CALDECOTT_AUTH"]
95
- end
96
-
97
- def helper_healthy?(token)
98
- return false unless helper.healthy?
99
-
100
- begin
101
- response = RestClient.get(
102
- "#{helper_url}/info",
103
- "Auth-Token" => token
104
- )
105
-
106
- info = JSON.parse(response)
107
- if info["version"] == HELPER_VERSION
108
- true
109
- else
110
- stop_helper
111
- false
112
- end
113
- rescue RestClient::Exception
114
- stop_helper
115
- false
116
- end
117
- end
118
-
119
- def helper_already_binds?
120
- helper.binds? @service
121
- end
122
-
123
- def push_helper(token)
124
- target_base = @client.target.sub(/^[^\.]+\./, "")
125
-
126
- url = "#{random_helper_url}.#{target_base}"
127
- is_v2 = @client.is_a?(CFoundry::V2::Client)
128
-
129
- app = @client.app
130
- app.name = HELPER_NAME
131
- app.framework = @client.framework_by_name("sinatra")
132
- app.runtime = @client.runtime_by_name("ruby19")
133
- app.command = "bundle exec ruby server.rb -p $VCAP_APP_PORT"
134
- app.total_instances = 1
135
- app.memory = 64
136
- app.env = { "CALDECOTT_AUTH" => token }
137
-
138
- if is_v2
139
- app.space = @client.current_space
140
- else
141
- app.services = [@service] if @service
142
- app.url = url
143
- end
144
-
145
- app.create!
146
-
147
- if is_v2
148
- app.bind(@service) if @service
149
- app.create_route(url)
150
- end
151
-
152
- begin
153
- app.upload(HELPER_APP)
154
- invalidate_tunnel_app_info
155
- rescue
156
- app.delete!
157
- raise
158
- end
159
- end
160
-
161
- def delete_helper
162
- helper.delete!
163
- invalidate_tunnel_app_info
164
- end
165
-
166
- def stop_helper
167
- helper.stop!
168
- invalidate_tunnel_app_info
169
- end
170
-
171
- TUNNEL_CHECK_LIMIT = 60
172
- def start_helper
173
- helper.start!
174
-
175
- seconds = 0
176
- until helper.healthy?
177
- sleep 1
178
- seconds += 1
179
- if seconds == TUNNEL_CHECK_LIMIT
180
- raise "Helper application failed to start."
181
- end
182
- end
183
-
184
- invalidate_tunnel_app_info
185
- end
186
-
187
- def bind_to_helper
188
- helper.bind(@service)
189
- helper.restart!
190
- end
191
-
192
- def invalidate_tunnel_app_info
193
- @helper_url = nil
194
- @helper = nil
195
- end
196
-
197
- def helper_url
198
- return @helper_url if @helper_url
199
-
200
- tun_url = helper.url
201
-
202
- ["https", "http"].each do |scheme|
203
- url = "#{scheme}://#{tun_url}"
204
- begin
205
- RestClient.get(url)
206
-
207
- # https failed
208
- rescue Errno::ECONNREFUSED
209
-
210
- # we expect a 404 since this request isn't auth'd
211
- rescue RestClient::ResourceNotFound
212
- return @helper_url = url
213
- end
214
- end
215
-
216
- raise "Cannot determine URL for #{tun_url}"
217
- end
218
-
219
- def get_connection_info(token)
220
- response = nil
221
- 10.times do
222
- begin
223
- response =
224
- RestClient.get(
225
- helper_url + "/" + safe_path("services", @service.name),
226
- "Auth-Token" => token)
227
-
228
- break
229
- rescue RestClient::Exception => e
230
- sleep 1
231
- end
232
- end
233
-
234
- unless response
235
- raise "Remote tunnel helper is unaware of #{@service.name}!"
236
- end
237
-
238
- is_v2 = @client.is_a?(CFoundry::V2::Client)
239
-
240
- info = JSON.parse(response)
241
- case (is_v2 ? @service.service_plan.service.label : @service.vendor)
242
- when "rabbitmq"
243
- uri = Addressable::URI.parse info["url"]
244
- info["hostname"] = uri.host
245
- info["port"] = uri.port
246
- info["vhost"] = uri.path[1..-1]
247
- info["user"] = uri.user
248
- info["password"] = uri.password
249
- info.delete "url"
250
-
251
- # we use "db" as the "name" for mongo
252
- # existing "name" is junk
253
- when "mongodb"
254
- info["name"] = info["db"]
255
- info.delete "db"
256
-
257
- # our "name" is irrelevant for redis
258
- when "redis"
259
- info.delete "name"
260
-
261
- when "filesystem"
262
- raise "Tunneling is not supported for this type of service"
263
- end
264
-
265
- ["hostname", "port", "password"].each do |k|
266
- raise "Could not determine #{k} for #{@service.name}" if info[k].nil?
267
- end
268
-
269
- info
270
- end
271
-
272
- def start_tunnel(conn_info, auth)
273
- @local_tunnel_thread = Thread.new do
274
- Caldecott::Client.start({
275
- :local_port => @port,
276
- :tun_url => helper_url,
277
- :dst_host => conn_info["hostname"],
278
- :dst_port => conn_info["port"],
279
- :log_file => STDOUT,
280
- :log_level => ENV["VMC_TUNNEL_DEBUG"] || "ERROR",
281
- :auth_token => auth,
282
- :quiet => true
283
- })
284
- end
285
-
286
- at_exit { @local_tunnel_thread.kill }
287
- end
288
-
289
- def random_helper_url
290
- random = sprintf("%x", rand(1000000))
291
- "caldecott-#{random}"
292
- end
293
-
294
- def safe_path(*segments)
295
- segments.flatten.collect { |x|
296
- URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
297
- }.join("/")
298
- end
299
-
300
- def grab_ephemeral_port
301
- socket = TCPServer.new("0.0.0.0", 0)
302
- socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
303
- Socket.do_not_reverse_lookup = true
304
- socket.addr[1]
305
- ensure
306
- socket.close
307
- end
308
- end
@@ -1,3 +0,0 @@
1
- module VMCTunnel
2
- VERSION = "0.2.2".freeze
3
- end