tmux-connector 0.0.4 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,11 +4,14 @@ Manage multiple servers using SSH and [tmux].
4
4
 
5
5
 
6
6
  ## Features:
7
+ * connect to multiple servers at once
8
+ * expressive layouts customizable for different server groups available
9
+ * issue commands to all servers or just a selected (custom) subgroup
7
10
  * work on multiple sessions in parallel
11
+ * multiple connections to individual servers possible
8
12
  * sessions can be persisted (actually recreated) after computer restarts
9
13
  - they are lost only if you delete them explicitly
10
- * complex layouts customizable for different server groups
11
- * issuing commands to all servers or just a selected subgroups
14
+
12
15
 
13
16
  ## Quick tease
14
17
 
@@ -61,8 +64,9 @@ Usage:
61
64
  tcon delete (<session-name> | --all)
62
65
  tcon list
63
66
  tcon send <session-name> (<command> | --command-file=<file>)
64
- [--server-filter=<regex>] [--group-filter=<regex>]
65
- [--filter=<regex>] [--verbose]
67
+ [ --server-filter=<regex> | --group-filter=<regex>
68
+ | --filter=<regex> | --window=<index> ]
69
+ [--verbose]
66
70
  tcon --help
67
71
  tcon --version
68
72
 
@@ -72,6 +76,7 @@ Options:
72
76
  <session-name> Name that identifies the session. Must be unique.
73
77
  <command> Command to be executed on remote server[s].
74
78
  <regex> String that represents valid Ruby regex.
79
+ <index> 0-based index.
75
80
  -s --ssh-config=file Path to ssh config file [default: ~/.ssh/config].
76
81
  -n --session-name=name Name of the session to be used in the tcon command.
77
82
  -p --purpose=description Description of session's purpose.
@@ -83,6 +88,7 @@ Options:
83
88
  -r --filter=regex Filter to select a subset of the servers via
84
89
  host names or group membership.
85
90
  Combines --server-filter and --group-filter.
91
+ -w --window=index Select a window via (0-based) index.
86
92
  -c --command-file=file File containing the list of commands to be
87
93
  executed on remote server[s].
88
94
  -v --verbose Report how many servers were affected by the send
@@ -99,7 +105,7 @@ that hard and here I provide exhaustive details about configuration files.
99
105
 
100
106
  (If there is enough interest, in future versions there could be a special
101
107
  command to simplify generation of configuration files. To accelerate the
102
- process, open an issue or drop me an email: << username >>@gmail.com)
108
+ process, open an issue or drop me an email: ivan@<< username >>.com)
103
109
 
104
110
  Let's get to it.
105
111
 
@@ -115,7 +121,7 @@ UserKnownHostsFile=/dev/null
115
121
  Host staging.cache-staging-1
116
122
  Hostname ec2-111-42-111-42.eu-west-1.compute.amazonaws.com
117
123
  Port 4242
118
- IdentityFile /Users/ikusalic/.ssh/some-pem-file.pem
124
+ IdentityFile /Users/some-user/.ssh/some-pem-file.pem
119
125
  User ubuntu
120
126
 
121
127
  Host dev.database-staging-1
@@ -145,12 +151,6 @@ Host dev.node-staging-127
145
151
  Host dev.node-staging-129
146
152
  << omitted >>
147
153
 
148
- Host dev.node-staging-130
149
- << omitted >>
150
-
151
- Host dev.node-staging-135
152
- << omitted >>
153
-
154
154
  << ... >>
155
155
  ~~~
156
156
 
@@ -165,7 +165,7 @@ regex-parts-to:
165
165
  sort-by: [3]
166
166
  ~~~
167
167
 
168
- And here's a 'real world' configuration file that shows of all the available
168
+ And here's a 'real world' configuration file that shows off all the available
169
169
  options and could be use with previous ssh config file:
170
170
 
171
171
  ~~~yaml
@@ -181,6 +181,11 @@ name:
181
181
  merge-groups:
182
182
  misc: ['cache', 'db', 'mongodb']
183
183
  lbs: ['haproxy', 'nginx']
184
+ multiple-hosts:
185
+ regexes:
186
+ - !ruby-regexp '(nginx|haproxy)-'
187
+ - !ruby-regexp '(db)-'
188
+ counts: [2, 3]
184
189
  layout:
185
190
  default:
186
191
  custom:
@@ -262,6 +267,25 @@ In this example two different kinds of loadbalancers are grouped together.
262
267
  Note that the servers from merge groups can later be referenced with both
263
268
  original and merge-group name.
264
269
 
