pair 0.0.1 → 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.
- data/.gitignore +1 -0
- data/lib/pair/session/authorized_keys_file.rb +16 -18
- data/lib/pair/session/hosted_session.rb +48 -27
- data/lib/pair/session/tmux.rb +18 -43
- data/lib/pair/session/tunnel.rb +10 -11
- data/lib/pair/version.rb +1 -1
- data/spec/{pairmill → pair}/session/authorized_keys_file_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -3
- metadata +8 -12
- data/spec/pairmill/session/joined_session_spec.rb +0 -16
- data/spec/pairmill/session/session_spec.rb +0 -40
data/.gitignore
CHANGED
@@ -1,21 +1,22 @@
|
|
1
1
|
module Pair
|
2
2
|
class Session
|
3
|
+
# TODO replace with authorized_keys gem
|
3
4
|
class AuthorizedKeysFile
|
4
5
|
ACCESS_TYPE = "type"
|
5
6
|
KEYS = "keys"
|
6
7
|
|
7
8
|
attr_accessor :member_keys
|
8
|
-
attr_accessor :
|
9
|
+
attr_accessor :attach_command
|
9
10
|
attr_accessor :key_file_path
|
10
11
|
|
11
|
-
def initialize(member_keys = {},
|
12
|
-
self.member_keys
|
13
|
-
self.
|
14
|
-
self.key_file_path
|
12
|
+
def initialize(member_keys = {}, attach_command)
|
13
|
+
self.member_keys = member_keys
|
14
|
+
self.attach_command = attach_command
|
15
|
+
self.key_file_path = File.expand_path("~/.ssh/authorized_keys")
|
15
16
|
end
|
16
17
|
|
17
18
|
def install
|
18
|
-
return
|
19
|
+
return if member_keys.empty?
|
19
20
|
|
20
21
|
backup_authorized_keys if key_file_exists?
|
21
22
|
create_authorized_keys
|
@@ -45,10 +46,6 @@ module Pair
|
|
45
46
|
FileUtils.mv(backup_key_file_path, self.key_file_path)
|
46
47
|
end
|
47
48
|
|
48
|
-
def line_numbers_of(key)
|
49
|
-
`grep -ns ".*#{key}.*" #{self.key_file_path} | sed 's/\:.*//'`.split('\n').map(&:strip)
|
50
|
-
end
|
51
|
-
|
52
49
|
def key_file_exists?
|
53
50
|
File.exists? self.key_file_path
|
54
51
|
end
|
@@ -79,17 +76,18 @@ module Pair
|
|
79
76
|
end
|
80
77
|
|
81
78
|
def write_rows_for keys, of_type, in_file
|
79
|
+
read_only = of_type == 'viewer'
|
80
|
+
|
82
81
|
keys.each do |key|
|
83
|
-
|
84
|
-
end
|
85
|
-
in_file.puts ""
|
86
|
-
end
|
82
|
+
command = attach_command[read_only].join(' ').inspect
|
87
83
|
|
88
|
-
|
89
|
-
|
90
|
-
|
84
|
+
options = ["command=#{command}", *key_options].join(',')
|
85
|
+
content = key["content"]
|
86
|
+
comment = "# id: #{key["id"]}"
|
91
87
|
|
92
|
-
|
88
|
+
in_file.puts [options, content, comment].join(' ')
|
89
|
+
end
|
90
|
+
in_file.puts ""
|
93
91
|
end
|
94
92
|
|
95
93
|
def key_options
|
@@ -1,8 +1,16 @@
|
|
1
1
|
module Pair
|
2
2
|
class Session
|
3
|
+
def self.host(options)
|
4
|
+
session = HostedSession.new(options)
|
5
|
+
at_exit { session.stop! }
|
6
|
+
|
7
|
+
session.start!
|
8
|
+
session.stop!
|
9
|
+
end
|
10
|
+
|
3
11
|
class HostedSession < self
|
4
|
-
attr_accessor :tunnel, :authorized_keys_file, :
|
5
|
-
private :tunnel=, :authorized_keys_file=, :
|
12
|
+
attr_accessor :tunnel, :authorized_keys_file, :response
|
13
|
+
private :tunnel=, :authorized_keys_file=, :response=
|
6
14
|
|
7
15
|
def initialize(options = {})
|
8
16
|
puts "Hosting session #{"called #{options[:name].inspect} " if options[:name]}..."
|
@@ -10,7 +18,6 @@ module Pair
|
|
10
18
|
self.name = options.delete(:name)
|
11
19
|
self.viewers = options.delete(:viewers) || []
|
12
20
|
self.participants = options.delete(:participants) || []
|
13
|
-
self.tmux = Tmux.new(self)
|
14
21
|
|
15
22
|
super(options)
|
16
23
|
end
|
@@ -24,25 +31,48 @@ module Pair
|
|
24
31
|
tmux.attach
|
25
32
|
end
|
26
33
|
else
|
27
|
-
|
28
|
-
puts "response: #{response.inspect}" if response && $-d
|
29
|
-
puts ""
|
34
|
+
connection_error!
|
30
35
|
end
|
31
36
|
|
32
37
|
exit
|
33
38
|
end
|
34
39
|
|
40
|
+
def stop!
|
41
|
+
tmux.stop
|
42
|
+
tunnel.close
|
43
|
+
cleanup_authorized_keys
|
44
|
+
rescue
|
45
|
+
raise if $-d
|
46
|
+
end
|
47
|
+
|
48
|
+
def host_login
|
49
|
+
`whoami`.chomp
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def tmux
|
55
|
+
@tmux ||= Tmux.new(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def tunnel
|
59
|
+
@tunnel ||= Tunnel.new(response["tunnel"])
|
60
|
+
end
|
61
|
+
|
62
|
+
def authorized_keys_file
|
63
|
+
@key_file ||= AuthorizedKeysFile.new(response["member_keys"], tmux.method(:attach_command))
|
64
|
+
end
|
65
|
+
|
35
66
|
def display_startup_message
|
36
67
|
puts "Your pairs can connect to this session using the following command:"
|
37
68
|
puts ""
|
38
69
|
puts " #{connect_command}"
|
39
70
|
puts ""
|
40
|
-
print "Press
|
71
|
+
print "Press [Enter] to continue..."
|
41
72
|
|
42
73
|
gets
|
43
74
|
end
|
44
75
|
|
45
|
-
# TODO: this should get host/user from response
|
46
76
|
def connect_command
|
47
77
|
"ssh-add; ssh -tqA -l#{bastion["join_user"]} #{bastion["host"]} #{name}"
|
48
78
|
end
|
@@ -51,40 +81,31 @@ module Pair
|
|
51
81
|
response["tunnel"]["bastion"]
|
52
82
|
end
|
53
83
|
|
54
|
-
def host_login
|
55
|
-
`whoami`.chomp
|
56
|
-
end
|
57
|
-
|
58
84
|
def cleanup_authorized_keys
|
59
85
|
authorized_keys_file.cleanup
|
60
86
|
end
|
61
87
|
|
62
|
-
private
|
63
|
-
|
64
88
|
def setup
|
65
89
|
create_session_on_server && authorized_keys_file.install
|
66
90
|
end
|
67
91
|
|
68
92
|
def create_session_on_server
|
69
93
|
self.response = Api.create_session(self)["session"]
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
94
|
+
if response
|
95
|
+
self.name = response['name']
|
96
|
+
true
|
97
|
+
else
|
98
|
+
false
|
75
99
|
end
|
76
100
|
end
|
77
101
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
102
|
+
def connection_error!
|
103
|
+
puts "There was a problem starting the host session %s" % (name && name.inspect)
|
104
|
+
puts "response: #{response.inspect}" if response && $-d
|
105
|
+
puts ""
|
106
|
+
abort
|
82
107
|
end
|
83
108
|
end
|
84
|
-
|
85
|
-
def self.host(options)
|
86
|
-
HostedSession.new(options).start!
|
87
|
-
end
|
88
109
|
end
|
89
110
|
end
|
90
111
|
|
data/lib/pair/session/tmux.rb
CHANGED
@@ -1,73 +1,48 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'tmpdir'
|
2
3
|
|
3
4
|
module Pair
|
4
5
|
class Session
|
5
6
|
class Tmux
|
6
|
-
|
7
|
+
attr_accessor :session_name
|
8
|
+
private :session_name=
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def initialize(session)
|
12
|
-
self.session = session
|
13
|
-
create_socket_directory
|
14
|
-
end
|
15
|
-
|
16
|
-
def app_path
|
17
|
-
self.class.to_s.split('::').first.downcase
|
18
|
-
end
|
19
|
-
|
20
|
-
def unique
|
21
|
-
@unique ||= srand.to_s[0,5]
|
10
|
+
def initialize(session_name)
|
11
|
+
self.session_name = session_name
|
22
12
|
end
|
23
13
|
|
24
14
|
def start
|
25
15
|
args = %W[-S #{socket_path} new-session -d]
|
26
|
-
system
|
16
|
+
system tmux, *args
|
17
|
+
end
|
27
18
|
|
28
|
-
|
19
|
+
def attach(read_only = false)
|
20
|
+
system(*attach_command(read_only))
|
29
21
|
end
|
30
22
|
|
31
23
|
def stop
|
32
|
-
`lsof -t #{socket_path}/ | xargs kill -9`
|
24
|
+
`lsof -t #{socket_path}/ 2>/dev/null | xargs kill -9`
|
33
25
|
FileUtils.rm_f(socket_path)
|
34
|
-
self.session.cleanup_authorized_keys if self.session.respond_to?(:cleanup_authorized_keys)
|
35
26
|
end
|
36
27
|
|
37
|
-
def
|
38
|
-
args = %W[
|
39
|
-
|
40
|
-
|
41
|
-
-t #{session.name}:0
|
42
|
-
-n 'Pairing'
|
43
|
-
'ssh pair@bastion.pairmill.com -A'
|
44
|
-
]
|
45
|
-
|
46
|
-
system "tmux", *args
|
47
|
-
end
|
48
|
-
|
49
|
-
def attach(read_only = false)
|
50
|
-
args = %W[-S #{socket_path} attach]
|
51
|
-
args += " -r" if read_only
|
52
|
-
|
53
|
-
system "tmux", *args
|
28
|
+
def attach_command(read_only = false)
|
29
|
+
args = %W[#{tmux} -S #{socket_path} attach]
|
30
|
+
args << " -r" if read_only
|
31
|
+
args
|
54
32
|
end
|
55
33
|
|
56
34
|
private
|
57
|
-
def create_socket_directory
|
58
|
-
FileUtils.mkdir_p(socket_directory, :mode => 0700)
|
59
|
-
end
|
60
35
|
|
61
|
-
def
|
62
|
-
|
36
|
+
def tmux
|
37
|
+
@tmux ||= `which tmux`.chomp
|
63
38
|
end
|
64
39
|
|
65
40
|
def socket_path
|
66
|
-
File.join
|
41
|
+
File.join(Dir.tmpdir, socket_name)
|
67
42
|
end
|
68
43
|
|
69
44
|
def socket_name
|
70
|
-
"
|
45
|
+
"pair-#{session_name.gsub(/[^\w_-]+/,'')}.tmux"
|
71
46
|
end
|
72
47
|
end
|
73
48
|
end
|
data/lib/pair/session/tunnel.rb
CHANGED
@@ -24,8 +24,11 @@ module Pair
|
|
24
24
|
def open
|
25
25
|
self.tunnel = IO.popen(ssh_command)
|
26
26
|
puts "SSH tunnel started (PID = #{tunnel.pid})" if $-d
|
27
|
-
|
28
|
-
|
27
|
+
|
28
|
+
if block_given?
|
29
|
+
yield
|
30
|
+
close
|
31
|
+
end
|
29
32
|
end
|
30
33
|
|
31
34
|
def close
|
@@ -41,15 +44,11 @@ module Pair
|
|
41
44
|
|
42
45
|
def ssh_command
|
43
46
|
options = []
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
else
|
50
|
-
options << "-tAq"
|
51
|
-
options << "-l #{bastion["join_user"]}"
|
52
|
-
end
|
47
|
+
|
48
|
+
options << "-nqT" # Run no command, be quiet, don't allocate pseudo-terminal
|
49
|
+
options << "-l #{bastion["host_user"]}"
|
50
|
+
options << "-p #{bastion["ssh_port"]}" unless bastion["ssh_port"] == 22
|
51
|
+
options << "-R #{port}:localhost:#{host_port}"
|
53
52
|
|
54
53
|
"ssh #{bastion["host"]} #{options.join(" ")}"
|
55
54
|
end
|
data/lib/pair/version.rb
CHANGED
@@ -2,10 +2,10 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe Pair::Session::AuthorizedKeysFile do
|
6
6
|
let(:member_keys) { FixtureHelper.user_keys }
|
7
7
|
let(:session) { "testing-session" }
|
8
|
-
let(:subject) {
|
8
|
+
let(:subject) { Pair::Session::AuthorizedKeysFile.new(member_keys, session) }
|
9
9
|
|
10
10
|
before do
|
11
11
|
subject.stub!(:backup_authorized_keys)
|
data/spec/spec_helper.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
3
4
|
require 'rspec'
|
4
5
|
require 'json'
|
5
6
|
require 'fixture_helper'
|
6
|
-
require '
|
7
|
-
require 'pairmill/session'
|
7
|
+
require 'pair'
|
8
8
|
|
9
9
|
# Requires supporting files with custom matchers and macros, etc,
|
10
10
|
# in ./support/ and its subdirectories.
|
11
11
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
12
12
|
|
13
13
|
RSpec.configure do |config|
|
14
|
-
|
14
|
+
|
15
15
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pair
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2011-11-
|
13
|
+
date: 2011-11-27 00:00:00.000000000Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: httparty
|
17
|
-
requirement: &
|
17
|
+
requirement: &70298620262940 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 0.8.1
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70298620262940
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: rspec
|
28
|
-
requirement: &
|
28
|
+
requirement: &70298620258860 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ~>
|
@@ -33,7 +33,7 @@ dependencies:
|
|
33
33
|
version: 2.3.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70298620258860
|
37
37
|
description: Effortless remote pairing
|
38
38
|
email:
|
39
39
|
- me@bjeanes.com
|
@@ -66,9 +66,7 @@ files:
|
|
66
66
|
- spec/fixtures/api_hosted_session.yml
|
67
67
|
- spec/fixtures/api_joined_session.yml
|
68
68
|
- spec/fixtures/user_keys.yml
|
69
|
-
- spec/
|
70
|
-
- spec/pairmill/session/joined_session_spec.rb
|
71
|
-
- spec/pairmill/session/session_spec.rb
|
69
|
+
- spec/pair/session/authorized_keys_file_spec.rb
|
72
70
|
- spec/spec_helper.rb
|
73
71
|
homepage: http://www.pairmill.com
|
74
72
|
licenses: []
|
@@ -99,7 +97,5 @@ test_files:
|
|
99
97
|
- spec/fixtures/api_hosted_session.yml
|
100
98
|
- spec/fixtures/api_joined_session.yml
|
101
99
|
- spec/fixtures/user_keys.yml
|
102
|
-
- spec/
|
103
|
-
- spec/pairmill/session/joined_session_spec.rb
|
104
|
-
- spec/pairmill/session/session_spec.rb
|
100
|
+
- spec/pair/session/authorized_keys_file_spec.rb
|
105
101
|
- spec/spec_helper.rb
|
@@ -1,16 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
-
|
3
|
-
describe Pairmill::Session::JoinedSession do
|
4
|
-
let(:host) { "chad.pry@gmail.com" }
|
5
|
-
let(:name) { "testing-conference" }
|
6
|
-
|
7
|
-
describe "when instantiating an object" do
|
8
|
-
it "sets the host" do
|
9
|
-
Pairmill::Session::JoinedSession.new(host, name).host.should == host
|
10
|
-
end
|
11
|
-
|
12
|
-
it "sets the name" do
|
13
|
-
Pairmill::Session::JoinedSession.new(host, name).name.should == name
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
-
|
3
|
-
describe Pairmill::Session do
|
4
|
-
let(:host) { "chad.pry@gmail.com" }
|
5
|
-
let(:name) { "testing-conference" }
|
6
|
-
|
7
|
-
# describe "when joining a session" do
|
8
|
-
# describe "the returned session" do
|
9
|
-
# let(:joined_session) { Pairmill::Session::JoinedSession.new(host, name) }
|
10
|
-
#
|
11
|
-
# before do
|
12
|
-
# Pairmill::Session::JoinedSession.stub!(:new).and_return(joined_session)
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# it "starts a joined session" do
|
16
|
-
# joined_session.should_receive(:start!)
|
17
|
-
# Pairmill::Session.join(:host => host, :session_name => name)
|
18
|
-
# end
|
19
|
-
# end
|
20
|
-
# end
|
21
|
-
|
22
|
-
describe "starting a session" do
|
23
|
-
let(:session_json) { FixtureHelper.api_joined_session }
|
24
|
-
|
25
|
-
before do
|
26
|
-
Pairmill::Api.stub!(:join_session).and_return(session_json)
|
27
|
-
end
|
28
|
-
|
29
|
-
it "fetches a session from the api" do
|
30
|
-
session = Pairmill::Session::JoinedSession.new(host, name)
|
31
|
-
session.should_receive(:fetch_session_details)
|
32
|
-
session.start!
|
33
|
-
end
|
34
|
-
|
35
|
-
it "returns a joined session instance" do
|
36
|
-
session = Pairmill::Session::JoinedSession.new(host, name)
|
37
|
-
session.should be_a(Pairmill::Session::JoinedSession)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|