appfog-tunnel-vmc-plugin 0.0.1

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OTZmMjcxMmM0ZGExOGM4YmMxZDFlNTIzODFiZjdiMTM3NDNlNmE0Mg==
5
+ data.tar.gz: !binary |-
6
+ YjY2OWM3M2FmZmViOTE5M2IwZjIyMTA4OWRmYTg2YzBhMzI1M2U5NQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NjY5Y2MwN2NkODhhM2QyYjY2MTBlYzU4MmJiMjk1M2Q2OWZjNmM5NTMyMjk4
10
+ MGU4NzRmNGNjODZjNzdiYjhkZGIyMWM3MDlmOTYwZTBlOTUyMzJlMGM4MDgx
11
+ MWE5NzVhODc0NjY5YWYyYjM1NzcwNDQxZDJiOTM2NjQ3ZDdmZmU=
12
+ data.tar.gz: !binary |-
13
+ NzU4NTYzOGM0Y2FiMmEzZDE0NDczYTIwMjZhY2Q2OGZmNTg1Y2RiZjQ0NDcw
14
+ NDgwMzU4MGIwN2U2MWRmMzliZGZmNDlmN2Y3NjE1ODZhZTdkNWExMWU5N2M1
15
+ Y2E5NmU0Njc0Y2ZiYTBkMzJhMDc5MjdiMmQxOWNiYTBkNWY0NmE=
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require "rake"
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
4
+ require "tunnel-vmc-plugin/version"
5
+
6
+ task :default => :spec
7
+
8
+ desc "Run specs"
9
+ task :spec => ["bundler:install", "test:spec"]
10
+
11
+ desc "Run integration tests"
12
+ task :test => ["bundler:install", "test:integration"]
13
+
14
+ task :build do
15
+ sh "gem build tunnel-vmc-plugin.gemspec"
16
+ end
17
+
18
+ task :install => :build do
19
+ sh "gem install --local tunnel-vmc-plugin-#{VMCTunnel::VERSION}.gem"
20
+ end
21
+
22
+ task :uninstall do
23
+ sh "gem uninstall tunnel-vmc-plugin"
24
+ end
25
+
26
+ task :reinstall => [:uninstall, :install]
27
+
28
+ task :release => :build do
29
+ sh "gem push tunnel-vmc-plugin-#{VMCTunnel::VERSION}.gem"
30
+ end
31
+
32
+ namespace "bundler" do
33
+ desc "Install gems"
34
+ task "install" do
35
+ sh("bundle install")
36
+ end
37
+ end
38
+
39
+ namespace "test" do
40
+ task "spec" do |t|
41
+ # nothing
42
+ end
43
+
44
+ task "integration" do |t|
45
+ sh("cd spec && bundle exec rake spec")
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ redis:
2
+ redis-cli: -h ${host} -p ${port} -a ${password}
3
+
4
+ mysql:
5
+ mysql: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name}
6
+ mysqldump: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name} > ${ask Output file}
7
+
8
+ mongodb:
9
+ mongo: --host ${host} --port ${port} -u ${user} -p ${password} ${name}
10
+ mongodump: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name}
11
+ mongorestore: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name} ${ask Directory or filename to restore from}
12
+
13
+ postgresql:
14
+ psql:
15
+ command: -h ${host} -p ${port} -d ${name} -U ${user} -w
16
+ environment:
17
+ - PGPASSWORD='${password}'
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rack', '~> 1.2.0'
4
+ gem 'caldecott', '= 0.0.3'
5
+ gem 'bundler'
6
+ gem 'em-websocket'
7
+ gem 'async_sinatra'
8
+ gem 'thin'
9
+ gem 'json'
10
+ gem 'uuidtools'
@@ -0,0 +1,48 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.6)
5
+ async_sinatra (0.5.0)
6
+ rack (>= 1.2.1)
7
+ sinatra (>= 1.0)
8
+ caldecott (0.0.3)
9
+ addressable (= 2.2.6)
10
+ async_sinatra (= 0.5.0)
11
+ em-http-request (= 0.3.0)
12
+ em-websocket (= 0.3.1)
13
+ json (= 1.6.1)
14
+ uuidtools (= 2.1.2)
15
+ daemons (1.1.8)
16
+ em-http-request (0.3.0)
17
+ addressable (>= 2.0.0)
18
+ escape_utils
19
+ eventmachine (>= 0.12.9)
20
+ em-websocket (0.3.1)
21
+ addressable (>= 2.1.1)
22
+ eventmachine (>= 0.12.9)
23
+ escape_utils (0.2.4)
24
+ eventmachine (0.12.10)
25
+ json (1.6.1)
26
+ rack (1.2.5)
27
+ sinatra (1.2.8)
28
+ rack (~> 1.1)
29
+ tilt (>= 1.2.2, < 2.0)
30
+ thin (1.4.1)
31
+ daemons (>= 1.0.9)
32
+ eventmachine (>= 0.12.6)
33
+ rack (>= 1.0.0)
34
+ tilt (1.3.3)
35
+ uuidtools (2.1.2)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ async_sinatra
42
+ bundler
43
+ caldecott (= 0.0.3)
44
+ em-websocket
45
+ json
46
+ rack (~> 1.2.0)
47
+ thin
48
+ uuidtools
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (c) 2009-2011 VMware, Inc.
3
+ $:.unshift(File.dirname(__FILE__) + '/lib')
4
+
5
+ require 'rubygems'
6
+ require 'bundler/setup'
7
+
8
+ require 'caldecott'
9
+ require 'sinatra'
10
+ require 'json'
11
+ require 'eventmachine'
12
+
13
+ port = ENV['VMC_APP_PORT']
14
+ port ||= 8081
15
+
16
+ # add vcap specific stuff to Caldecott
17
+ class VcapHttpTunnel < Caldecott::Server::HttpTunnel
18
+ get '/info' do
19
+ { "version" => '0.0.4' }.to_json
20
+ end
21
+
22
+ def self.get_tunnels
23
+ super
24
+ end
25
+
26
+ get '/services' do
27
+ services_env = ENV['VMC_SERVICES']
28
+ return "no services env" if services_env.nil? or services_env.empty?
29
+ services_env
30
+ end
31
+
32
+ get '/services/:service' do |service_name|
33
+ services_env = ENV['VMC_SERVICES']
34
+ not_found if services_env.nil?
35
+
36
+ services = JSON.parse(services_env)
37
+ service = services.find { |s| s["name"] == service_name }
38
+ not_found if service.nil?
39
+ service["options"].to_json
40
+ end
41
+ end
42
+
43
+ VcapHttpTunnel.run!(:port => port, :auth_token => ENV["CALDECOTT_AUTH"])
@@ -0,0 +1,178 @@
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
@@ -0,0 +1,306 @@
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}-#{@service.infra.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
+ url = "#{random_helper_url}.#{@service.infra.name}.af.cm"
125
+ is_v2 = @client.is_a?(CFoundry::V2::Client)
126
+
127
+ app = @client.app
128
+ app.name = "#{HELPER_NAME}-#{@service.infra.name}"
129
+ app.infra = @service.infra
130
+ app.framework = @client.framework_by_name("sinatra")
131
+ app.runtime = @client.runtime_by_name("ruby192")
132
+ app.total_instances = 1
133
+ app.memory = 64
134
+ app.env = { "CALDECOTT_AUTH" => token }
135
+
136
+ if is_v2
137
+ app.space = @client.current_space
138
+ else
139
+ app.services = [@service] if @service
140
+ app.url = url
141
+ end
142
+
143
+ app.create!
144
+
145
+ if is_v2
146
+ app.bind(@service) if @service
147
+ app.create_route(url)
148
+ end
149
+
150
+ begin
151
+ app.upload(HELPER_APP)
152
+ invalidate_tunnel_app_info
153
+ rescue
154
+ app.delete!
155
+ raise
156
+ end
157
+ end
158
+
159
+ def delete_helper
160
+ helper.delete!
161
+ invalidate_tunnel_app_info
162
+ end
163
+
164
+ def stop_helper
165
+ helper.stop!
166
+ invalidate_tunnel_app_info
167
+ end
168
+
169
+ TUNNEL_CHECK_LIMIT = 60
170
+ def start_helper
171
+ helper.start!
172
+
173
+ seconds = 0
174
+ until helper.healthy?
175
+ sleep 1
176
+ seconds += 1
177
+ if seconds == TUNNEL_CHECK_LIMIT
178
+ raise "Helper application failed to start."
179
+ end
180
+ end
181
+
182
+ invalidate_tunnel_app_info
183
+ end
184
+
185
+ def bind_to_helper
186
+ helper.bind(@service)
187
+ helper.restart!
188
+ end
189
+
190
+ def invalidate_tunnel_app_info
191
+ @helper_url = nil
192
+ @helper = nil
193
+ end
194
+
195
+ def helper_url
196
+ return @helper_url if @helper_url
197
+
198
+ tun_url = helper.url
199
+
200
+ ["https", "http"].each do |scheme|
201
+ url = "#{scheme}://#{tun_url}"
202
+ begin
203
+ RestClient.get(url)
204
+
205
+ # https failed
206
+ rescue Errno::ECONNREFUSED
207
+
208
+ # we expect a 404 since this request isn't auth'd
209
+ rescue RestClient::ResourceNotFound
210
+ return @helper_url = url
211
+ end
212
+ end
213
+
214
+ raise "Cannot determine URL for #{tun_url}"
215
+ end
216
+
217
+ def get_connection_info(token)
218
+ response = nil
219
+ 10.times do
220
+ begin
221
+ response =
222
+ RestClient.get(
223
+ helper_url + "/" + safe_path("services", @service.name),
224
+ "Auth-Token" => token)
225
+
226
+ break
227
+ rescue RestClient::Exception => e
228
+ sleep 1
229
+ end
230
+ end
231
+
232
+ unless response
233
+ raise "Remote tunnel helper is unaware of #{@service.name}!"
234
+ end
235
+
236
+ is_v2 = @client.is_a?(CFoundry::V2::Client)
237
+
238
+ info = JSON.parse(response)
239
+ case (is_v2 ? @service.service_plan.service.label : @service.vendor)
240
+ when "rabbitmq"
241
+ uri = Addressable::URI.parse info["url"]
242
+ info["hostname"] = uri.host
243
+ info["port"] = uri.port
244
+ info["vhost"] = uri.path[1..-1]
245
+ info["user"] = uri.user
246
+ info["password"] = uri.password
247
+ info.delete "url"
248
+
249
+ # we use "db" as the "name" for mongo
250
+ # existing "name" is junk
251
+ when "mongodb"
252
+ info["name"] = info["db"]
253
+ info.delete "db"
254
+
255
+ # our "name" is irrelevant for redis
256
+ when "redis"
257
+ info.delete "name"
258
+
259
+ when "filesystem"
260
+ raise "Tunneling is not supported for this type of service"
261
+ end
262
+
263
+ ["hostname", "port", "password"].each do |k|
264
+ raise "Could not determine #{k} for #{@service.name}" if info[k].nil?
265
+ end
266
+
267
+ info
268
+ end
269
+
270
+ def start_tunnel(conn_info, auth)
271
+ @local_tunnel_thread = Thread.new do
272
+ Caldecott::Client.start({
273
+ :local_port => @port,
274
+ :tun_url => helper_url,
275
+ :dst_host => conn_info["hostname"],
276
+ :dst_port => conn_info["port"],
277
+ :log_file => STDOUT,
278
+ :log_level => ENV["VMC_TUNNEL_DEBUG"] || "ERROR",
279
+ :auth_token => auth,
280
+ :quiet => true
281
+ })
282
+ end
283
+
284
+ at_exit { @local_tunnel_thread.kill }
285
+ end
286
+
287
+ def random_helper_url
288
+ random = sprintf("%x", rand(1000000))
289
+ "caldecott-#{random}"
290
+ end
291
+
292
+ def safe_path(*segments)
293
+ segments.flatten.collect { |x|
294
+ URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
295
+ }.join("/")
296
+ end
297
+
298
+ def grab_ephemeral_port
299
+ socket = TCPServer.new("0.0.0.0", 0)
300
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
301
+ Socket.do_not_reverse_lookup = true
302
+ socket.addr[1]
303
+ ensure
304
+ socket.close
305
+ end
306
+ end
@@ -0,0 +1,3 @@
1
+ module VMCTunnel
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe VMCTunnel::Tunnel do
4
+ describe "#tunnel_clients" do
5
+ context "when the user has a custom clients.yml in their vmc directory" do
6
+ use_fake_home_dir { "#{SPEC_ROOT}/fixtures/fake_home_dirs/with_custom_clients" }
7
+
8
+ it "overrides the default client config with the user's customizations" do
9
+ expect(subject.tunnel_clients["postgresql"]).to eq({
10
+ "psql" => {
11
+ "command"=>"-h ${host} -p ${port} -d ${name} -U ${user} -w",
12
+ "environment" => ["PGPASSWORD='dont_ask_password'"]
13
+ }
14
+ })
15
+ end
16
+ end
17
+
18
+ context "when the user does not have a custom clients.yml" do
19
+ it "returns the default client config" do
20
+ expect(subject.tunnel_clients["postgresql"]).to eq({
21
+ "psql" => {
22
+ "command"=>"-h ${host} -p ${port} -d ${name} -U ${user} -w",
23
+ "environment" => ["PGPASSWORD='${password}'"]
24
+ }
25
+ })
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ SPEC_ROOT = File.dirname(__FILE__).freeze
2
+
3
+ require "rspec"
4
+ require "cfoundry"
5
+ require "cfoundry/test_support"
6
+ require "vmc"
7
+ require "vmc/test_support"
8
+
9
+ require "#{SPEC_ROOT}/../lib/tunnel-vmc-plugin/plugin"
10
+
11
+ RSpec.configure do |c|
12
+ c.include Fake::FakeMethods
13
+ c.mock_with :rr
14
+ c.include VMC::TestSupport::FakeHomeDir
15
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: appfog-tunnel-vmc-plugin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alex Suraci, Tim Santeford
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cfoundry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: caldecott-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.0.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: rest-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: uuidtools
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '2.11'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '2.11'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '1.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '1.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rr
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: '1.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: '1.0'
139
+ description:
140
+ email:
141
+ - support@appfog.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - Rakefile
147
+ - lib/tunnel-vmc-plugin/plugin.rb
148
+ - lib/tunnel-vmc-plugin/tunnel.rb
149
+ - lib/tunnel-vmc-plugin/version.rb
150
+ - helper-app/Gemfile
151
+ - helper-app/Gemfile.lock
152
+ - helper-app/server.rb
153
+ - config/clients.yml
154
+ - spec/plugin_spec.rb
155
+ - spec/spec_helper.rb
156
+ homepage: http://appfog.com/
157
+ licenses: []
158
+ metadata: {}
159
+ post_install_message:
160
+ rdoc_options: []
161
+ require_paths:
162
+ - lib
163
+ required_ruby_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ! '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ! '>='
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ requirements: []
174
+ rubyforge_project: appfog-tunnel-vmc-plugin
175
+ rubygems_version: 2.0.1
176
+ signing_key:
177
+ specification_version: 4
178
+ summary: External access to your services on AppFog via a Caldecott HTTP tunnel.
179
+ test_files:
180
+ - spec/plugin_spec.rb
181
+ - spec/spec_helper.rb