hammer_cli 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/LICENSE +5 -0
  2. data/README.md +105 -0
  3. data/bin/hammer +53 -0
  4. data/config/cli_config.template.yml +14 -0
  5. data/doc/design.png +0 -0
  6. data/doc/design.uml +24 -0
  7. data/hammer_cli_complete +13 -0
  8. data/lib/hammer_cli/abstract.rb +95 -0
  9. data/lib/hammer_cli/apipie/command.rb +75 -0
  10. data/lib/hammer_cli/apipie/options.rb +97 -0
  11. data/lib/hammer_cli/apipie/read_command.rb +58 -0
  12. data/lib/hammer_cli/apipie/resource.rb +54 -0
  13. data/lib/hammer_cli/apipie/write_command.rb +35 -0
  14. data/lib/hammer_cli/apipie.rb +3 -0
  15. data/lib/hammer_cli/autocompletion.rb +46 -0
  16. data/lib/hammer_cli/exception_handler.rb +69 -0
  17. data/lib/hammer_cli/exit_codes.rb +23 -0
  18. data/lib/hammer_cli/logger.rb +50 -0
  19. data/lib/hammer_cli/logger_watch.rb +14 -0
  20. data/lib/hammer_cli/main.rb +53 -0
  21. data/lib/hammer_cli/messages.rb +55 -0
  22. data/lib/hammer_cli/option_formatters.rb +13 -0
  23. data/lib/hammer_cli/output/adapter/abstract.rb +27 -0
  24. data/lib/hammer_cli/output/adapter/base.rb +143 -0
  25. data/lib/hammer_cli/output/adapter/silent.rb +17 -0
  26. data/lib/hammer_cli/output/adapter/table.rb +53 -0
  27. data/lib/hammer_cli/output/adapter.rb +6 -0
  28. data/lib/hammer_cli/output/definition.rb +17 -0
  29. data/lib/hammer_cli/output/dsl.rb +56 -0
  30. data/lib/hammer_cli/output/fields.rb +128 -0
  31. data/lib/hammer_cli/output/output.rb +27 -0
  32. data/lib/hammer_cli/output.rb +6 -0
  33. data/lib/hammer_cli/settings.rb +48 -0
  34. data/lib/hammer_cli/shell.rb +49 -0
  35. data/lib/hammer_cli/validator.rb +114 -0
  36. data/lib/hammer_cli/version.rb +5 -0
  37. data/lib/hammer_cli.rb +13 -0
  38. metadata +189 -0
