tmux-connector 0.8.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +72 -9
- data/lib/tmux-connector.rb +5 -2
- data/lib/tmux-connector/commands/send.rb +79 -3
- data/lib/tmux-connector/commands/start.rb +15 -13
- data/lib/tmux-connector/host.rb +2 -1
- data/lib/tmux-connector/tmux_handler.rb +19 -4
- data/lib/tmux-connector/version.rb +1 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Manage multiple servers using SSH and [tmux].
|
|
11
11
|
* multiple connections to individual servers possible
|
12
12
|
* sessions can be persisted (actually recreated) after computer restarts
|
13
13
|
- they are lost only if you delete them explicitly
|
14
|
-
* panes not associated with hosts
|
14
|
+
* panes not associated with hosts available
|
15
15
|
|
16
16
|
|
17
17
|
## Quick tease
|
@@ -28,20 +28,24 @@ Manage multiple servers using SSH and [tmux].
|
|
28
28
|
|
29
29
|
- send to all servers (in 'staging' session) `sudo su` command
|
30
30
|
|
31
|
-
`tcon send staging
|
31
|
+
`tcon send staging -v -g 'lbs' 'top'`
|
32
32
|
|
33
|
-
- send `top` command to all loadbalancing nodes in 'staging' session
|
33
|
+
- send `top` command to all loadbalancing nodes in 'staging' session and report
|
34
|
+
the number of affected nodes
|
34
35
|
|
35
|
-
`tcon send production 'tail -f /var/log/syslog'
|
36
|
+
`tcon send production -f 'worker' 'tail -f /var/log/syslog'`
|
36
37
|
|
37
|
-
- send `tail -f /var/log/syslog` command to all
|
38
|
+
- send `tail -f /var/log/syslog` command to all worker nodes in 'production'
|
38
39
|
session
|
39
40
|
|
40
|
-
`tcon send production -f '
|
41
|
+
`tcon send production -f 'worker :: <,3]; 7; <9,13>' 'C-c'`
|
41
42
|
|
42
|
-
- send `Ctrl-C` to
|
43
|
+
- send `Ctrl-C` to some worker nodes in 'production' session, if executed
|
43
44
|
after the `tail -f /var/log/syslog` command, it will exit the `tail -f`
|
44
45
|
command
|
46
|
+
- if there are worker nodes numbered from 1 to 20 (e.g. staging-worker-1,
|
47
|
+
staging-worker-2, etc.) this command affects worker nodes: 1, 2, 3, 7, 10,
|
48
|
+
11 and 12
|
45
49
|
|
46
50
|
`tcon resume s#3`
|
47
51
|
|
@@ -65,7 +69,7 @@ Usage:
|
|
65
69
|
tcon delete (<session-name> | --all)
|
66
70
|
tcon list
|
67
71
|
tcon send <session-name> (<command> | --command-file=<file>)
|
68
|
-
[ --server-filter=<
|
72
|
+
[ --server-filter=<filter> | --group-filter=<regex>
|
69
73
|
| --filter=<regex> | --window=<index> ]
|
70
74
|
[--verbose]
|
71
75
|
tcon --help
|
@@ -78,11 +82,14 @@ Options:
|
|
78
82
|
<command> Command to be executed on remote server[s].
|
79
83
|
<regex> String that represents valid Ruby regex.
|
80
84
|
<index> 0-based index.
|
85
|
+
<filter> Filter consisting of a valid ruby regex and
|
86
|
+
optionally of a special predicate.
|
87
|
+
For more information see README file.
|
81
88
|
-s --ssh-config=file Path to ssh config file [default: ~/.ssh/config].
|
82
89
|
-n --session-name=name Name of the session to be used in the tcon command.
|
83
90
|
-p --purpose=description Description of session's purpose.
|
84
91
|
--all Delete all existing sessions.
|
85
|
-
-f --server-filter=
|
92
|
+
-f --server-filter=filter Filter to select a subset of the servers via
|
86
93
|
host names.
|
87
94
|
-g --group-filter=regex Filter to select a subset of the servers via
|
88
95
|
group membership.
|
@@ -98,6 +105,62 @@ Options:
|
|
98
105
|
--version Show version.
|
99
106
|
~~~
|
100
107
|
|
108
|
+
### Send command
|
109
|
+
|
110
|
+
Send command sends user specified command(s) to chosen panes.
|
111
|
+
|
112
|
+
Once more, here's the command syntax:
|
113
|
+
~~~
|
114
|
+
tcon send <session-name> (<command> | --command-file=<file>)
|
115
|
+
[ --server-filter=<filter> | --group-filter=<regex>
|
116
|
+
| --filter=<regex> | --window=<index> ]
|
117
|
+
[--verbose]
|
118
|
+
~~~
|
119
|
+
where long flags can be shortened to: `-c`, `-f`, `-g`, `-r`, `-w` and `-v`.
|
120
|
+
|
121
|
+
There are 4 flags used to filter the servers (panes) that are going to receive
|
122
|
+
the command(s):
|
123
|
+
|
124
|
+
* `-f` filters the servers via host names
|
125
|
+
- accept valid ruby regex and optionally intervals specification
|
126
|
+
- syntax: `<ruby-regex>` or `<ruby-regex> :: <intervals-specification>`
|
127
|
+
* `-g` filters the servers via group names
|
128
|
+
- accepts valid ruby regex
|
129
|
+
* `-r` filters the servers via host names or group names
|
130
|
+
- accepts valid ruby regex
|
131
|
+
* `-w` filters the servers that belong to particular layout window
|
132
|
+
- accepts 0-based index
|
133
|
+
|
134
|
+
The `-f` filter can accept optional interval specification:
|
135
|
+
* interval specification operates on __'sort-by'__ part of host names (defined in
|
136
|
+
configuration file)
|
137
|
+
- intervals specification consists of one or more elements separated
|
138
|
+
by semicollon (the split regex is: `/;\s*/`)
|
139
|
+
- individual element is either an interval description or white-listed element
|
140
|
+
- interval description consists of 4 parts:
|
141
|
+
+ '[' or '<' - include or exclude the first element
|
142
|
+
+ first element (can be empty)
|
143
|
+
+ ',' (can have trailing whitespace)
|
144
|
+
+ second element (can be empty)
|
145
|
+
+ ']' or '>' - include or exclude the last element
|
146
|
+
- regex used:
|
147
|
+
`/(?<start>[\[<])(?<first>[^,]*),\s*(?<second>[^\]>]*)(?<end>[\]>])/`
|
148
|
+
- examples:
|
149
|
+
+ `2; 4` - 2, 4
|
150
|
+
+ `[1,3]` - 1, 2, 3
|
151
|
+
+ `[1, 3>` - 1, 2
|
152
|
+
+ `<1, 3>` - 2
|
153
|
+
+ `<,5>` - ... 3, 4
|
154
|
+
+ `[5, >` - 5, 6, ...
|
155
|
+
|
156
|
+
Hopefully now the following examples make more sense:
|
157
|
+
|
158
|
+
~~~bash
|
159
|
+
tcon send staging 'sudo su'
|
160
|
+
tcon send staging -v -g 'lbs' 'top'
|
161
|
+
tcon send production -f 'worker' 'tail -f /var/log/syslog'
|
162
|
+
tcon send production -f 'worker :: <,3]; 7; <9,13>' 'C-c'
|
163
|
+
~~~
|
101
164
|
|
102
165
|
## Configuration
|
103
166
|
|
data/lib/tmux-connector.rb
CHANGED
@@ -18,7 +18,7 @@ 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=<
|
21
|
+
[ --server-filter=<filter> | --group-filter=<regex>
|
22
22
|
| --filter=<regex> | --window=<index> ]
|
23
23
|
[--verbose]
|
24
24
|
tcon --help
|
@@ -31,11 +31,14 @@ Options:
|
|
31
31
|
<command> Command to be executed on remote server[s].
|
32
32
|
<regex> String that represents valid Ruby regex.
|
33
33
|
<index> 0-based index.
|
34
|
+
<filter> Filter consisting of a valid ruby regex and
|
35
|
+
optionally of a special predicate.
|
36
|
+
For more information see README file.
|
34
37
|
-s --ssh-config=file Path to ssh config file [default: ~/.ssh/config].
|
35
38
|
-n --session-name=name Name of the session to be used in the tcon command.
|
36
39
|
-p --purpose=description Description of session's purpose.
|
37
40
|
--all Delete all existing sessions.
|
38
|
-
-f --server-filter=
|
41
|
+
-f --server-filter=filter Filter to select a subset of the servers via
|
39
42
|
host names.
|
40
43
|
-g --group-filter=regex Filter to select a subset of the servers via
|
41
44
|
group membership.
|
@@ -5,6 +5,7 @@ require_relative '../tmux_handler'
|
|
5
5
|
module TmuxConnector
|
6
6
|
class Send
|
7
7
|
attr_reader :commands
|
8
|
+
attr_reader :filter_predicate
|
8
9
|
attr_reader :group_filter
|
9
10
|
attr_reader :name
|
10
11
|
attr_reader :server_filter
|
@@ -18,7 +19,8 @@ module TmuxConnector
|
|
18
19
|
|
19
20
|
load_commands args
|
20
21
|
|
21
|
-
|
22
|
+
process_server_filter args['--server-filter'] rescue raise "error parsing server filter ('#{ args['--server-filter'] }')"
|
23
|
+
|
22
24
|
@group_filter = Regexp.new(args['--group-filter']) rescue nil
|
23
25
|
|
24
26
|
@server_filter ||= Regexp.new(args['--filter']) rescue nil
|
@@ -30,11 +32,16 @@ module TmuxConnector
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def run()
|
33
|
-
|
35
|
+
opts = {
|
36
|
+
verbose: verbose,
|
37
|
+
filter_predicate: filter_predicate
|
38
|
+
}
|
39
|
+
|
40
|
+
session.tmux_session.send_commands(commands, server_filter, group_filter, window, opts)
|
34
41
|
end
|
35
42
|
|
36
43
|
private
|
37
|
-
|
44
|
+
|
38
45
|
def load_commands(args)
|
39
46
|
if args['<command>']
|
40
47
|
@commands = args['<command>']
|
@@ -44,5 +51,74 @@ module TmuxConnector
|
|
44
51
|
@commands = open(file) { |f| f.read }
|
45
52
|
end
|
46
53
|
end
|
54
|
+
|
55
|
+
def process_server_filter(raw_filter)
|
56
|
+
return unless raw_filter
|
57
|
+
|
58
|
+
str_filter, str_predicate = raw_filter.split(' :: ')
|
59
|
+
|
60
|
+
@server_filter = (Regexp.new(str_filter) rescue nil)
|
61
|
+
|
62
|
+
return if str_predicate.nil?
|
63
|
+
|
64
|
+
predicate_parts = parse_predicate(str_predicate)
|
65
|
+
@filter_predicate = build_predicate(predicate_parts)
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_predicate(str_predicate)
|
69
|
+
return nil if str_predicate.nil?
|
70
|
+
|
71
|
+
predicate_delimiter = /;\s*/
|
72
|
+
interval_regex = /(?<start>[\[<])(?<first>[^,]*),\s*(?<second>[^\]>]*)(?<end>[\]>])/
|
73
|
+
|
74
|
+
return str_predicate.split(predicate_delimiter).map do |element|
|
75
|
+
m = element.match(interval_regex)
|
76
|
+
Hash[ m.names.map(&:to_sym).zip(m.captures) ] rescue element
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def build_predicate(parts)
|
81
|
+
return lambda do |sort_value|
|
82
|
+
return false if sort_value.nil?
|
83
|
+
|
84
|
+
numeric_comparison = sort_value.instance_of? Fixnum
|
85
|
+
|
86
|
+
intervals, elements = parts.partition { |e| e.instance_of? Hash }
|
87
|
+
|
88
|
+
elements = elements.map(&:to_i) if numeric_comparison
|
89
|
+
return true if elements.include? sort_value
|
90
|
+
|
91
|
+
intervals.each do |interval|
|
92
|
+
first = interval[:first]
|
93
|
+
second = interval[:second]
|
94
|
+
|
95
|
+
matches = true
|
96
|
+
|
97
|
+
unless first.empty?
|
98
|
+
first = Integer(first, 10) if numeric_comparison
|
99
|
+
|
100
|
+
if interval[:start] == '['
|
101
|
+
matches &&= sort_value >= first
|
102
|
+
elsif interval[:start] == '<'
|
103
|
+
matches &&= sort_value > first
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
unless second.empty?
|
108
|
+
second = Integer(second, 10) if numeric_comparison
|
109
|
+
|
110
|
+
if interval[:end] == ']'
|
111
|
+
matches &&= sort_value <= second
|
112
|
+
elsif interval[:end] == '>'
|
113
|
+
matches &&= sort_value < second
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
return true if matches
|
118
|
+
end
|
119
|
+
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
end
|
47
123
|
end
|
48
124
|
end
|
@@ -22,7 +22,6 @@ module TmuxConnector
|
|
22
22
|
end
|
23
23
|
raise "no hosts matching given configuration found, check your configuration file" if hosts.empty?
|
24
24
|
|
25
|
-
|
26
25
|
generate_groups
|
27
26
|
generate_merge_rules
|
28
27
|
|
@@ -47,7 +46,21 @@ module TmuxConnector
|
|
47
46
|
@groups.merge! Hash[hostless.map { |name, count| [ name, [FakeHost.new(name, count)] ] }]
|
48
47
|
end
|
49
48
|
|
50
|
-
|
49
|
+
update_sort_values!
|
50
|
+
|
51
|
+
groups.each do |_, hosts|
|
52
|
+
hosts.sort_by!(&:sort_value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_sort_values!()
|
57
|
+
groups.each do |_, hosts|
|
58
|
+
numbers_only = hosts.all? { |e| e.sort_value =~ /^[-+]?[0-9]+$/ }
|
59
|
+
|
60
|
+
if numbers_only
|
61
|
+
hosts.each { |h| h.sort_value = Integer(h.sort_value, 10) }
|
62
|
+
end
|
63
|
+
end
|
51
64
|
end
|
52
65
|
|
53
66
|
def generate_merge_rules()
|
@@ -59,16 +72,5 @@ module TmuxConnector
|
|
59
72
|
end
|
60
73
|
groups.keys.each { |e| @merge_rules[e] ||= e }
|
61
74
|
end
|
62
|
-
|
63
|
-
def sort_groups!()
|
64
|
-
groups.each do |k, v|
|
65
|
-
numbers_only = v.all? { |e| e.sort_value =~ /^[-+]?[0-9]+$/ }
|
66
|
-
if numbers_only
|
67
|
-
v.sort! { |a, b| a.sort_value.to_i <=> b.sort_value.to_i }
|
68
|
-
else
|
69
|
-
v.sort_by!(&:sort_value)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
75
|
end
|
74
76
|
end
|
data/lib/tmux-connector/host.rb
CHANGED
@@ -36,16 +36,31 @@ module TmuxConnector
|
|
36
36
|
execute
|
37
37
|
end
|
38
38
|
|
39
|
-
def send_commands(send_commands, server_regex, group_regex, window,
|
39
|
+
def send_commands(send_commands, server_regex, group_regex, window, opts={})
|
40
|
+
predicate = opts[:filter_predicate]
|
41
|
+
|
40
42
|
count = 0
|
41
43
|
each_pane do |window_index, pane_index, pane|
|
42
44
|
if window
|
43
45
|
matches = window == window_index.to_s
|
44
46
|
else
|
45
47
|
matches = server_regex.nil? && group_regex.nil?
|
46
|
-
matches ||= !server_regex.nil? && pane.host.ssh_name.match(server_regex)
|
47
48
|
matches ||= !group_regex.nil? && pane.host.group_id.match(group_regex)
|
48
49
|
matches ||= !group_regex.nil? && session.merge_rules[pane.host.group_id].match(group_regex)
|
50
|
+
|
51
|
+
unless server_regex.nil?
|
52
|
+
name_match = pane.host.ssh_name.match(server_regex)
|
53
|
+
|
54
|
+
if predicate
|
55
|
+
begin
|
56
|
+
matches ||= name_match && predicate.call(pane.host.sort_value)
|
57
|
+
rescue
|
58
|
+
raise "error while using send predicate for '#{ pane.host.display_name }'; command already sent to #{ count } servers"
|
59
|
+
end
|
60
|
+
else
|
61
|
+
matches ||= name_match
|
62
|
+
end
|
63
|
+
end
|
49
64
|
end
|
50
65
|
|
51
66
|
if matches
|
@@ -54,7 +69,7 @@ module TmuxConnector
|
|
54
69
|
end
|
55
70
|
end
|
56
71
|
|
57
|
-
puts "command sent to #{ count } server[s]" if verbose
|
72
|
+
puts "command sent to #{ count } server[s]" if opts[:verbose]
|
58
73
|
end
|
59
74
|
|
60
75
|
private
|
@@ -115,7 +130,7 @@ HERE
|
|
115
130
|
ssh_config_path = File.expand_path session.args['--ssh-config']
|
116
131
|
|
117
132
|
each_pane do |window_index, pane_index, pane|
|
118
|
-
next unless pane.host.instance_of?
|
133
|
+
next unless pane.host.instance_of? Host
|
119
134
|
|
120
135
|
ssh_command = "ssh -F #{ ssh_config_path } #{ pane.host.ssh_name }"
|
121
136
|
commands << "tmux send-keys -t #{ name }:#{ window_index }.#{ pane_index } '#{ ssh_command }' C-m"
|
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.
|
4
|
+
version: 0.9.7
|
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-
|
12
|
+
date: 2013-06-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: docopt
|