tmux-connector 0.0.4 → 0.8.5

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/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