collins_shell 0.2.14
Sign up to get free protection for your applications and to get access to all the features.
- data/.pryrc +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +59 -0
- data/README.md +335 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/bin/collins-shell +36 -0
- data/collins_shell.gemspec +95 -0
- data/lib/collins_shell.rb +3 -0
- data/lib/collins_shell/asset.rb +198 -0
- data/lib/collins_shell/cli.rb +185 -0
- data/lib/collins_shell/console.rb +129 -0
- data/lib/collins_shell/console/asset.rb +127 -0
- data/lib/collins_shell/console/cache.rb +17 -0
- data/lib/collins_shell/console/command_helpers.rb +131 -0
- data/lib/collins_shell/console/commands.rb +28 -0
- data/lib/collins_shell/console/commands/cat.rb +123 -0
- data/lib/collins_shell/console/commands/cd.rb +61 -0
- data/lib/collins_shell/console/commands/io.rb +26 -0
- data/lib/collins_shell/console/commands/iterators.rb +190 -0
- data/lib/collins_shell/console/commands/tail.rb +178 -0
- data/lib/collins_shell/console/commands/versions.rb +42 -0
- data/lib/collins_shell/console/filesystem.rb +121 -0
- data/lib/collins_shell/console/options_helpers.rb +8 -0
- data/lib/collins_shell/errors.rb +7 -0
- data/lib/collins_shell/ip_address.rb +144 -0
- data/lib/collins_shell/ipmi.rb +67 -0
- data/lib/collins_shell/monkeypatch.rb +60 -0
- data/lib/collins_shell/provision.rb +152 -0
- data/lib/collins_shell/state.rb +98 -0
- data/lib/collins_shell/tag.rb +41 -0
- data/lib/collins_shell/thor.rb +209 -0
- data/lib/collins_shell/util.rb +120 -0
- data/lib/collins_shell/util/asset_printer.rb +265 -0
- data/lib/collins_shell/util/asset_stache.rb +32 -0
- data/lib/collins_shell/util/log_printer.rb +187 -0
- data/lib/collins_shell/util/printer_util.rb +28 -0
- metadata +200 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
require 'collins_shell/console/filesystem'
|
4
|
+
|
5
|
+
module CollinsShell; module Console; module Commands
|
6
|
+
Cd = Pry::CommandSet.new do
|
7
|
+
create_command "cd" do
|
8
|
+
include CollinsShell::Console::CommandHelpers
|
9
|
+
|
10
|
+
description "Change context to an asset or path"
|
11
|
+
group "Context"
|
12
|
+
|
13
|
+
banner <<-BANNER
|
14
|
+
Usage: cd /<tag>
|
15
|
+
cd <tag>
|
16
|
+
cd /path
|
17
|
+
cd path
|
18
|
+
|
19
|
+
Changes context to the specified tag or path.
|
20
|
+
Further commands apply to the asset or path.
|
21
|
+
BANNER
|
22
|
+
def process
|
23
|
+
path = arg_string.split(/\//)
|
24
|
+
stack = _pry_.binding_stack.dup
|
25
|
+
|
26
|
+
stack = [stack.first] if path.empty?
|
27
|
+
|
28
|
+
path.each do |context|
|
29
|
+
begin
|
30
|
+
case context.chomp
|
31
|
+
when ""
|
32
|
+
stack = [stack.first]
|
33
|
+
when "."
|
34
|
+
next
|
35
|
+
when ".."
|
36
|
+
unless stack.size == 1
|
37
|
+
stack.pop
|
38
|
+
end
|
39
|
+
else
|
40
|
+
# We know we start out with the root fs being the context
|
41
|
+
fs_parent = stack.last.eval('self')
|
42
|
+
# Pushing the path value onto the parent gives us back a child
|
43
|
+
begin
|
44
|
+
fs_child = fs_parent.push(context)
|
45
|
+
# We can't have assets as children of assets, replace current asset with new one
|
46
|
+
output.puts fs_child.path
|
47
|
+
stack.push(Pry.binding_for(fs_child))
|
48
|
+
rescue Exception => e
|
49
|
+
output.puts("#{text.bold('Could not change context:')} #{e}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
rescue Exception => e
|
53
|
+
output.puts e.backtrace
|
54
|
+
output.puts("Got exception: #{e}")
|
55
|
+
end
|
56
|
+
end # path.each
|
57
|
+
_pry_.binding_stack = stack
|
58
|
+
end # def process
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end; end; end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CollinsShell; module Console; module Commands
|
2
|
+
Io = Pry::CommandSet.new do
|
3
|
+
create_command "wc", "Pipe results to wc to get the number of assets/lines" do
|
4
|
+
command_options :keep_retval => true
|
5
|
+
|
6
|
+
group "I/O"
|
7
|
+
|
8
|
+
def process
|
9
|
+
args.size
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
create_command "more", "Ensure results are paginated" do
|
14
|
+
command_options :keep_retval => true
|
15
|
+
|
16
|
+
group "I/O"
|
17
|
+
|
18
|
+
def process
|
19
|
+
value = args.map {|a| a.to_s}.join("\n")
|
20
|
+
render_output value, opts
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end; end; end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'collins_shell/util/asset_stache'
|
3
|
+
|
4
|
+
module CollinsShell; module Console; module Commands
|
5
|
+
Iterators = Pry::CommandSet.new do
|
6
|
+
|
7
|
+
create_command "ls", "Find assets according to specific criteria" do
|
8
|
+
include CollinsShell::Console::OptionsHelpers
|
9
|
+
include CollinsShell::Console::CommandHelpers
|
10
|
+
|
11
|
+
command_options :keep_retval => true, :takes_block => true
|
12
|
+
group "Context"
|
13
|
+
|
14
|
+
# --return (after printing also return value) and --flood (disable paging)
|
15
|
+
def options opt
|
16
|
+
opt.banner <<-BANNER
|
17
|
+
Usage: ls [-d|--delimiter] [-g|--grep] [-F--format] [-f|--flood] [path]
|
18
|
+
|
19
|
+
ls provides you information based on your current context (either a path or an asset)
|
20
|
+
|
21
|
+
When in an asset, ls will show you available commands
|
22
|
+
When in a path, ls will show you either assets that match the path or values appropriate for the path
|
23
|
+
When in no context, will show you available tags (to use in a path)
|
24
|
+
|
25
|
+
Examples:
|
26
|
+
ls /HOSTNAME/.*dev.*
|
27
|
+
ls /HOSTNAME/.*dev.* --format='{{hostname}} {{status}} {{tag}}' --grep=blake
|
28
|
+
|
29
|
+
You can customize the default format used by ls (when applied to assets) by creating a ~/.pryrc file with contents like:
|
30
|
+
|
31
|
+
Pry.config.default_asset_format = '{{tag}} {{hostname}} {{status}}'
|
32
|
+
|
33
|
+
Where the rhs of the default_asset_format is the format you want to use
|
34
|
+
BANNER
|
35
|
+
pager_options opt
|
36
|
+
opt.on :d, "delimiter", "Delimiter for use with --format, defaults to \\n", :argument => true
|
37
|
+
opt.on :r, "return", "Return values as well as outputting them"
|
38
|
+
opt.on :g, "grep", "A regular expression to provide to results", :argument => true, :optional => false
|
39
|
+
opt.on :F, "format", "Provide a format for output", :argument => true, :optional => false
|
40
|
+
end
|
41
|
+
|
42
|
+
def process
|
43
|
+
# If a pipe is being used, grab the first pipe command
|
44
|
+
first_after_pipe = arg_string.split('|', 2)[1].to_s.split(' ').first
|
45
|
+
# Take a /PATH/FORMAT and convert it to an array
|
46
|
+
path = args.first.to_s.split(/\//).reject{|s| s.empty? || s == "|" }
|
47
|
+
# Account for Filesystem context
|
48
|
+
stack = _pry_.binding_stack.dup
|
49
|
+
fs_node = stack.last.eval('self')
|
50
|
+
# Are we just getting a naked query?
|
51
|
+
display_commands = (fs_node.asset? && args.empty?)
|
52
|
+
# Doing an ls in the stack, relative
|
53
|
+
if not fs_node.root? and not arg_string.start_with?('/') then
|
54
|
+
path.each do |context|
|
55
|
+
case context.chomp
|
56
|
+
when "", "." # blank is empty root node, . is self. Do nothing
|
57
|
+
next
|
58
|
+
when ".." # Up one level
|
59
|
+
fs_node = fs_node.pop
|
60
|
+
else
|
61
|
+
begin
|
62
|
+
fs_node = fs_node.push(context)
|
63
|
+
rescue Exception => e
|
64
|
+
output.puts("#{text.bold('Could not check context:')} #{e}")
|
65
|
+
raise e
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
path = fs_node.stack
|
70
|
+
end
|
71
|
+
if command_block || get_format then
|
72
|
+
details = true
|
73
|
+
else
|
74
|
+
details = false
|
75
|
+
end
|
76
|
+
# Should we just display commands?
|
77
|
+
if display_commands then
|
78
|
+
output.puts("Available commands:")
|
79
|
+
process_values fs_node.available_commands, 8
|
80
|
+
output.puts()
|
81
|
+
# If we have nothing, grab all tags
|
82
|
+
elsif path.size == 0 then
|
83
|
+
value = get_all_tags
|
84
|
+
process_values value
|
85
|
+
# If we have an odd number, the last one is a tag so grab the values
|
86
|
+
# for that tag
|
87
|
+
elsif path.size % 2 == 1 then
|
88
|
+
if virtual_tags.include?(path.last) then
|
89
|
+
value = ["virtual tags have no values"]
|
90
|
+
else
|
91
|
+
value = get_tag_values(path.last) || []
|
92
|
+
end
|
93
|
+
process_values value
|
94
|
+
# If we have an even number, grab assets that have these tag/values
|
95
|
+
else
|
96
|
+
assets = find_assets(path, details)
|
97
|
+
process_values assets, 6
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Handle faking unix pipes to commands, block invocation, and printing
|
102
|
+
def process_values init_value, size = 4
|
103
|
+
should_return = opts.return?
|
104
|
+
cmd = arg_string.split('|',2)
|
105
|
+
|
106
|
+
if cmd.size == 2 then
|
107
|
+
cmds = cmd.last.split('|').map{|s| s.strip}
|
108
|
+
else
|
109
|
+
cmds = []
|
110
|
+
end
|
111
|
+
|
112
|
+
formatted = init_value
|
113
|
+
formatter = nil
|
114
|
+
if opts.format? then
|
115
|
+
formatter = opts[:format]
|
116
|
+
elsif Pry.config.default_asset_format then
|
117
|
+
if formatted.any? {|o| o.is_a?(Collins::Asset)} then
|
118
|
+
formatter = Pry.config.default_asset_format
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
if formatter then
|
123
|
+
formatted = formatted.map do |v|
|
124
|
+
a = CollinsShell::Util::AssetStache.new(v)
|
125
|
+
a.render formatter
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
grep_regex = Regexp.new(opts[:g] || ".")
|
130
|
+
if formatted.respond_to?(:grep) then
|
131
|
+
formatted = formatted.grep(grep_regex)
|
132
|
+
end
|
133
|
+
|
134
|
+
if cmds.size > 0 then
|
135
|
+
results = formatted
|
136
|
+
while cmd = cmds.shift do
|
137
|
+
results = run(cmd, results).retval
|
138
|
+
end
|
139
|
+
value = results
|
140
|
+
value_s = results.to_s
|
141
|
+
else
|
142
|
+
value = formatted
|
143
|
+
if formatter then
|
144
|
+
delim = Collins::Option(opts[:delimiter]).get_or_else("\n")
|
145
|
+
value_s = value.join(delim)
|
146
|
+
else
|
147
|
+
value_s = format_values(value, size)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
if command_block then
|
151
|
+
command_block.call(value)
|
152
|
+
else
|
153
|
+
# Handle commands like 'more' that should not return a value
|
154
|
+
if not value.nil? and not value_s.empty? then
|
155
|
+
render_output value_s, opts
|
156
|
+
end
|
157
|
+
value if should_return
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_format
|
162
|
+
if opts.format? then
|
163
|
+
opts[:format]
|
164
|
+
elsif Pry.config.default_asset_format then
|
165
|
+
Pry.config.default_asset_format
|
166
|
+
else
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def format_values array, width = 4
|
172
|
+
return "" if array.empty?
|
173
|
+
t = Terminal::Table.new
|
174
|
+
t.style = {:border_x => "", :border_y => "", :border_i => ""}
|
175
|
+
line = []
|
176
|
+
array.each do |o|
|
177
|
+
line << o
|
178
|
+
if line.size >= width then
|
179
|
+
t << line
|
180
|
+
line = []
|
181
|
+
end
|
182
|
+
end
|
183
|
+
if not line.empty? then
|
184
|
+
t << line
|
185
|
+
end
|
186
|
+
t.to_s
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end # CommandSet
|
190
|
+
end; end; end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module CollinsShell; module Console; module Commands
|
5
|
+
Tail = Pry::CommandSet.new do
|
6
|
+
create_command "tail", "Print the last lines of a log file" do
|
7
|
+
|
8
|
+
include CollinsShell::Console::CommandHelpers
|
9
|
+
|
10
|
+
group "I/O"
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@lines = 10
|
14
|
+
@follow = false
|
15
|
+
@got_flags = false
|
16
|
+
@sleep = 10
|
17
|
+
@test = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def integer_arg(opt, short, long, description, &block)
|
21
|
+
opt.on short, long, description, :argument => true, :as => :integer do |i|
|
22
|
+
if i < 1 then
|
23
|
+
raise ArgumentError.new("Missing a required argument for --#{long}")
|
24
|
+
end
|
25
|
+
@got_flags = true
|
26
|
+
block.call(i)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def options(opt)
|
31
|
+
opt.banner <<-BANNER
|
32
|
+
Usage: tail [OPTION] [FILE]
|
33
|
+
tail --follow [FILE]
|
34
|
+
tail --lines N [FILE]
|
35
|
+
tail --sleep N [FILE]
|
36
|
+
|
37
|
+
Tail the specified log.
|
38
|
+
|
39
|
+
If you are in an asset context, tail does not require the asset tag. In an asset context you can do:
|
40
|
+
tail # display this help
|
41
|
+
tail -n 10 # same as tail -n 10 /var/log/messages
|
42
|
+
tail -f # same as tail -f /var/log/messages
|
43
|
+
tail [-n|-f] /var/log/SEVERITY # tail /var/log/SEVERITY
|
44
|
+
tail /var/log/messages # last 10 messages
|
45
|
+
tail -n 10 /var/log/messages # last 10 messages
|
46
|
+
tail -f /var/log/messages # follow log messages
|
47
|
+
If you are not in an asset context, log requires the tag of the asset you want to display.
|
48
|
+
tail # display this help
|
49
|
+
tail [-n|-f] asset-tag # same as tail in asset context
|
50
|
+
tail [-n|-f] /var/log/messages # show logs for all assets (requires permission)
|
51
|
+
tail [-n|-f] /var/log/assets/asset-tag # same as tail in asset context
|
52
|
+
tail [-n|-f] /var/log/hosts/hostname # same as tail in asset context, but finds host
|
53
|
+
BANNER
|
54
|
+
opt.on :f, "follow", "Output appended data as file grows" do
|
55
|
+
@got_flags = true
|
56
|
+
@follow = true
|
57
|
+
end
|
58
|
+
opt.on :t, :test, "Show logs that have already been seen" do
|
59
|
+
@got_flags = true
|
60
|
+
@test = true
|
61
|
+
end
|
62
|
+
integer_arg(opt, :n, :lines, "Show the last n lines of the file") do |i|
|
63
|
+
@lines = i
|
64
|
+
end
|
65
|
+
integer_arg(opt, :s, :sleep, "Sleep this many seconds between pools") do |i|
|
66
|
+
@sleep = i
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def process
|
71
|
+
stack = _pry_.binding_stack
|
72
|
+
if args.first.to_s.start_with?('/var/log/') then
|
73
|
+
display_logs args.first, stack
|
74
|
+
else
|
75
|
+
tag = args.first.to_s.strip
|
76
|
+
if tag.empty? then
|
77
|
+
if asset_context?(stack) and @got_flags then
|
78
|
+
display_asset_logs tag_from_stack(stack), 'messages'
|
79
|
+
else
|
80
|
+
run "help", "tail"
|
81
|
+
return
|
82
|
+
end
|
83
|
+
else
|
84
|
+
display_asset_logs tag, 'messages'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Given a logfile specification, parse it and render it
|
90
|
+
def display_logs logfile, stack
|
91
|
+
rejects = ['var', 'log']
|
92
|
+
paths = logfile.split('/').reject{|s| s.empty? || rejects.include?(s)}
|
93
|
+
if paths.size == 2 then
|
94
|
+
display_sub_logs paths[0], paths[1]
|
95
|
+
elsif paths.size == 1 then
|
96
|
+
display_asset_logs (tag_from_stack(stack) || 'all'), paths[0]
|
97
|
+
else
|
98
|
+
run "help", "tag"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Render logs of the type `/var/log/assets/TAG` or `/var/log/hosts/HOSTNAME`
|
103
|
+
def display_sub_logs arg1, arg2
|
104
|
+
if arg1 == 'assets' then
|
105
|
+
display_asset_logs arg2, 'messages'
|
106
|
+
elsif arg1 == 'hosts' then
|
107
|
+
asset = find_one_asset(['HOSTNAME', arg2])
|
108
|
+
display_asset_logs asset, 'messages'
|
109
|
+
else
|
110
|
+
output.puts "#{text.bold('Invalid log type:')} Only 'assets' or 'hosts' are valid, found '#{arg1}'"
|
111
|
+
output.puts
|
112
|
+
run "help", "tag"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Render logs for an asset according to type, where type is 'messages' (all) or a severity
|
117
|
+
def display_asset_logs asset_tag, type
|
118
|
+
begin
|
119
|
+
asset = Collins::Util.get_asset_or_tag(asset_tag).tag
|
120
|
+
rescue => e
|
121
|
+
output.puts "#{text.bold('Invalid asset:')} '#{asset_tag}' not valid - #{e}"
|
122
|
+
return
|
123
|
+
end
|
124
|
+
severity = Collins::Api::Logging::Severity
|
125
|
+
severity_level = severity.value_of type
|
126
|
+
if type == 'messages' then
|
127
|
+
get_and_print_logs asset_tag
|
128
|
+
elsif not severity_level.nil? then
|
129
|
+
get_and_print_logs asset_tag, severity_level
|
130
|
+
else
|
131
|
+
message = "Only '/var/log/messages' or '/var/log/SEVERITY' are valid here"
|
132
|
+
sevs = severity.to_a.join(', ')
|
133
|
+
output.puts "#{text.bold('Invalid path specified:')}: #{message}"
|
134
|
+
output.puts "Valid severity levels are: #{sevs}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def all? tag
|
139
|
+
tag.to_s.downcase == 'all'
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_and_print_logs asset_tag, filter = nil
|
143
|
+
size = @lines
|
144
|
+
if not @follow then
|
145
|
+
logs = call_collins "logs(#{asset_tag})" do |client|
|
146
|
+
client.logs asset_tag, :sort => "DESC", :size => size, :filter => filter, :all_tag => 'all'
|
147
|
+
end.reverse
|
148
|
+
printer = CollinsShell::LogPrinter.new asset_tag, :logs => logs, :all_tag => 'all'
|
149
|
+
output.puts printer.to_s
|
150
|
+
else
|
151
|
+
seen = Set.new
|
152
|
+
printer = CollinsShell::LogPrinter.new asset_tag, :streaming => true, :all_tag => 'all'
|
153
|
+
while true do
|
154
|
+
logs = call_collins "logs(#{asset_tag})" do |client|
|
155
|
+
client.logs asset_tag, :sort => "DESC", :size => size, :filter => filter, :all_tag => 'all'
|
156
|
+
end.reverse
|
157
|
+
unseen_logs = select_logs_for_follow seen, logs
|
158
|
+
if unseen_logs.size > 0 then
|
159
|
+
output.puts printer.render(unseen_logs)
|
160
|
+
end
|
161
|
+
sleep(@sleep)
|
162
|
+
end # while true
|
163
|
+
end # else for follow
|
164
|
+
end
|
165
|
+
|
166
|
+
def select_logs_for_follow seen_set, logs
|
167
|
+
if @test then
|
168
|
+
logs
|
169
|
+
else
|
170
|
+
unseen_logs = logs.reject {|l| seen_set.include?(l.to_s.hash)}
|
171
|
+
unseen_logs.each {|l| seen_set << l.to_s.hash}
|
172
|
+
unseen_logs
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end # create_command
|
177
|
+
end # CommandSet
|
178
|
+
end; end; end
|