@@ -0,0 +1,69 @@
1
+ require 'rest_client'
2
+ require 'logging'
3
+
4
+ module HammerCLI
5
+ class ExceptionHandler
6
+
7
+ def initialize options={}
8
+ @output = options[:output] or raise "Missing option output"
9
+ @logger = Logging.logger['Exception']
10
+ end
11
+
12
+ def mappings
13
+ [
14
+ [Exception, :handle_general_exception], # catch all
15
+ [RestClient::ResourceNotFound, :handle_not_found],
16
+ [RestClient::Unauthorized, :handle_unauthorized],
17
+ ]
18
+ end
19
+
20
+ def handle_exception e, options={}
21
+ @options = options
22
+ handler = mappings.reverse.find { |m| e.class.respond_to?(:"<=") ? e.class <= m[0] : false }
23
+ return send(handler[1], e) if handler
24
+ raise e
25
+ end
26
+
27
+ protected
28
+
29
+ def print_error error
30
+ error = error.join("\n") if error.kind_of? Array
31
+ @logger.error error
32
+
33
+ if @options[:heading]
34
+ @output.print_error @options[:heading], error
35
+ else
36
+ @output.print_error error
37
+ end
38
+ end
39
+
40
+ def log_full_error e
41
+ backtrace = e.backtrace || []
42
+ @logger.error "\n\n#{e.class} (#{e.message}):\n " +
43
+ backtrace.join("\n ")
44
+ "\n\n"
45
+ end
46
+
47
+ def handle_general_exception e
48
+ print_error "Error: " + e.message
49
+ log_full_error e
50
+ HammerCLI::EX_SOFTWARE
51
+ end
52
+
53
+ def handle_not_found e
54
+ print_error e.message
55
+ log_full_error e
56
+ HammerCLI::EX_NOT_FOUND
57
+ end
58
+
59
+ def handle_unauthorized e
60
+ print_error "Invalid username or password"
61
+ log_full_error e
62
+ HammerCLI::EX_UNAUTHORIZED
63
+ end
64
+
65
+ end
66
+ end
67
+
68
+
69
+
@@ -0,0 +1,23 @@
1
+ module HammerCLI
2
+ # taken from sysexits.h
3
+ EX_OK = 0 # successful termination
4
+ EX_USAGE = 64 # command line usage error
5
+ EX_DATAERR = 65 # data format error
6
+ EX_NOINPUT = 66 # cannot open input
7
+ EX_NOUSER = 67 # addressee unknown
8
+ EX_NOHOST = 68 # host name unknown
9
+ EX_UNAVAILABLE = 69 # service unavailable
10
+ EX_SOFTWARE = 70 # internal software error
11
+ EX_OSERR = 71 # system error (e.g., can't fork)
12
+ EX_OSFILE = 72 # critical OS file missing
13
+ EX_CANTCREAT = 73 # can't create (user) output file
14
+ EX_IOERR = 74 # input/output error
15
+ EX_TEMPFAIL = 75 # temp failure; user is invited to retry
16
+ EX_PROTOCOL = 76 # remote error in protocol
17
+ EX_NOPERM = 77 # permission denied
18
+ EX_CONFIG = 78 # configuration error
19
+
20
+ # non POSIX codes
21
+ EX_NOT_FOUND = 128 # resource was not found
22
+ EX_UNAUTHORIZED = 129 # authorization failed
23
+ end
@@ -0,0 +1,50 @@
1
+ require 'fileutils'
2
+ require 'logging'
3
+
4
+ module HammerCLI
5
+ module Logger
6
+
7
+ Logging.color_scheme('bright',
8
+ :levels => {
9
+ :info => :green,
10
+ :warn => :yellow,
11
+ :error => :red,
12
+ :fatal => [:white, :on_red]},
13
+ :date => :blue,
14
+ :logger => :cyan,
15
+ :line => :yellow,
16
+ :file => :yellow,
17
+ :method => :yellow)
18
+
19
+ pattern = "[%5l %d %c] %m\n"
20
+ COLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => pattern, :color_scheme => 'bright')
21
+ NOCOLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => pattern, :color_scheme => nil)
22
+ DEFAULT_LOG_DIR = '/var/log/foreman'
23
+
24
+ log_dir = File.expand_path(HammerCLI::Settings[:log_dir] || DEFAULT_LOG_DIR)
25
+ begin
26
+ FileUtils.mkdir_p(log_dir, :mode => 0750)
27
+ rescue Errno::EACCES => e
28
+ puts "No permissions to create log dir #{log_dir}"
29
+ end
30
+
31
+ logger = Logging.logger.root
32
+ filename = "#{log_dir}/hammer.log"
33
+ begin
34
+ logger.appenders = ::Logging.appenders.rolling_file('configure',
35
+ :filename => filename,
36
+ :layout => NOCOLOR_LAYOUT,
37
+ :truncate => false,
38
+ :keep => 5,
39
+ :size => HammerCLI::Settings[:log_size] || 1024*1024) # 1MB
40
+ # set owner and group (it's ignored if attribute is nil)
41
+ FileUtils.chown HammerCLI::Settings[:log_owner], HammerCLI::Settings[:log_group], filename
42
+ rescue ArgumentError => e
43
+ puts "File #{filename} not writeable, won't log anything to file!"
44
+ end
45
+
46
+ logger.level = HammerCLI::Settings[:log_level]
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,14 @@
1
+ require 'awesome_print'
2
+
3
+ module HammerCLI
4
+ module Logger
5
+ module Watch
6
+ def watch(label, obj, options={})
7
+ if debug?
8
+ options = { :plain => HammerCLI::Settings[:watch_plain], :indent => -2 }.merge(options)
9
+ debug label + "\n" + obj.ai(options)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,53 @@
1
+ require 'highline/import'
2
+
3
+ module HammerCLI
4
+
5
+ class MainCommand < AbstractCommand
6
+
7
+ option ["-v", "--verbose"], :flag, "be verbose"
8
+ option ["-c", "--config"], "CFG_FILE", "path to custom config file"
9
+
10
+ option ["-u", "--username"], "USERNAME", "username to access the remote system"
11
+ option ["-p", "--password"], "PASSWORD", "password to access the remote system"
12
+
13
+ option "--version", :flag, "show version" do
14
+ puts "hammer-%s" % HammerCLI.version
15
+ exit(HammerCLI::EX_OK)
16
+ end
17
+
18
+ option ["-P", "--ask-pass"], :flag, "Ask for password" do
19
+ context[:password] = get_password()
20
+ ''
21
+ end
22
+
23
+ option "--autocomplete", "LINE", "Get list of possible endings" do |line|
24
+ line = line.split
25
+ line.shift
26
+ endings = self.class.autocomplete(line).map { |l| l[0] }
27
+ puts endings.join(' ')
28
+ exit(HammerCLI::EX_OK)
29
+ end
30
+
31
+ def password=(password)
32
+ context[:password] = password.nil? ? ENV['FOREMAN_PASSWORD'] : password
33
+ end
34
+
35
+ def username=(username)
36
+ context[:username] = username.nil? ? ENV['FOREMAN_USERNAME'] : username
37
+ end
38
+
39
+ private
40
+
41
+ def get_password(prompt="Enter Password ")
42
+ ask(prompt) {|q| q.echo = false}
43
+ end
44
+
45
+
46
+
47
+ end
48
+
49
+ end
50
+
51
+ # extend MainCommand
52
+ require 'hammer_cli/shell'
53
+
@@ -0,0 +1,55 @@
1
+
2
+ module HammerCLI
3
+ module Messages
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def success_message_for action
10
+ self.class.success_message_for action
11
+ end
12
+
13
+ def success_message
14
+ self.class.success_message
15
+ end
16
+
17
+ def failure_message_for action
18
+ self.class.failure_message_for action
19
+ end
20
+
21
+ def failure_message
22
+ self.class.failure_message
23
+ end
24
+
25
+ def handle_exception e
26
+ exception_handler.handle_exception e, :heading => failure_message
27
+ end
28
+
29
+ module ClassMethods
30
+ def success_message_for action, msg=nil
31
+ @success_message ||= {}
32
+ @success_message[action] = msg unless msg.nil?
33
+ @success_message[action]
34
+ end
35
+
36
+ def success_message msg=nil
37
+ success_message_for :default, msg
38
+ end
39
+
40
+ def failure_message_for action, msg=nil
41
+ @failure_message ||= {}
42
+ @failure_message[action] = msg unless msg.nil?
43
+ @failure_message[action]
44
+ end
45
+
46
+ def failure_message msg=nil
47
+ failure_message_for :default, msg
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+
55
+
@@ -0,0 +1,13 @@
1
+ module HammerCLI
2
+ module OptionFormatters
3
+
4
+ def self.list(val)
5
+ val.is_a?(String) ? val.split(",") : []
6
+ end
7
+
8
+ def self.file(path)
9
+ File.read(File.expand_path(path))
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+
2
+ module HammerCLI::Output::Adapter
3
+
4
+ class Abstract
5
+
6
+ def print_message msg
7
+ puts msg
8
+ end
9
+
10
+ def print_error msg, details=nil
11
+ details = details.split("\n") if details.kind_of? String
12
+
13
+ if details
14
+ indent = " "
15
+ $stderr.puts msg+":"
16
+ $stderr.puts indent + details.join("\n"+indent)
17
+ else
18
+ $stderr.puts msg
19
+ end
20
+ end
21
+
22
+ def print_records fields, data, heading=nil
23
+ raise NotImplementedError
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,143 @@
1
+ module HammerCLI::Output::Adapter
2
+ class Base < Abstract
3
+
4
+ HEADING_LINE_WIDTH = 80
5
+ GROUP_INDENT = " "*2
6
+ LABEL_DIVIDER = ": "
7
+
8
+ def print_records fields, data, heading=nil
9
+ self.fields = fields
10
+
11
+ puts "-"*HEADING_LINE_WIDTH
12
+ puts " " + heading.to_s
13
+ puts "-"*HEADING_LINE_WIDTH
14
+
15
+ data.each do |d|
16
+ fields.collect do |f|
17
+ render_field(f, d)
18
+ end
19
+ puts
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def render_Field(field, data, indent="")
26
+ puts indent.to_s+" "+format_value(field, data).to_s
27
+ end
28
+
29
+ def render_Label(field, data, indent="")
30
+ render_label(field, indent)
31
+ puts
32
+ indent = indent.to_s + GROUP_INDENT
33
+
34
+ field.output_definition.fields.collect do |f|
35
+ render_field(f, data, indent)
36
+ end
37
+ end
38
+
39
+ def render_LabeledField(field, data, indent="")
40
+ render_label(field, indent)
41
+ puts format_value(field, data).to_s
42
+ end
43
+
44
+ def render_KeyValue(field, data, indent="")
45
+ render_label(field, indent)
46
+ params = field.get_value(data) || []
47
+ print "%{name} => %{value}" % params
48
+ end
49
+
50
+ def render_Collection(field, data, indent="")
51
+ render_label(field, indent)
52
+ puts
53
+ indent = indent.to_s + " "*(label_width_for_list(self.fields)+LABEL_DIVIDER.size)
54
+
55
+ field.get_value(data).each do |d|
56
+ field.output_definition.fields.collect do |f|
57
+ render_field(f, d, indent)
58
+ end
59
+ puts
60
+ end
61
+ end
62
+
63
+ def render_Joint(field, data, indent="")
64
+ render_label(field, indent)
65
+ data = field.get_value(data)
66
+ puts field.attributes.collect{|attr| data[attr] }.join(" ")
67
+ end
68
+
69
+ def format_Date(string_date)
70
+ t = DateTime.parse(string_date.to_s)
71
+ t.strftime("%Y/%m/%d %H:%M:%S")
72
+ rescue ArgumentError
73
+ ""
74
+ end
75
+
76
+ def format_List(list)
77
+ list.join(", ") if list
78
+ end
79
+
80
+ def format_OSName(os)
81
+ "%{name} %{major}.%{minor}" % os
82
+ end
83
+
84
+ def format_Server(server)
85
+ "%{name} (%{url})" % server
86
+ end
87
+
88
+
89
+ def render_field(field, data, indent="")
90
+ renderer = find_renderer(field)
91
+ renderer.call(field, data, indent)
92
+ end
93
+
94
+ def render_label(field, indent="")
95
+ if field.label
96
+ w = label_width_for_list(self.fields) - indent.size + 1
97
+ print indent.to_s+" %#{-w}s" % (field.label.to_s+LABEL_DIVIDER)
98
+ else
99
+ print indent.to_s+" "
100
+ end
101
+ end
102
+
103
+ def format_value(field, data)
104
+ format_method = "format_"+field_type(field.class)
105
+
106
+ value = field.get_value(data)
107
+ value = send(format_method, value) if respond_to?(format_method, true)
108
+ value
109
+ end
110
+
111
+ def find_renderer(field)
112
+ field.class.ancestors.each do |cls|
113
+ render_method = "render_"+field_type(cls)
114
+ return method(render_method) if respond_to?(render_method, true)
115
+ end
116
+ raise "No renderer found for field %s" % field.class
117
+ end
118
+
119
+ def field_type(field_class)
120
+ field_class.name.split("::")[-1]
121
+ end
122
+
123
+ def label_width_for(field)
124
+ if field.respond_to?(:output_definition)
125
+ label_width_for_list(field.output_definition.fields)+GROUP_INDENT.size
126
+ elsif field.respond_to?(:label)
127
+ field.label.size+LABEL_DIVIDER.size rescue 0
128
+ else
129
+ 0
130
+ end
131
+ end
132
+
133
+ def label_width_for_list(fields)
134
+ fields.inject(0) do |result, f|
135
+ width = label_width_for(f)
136
+ (width > result) ? width : result
137
+ end
138
+ end
139
+
140
+ attr_accessor :fields
141
+
142
+ end
143
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module HammerCLI::Output::Adapter
3
+
4
+ class Silent
5
+
6
+ def print_message msg
7
+ end
8
+
9
+ def print_error msg, details=[]
10
+ end
11
+
12
+ def print_records fields, data, heading=nil
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,53 @@
1
+ require 'table_print'
2
+
3
+ module HammerCLI::Output::Adapter
4
+
5
+ class Table < Base
6
+
7
+ def print_records fields, data, heading=nil
8
+
9
+ rows = data.collect do |d|
10
+ row = {}
11
+ fields.each do |f|
12
+ row[f.label.to_sym] = f.get_value(d)
13
+ end
14
+ row
15
+ end
16
+
17
+ options = fields.collect do |f|
18
+ { f.label.to_sym => {
19
+ :formatters => [ Formatter.new(self, "format_"+field_type(f.class)) ] } }
20
+ end
21
+
22
+ printer = TablePrint::Printer.new(rows, options)
23
+ output = printer.table_print
24
+ dashes = /\n(-+)\n/.match output
25
+ puts dashes[1]
26
+ puts output
27
+ puts dashes[1]
28
+ end
29
+
30
+ def print_heading heading, size
31
+ size = heading.size if heading.size > size
32
+ puts '-' * size
33
+ puts ' ' * ((size-heading.size)/2) + heading
34
+ puts '-' * size
35
+ end
36
+
37
+ end
38
+
39
+ class Formatter
40
+ def initialize adapter, method
41
+ @adapter = adapter
42
+ @method = method
43
+ end
44
+
45
+ def format value
46
+ if @adapter.respond_to?(@method, true)
47
+ value = @adapter.send(@method, value)
48
+ end
49
+ value
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,6 @@
1
+ require File.join(File.dirname(__FILE__), 'adapter/abstract')
2
+ require File.join(File.dirname(__FILE__), 'adapter/base')
3
+ require File.join(File.dirname(__FILE__), 'adapter/table')
4
+ require File.join(File.dirname(__FILE__), 'adapter/silent')
5
+
6
+
@@ -0,0 +1,17 @@
1
+ module HammerCLI::Output
2
+
3
+ class Definition
4
+
5
+ attr_accessor :fields
6
+
7
+ def initialize
8
+ @fields = []
9
+ end
10
+
11
+ def append(fields)
12
+ @fields += fields
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,56 @@
1
+ module HammerCLI::Output
2
+
3
+ class Dsl
4
+
5
+ def initialize options={}
6
+ @current_path = options[:path] || []
7
+ end
8
+
9
+ def fields
10
+ @fields ||= []
11
+ @fields
12
+ end
13
+
14
+ def build &block
15
+ self.instance_eval &block
16
+ end
17
+
18
+ protected
19
+
20
+ def field key, label, type=nil, options={}, &block
21
+ options[:path] = current_path.clone << key
22
+ options[:label] = label
23
+ type ||= HammerCLI::Output::DataField
24
+ custom_field type, options, &block
25
+ end
26
+
27
+ def custom_field type, options={}, &block
28
+ self.fields << type.new(options, &block)
29
+ end
30
+
31
+ def label label, &block
32
+ options = {}
33
+ options[:path] = current_path.clone
34
+ options[:label] = label
35
+ custom_field HammerCLI::Output::Fields::Label, options, &block
36
+ end
37
+
38
+ def from key
39
+ self.current_path.push key
40
+ yield if block_given?
41
+ self.current_path.pop
42
+ end
43
+
44
+ def collection key, label, options={}, &block
45
+ field key, label, HammerCLI::Output::Fields::Collection, options, &block
46
+ end
47
+
48
+
49
+ def current_path
50
+ @current_path ||= []
51
+ @current_path
52
+ end
53
+
54
+ end
55
+
56
+ end