270
+ * * *
271
+ (optional) field __'multiple-hosts'__ contains __'regexes'__ and __'counts'__
272
+ fields. With those, some hosts can have multiple connections established, not
273
+ just the default one connection per host.
274
+
275
+ For example:
276
+ ~~~yaml
277
+ multiple-hosts:
278
+ regexes:
279
+ - !ruby-regexp '(nginx|haproxy)-'
280
+ - !ruby-regexp '(db)-'
281
+ counts: [2, 3]
282
+ ~~~
283
+ creates 2 connections for each of nginx or haproxy nodes, as well as 3
284
+ connection for db nodes.
285
+
286
+ Fields __'regexes'__ and __'counts'__ must have the same number of elements.
287
+ Each element in __'regexes'__ must contain valid ruby regex.
288
+
265
289
  * * *
266
290
  Finally, what's left is the (optional) __layout__ definition:
267
291
 
@@ -284,6 +308,7 @@ The layouts are applied individually to any merge group and to any normal
284
308
  a group then layout allows on a single window, next window for that group is
285
309
  added. Servers from different groups never share a window.
286
310
 
311
+
287
312
  ## Requirements
288
313
  To be able to use the gem you should have ruby 1.9+ and tmux installed on a *nix
289
314
  (Mac OS X, Linux, ...) machine. (Windows: here be dragons)
@@ -351,7 +376,7 @@ information, check my [dotfiles].
351
376
  4. Push to the branch (`git push origin my-new-feature`)
352
377
  5. Create new Pull Request
353
378
 
354
- Or just mail me, mail: << username >>@gmail.com
379
+ Or just mail me, mail: ivan@<< username >>.com
355
380
 
356
381
  This is my first real gem, so all your comments are more than welcome.
357
382
  I'd really appreciate ruby code improvements/refactoring comments or usability
@@ -363,7 +388,7 @@ comments (all other are welcome too). Just _drop me a line_. :)
363
388
  Take a look at `TODO.md` file (in the repository) for ideas about additional
364
389
  features in new versions.
365
390
 
366
- << username >>@gmail.com
391
+ ivan@<< username >>.com
367
392
 
368
393
  I'd be happy to hear from you.
369
394
 
@@ -10,6 +10,7 @@ module TmuxConnector
10
10
  attr_reader :server_filter
11
11
  attr_reader :session
12
12
  attr_reader :verbose
13
+ attr_reader :window
13
14
 
14
15
  def initialize(args)
15
16
  @name = args['<session-name>']
@@ -23,11 +24,13 @@ module TmuxConnector
23
24
  @server_filter ||= Regexp.new(args['--filter']) rescue nil
24
25
  @group_filter ||= Regexp.new(args['--filter']) rescue nil
25
26
 
27
+ @window = args['--window']
28
+
26
29
  @session = Session.load_by_name args['<session-name>']
27
30
  end
28
31
 
29
32
  def run()
30
- session.tmux_session.send_commands(commands, server_filter, group_filter, verbose)
33
+ session.tmux_session.send_commands(commands, server_filter, group_filter, window, verbose)
31
34
  end
32
35
 
33
36
  private
@@ -1,9 +1,10 @@
1
1
  module TmuxConnector
2
2
  class Host
3
- attr_reader :ssh_name
3
+ attr_reader :count
4
4
  attr_reader :display_name
5
5
  attr_reader :group_id
6
6
  attr_reader :sort_value
7
+ attr_reader :ssh_name
7
8
 
8
9
  def initialize(name, config)
9
10
  @ssh_name = name
@@ -12,6 +13,8 @@ module TmuxConnector
12
13
  @display_name = create_display_name groups, config
13
14
  @sort_value = config['regex-parts-to']['sort-by'].map { |i| groups[i] }.join '-'
14
15
  @group_id = config['regex-parts-to']['group-by'].map { |i| groups[i] }.join '-'
16
+
17
+ @count = get_count config
15
18
  end
16
19
 
17
20
  def to_s()
@@ -20,7 +23,7 @@ module TmuxConnector
20
23
 
21
24
  private
22
25
 
23
- def create_display_name groups, config
26
+ def create_display_name(groups, config)
24
27
  if config['name']
25
28
  parts = []
26
29
  groups.each_with_index do |e, i|
@@ -32,5 +35,16 @@ module TmuxConnector
32
35
 
33
36
  return @ssh_name
34
37
  end
38
+
39
+ def get_count(config)
40
+ multiple = config['multiple-hosts']
41
+ return 1 if multiple.nil?
42
+
43
+ [ multiple['regexes'], multiple['counts'] ].transpose.each do |re, n|
44
+ return n if ssh_name.match re
45
+ end
46
+
47
+ return 1
48
+ end
35
49
  end
36
50
  end
@@ -1,4 +1,18 @@
1
1
  module TmuxConnector
2
+ class Pane
3
+ attr_reader :host
4
+ attr_reader :name
5
+ attr_reader :ordinal
6
+
7
+ def initialize(host, ordinal)
8
+ @host = host
9
+ @ordinal = ordinal
10
+
11
+ @name = host.display_name
12
+ @name += "##{ ordinal }" if ordinal > 1
13
+ end
14
+ end
15
+
2
16
  class Layout
