pipe2me-client 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 45f85801ad57c6402d4c6aecbe01cd335e6363e5
4
+ data.tar.gz: c72fa32c04e45830226f4f5a103e112ef0448cc6
5
+ SHA512:
6
+ metadata.gz: 65c12c51649a8b1b01cdaf274012a9d3ac2accba70dff89064cc260c3c4423288fa40b2042a16f9c859eda270d5df0d789082db93d526aeb5386fc1c0ac6bb3c
7
+ data.tar.gz: f934aeef86119c2feb2c5fba0c824c59366f9d79d29d77173750397ac16b11077147f8e64e5e4d6b1df10e6480020b8ede0ce166969d34d9c5f5f56b8fd23689
data/README.mdown ADDED
@@ -0,0 +1,96 @@
1
+ # pipe2me client
2
+
3
+ This is the ruby client for the pipe2me server package.
4
+ The pipe2me client lets you publish local services to the public internet
5
+ with the help and orchestration of a pipe2me server. For more details
6
+ see [pipe2me](https://github.com/kinko/pipe2me)
7
+
8
+ ## Installation
9
+
10
+ gem install pipe2me-client
11
+ # optional: install man page
12
+ sudo rake install
13
+
14
+ ## Usage
15
+
16
+ Mini-example: This registers two tunnels with a single hostname for
17
+ two services on localhost (http on port 9090, https on port 9091).
18
+
19
+ # Setup tunnels. This responds with the domain name
20
+ > ./bin/pipe2me setup -p http,https --server http://pipe2.me --auth 123 --ports 9090,9091
21
+ pretty-ivory-horse.test.pipe2.me
22
+
23
+ # Review the assigned URLs:
24
+ > cat pipe2me.info.inc | grep URL
25
+ PIPE2ME_URLS_0=http://pretty-ivory-horse.test.pipe2.me:10003
26
+ PIPE2ME_URLS_1=https://pretty-ivory-horse.test.pipe2.me:10004
27
+
28
+ # Start the tunnels via foreman (but please review on using foreman)
29
+ > /bin/pipe2me start
30
+
31
+ See also the [example session](http://test.pipe2.me/example_session.html)
32
+ and the [man page](http://test.pipe2.me/pipe2me.1.html).
33
+
34
+ ## Testing
35
+
36
+ Tests are implemented using
37
+ [roundup](https://github.com/bmizerany/roundup/blob/master/INSTALLING#files).
38
+ To install roundup on OSX, run `brew install roundup`. Other systems are
39
+ supported as well, compare roundup's documentation for details.
40
+
41
+ The implemented tests are *integration tests* in the sense, that they test the
42
+ behaviour of the *pipe2me-client* package in connection to an external pipe2me
43
+ server. That means you should run a pipe2me server on your local machine. Note
44
+ that the server must be configured to support test mode. (In test mode a pipe2me
45
+ server accepts test auth tokens, that create short lived tunnels
46
+ with self-signed certificates.)
47
+
48
+ To run the tests against a locally installed test server run `rake`. Note that
49
+ the local test server is expected at "pipe2.dev:8080". You might have to adjust
50
+ the `/etc/hosts` file to add an entry
51
+
52
+ 127.0.0.1 pipe2.dev
53
+
54
+ Before submitting a pull request you should also run a test against the
55
+ test.pipe2.me instance, which is available most of the time for that purpose.
56
+ To do that, run `rake test:release`.
57
+
58
+ ## Auth tokens
59
+
60
+ As ports and domain names are sparse resources the pipe2me server API
61
+ requires the use of authorization tokens when requesting a tunnel. A
62
+ token is similar to a currency in that it describes which tunnels are
63
+ supported. This could limit the number of tunneled ports, the traffic
64
+ for those ports, whether or not a certificate is self-signed or signed
65
+ by a regular CA, etc.
66
+
67
+ The features of a token are not defined within this protocol. However,
68
+ there has to be one. Contact the administrator of the pipe2me server
69
+ to know more.
70
+
71
+ ### Auth tokens on test.pipe2.me
72
+
73
+ The test.pipe2.me server is configured to use some public tokens.
74
+
75
+ - a test token: this builds tunnels that are available for 5 minutes.
76
+ The test token is intended for use with automated test scenarios.
77
+ The current test token is `test@test.kinko.me`.
78
+ - a review token: this builds tunnels that are available for up to
79
+ one day. A review token is intended for get a feel for the pipe2me
80
+ package. The current review token is `review@test.kinko.me`.
81
+
82
+ If you need a longer lived token for development, review and/or test
83
+ feel free to contact us at contact@kinko.me.
84
+
85
+ If you want to use pipe2me for any practical purpose consider
86
+ signing up at https://pipe2.me, which should be online end of January.
87
+
88
+ ## Licensing
89
+
90
+ **The pipe2me client software** is (c) The Kinko Team, 2014 and released to you
91
+ under the terms of the MIT License (MIT), see COPYING.MIT for details.
92
+
93
+ The subdirectory lib/vendor contains third-party code, which is subject to its own copyrights.
94
+ Please see the respective source files for copyright information.
95
+
96
+ (c) The kinko team, 2014
data/bin/pipe2me ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
3
+
4
+ require "pipe2me"
5
+ require "pipe2me/cli"
6
+ require "pipe2me/cli-foreman"
7
+ require "pipe2me/cli-monit"
8
+
9
+ begin
10
+ Pipe2me::CLI.start ARGV
11
+ rescue
12
+ if UI.verbosity >= 2
13
+ UI.error $!
14
+ raise
15
+ elsif UI.verbosity >= 0
16
+ UI.error $!
17
+ else
18
+ STDERR.puts $!
19
+ end
20
+ exit 1
21
+ end
data/lib/pipe2me.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Pipe2me
2
+ end
3
+
4
+ require "simple/ui"
5
+
6
+ require_relative "pipe2me/version"
7
+ require_relative "pipe2me/config"
8
+ require_relative "pipe2me/tunnel"
9
+
@@ -0,0 +1,21 @@
1
+ class Pipe2me::CLI < Thor
2
+ desc "start", "Start tunnels"
3
+ option :echo, :type => :boolean, :banner => "Also run echo servers"
4
+ def start
5
+ procfile = options[:echo] ? "pipe2me.procfile.echo" : "pipe2me.procfile"
6
+
7
+ File.open procfile, "w" do |io|
8
+ Pipe2me::Tunnel.tunnel_commands.each do |name, cmd|
9
+ io.write "#{name}: #{cmd}\n"
10
+ end
11
+
12
+ next unless options[:echo]
13
+
14
+ Pipe2me::Tunnel.echo_commands.each do |name, cmd|
15
+ io.write "#{name}: #{cmd}\n"
16
+ end
17
+ end
18
+
19
+ Kernel.exec "foreman start -f #{procfile}"
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ class Pipe2me::CLI < Thor
2
+ desc "monit", "Create monitrc file and run monit"
3
+ option :port, :default => 5555, :banner => "control port"
4
+ option :echo, :type => :boolean, :banner => "Also run echo servers"
5
+ def monit(*args)
6
+ monitrc_file = create_monitrc
7
+
8
+ UI.warn "Running: monit -c #{monitrc_file} #{args.join(" ")}"
9
+ Kernel.exec "monit", "-c", monitrc_file, *args
10
+ end
11
+
12
+ desc "monitrc", "Create monitrc file"
13
+ option :port, :default => 5555, :banner => "control port"
14
+ option :echo, :type => :boolean, :banner => "Also run echo servers"
15
+ def monitrc
16
+ monitrc_file = create_monitrc
17
+ Kernel.exec "monit", "-c", monitrc_file, "-t"
18
+ end
19
+
20
+ private
21
+
22
+ def create_monitrc
23
+ path = options[:echo] ? "pipe2me.monitrc.echo" : "pipe2me.monitrc"
24
+
25
+ # The daemon binary
26
+ daemon_bin = `which daemon`.chomp
27
+ daemon = "#{daemon_bin} -D #{File.expand_path(Dir.getwd)}"
28
+
29
+ port = options[:port]
30
+
31
+ logfile = File.expand_path "pipe2me.monit.log"
32
+ piddir = File.expand_path "pipe2me.monit.pids"
33
+ FileUtils.mkdir_p piddir
34
+
35
+ commands = Pipe2me::Tunnel.tunnel_commands
36
+ commands += Pipe2me::Tunnel.echo_commands if options[:echo]
37
+
38
+ File.open path, "w", 0600 do |io|
39
+ require "erb"
40
+
41
+ erb = ERB.new MONITRC_ERB
42
+ io.write erb.result(self.send(:binding))
43
+ end
44
+
45
+ UI.success "Created #{path}"
46
+
47
+ path
48
+ end
49
+
50
+ MONITRC_ERB = %q{
51
+ set daemon 10
52
+ set httpd port <%= port %> and use address localhost allow localhost
53
+
54
+ <% commands.each do |name, cmd| %>
55
+ check process <%= name %> with pidfile <%= piddir %>/<%= name %>.pid
56
+ <% daemon = "#{daemon} -N --name #{name} --pidfiles #{piddir} --output #{logfile}" %>
57
+ start program = "<%= daemon %> -- <%= cmd %>" with timeout 60 seconds
58
+ stop program = "<%= daemon %> --stop"
59
+ <% end %>
60
+ }
61
+
62
+ end
@@ -0,0 +1,29 @@
1
+ require "thor"
2
+
3
+ class Pipe2me::CLI < Thor
4
+ def self.exit_on_failure?; true; end
5
+
6
+ desc "setup", "fetch a new tunnel setup"
7
+ option :server, :default => "http://test.pipe2.me:8080"
8
+ option :auth, :required => true # "auth token"
9
+ option :protocols, :default => "https" # "protocol names, e.g. 'http,https,imap'"
10
+ option :ports # "local ports, one per protocol"
11
+ def setup
12
+ Pipe2me::Config.server = options[:server]
13
+ server_info = Pipe2me::Tunnel.setup options
14
+
15
+ update
16
+ puts server_info[:fqdn]
17
+ end
18
+
19
+ desc "env", "show tunnel configuration"
20
+ def env(*args)
21
+ puts File.read("pipe2me.local.inc")
22
+ puts File.read("pipe2me.info.inc")
23
+ end
24
+
25
+ desc "update", "Updates configuration"
26
+ def update
27
+ Pipe2me::Tunnel.update
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ require "tmpdir"
2
+
3
+ module Pipe2me::Config
4
+ extend self
5
+
6
+ attr :server, true
7
+ end
@@ -0,0 +1,16 @@
1
+ require "tempfile"
2
+
3
+ class File
4
+ def self.atomic_write(path, data=nil, &block)
5
+ tmpfile = Tempfile.new(File.basename(path))
6
+ tmpfile.write data if data
7
+ yield(tmpfile) if block
8
+ tmpfile.write data if data
9
+ tmpfile.close
10
+ FileUtils.mv tmpfile.path, path # atomic replace
11
+ tmpfile.unlink # deletes the temp file
12
+ rescue
13
+ tmpfile.close! rescue nil # close and deletes the temp file
14
+ raise
15
+ end
16
+ end
@@ -0,0 +1,213 @@
1
+ require "net/http"
2
+ require "ostruct"
3
+
4
+ # The HTTP module implements a simple wrapper around Net::HTTP, intended to
5
+ # ease the pain of dealing with HTTP requests.
6
+ module Pipe2me::HTTP
7
+ extend self
8
+
9
+ # Error to be raised when maximum number of redirections is reached.
10
+ class RedirectionLimit < RuntimeError; end
11
+
12
+ # Base class for HTTP related errors.
13
+ class Error < RuntimeError
14
+
15
+ # The response object (of class HTTP::Response).
16
+ attr_reader :response
17
+
18
+ def initialize(response) #:nodoc:
19
+ @response = response
20
+ end
21
+
22
+ def code
23
+ response.code
24
+ end
25
+
26
+ def message #:nodoc:
27
+ "#{@response.response.class}: #{@response[0..120]}"
28
+ end
29
+ end
30
+
31
+ # Raised when a server responded with an error 5xx.
32
+ class ServerError < Error; end
33
+
34
+ # Raised when a server responded with an error 4xx.
35
+ class ResourceNotFound < Error; end
36
+
37
+ # -- configuration
38
+
39
+ @@config = OpenStruct.new
40
+
41
+ # The configuration object. It supports the following entries:
42
+ # - <tt>config.headers</tt>: default headers to use when doing HTTP requests. Default: "Ruby HTTP client/1.0"
43
+ # - <tt>config.max_redirections</tt>: the number of maximum redirections to follow. Default: 10
44
+ #
45
+ # To adjust the configuration change these objects, like so:
46
+ #
47
+ # HTTP.config.headers = { "User-Agent" => "My awesome thingy/1.0" }
48
+ def config
49
+ @@config
50
+ end
51
+
52
+ # A default set of headers
53
+ config.headers = {
54
+ "User-Agent" => "Ruby HTTP client/1.0"
55
+ }
56
+
57
+ # The default number of max redirections.
58
+ config.max_redirections = 10
59
+
60
+ # -- return types
61
+
62
+ # The HTTP::Response class works like a string, but contains extra "attributes"
63
+ # status and headers, which return the response status and response headers.
64
+ class Response < String
65
+
66
+ # The URL of the final request.
67
+ attr_reader :url
68
+
69
+ # The URL of the original request.
70
+ attr_reader :original_url
71
+
72
+ # The response object.
73
+ attr_reader :response
74
+
75
+ def initialize(response, url, original_url) #:nodoc:
76
+ @response, @url, @original_url = response, url, original_url
77
+ super(response.body || "")
78
+ end
79
+
80
+ # returns true if the status is in the 2xx range.
81
+ def valid?
82
+ (200..299).include? status
83
+ end
84
+
85
+ # returns the response object itself, if it is valid (i.e. has a ), or raise
86
+ def validate!
87
+ return self if valid?
88
+
89
+ case status
90
+ when 400..499 then raise ResourceNotFound, self
91
+ when 500..599 then raise ServerError, self
92
+ else raise Error, self
93
+ end
94
+ end
95
+
96
+ # returns the HTTP status code, as an Integer.
97
+ def code
98
+ @response.code.to_i
99
+ end
100
+
101
+ alias :status :code
102
+
103
+ # returns all headers.
104
+ def headers
105
+ @headers ||= {}.tap do |h|
106
+ @response.each_header do |key, value|
107
+ h[key] = value
108
+ end
109
+ end
110
+ end
111
+
112
+ def content_type
113
+ headers["content-type"]
114
+ end
115
+
116
+ def parse
117
+ case content_type
118
+ when /application\/json/
119
+ require "json" unless defined?(JSON)
120
+ JSON.parse(self)
121
+ else
122
+ UI.warn "#{url}: Cannot parse #{content_type.inspect} response"
123
+ self
124
+ end
125
+ end
126
+ end
127
+
128
+ # -- do requests ----------------------------------------------------
129
+
130
+ # runs a get request and return a HTTP::Response object.
131
+ def get(url, headers = {})
132
+ do_request Net::HTTP::Get, url, headers
133
+ end
134
+
135
+ # runs a post request and return a HTTP::Response object.
136
+ def post(url, body, headers = {})
137
+ do_request Net::HTTP::Post, url, headers, body
138
+ end
139
+
140
+ private
141
+
142
+ def method_missing(sym, *args, &block)
143
+ case sym.to_s
144
+ when /^(.*)\!$/
145
+ response = send $1, *args, &block
146
+ response.validate!
147
+ when /^(.*)\?$/
148
+ response = send $1, *args, &block
149
+ response if response.valid?
150
+ else
151
+ super
152
+ end
153
+ end
154
+
155
+ #:nodoc:
156
+ def do_request(verb, uri, headers, body = nil)
157
+ UI.benchmark :info, "[#{verb.name.gsub(/.*::/, "").upcase}] #{uri}" do
158
+ do_raw_request verb, uri, headers, body, config.max_redirections, uri, Net::HTTP::Get
159
+ end
160
+
161
+ rescue
162
+ raise "#{uri}: #{$!}"
163
+ end
164
+
165
+ def do_raw_request(verb, uri, headers, body, max_redirections, original_url, redirection_verb = Net::HTTP::Get)
166
+ # merge default headers
167
+ headers = config.headers.merge(headers)
168
+
169
+ # create connection
170
+
171
+ uri = URI.parse(uri) if uri.is_a?(String)
172
+
173
+ default_port = uri.scheme == "https" ? 443 : 80
174
+ http = Net::HTTP.new(uri.host, uri.port || default_port)
175
+ if uri.scheme == "https"
176
+ http.use_ssl = true
177
+ end
178
+
179
+ # create request and get response
180
+
181
+ request = verb.new(uri.request_uri)
182
+ request.initialize_http_header headers
183
+
184
+ if verb.const_get "REQUEST_HAS_BODY"
185
+ case body
186
+ when String
187
+ request.body = body
188
+ when Hash
189
+ request.form_data = body
190
+ when nil
191
+ else
192
+ raise ArgumentError, "Unsupported body of class #{body.class.name}"
193
+ end
194
+ end
195
+
196
+
197
+ request.basic_auth(r.user, r.password) if uri.user && uri.password
198
+ response = http.request(request)
199
+
200
+ # follow redirection, if needed
201
+
202
+ case response
203
+ when Net::HTTPRedirection
204
+ # Note: we always follow the redirect using a GET. This seems to violate parts of
205
+ # RFC 2616, but sadly seems the best default behaviour, as is implemented that way
206
+ # in many clients..
207
+ raise RedirectionLimit.new(original_url) if max_redirections <= 0
208
+ return do_raw_request redirection_verb, response["Location"], headers, max_redirections-1, original_url, redirection_verb
209
+ else
210
+ Response.new(response, uri.to_s, original_url)
211
+ end
212
+ end
213
+ end