tmux-connector 0.8.6 → 0.9.7
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 +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
|