collins_shell 0.2.14
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/.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
|