3
17
  attr_reader :groups
4
18
  attr_reader :merge_rules
@@ -46,7 +60,12 @@ module TmuxConnector
46
60
  n = config['tmux']['max-panes']
47
61
  end
48
62
 
49
- hosts.each_slice(n).with_index do |arr, i|
63
+ panes = hosts.reduce([]) do |acc, h|
64
+ h.count.times { |i| acc << Pane.new(h, i + 1) }
65
+ acc
66
+ end
67
+
68
+ panes.each_slice(n).with_index do |arr, i|
50
69
  window = {
51
70
  name: "#{ group_name }##{ i + 1 }",
52
71
  group_name: group_name,
@@ -36,12 +36,16 @@ module TmuxConnector
36
36
  execute
37
37
  end
38
38
 
39
- def send_commands(send_commands, server_regex, group_regex, verbose)
39
+ def send_commands(send_commands, server_regex, group_regex, window, verbose)
40
40
  count = 0
41
- each_pane do |window_index, pane_index, host|
42
- matches = server_regex.nil? && group_regex.nil?
43
- matches ||= !server_regex.nil? && host.ssh_name.match(server_regex)
44
- matches ||= !group_regex.nil? && session.merge_rules[host.group_id].match(group_regex)
41
+ each_pane do |window_index, pane_index, pane|
42
+ if window
43
+ matches = window == window_index.to_s
44
+ else
45
+ matches = server_regex.nil? && group_regex.nil?
46
+ matches ||= !server_regex.nil? && pane.host.ssh_name.match(server_regex)
47
+ matches ||= !group_regex.nil? && session.merge_rules[pane.host.group_id].match(group_regex)
48
+ end
45
49
 
46
50
  if matches
47
51
  system("tmux send-keys -t #{ name }:#{ window_index }.#{ pane_index } '#{ send_commands }' C-m")
@@ -88,7 +92,7 @@ HERE
88
92
  size = (100.0 * (w[:panes].size - pi - 1) / (w[:panes].size - pi)).round
89
93
 
90
94
  commands << "tmux split-window -p #{ size } -t #{ name }:#{ wi }" unless pi == 0
91
- commands << tmux_set_title_cmd(p.display_name, wi, pi)
95
+ commands << tmux_set_title_cmd(p.name, wi, pi)
92
96
  end
93
97
 
94
98
  commands << "tmux select-layout -t #{ name }:#{ wi } #{ w[:tmux] } &> /dev/null"
@@ -101,7 +105,7 @@ HERE
101
105
  end
102
106
 
103
107
  def clear_panes()
104
- each_pane do |window_index, pane_index|
108
+ each_pane do |window_index, pane_index, _|
105
109
  commands << "tmux send-keys -t #{ name }:#{ window_index }.#{ pane_index } clear C-m"
106
110
  end
107
111
  end
@@ -109,8 +113,8 @@ HERE
109
113
  def connect()
110
114
  ssh_config_path = File.expand_path session.args['--ssh-config']
111
115
 
112
- each_pane do |window_index, pane_index, host|
113
- ssh_command = "ssh -F #{ ssh_config_path } #{ host.ssh_name }"
116
+ each_pane do |window_index, pane_index, pane|
117
+ ssh_command = "ssh -F #{ ssh_config_path } #{ pane.host.ssh_name }"
114
118
  commands << "tmux send-keys -t #{ name }:#{ window_index }.#{ pane_index } '#{ ssh_command }' C-m"
115
119
  end
116
120
  end
@@ -126,14 +130,14 @@ HERE
126
130
  def each_pane(&block)
127
131
  session.windows.each_with_index do |window, window_index|
128
132
  if window[:tmux]
129
- window[:panes].each_with_index do |host, pane_index|
130
- yield(window_index, pane_index, host)
133
+ window[:panes].each_with_index do |pane, pane_index|
134
+ yield(window_index, pane_index, pane)
131
135
  end
132
136
  else
133
137
  pane_index = 0
134
138
  window[:panes].each do |g|
135
- g.each do |host|
136
- yield(window_index, pane_index, host)
139
+ g.each do |pane|
140
+ yield(window_index, pane_index, pane)
137
141
  pane_index += 1
138
142
  end
139
143
  end
@@ -144,25 +148,25 @@ HERE
144
148
  def create_custom_layout(window, window_index)
145
149
  direction = (window[:flow] == 'horizontal') ? ['-h', '-v'] : ['-v', '-h']
146
150
 
147
- pane_index = 0
151
+ in_window_index = 0
148
152
  window[:panes].each_with_index do |group, group_index|
149
- commands << "tmux select-pane -t #{ name }:#{ window_index }.#{ pane_index }"
153
+ commands << "tmux select-pane -t #{ name }:#{ window_index }.#{ in_window_index }"
150
154
 
151
- # create pane in a next row ahead of time so pane indexes match hosts
155
+ # create tmux-pane in a next row ahead of time so tmux-pane indexes match host-panes
152
156
  if group_index < window[:panes].size - 1
153
157
  size = (100.0 * (window[:panes].size - group_index - 1) / (window[:panes].size - group_index)).round
154
158
  commands << "tmux split-window #{ direction[1] } -p #{ size } -t #{ name }:#{ window_index }"
155
- display_name = window[:panes][group_index + 1][0].display_name
156
- commands << tmux_set_title_cmd(display_name, window_index, -1)
157
- commands << "tmux select-pane -t #{ name }:#{ window_index }.#{ pane_index }"
159
+ pane_name = window[:panes][group_index + 1][0].name
160
+ commands << tmux_set_title_cmd(pane_name, window_index, -1)
161
+ commands << "tmux select-pane -t #{ name }:#{ window_index }.#{ in_window_index }"
158
162
  end
159
163
 
160
- group.each_with_index do |host, host_index|
161
- size = (100.0 * (group.size - host_index) / (group.size - host_index + 1)).round
162
- commands << "tmux split-window #{ direction[0] } -p #{ size } -t #{ name }:#{ window_index }" unless host_index == 0
163
- commands << tmux_set_title_cmd(host.display_name, window_index, pane_index)
164
+ group.each_with_index do |pane, pane_index|
165
+ size = (100.0 * (group.size - pane_index) / (group.size - pane_index + 1)).round
166
+ commands << "tmux split-window #{ direction[0] } -p #{ size } -t #{ name }:#{ window_index }" unless pane_index == 0
167
+ commands << tmux_set_title_cmd(pane.name, window_index, in_window_index)
164
168
 
165
- pane_index += 1
169
+ in_window_index += 1
166
170
  end
167
171
  end
168
172
  end
@@ -1,3 +1,3 @@
1
1
  module TmuxConnector
2
- VERSION = "0.0.4"
2
+ VERSION = "0.8.5"
3
3
  end
@@ -18,8 +18,9 @@ Usage:
18
18
  tcon delete (<session-name> | --all)
19
19
  tcon list
20
20
  tcon send <session-name> (<command> | --command-file=<file>)
21
- [--server-filter=<regex>] [--group-filter=<regex>]
22
- [--filter=<regex>] [--verbose]
21
+ [ --server-filter=<regex> | --group-filter=<regex>
22
+ | --filter=<regex> | --window=<index> ]
23
+ [--verbose]
23
24
  tcon --help
24
25
  tcon --version
25
26
 
@@ -29,6 +30,7 @@ Options:
29
30
  <session-name> Name that identifies the session. Must be unique.
30
31
  <command> Command to be executed on remote server[s].
31
32
  <regex> String that represents valid Ruby regex.
33
+ <index> 0-based index.
32
34
  -s --ssh-config=file Path to ssh config file [default: ~/.ssh/config].
33
35
  -n --session-name=name Name of the session to be used in the tcon command.
34
36
  -p --purpose=description Description of session's purpose.
@@ -40,6 +42,7 @@ Options:
40
42
  -r --filter=regex Filter to select a subset of the servers via
41
43
  host names or group membership.
42
44
  Combines --server-filter and --group-filter.
45
+ -w --window=index Select a window via (0-based) index.
43
46
  -c --command-file=file File containing the list of commands to be
44
47
  executed on remote server[s].
45
48
  -v --verbose Report how many servers were affected by the send
data/spec/config_spec.rb CHANGED
@@ -46,5 +46,9 @@ describe "Configuration file" do
46
46
  it_should_behave_like "config test", 'layout-group'
47
47
  it_should_behave_like "config test", 'layout-both'
48
48
  end
49
+
50
+ describe "multiple hosts" do
51
+ it_should_behave_like "config test", 'multiple-hosts'
52
+ end
49
53
  end
50
54
  end
@@ -113,3 +113,19 @@ layout-both:
113
113
  max-horizontal: 2
114
114
  max-vertical: 2
115
115
  panes-flow: vertical
116
+
117
+ multiple-hosts:
118
+ input:
119
+ <<: *input-min
120
+ multiple-hosts:
121
+ regexes:
122
+ - !ruby-regexp '(loadbalancer)-'
123
+ - !ruby-regexp '(database)-'
124
+ counts: [2, 3]
125
+ expected:
126
+ <<: *expected-min
127
+ multiple-hosts:
128
+ regexes:
129
+ - !ruby-regexp '(loadbalancer)-'
130
+ - !ruby-regexp '(database)-'
131
+ counts: [2, 3]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tmux-connector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.8.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-01 00:00:00.000000000 Z
12
+ date: 2013-06-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: docopt