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.
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