collins_shell 0.2.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.pryrc +1 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +18 -0
  4. data/Gemfile.lock +59 -0
  5. data/README.md +335 -0
  6. data/Rakefile +64 -0
  7. data/VERSION +1 -0
  8. data/bin/collins-shell +36 -0
  9. data/collins_shell.gemspec +95 -0
  10. data/lib/collins_shell.rb +3 -0
  11. data/lib/collins_shell/asset.rb +198 -0
  12. data/lib/collins_shell/cli.rb +185 -0
  13. data/lib/collins_shell/console.rb +129 -0
  14. data/lib/collins_shell/console/asset.rb +127 -0
  15. data/lib/collins_shell/console/cache.rb +17 -0
  16. data/lib/collins_shell/console/command_helpers.rb +131 -0
  17. data/lib/collins_shell/console/commands.rb +28 -0
  18. data/lib/collins_shell/console/commands/cat.rb +123 -0
  19. data/lib/collins_shell/console/commands/cd.rb +61 -0
  20. data/lib/collins_shell/console/commands/io.rb +26 -0
  21. data/lib/collins_shell/console/commands/iterators.rb +190 -0
  22. data/lib/collins_shell/console/commands/tail.rb +178 -0
  23. data/lib/collins_shell/console/commands/versions.rb +42 -0
  24. data/lib/collins_shell/console/filesystem.rb +121 -0
  25. data/lib/collins_shell/console/options_helpers.rb +8 -0
  26. data/lib/collins_shell/errors.rb +7 -0
  27. data/lib/collins_shell/ip_address.rb +144 -0
  28. data/lib/collins_shell/ipmi.rb +67 -0
  29. data/lib/collins_shell/monkeypatch.rb +60 -0
  30. data/lib/collins_shell/provision.rb +152 -0
  31. data/lib/collins_shell/state.rb +98 -0
  32. data/lib/collins_shell/tag.rb +41 -0
  33. data/lib/collins_shell/thor.rb +209 -0
  34. data/lib/collins_shell/util.rb +120 -0
  35. data/lib/collins_shell/util/asset_printer.rb +265 -0
  36. data/lib/collins_shell/util/asset_stache.rb +32 -0
  37. data/lib/collins_shell/util/log_printer.rb +187 -0
  38. data/lib/collins_shell/util/printer_util.rb +28 -0
  39. 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